Really Simple Live Comment Preview

read 32 comments

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:

HTML:
  1. <form action="..." method="post" id="commentform">
  2.   <p><textarea name="comment" id="comment" cols="100" rows="10" tabindex="4"></textarea></p>
  3.   <p><input name="submit" id="submit" tabindex="5" value="Submit Comment" type="submit"/>
  4.   <input name="comment_post_ID" value="30" type="hidden"/>
  5.   </p>
  6. </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 in bind() 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:

JavaScript:
  1. $('#comment').one('focus', function() {
  2.   $(this).parent().after('<div id="preview-box"><div class="comment-by">Live Comment Preview</div><div id="live-preview"></div></div>');
  3. });

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:

JavaScript:
  1. $('#comment').keyup(function() {
  2.   $('#live-preview').html( $(this).val() );
  3. });

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:

JavaScript:
  1. $comment = $comment.replace(/\n/g, "<br />");
  2. $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 "&lt;" because of the way my syntax highlighter is parsing things. If you copy and paste the code, you'll need to change that to "<"):

JavaScript:
  1. $(document).ready(function() {
  2.   $('#comment').one('focus',function() {
  3.     $('#comment').parent().after('<div id="preview-box"><div class="comment-by">Live Comment Preview</div><div id="live-preview"></div></div>');
  4.   });
  5.   var $comment = ''; // that's two single quotation-marks at the end
  6.   $('#comment').keyup(function() {
  7.     $comment = $(this).val();
  8.     $comment = $comment.replace(/\n/g, "<br />").replace(/\n\n+/g, '<br /><br />').replace(/(<\/?)script/g,"$1noscript");
  9.     $('#live-preview').html( $comment );
  10.   });
  11. });

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!

comment feed

32 comments

  1. 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?

  2. 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.

  3. Very nice example to show the advantages of jQuery. Thx

  4. Just had to give it a test!

    Yep, looking real good!

  5. 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?

  6. 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

  7. sam s

    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.

  8. Jacob Levy

    Very nice! I think however you meant 'onfocus' not 'onefocus' :) Other than that I have no complaints about the code.

  9. this is just a test

  10. 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 other one- methods as well. Check out the jQuery API at Visual jQuery and click on the Events button for more.

  11. 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.

  12. 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.

  13. 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. :)

  14. Nice

    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!

  15. Just had to test this. Testing link .Well done! Thanks for your work! I will use this on some of my future sites.

  16. 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 :)

  17. 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.

  18. Brian Miller

    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.

  19. Karl...

    This is a really cool idea and it works really well. Great job!

  20. Cool, I was looking for something like this. Thanks!

  21. 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! :)

  22. Tomás,
    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! :) Besides, it's under 20KB compressed, not exactly a heavy load.

  23. 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.

  24. Nikolei

    Cool. :)

  25. kevin

    thats's pretty awesome. nice job.

  26. 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.
    :)

  27. Rick

    Wanted to see my live preview... very interesting... jQuery makes a lot of things possible, even for non-Javascript programmers like myself!

    Rick

  28. Karl,

    Thank you for creating this useful website.

    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 [...]

    Because block-level elements are not allowed inside P.
    That is, DIVs and TABLEs are illegal inside P.

    [...] 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.

    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:

    [...] 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.

    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.

    Fortunately, jQuery has a "onefocus" event handler, which, as the name makes painfully clear, is executed only once

    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.

    We're going to make everything inside the comment textarea appear inside the "live-preview" DIV as it is being typed.
    [...]
    for this type of job. I used keyup

    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.

    $comment = $comment.replace(/\n/g, "<br />");
    $comment = $comment.replace(/\n\n+/g, '<br /><br />');

    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...)

    replace(/(\&lt;\/?)script/g,

    Why is a "\" preceeding the "&lt;" ? Another issue of your highligher?
    BTW, if you use "\x3C" instead of "&lt;" users won't have to change anything after they paste your code.

  29. Mooffie,

    Wow, thank you so much for your detailed, carefully constructed comment!

    That is, DIVs and TABLEs are illegal inside P.

    Of course! D'oh! I should have known that's what was going on there.

    BTW, you regexp catches "<script" but it fails to catch "<Script", "< script", "onevent='...'" and "href='javascript:...'".

    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 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.

    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.

    Hmmm... I don't get it. The first line gets rid of any \n. So the second line doesn't see them.

    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!

    (And you may have to replace any "\n" with "\r?\n". And there are Macs too...)

    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.

    Why is a "\" preceeding the "<" ? Another issue of your highlighter?

    Nope, must have been a typo.

    Thanks again, Mooffie, for leaving such an excellent reply! I really appreciate the suggestions.

  30. test

    Bold
    Kursiv
    Link
    Code
    Em
    Paragraf
    Gjennomstrek
    sterk

    heisann

    sdsd

  31. justatest

    Bold
    Kursiv
    Link
    Code
    Em
    Paragraf
    Gjennomstrek
    sterk

  32. vik

    Muy Cool, se ve bien...

6 Pings

  1. [...] Learning jQuery: preview des commentaires dans WordPress [...]

  2. [...] 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. [...]

  3. [...] результате дальнейших поисков я очень удачно нашел одну статью, в которой прекрасно расписано то, что и было нужно мне [...]

  4. [...] I did some searching and I found this post on the subject. The idea and concept on the post was good, however it lacked a few things that I [...]

  5. [...] Really Simple Live Comment Preview Artikel #541, 13. November 2007 · Code, Tipps, Webküche, WordPress · 15 Kommentare · Kommentar schreiben Tags: Javascript, jQuery, Kommentar, Theme, WordPress, WP gelesen: 5349 · heute: 5 · zuletzt: 5. August 2009 Kommentar-Feed zum Artikel · TrackBack URL [...]

Sorry, but comments for this entry are now closed.