Really Simple Live Comment Preview
Introduction and Caveat
I'm sure that some of you were expecting this entry to discuss traversing the DOM using jQuery methods. After all, I said I would do just that in my last entry. But a couple nights ago, I had the impulse to try a "live comment preview," so I'm going with it. My next entry, then, will be "How to Get Anything You Want - Part 2." I promise.
Please keep in mind as you read through this tutorial the "learning" part of this site's title: I'm learning jQuery just as many of those who will read this entry are. I mention this now because I have the nagging feeling that it was just too easy to create the live preview. Surely I must be doing something wrong. As always, I am counting on the superior knowledge of my readers to point out where my code fails. But enough of the apologies. Let's get started.
Quick Setup
For the live preview I started with the default comment form in WordPress's Kubrick template:
-
<form action="..." method="post" id="commentform">
-
<p><textarea name="comment" id="comment" cols="100" rows="10" tabindex="4"></textarea></p>
-
<p><input name="submit" id="submit" tabindex="5" value="Submit Comment" type="submit">
-
<input name="comment_post_ID" value="30" type="hidden">
-
</p>
-
</form>
The important part here is the textarea's id="comment". We want to show the contents of that textarea as the user inputs them.
Create the Preview DIV
Now on to the jQuery code. We first need to create a container in which we can inject the comment text. The container could have been a part of the HTML, or it could have been created when the page loaded, but I decided to create it only when the user first focuses on the comment textarea. Since the live preview is only an enhancement, and since it is a purely visual enhancement, I don't see any need to have either the extra markup inserted or the jQuery triggered unless someone is actually going to use it.
Fortunately, jQuery has a "onefocus" .one('focus') event handler, which, as the name makes painfully clear, is executed only once:
Unlike a call to the normal .focus() method, calling .onefocus() causes the bound function to be executed only the first time it is triggered, and never again (unless it is re-bound).The handler is executed only once for each element. Otherwise, the same rules as described inbind()apply.
Using "onefocus" .one('focus'), we can avoid creating multiple live preview DIVs if the user clicks in, then out, then back in the comment textarea.
Note that in jQuery 1.1, the .onefocus() method has been removed. Now you can achieve the same thing with .one('focus', function() { //your code });
Here is what the DIV creation code looks like:
So, the first time an element with an ID of "comment" gets the focus, we move up to this element's parent (<p>) and insert a collection of three DIVs after it. I use a containing DIV (#preview-box) and inside it place a heading DIV (.comment-by) and the important live preview DIV (#live-preview).
Note: I initially created the preview box immediately after the textarea, still within the paragraph, but Internet Explorer 6 did not like that one bit. The preview text wouldn't appear, and I instead got the very helpful JavaScript error: "Unknown runtime error." IE didn't seem to mind the preview box outside the paragraph. Go figure.
Make the Preview DIV Work
Now here comes the really tough part (wink, wink). We're going to make everything inside the comment textarea appear inside the "live-preview" DIV as well, as it is being typed. Unlike the textarea, the live preview will be nicely formatted and show the result of tags such as <strong> and <a href="">, not the tags themselves.
As you might have guessed, jQuery has a bunch of "helper" event functions — keyup, keydown, keypress, etc. — for this type of job. I used keyup:
Inside the event handler for #comment, $(this) refers to #comment.
Iron Out the Wrinkles
Our live preview works pretty well, just the way it is. The only problem occurs when we hit the Enter key: The preview text stays on the same line.
To fix this little problem, we can use a couple regular expressions. We can start by declaring a variable outside the the keyup function, so it is only done once: var $comment = '' (while the "$" isn't necessary before "comment," it helps me remember that the variable will be storing a jQuery object). Then inside the keyup function, we set the variable to be the value of the comment textarea: $comment = $(this).val(). Finally, we account for the line breaks by using two regular expressions. Since a line break in textareas is read as "\n", we need to replace that with the <br /> tag. If there are two or more line breaks in a row, we'll replace them with two <br /> tags. I realize that it's not properly "semantic," but I think we can get away with it since it's just for preview text that will be discarded anyway. Here are the two "replace" lines:
-
$comment = $comment.replace(/\n/g, "<br />");
-
$comment = $comment.replace(/\n\n+/g, '<br /><br />');
Notice the "/g"? That's a "global" switch to ensure that every occurrence is replaced. (If you want to learn more about regular expressions in JavaScript, tutorials and references abound on the web).
Now we can put $comment — instead of $(this).val() — inside the parentheses of .html().
UPDATE
Based on helpful comments from "seventoes" and Mary, I have combined the two Regular Expression lines and added a third replace() to prevent anyone from using the Live Preview for cross-site scripting or cross-site request forgery: $comment = $comment.replace(/\n/g, "<br />").replace(/\n\n+/g, '<br /><br />').replace(/(\<\/?)script/g,"$1noscript");. The last replace() rewrites "<script" as "<noscript" and "<script" as "</noscript." That'll teach those mean hackers to try to exploit our JavaScript! Thank you, seventoes and Mary!
The Full Code
Here is what the jQuery looks like in its entirety, wrapped inside $(document).ready() (Important: The third replace() in line 8 is showing "<" because of the way my syntax highlighter is parsing things. If you copy and paste the code, you'll need to change that to "<"):
-
});
-
var $comment = ''; // that's two single quotation-marks at the end
-
$comment = $comment.replace(/\n/g, "<br />").replace(/\n\n+/g, '<br /><br />').replace(/(<\/?)script/g,"$1noscript");
-
});
-
});
That's all there is to it. Successfully tested in Firefox 1.5 - 2.0, Safari 2.0.4, and Internet Explorer 6. As I suggested earlier, it almost seems too easy to be real.
To test my implementation of this code yourself, just post a comment to this entry. Unfortunately, I have had to turn off comments for this entry only, because of spammers' targeted attacks. It has become too much of a hassle to sift through the 100 or so spams I get for this entry each day. I'd love to hear from you anyway. Comment moderation is turned on for anyone who has not previously posted an approved comment. I'm happy to approve comments, even if they only say "testing, testing, this is great!" However, if you want to test submitting a comment with the Live Preview, but you don't care if it is actually published, just type "don't publish" or "don't approve" or something to that effect in the comment so I know to delete it. Please feel free to comment on any of the other entries on this site. Thanks!



November 19th, 2006 at 9:53 pm
I love it! This really simple code that's fore sure.
Now, I wonder how easy it would be to render the preview with markdown?
November 19th, 2006 at 11:32 pm
I did not want to post this here, but I could not find a contact form/address.
I really would not recommend this, it leaves your site open to Cross-site Scripting. What I would suggest is using AJAX to first pass the content to a server-side language (such as PHP) to clean it, then add it to the page.
November 20th, 2006 at 3:43 am
Very nice example to show the advantages of jQuery. Thx
November 20th, 2006 at 9:25 am
Just had to give it a test!
Yep, looking real good!
November 20th, 2006 at 11:16 am
Hi Mary,
Thanks for the warning. I really should get a contact form set up on the site. In the meantime, though, feel free to post your concerns in the comments. I welcome them!
That said, I wonder if you could explain a bit more about the vulnerability you see here. When the comment is submitted, WordPress still takes it from the textarea and filters it the same way it would without the live preview. If there is a vulnerability, it would only affect the one who is entering the script, because as soon as the page is refreshed, the live-preview div is destroyed.
Admittedly, my understanding of security issues is tiny, but I can't see how there would be any more of a security issue here than, say, if someone had the Grease Monkey Firefox extension installed.
What am I missing?
November 20th, 2006 at 3:23 pm
Just a great Commenter, just what I want except of doing it with AJAX, yes I know that not to use AJAX overhelemed, but it's effective enough for me.
Bye
Alex
November 20th, 2006 at 7:05 pm
I would like to preview my comment. It a live comment. Is there a final preview of the comment?
jQuery makes the once imaginable readily possible! Anyone whos made a website should check it out.
November 20th, 2006 at 7:44 pm
Very nice! I think however you meant 'onfocus' not 'onefocus'
Other than that I have no complaints about the code.
November 20th, 2006 at 7:47 pm
this is just a test
November 20th, 2006 at 8:36 pm
Hi Jacob,
Actually, I did mean "onefocus," not "onfocus."
onefocus()is a jQuery method that executes the first time the selector is focused. There are many otherone-methods as well. Check out the jQuery API at Visual jQuery and click on the Events button for more.November 20th, 2006 at 9:54 pm
I love javascript! You can replace these two lines:
$comment = $comment.replace(/\n/g, "<br />");
$comment = $comment.replace(/\n\n+/g, "<br /><br />");
With this:
$comment = $comment.replace(/\n/g, "").replace(/\n\n+/g, "<br />");
'replace' returns a string object, which we can chain another 'replace' onto.
November 20th, 2006 at 10:39 pm
Nice. The implementation itself is good, but what I really like is:
1. With code as small as this in working examples, there will be more people attracted to jQuery. I like jQuery.
2. It's real nice that you share your learnings. If everyone did that, we'd be spending less time searching and more time actually learning. You don't happen to know the 1-2-3 of RoR, do you?
I applaud your initiative. Please keep posting.
November 21st, 2006 at 12:01 am
Brito, thanks for the compliments. Unfortunately, I don't know RoR, but I hear that's where all the cool kids are hanging out these days.
Maybe that will be my next project; but by the time I get to it, it'll already be passé.
seventoes, thanks for the code suggestion! We jQuerians sure do love our chaining! (btw, I merged your two comments. Looks like you might have forgotten to encode the "<" and ">" around the "br /" in the first one.)
test, thanks for, um, testing.
November 21st, 2006 at 1:18 am
I think Mary's been drinkin' a lil' too much of the Koolaid. Nice tutorial. Now you should use the jquery form function do error test the reply contact form
next tutorial!
November 21st, 2006 at 4:48 am
[...] Learning jQuery: preview des commentaires dans WordPress [...]
November 21st, 2006 at 5:45 am
[...] Using jQuery you can show a live preview of the comment someone is leaving on your blog!read more | digg story Share and Enjoy:These icons link to social bookmarking sites where readers can share and discover new web pages. [...]
November 21st, 2006 at 6:52 am
Just had to test this. Testing link .Well done! Thanks for your work! I will use this on some of my future sites.
November 21st, 2006 at 9:16 am
This is a great tutorial, exactly what I am needing. I'm building a Microformat tool and examples that are currently out there use MochiKit for their examples, but now I can build in jQuery using this tutorial, thanks
November 21st, 2006 at 9:44 am
Tane, Welcome to the jQuery revolution! I'm thrilled that you are able to make use of this tutorial. If you need any additional help as you work on your jQuery project, you can find some excellent resources mentioned in my previous entry, Documentation and Support Resources for jQuery.
November 21st, 2006 at 10:01 am
re: XSS
The issue is that someone can drop HTML into the comment, and $.html(...) will happily drop that into the DIV, making it live. This includes references to other sites, which could be harmful. For example, a script or object tag.
The way to solve that is to simply disallow any tags in the comment. You can scrub it on the client side - there's no need for an AJAX call just to do a live preview. You just have to add some additional replacements where you do the substitutions for newlines.
November 21st, 2006 at 11:32 am
Karl...
This is a really cool idea and it works really well. Great job!
November 21st, 2006 at 4:16 pm
Cool, I was looking for something like this. Thanks!
November 22nd, 2006 at 2:24 pm
hiya karl, it sure works!
however, personally i wouldn't use it on my site, since you can achieve the same without the whole JQuery library... just a few lines of code does it!
November 22nd, 2006 at 4:35 pm
Tomás,
Besides, it's under 20KB compressed, not exactly a heavy load.
You're absolutely right that it doesn't require jQuery — just a few more lines of code. A little getElementById and createElement, some setAttribute, and a sprinking of innerHTML should do the trick. But then you wouldn't have jQuery to do all the other cool stuff!
November 24th, 2006 at 12:58 am
I'm not entirely certain what the commenter's reference to "Koolaid" is supposed to mean, but my response is: If I am wrong, correct me. If you don't understand what I'm talking about, then you've got no business saying anything about it, now do you?
Karl,
The difference is that users don't typically use GreaseMonkey to silently attack third-party sites (visit one site to execute scripts on another). There may be those who do, but we certainly don't want to make this kind of thing easier for them (like making it look like your site is the one doing the attacking, rather than the actual bozo). Instead of me trying to repeat what others have explained better, you can do a Google search on the topic and you'll find out the principles of how it works, if you're not familiar with it. Brian Miller has given a nice summary above of how it could be done here.
As to Brian's proposed solution: off the top of my head, that would certainly work, I think.
My suggestion was based upon my own perspective, that it would be easy to use jQuery to pass the comment preview to a script, which could run KSES (the markup cleaning script WordPress uses) on the comment preview, and then include it in the page source. If done that way, you could save on server requests by either putting a timeout on the preview, or by adding a Preview button (clicking the button runs/updates the live preview on the page). You can see the latter being done in Vanilla, for example. I personally find it just as convenient as a letter-by-letter update.
December 5th, 2006 at 11:10 am
Cool.
December 6th, 2006 at 4:25 pm
thats's pretty awesome. nice job.
December 8th, 2006 at 1:23 pm
this is really neat.
i run expression engine for a few of my sites.
id like to try to implement this technique to display a live comment preview, complete with the commenter's information that would be inserted INTO the post itself (ie: name, time/date stamp, etc).
i love jQuery more and more.

December 13th, 2006 at 12:44 pm
Wanted to see my live preview... very interesting... jQuery makes a lot of things possible, even for non-Javascript programmers like myself!
Rick
December 17th, 2006 at 7:31 am
Karl,
Thank you for creating this useful website.
Because block-level elements are not allowed inside P.
That is, DIVs and TABLEs are illegal inside P.
I believe you're correct. I'm using Opera, which allows me to easily edit-and-reload any page I see.
BTW, you regexp catches "<script" but it fails to catch "<Script", "< script", "onevent='...'" and "href='javascript:...'".
Mary said:
Marry, this is an excellent idea. But not only beacause of some possible security hole.
This idea, of round-tripping to the server to get the displayed HTML, has an important advantage:
HTML is not the only format for user input. I'm using Drupal, where lots of other "input formats" are possible. Users can use Wiki, AsciiMath, BBCode, Tex, Textile, SmartyPants, CSV, Opus, whatever, and the browser itself doesn't know these languages.
In fact, HTML is not even WordPress' input format! WordPress' input format is: filtered HTML. That is, not all tags are allowed. I could type a TABLE tag here and see it in the preview box, but WordPress would throw it out anyway...
And I believe WordPress also converts URLs into links and smilies into icons. You don't see this in the preview. In other words, your preview is very "low fidelity".
So Marry's idea is good for CMSs like WordPress too.
You should mention, that for almost any event there is a corresponding oneevent(). Why is this so important? So that people know this is not a special case. I hate libraries with unnecessary special cases.
My Opera, for some reason, doesn't trigger this event for "special" keys like BACKSPACE, ENTER and... SPACE. Word has it that BACKSPACE does't trigger keyup on IE either (sorry I can't verify this claim).
This problem doesn't seem to occur for the keydown and keypress events. Are these two events triggered before or after the textarea's content is modified? If before, Mary's timeout idea would solve the problem.
Hmmm... I don't get it. The first line gets rid of any \n. So the second line doesn't see them.
(And you may have to replace any "\n" with "\r?\n". And there are Macs too...)
Why is a "\" preceeding the "<" ? Another issue of your highligher?
BTW, if you use "\x3C" instead of "<" users won't have to change anything after they paste your code.
December 17th, 2006 at 5:30 pm
Mooffie,
Wow, thank you so much for your detailed, carefully constructed comment!
Of course! D'oh! I should have known that's what was going on there.
True. It was clearly not a robust solution. And I agree that using WordPress's (or any other blog platform/CMS's) parser would be better. I guess that's one of the reasons I call this "Really Simple."
You are correct here, too. I will update the entry to include that information. It might also be worth noting that the syntax for the "one..." events might be changing when 1.1 is released.
Ah, you're right again. If I switch the order, though, I can prevent the appearance of multiple (3 or more) line breaks. that's what I had intended, and it worked at one point, so I must have switched them around at some point inadvertently. Good catch!
I can do that. It works fine on my Macs with just the "\n", but there might be some edge cases that don't work.
Nope, must have been a typo.
Thanks again, Mooffie, for leaving such an excellent reply! I really appreciate the suggestions.
January 19th, 2007 at 8:35 am
Bold
Kursiv
Link
CodeEm
Paragraf
Gjennomstreksterk
sdsd
January 21st, 2007 at 5:37 pm
Bold
Kursiv
Link
Code
Em
Paragraf
Gjennomstrek
sterk
January 25th, 2007 at 2:07 pm
Muy Cool, se ve bien...