MyBB Community Forums

Full Version: Post preview on threadlist
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2 3 4
Hi guys,

Quick tutorial here on how to create some very simple and easy to use post preview using AJAX. I found a very handy jQuery function called .load(), which allows us to send an ajax request with parameters requesting a specific element on the page. I wanted to give it a try with something so I decided to create a little post preview java script plugin for MyBB. I wrote this based on the MyBB Community forums, not the default theme so please let me know if there are any compatibility issues.

As far as I'm aware, this won't be supported in IE11 due to the use of async await, however this could easily be modified to fix that, or use a polyfill.

The plugin stores fetched previews in an object ready for re-use to prevent sending too many requests.

On with the tutorial..

We're going to be using a data selector "[data-preview-post]" to target the posts we want to be able to preview. With this in mind we need to add this selector to each posts subject line.

Go to forumdisplay_thread:
- Find:
<span class="{$inline_edit_class} {$new_class}" id="tid_{$inline_edit_tid}">
- Replace with:
<span class="{$inline_edit_class} {$new_class}" id="tid_{$inline_edit_tid}" data-preview-post="{$inline_edit_tid}">

Once we have our selectors setup, we need to include the post_previews.js JavaScript file. Download the file, edit the config options and add to your jscripts folder, then point to it in your footer template before the closing </body> tag:
<script src="{$mybb->asset_url}/jscripts/post_preview.js" defer></script>
Download from github: https://github.com/XigeTime/MyBB-Post-Preview

You can also add the js directly to your headerinclude template:
<script>const post_preview = {

	saved_posts: {},

	options: {
		/* 
			Fetch URL is the target thread's URL. This works with both the standard MyBB URL's and
			SEO URL's. Any URL that has a unique thread id should work.
			Grab any threads URL then replace the thread id with ${tid} then paste in the fetch URL section.

			For example;
			 - https://example.com/thread-1111.html
			 becomes: https://example.com/thread-${tid}.html

			 - https://example.com/showthread.php?tid=1111
			 becomes: https://example.com/showthread.php?tid=${tid}
		*/

		// fetch url: tid => `https://example.com/showthread.php?tid=${tid}`
		fetch_url: tid => `https://example/thread-${tid}.html`,

		preview_class: "post_preview", // Class name for the container of the post preview.

		fetch_selectors: {
			body: ".post_body", // Class name of the post content
			author: ".author_avatar", // Class name of the author's section.
		},

		preview_css: {
			height: 100,
			width: 500, 
			margin: 30, // distance in pixels the preview box should be from the post title
		}

	},


	get_post: async (el,url) => {
		url = `${url} ${post_preview.options.fetch_selectors.body}:eq(0)`;

		await new Promise((resolve,reject) => {
			$(el).load(url, (response,status,xhr) => {

				if (status == "error") reject(xhr);
				resolve(response);

			})
		})
		.catch(xhr => console.log(`Unable to fetch post content: ${xhr.status}, ${xhr.statusText}`));

		return el;

	},

	get_author: async (el,url) => {
		url = `${url} ${post_preview.options.fetch_selectors.author}:eq(0)`;
		
		await new Promise((resolve,reject) => {
			$(el).load(url, { limit: 1 }, (response,status,xhr) => {

				if (status == "error") reject(xhr);
				resolve(response);

			});
		})
		.catch(xhr => console.log(`Unable to fetch content: ${xhr.status}, ${xhr.statusText}`));

		return el;

	},

	get_preview: async post => {
		
		// create container for our post and author sections of the preview
		let post_container = document.createElement("div");
		let author = document.createElement("div");

		
		author.setAttribute("class", "post_author");

		// build fetch url
		let url = post_preview.options.fetch_url(post); 

		// fetch post and author content
		post_container = await post_preview.get_post(post_container, url);
		author = await post_preview.get_author(author, url);

		return { a: author, p: post_container };

	},

	preview: async e => {
		if (!e) return;
		
		// remove any lingering previews
		post_preview.remove_previews();

		let selector = e.target.closest("[data-preview-post]");
	
		// build preview container
		let preview_container = document.createElement("div");
		preview_container.setAttribute("class", post_preview.options.preview_class);

		// add a loader to the preview container for time being
		let loader = document.createElement("span");
		loader.id = "preview_loader";
		loader.innerText = "Loading preview...";

		preview_container.appendChild(loader);
		
		// output preview with loader & position correctly against link
		selector.appendChild(preview_container);
		$(preview_container).css({
			height: post_preview.options.preview_css.height + "px",
			width: post_preview.options.preview_css.width + "px",
			bottom: 40 + "px"
		});

		// extract requested post id from link url
		let post_id = selector.dataset.previewPost;

		// if we have already loaded this preview use that data otherwise send ajax request for page
		let post;
		if (!post_preview.saved_posts[post_id]) {
			// send request
			post = await post_preview.get_preview(post_id);
			// save data so we don't need to request it again
			post_preview.saved_posts[post_id] = post;
		} else {
			// if data is already available, simply use that
			post = post_preview.saved_posts[post_id];
		}
		
		// remove loader and output our fetched data
		loader.outerHTML = null;
		post_preview.output_preview(preview_container,post)

	},

	output_preview: (container,output) => {
		container.appendChild(output.a);
		container.appendChild(output.p);
	},

	remove_previews: () => {
		$("." + post_preview.options.preview_class).remove();
	}
}

// add hover handler
$(document).ready(() => {
    $("[data-preview-post]").hover((e) => {
	post_preview.preview(e);
    }, () => {
	post_preview.remove_previews();
    })
})</script>
Make sure you configure the options at the top of the javascript file!


Finally, we need to style our previews. Here is some basic CSS to get you started, but there are endless possibilities!
Add to the bottom of global.css:
[data-preview-post] { position: relative }
.post_preview {
    position: absolute;
    background: white;
    border-radius: 4px;
    border: 1px solid #d4d4d4;
    box-shadow: 1px 1px 3px #d4d4d4;
    overflow: hidden;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: .5rem;
}
.post_preview > div:not(.post_author) {
    padding: 0 10px;
    height: 100%;
    overflow: hidden;
}
.post_preview .post_body {
  all: unset;
}
.post_preview > .post_author img {
    border-radius: 100px;
    height: 50px;
    width: 50px;
}
.post_preview div br {display:none}


Please enjoy! This can be modified to work with any post's/content you like, this is just a very simple implementation.

Here is a gif of it working on MyBB.com

[Image: EpZWeVM.gif]


Here is a good example of some of the things you can accomplish with the script above. The possibilities are endless, allowing you to provide a more user friendly, speedy and intuitive experience for your users. The JavaScript is on github if you would like to try and accomplish similar, the customisation were written for a heavily modified (and locally hosted) theme, so don't expect it to work without any edits!
[Image: 5Aefrj3.gif]
Nice one Xige Big Grin
Nice tutorial, but unfortunately not working for me... I changed URL to fits my setting in JS file and change everything according to your tut, no preview is displayed on mouse hover Sad

URL: https://www.carcassonneforum.cz/forum-5.html
(2019-08-18, 07:38 PM)Eldenroot Wrote: [ -> ]Nice tutorial, but unfortunately not working for me... I changed URL to fits my setting in JS file and change everything according to your tut, no preview is displayed on mouse hover Sad

URL: https://www.carcassonneforum.cz/forum-5.html

Thank you both! And I should have wrapped the event listener in a doc.ready tag thanks for finding this my bad. Also I recommend deferring the javascript file like below and maybe putting it in the footer to ensure it loads after all content has loaded:
<script src="{$mybb->asset_url}/jscripts/post_preview.js"></script>

Either grab the edited code from above, or change this the hover handler to this in post_preview.js:
$(document).ready(() => {
// add hover handler
    $("[data-preview-post]").hover((e) => {
	post_preview.preview(e);
    }, () => {
	post_preview.remove_previews();
    })
});
OK, now it works! Great!

Anyway, I would like to remove "online" indicator or at least customize the output (or at least what will be displayed. How to do that?

EDIT: This script breaks "hold to edit" subject text... check it yourself Sad
(2019-08-20, 05:05 PM)Eldenroot Wrote: [ -> ]OK, now it works! Great!

Anyway, I would like to remove "online" indicator or at least customize the output (or at least what will be displayed. How to do that?

EDIT: This script breaks "hold to edit" subject text... check it yourself Sad

Hi mate, just checked this and it seems to be working fine on my end? I had a quick look on your live website and I can't see the inline edit id:
<span class="{$inline_edit_class} {$new_class}" id="tid_{$inline_edit_tid}">

This could be the issue, is your inline edit working currently without the post preview?
I disabled it, I cannot edit the subject when I click and hold the subject text. It loads text from post into the subject
Seems to be working for me. Sad
[Image: r2k2IVP.gif]

You could try changing where the preview is viewed, maybe add an icon, or a small "preview" button.
Please check the whole subject - it starts with subject text and then text from post is following
(2019-08-21, 02:41 PM)Eldenroot Wrote: [ -> ]Please check the whole subject - it starts with subject text and then text from post is following

I'm not sure what you mean, sorry. The script shouldn't break or interfere with the inline edit script, however in your case I suggest trying to change your thread link html to be something like follows:
<span class="{$inline_edit_class} {$new_class}" id="tid_{$inline_edit_tid}">
    <a href="{$thread['threadlink']}">{$thread['subject']}</a>
</span> &nbsp;
<i class="far fa-eye" data-preview-post="{$inline_edit_tid}"></i>
Pages: 1 2 3 4