Skip to main content
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Chris Simmons
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Chris Simmons

Showing A Comment Preview As You Type On This Blog

By
Published in ,

Since comments, on this blog, are authored using Markdown (and ColdFusion), there is a delta between what you write in the intake form and what is eventually rendered in the HTML. Much of the time, this delta is expected; however, if you have small errors in your markdown syntax, you can end up with HTML that does not reflect what you had intended to publish. To help narrow the gap between input and output, I've added a comment preview functionality to this blog.

Now, as you type your comment, I'm making an AJAX request (debounced at 300ms) in the background to my API. This AJAX request is taking your pending comment and is processing it through the same Markdown-to-HTML pipeline that your comment would enter if you were to submit the comment form. The returned HTML is then rendered below the comment intake so that you can get a better sense of what your comment will look like once it is published.

The server-side portion of this code is uninteresting since it's just calling the same Flexmark markdown conversion and OWASP AntiSamy HTML sanitization that my normal comment workflow uses. And, the client-side portion of the code is some hacky jQuery that I whipped together (truncated for demo):

var previewTimer = null;
var previewRequest = null;
var lastPreviewTarget = null;
var previewSection = form.find( ".comment-preview" )
var previewContent = previewSection.find( ".comment-preview__content" );
var errorContent = previewSection.find( ".comment-preview__error" );


// I handle keydown events on the comment intake.
comment.keydown(
	function handleKeydown( event ) {

		// Debounce typing so that we aren't triggering an AJAX request after
		// every single keypress.
		clearTimeout( previewTimer );
		previewTimer = setTimeout( showCommentPreview, 300 );

	}
);


// I render the HTML preview for the current Markdown comment.
function showCommentPreview() {

	var commentMarkdown = $.trim( comment.val() );

	// Since this call is, in part, being triggered by key-events, we want to
	// make sure that something has actually changed. If the users is moving
	// around the comment using arrow-keys (for example), we have no need to
	// update the comment preview.
	if ( lastPreviewTarget === commentMarkdown ) {

		return;

	}

	lastPreviewTarget = commentMarkdown;

	// If the user cleared the comment form, hide the preview rendering.
	if ( ! commentMarkdown ) {

		previewSection.removeClass( "comment-preview--active" );
		return;

	}

	// Since the AJAX calls are all asynchronous, there's a good chance that
	// we'll end up with multiple, concurrent calls. Kill the last one so that
	// we don't end-up processing the responses out-of-order.
	if ( previewRequest ) {

		previewRequest.abort();
		previewRequest = null;

	}

	previewRequest = $.ajax({
		type : "post",
		url : "/index.cfm",
		data : {
			event: "api.blog.previewComment",
			content: commentMarkdown
		},
		dataType: "json"
	});
	// NOTE: We have to break-up the AJAX request from the Promise chain since
	// the resultant promise won't have an .abort() method on it - that's only
	// returned on the AJAX response object (for backwards compatibility).
	previewRequest.then(
		function handleSuccess( response ) {

			previewRequest = null;
			previewSection.addClass( "comment-preview--active" );
			previewContent.html( response.preview ).show();
			errorContent.hide().empty();

		},
		function handleError( response ) {

			if ( response.statusText === "abort" ) {

				return;

			}

			previewRequest = null;
			previewSection.addClass( "comment-preview--active" );
			previewContent.hide().empty();
			errorContent.text( response?.responseJSON?.message || "Something went wrong - the comment preview cannot be rendered." ).show();

		}
	);

}

It's not the prettiest code, but it seems to work quite nicely. And now, when you type your comment, you should see a preview of the comment just below the form:

Screenshot of the markdown preview being rendered as HTML.

As I've been trying to modernize my blogging platform, I've definitely been focusing much more on the server-side, ColdFusion code. Eventually, I'll start to refactor and clean-up the client-side code. But for now, this seems to work well enough.

Why AJAX? Why Not Just Process the Markdown in JavaScript?

At first glance, it may seem silly to make an AJAX call just to convert markdown into HTML. After all, there must be JavaScript libraries that can do this for me. But, the reality is, making the AJAX call has many benefits over trying to perform the translation locally:

  • It's less JavaScript that the client has to load, including the fact that the client won't even need to deal with the preview unless they are leaving a comment in the first place (which is the minority of site visitors).

  • I'm not just converting Markdown to HTML - I'm converting it to HTML using a subset of Markdown features. And, the resultant Markdown is then being post-processed and sanitized using the OWASP AntiSamy library. This would be unnecessarily complex to reproduce on the client-side.

While the AJAX request and the debouncing of calls adds some latency, I think the benefits of performing the translation on the server-side far outweighs the latency issues.

Want to use code from this post? Check out the license.

Reader Comments

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel