Creating HTML blocks with Mootools

Sep 02 2009 Published by Eneko Alonso under uncategorized

Sometimes we need to create HTML from JS. There are multiple ways to do this, from using innerHTML to creating element by element appending child to parents, setting attributes, etc. And there are as many different opinions about what is the right way to do it.

Meanwhile, once thing I missed from jQuery, not available on Mootools, was the ability to do something like this:

  1. var element = jQuery('<div id="foo">bar</div>');

In Mootools, the equivalent should be:

  1. var element = new Element('<div id="foo">bar</div>');

But that does not work.

On the Mootools mailing list, someone was asking about how to implement this and we just found a different approach, which instead of using the Element class, extends the String class like this:

  1. String.implement({
  2.  toElement: function() {
  3.    return new Element('div', {html:this}).getFirst();
  4.  }
  5. });

Now you can do this:

  1. var element  = '<div id="foo">bar</div>'.toElement();

To try it, paste this snippet on Firebug’s console:

  1. String.implement({
  2.  toElement: function() {
  3.    return new Element('div', {html:this}).getFirst();
  4.  }
  5. });
  6. console.log('<div id="foo">bar</div>'.toElement().get('id'));

Nice, uh? Of course, the HTML can be as complex as you need and where do you get the content of the string is up to you :)

3 responses so far

Keyboard shortcuts in Drupal: Navigating through comments

Dec 14 2008 Published by Eneko Alonso under uncategorized

It was one of the best features of Google Reader and Google GMail: the keyboard shortcuts, specially the ones to navigate through messages or posts (n = next, p = previous).

I decided it was time to implement it on spaniards.es, so here is the code I used. So far it only navigates through comments back and forth on the same page (does not go to next page or so). I am planning to add other shortcuts soon.

  1. $(document).keyup(function(event) {
  2.  
  3.   if (event.target.tagName == "INPUT" ||
  4.       event.target.tagName == "TEXTAREA")
  5.     return;
  6.  
  7.   var selected = $('.comment-selected');
  8.  
  9.   // Next comment
  10.   if (event.keyCode == 78) {
  11.     if (selected.length) {
  12.       selected.removeClass('comment-selected');
  13.       selected = selected.nextAll('.comment:first');
  14.       selected.addClass('comment-selected');
  15.     } else {
  16.       selected = $('.comment:first').addClass('comment-selected');
  17.     }
  18.     if (selected.length) {
  19.       window.scrollTo(0, selected.offset().top);
  20.     }
  21.     return;
  22.   }
  23.  
  24.   // Previous comment
  25.   if (event.keyCode == 80) {
  26.     if (selected.length) {
  27.       selected.removeClass('comment-selected');
  28.       selected = selected.prevAll('.comment:first');
  29.       selected.addClass('comment-selected');
  30.     } else {
  31.       selected = $('.comment:last').addClass('comment-selected');
  32.     }
  33.     if (selected.length) {
  34.       window.scrollTo(0, selected.offset().top);
  35.     }
  36.     return;
  37.   }
  38. });

As you can see, it is important to leave the input and textarea fields alone, so the keyboard shortcuts don’t interfere with the user typing some text. With a little bit of CSS the selected comment can be displayed with a different border color or background. And that’s it!

Check an example topic at www.spaniards.es

2 responses so far

Ajax comments in Drupal 5: How I made it – Part III

Dec 13 2008 Published by Eneko Alonso under uncategorized

In Ajax comments in Drupal 5: How I made it parts I and II we saw the code needed on the back-end. Now, let’s see what goes on the client side: the Javascript.

Submitting the comment with an ajax request

Basically, to add a comment we need the node id (nid) and the comment text. The code in the back-end will handle the user info and permissions. The first thing we do is find the comment form and the submit button. Then we hook the click event on the submit button.

  1.   var commentForm = jQuery('form#comment-form');
  2.   if (!commentForm.length) return;
  3.   var submitBtn = jQuery('#edit-submit');
  4.   if (!submitBtn.length) return;
  5.  
  6.   submitBtn.click(function(ev)
  7.   {
  8.     // …
  9.   }

When the user clicks the submit button, we get the node id (we could have done on domready) and the comment text. If the comment is empty, we don’t do anything, so the comment will be submitted the standard way and Drupal will fail the comment form validation.

  1.     // Get Node Id
  2.     var nid = jQuery('#node-nid').text();
  3.     if (!nid) return true; // Do nothing…
  4.     if (parseInt(nid) <= 0) return true;
  5.  
  6.     // Get Comment Text
  7.     var text = jQuery('#edit-comment').val();
  8.     if (!text) return true; // Let Drupal validate the form

Since we don’t want to get back just the comment added but all the new comments added by any user on this node, we want to pass the last comment id on screen.

  1.     // Get last comment Id
  2.     var lastComment = jQuery('div.comment:last');
  3.     var lastCommentId = (lastComment.length)? lastComment.id().split('-')[1] : 0;

Due to my limited knowledge of jQuery at the time, I used the load function for the ajax request. The problem with this function is that, from what I know, it only works on elements already included in the DOM. So before the request, I had to create a container for the new comments. The point of insertion will differ if the page already has comments or not:

  1.     // We got both nid and text. Let's create the comment holder
  2.     var comment = jQuery(document.createElement('div'));
  3.     comment.hide();
  4.     if (lastComment.length) {
  5.       lastComment.after(comment);
  6.     } else {
  7.       comment.insertBefore(jQuery('div.box'));
  8.     }

Finally, the load request:

  1.     // Ajax submit/load
  2.     comment.load('/add/comment',
  3.       {  'nid': nid,
  4.         'text': text,
  5.         'lastcid': lastCommentId
  6.       },
  7.       function(responseText, textStatus, XMLHttpRequest) {
  8.         if (textStatus == 'success' && responseText) {
  9.           ajax_insert_comment(comment);
  10.         } else {
  11.           comment.remove();
  12.           alert("Error: No se pudo enviar el comentario");
  13.         }
  14.       }
  15.     );

If the request failed, the container was removed. If the request succeeded, the new comments were added to the page:

  1. ajax_insert_comment = function(comment) {
  2.   // Insert new comments
  3.   comment.show();
  4. };

And that was it! Ajax comments in Drupal working like a charm.

Adding/removing the “new” indicator

Now that I had my Ajax comments working I wanted them to look better. For example, I wanted the new added comments to have the “new” indicator, while at the same time I wanted to remove the indicator from the comments already on the screen. On ajax_insert_comment:

  1.   // Clear 'new' from previous comments
  2.   jQuery('div.comment').find('span.new').remove();
  3.   // Add 'new' indicator
  4.   var newcomment = jQuery(document.createElement('span'));
  5.   newcomment.addClass('new').html('new');
  6.   newcomment.insertBefore(comment.find('div.comment-author'));

And many other tweaks like that.

The code

Here is the whole code for ajax comment submission I currently use:

  1. ajax_comments = function() {
  2.   var commentForm = jQuery('form#comment-form');
  3.   if (!commentForm.length) return;
  4.   var submitBtn = jQuery('#edit-submit');
  5.   if (!submitBtn.length) return;
  6.  
  7.   submitBtn.val('Enviar comentario');
  8.   submitBtn.click(function(ev)
  9.   {
  10.     // Disable submit button
  11.     submitBtn.val('Enviando…').attr("disabled","disabled");
  12.  
  13.     // Clear the 'edit' link from previous comments
  14.     jQuery('li.edit-comment-link').remove();
  15.  
  16.     // Get Node Id
  17.     var nid = jQuery('#node-nid').text();
  18.     if (!nid) return true; // Do nothing…
  19.     if (parseInt(nid) <= 0) return true;
  20.  
  21.     // Get Comment Text
  22.     var text = jQuery('#edit-comment').val();
  23.     if (!text) return true; // Let Drupal validate the form
  24.  
  25.     // Get last comment Id
  26.     var lastComment = jQuery('div.comment:last');
  27.     var lastCommentId = (lastComment.length)? lastComment.id().split('-')[1] : 0;
  28.  
  29.     // // We got both nid and text. Let's create the comment holder
  30.     var comment = jQuery(document.createElement('div'));
  31.     comment.hide();
  32.     if (lastComment.length) {
  33.       lastComment.after(comment);
  34.     } else {
  35.       comment.insertBefore(jQuery('div.box'));
  36.     }
  37.  
  38.     // Ajax submit/load
  39.     comment.load('/add/comment',
  40.       {  'nid': nid,
  41.         'text': text,
  42.         'lastcid': lastCommentId
  43.       },
  44.       function(responseText, textStatus, XMLHttpRequest) {
  45.         if (textStatus == 'success' && responseText) {
  46.           ajax_insert_comment(comment);
  47.           jQuery('#edit-comment').val('');
  48.         } else {
  49.           comment.remove();
  50.           alert("Error: No se pudo enviar el comentario");
  51.         }
  52.         // Reenable submit button
  53.         submitBtn.val('Enviar comentario').removeAttr("disabled");
  54.       }
  55.     );
  56.  
  57.     return false;
  58.   });
  59. };
  60.  
  61. ajax_insert_comment = function(comment) {
  62.   // Clear 'new' from previous comments
  63.   jQuery('div.comment').find('span.new').remove();
  64.   // Add 'new' indicator
  65.   var newcomment = jQuery(document.createElement('span'));
  66.   newcomment.addClass('new').html('new');
  67.   newcomment.insertBefore(comment.find('div.comment-author'));
  68.   // Remove comment count
  69.   comment.find('span.commentcount').empty();
  70.   // Insert new comments
  71.   comment.show();
  72.   jQuery('#edit-comment').focus();
  73. };
  74.  
  75. jQuery(document).ready(function() {
  76.   ajax_comments();
  77. });

As you can see, when I wrote this code back in July, I have just started using jQuery and I didn’t know much about event handling. So I used the old return true/false to stop the submit event if needed. Also, now that I see the code again I can think on better ways to do parts of it. Well, that’s a good sign: it means I’m getting better :)

Series:
Ajax comments in Drupal 5: How I made it – Part I
Ajax comments in Drupal 5: How I made it – Part II
Ajax comments in Drupal 5: How I made it – Part III

See the ajax comments in action

Please, note this is a WordPress based blog. The Drupal website where I have implemented the ajax comments is www.spaniards.es

10 responses so far

Ajax comments in Drupal 5: How I made it – Part II

Dec 11 2008 Published by Eneko Alonso under uncategorized

In Axaj comments in Drupal 5: How I made it – Part I I covered the most important part of the module code: saving comments to the database. Now, let’s see how to display them properly.

Rendering the new comment

Comments usually have more information than the user name and the comment text. In my website, for instance, comments have information like the user registered date, where he/she lives, the country flag, the comment number on the thread, etc. All this information could not be made up at the client side; the server had to send it back to the client once the comment was saved.

Also, we want to take full advantage of Drupal: we want the corresponding filters applied to the comment text to remove malicious HTML tags, convert plain URLs into HTML links, etc.

The best way to do this is to use the function theme with the parameter ‘comment_view’. The difference between theme(‘comment’,…) and theme(‘comment_view’,…) is that filters are applied only in the second case, if I am not mistaken.

  1. function _spaniards_get_latest_comments($nid, $fromcid) {
  2.   $result = db_query('select * from comments where nid = %d and cid > %d', $nid, $fromcid);
  3.   $content = '';
  4.   while ($comment = db_fetch_object($result)) {
  5.     if ($comment->status == COMMENT_NOT_PUBLISHED) continue;
  6.     $links = module_invoke_all('link', 'comment', $comment, 1);
  7.     $content .= theme('comment_view', $comment, $links);
  8.   }
  9.   return $content;
  10. }

As I mentioned before, we don’t want to return just the comment added by the user but any comment added by any other user since the user loaded the page. This function will return any comment on the thread with comment Id greater than the parameter $fromcid.

Besides using theme(‘comment_view’,…) it is a good idea also to invoke the link hook. This will let other modules like fasttoggle inject their very useful links in out comment.

And that is it for now. This code, together with the code on Part I will be the only PHP needed. We will see the client side Javascript code on Part III.

Series:
Ajax comments in Drupal 5: How I made it – Part I
Ajax comments in Drupal 5: How I made it – Part II
Ajax comments in Drupal 5: How I made it – Part III

See the ajax comments in action

Please, note this is a WordPress based blog. The Drupal website where I have implemented the ajax comments is www.spaniards.es

No responses yet

Ajax comments in Drupal 5: How I made it – Part I

Dec 10 2008 Published by Eneko Alonso under uncategorized

First of all, I would like to apologize for not publishing this the right way. I haven’t had time to put it all on a standalone module, which will make it easier to share the code at www.drupal.org. Feel free to grab the code and put it on a module if you wish (and let me know if you do so :)

Why Ajax comments?

Ajax comments let users to submit comments without having to reload the whole page. Reloading a page is not only slow, but its an overload for the server which has to process and send the whole page again just to add a simple comment to the DOM. So yes, Ajax comments are a big benefit, both for the user, which has to wait less, and for the server, which has to process and send less information.

Available modules

Point explained, lets see how it went. In the past I tried the Javascript Tools set of modules, which had other dependencies if I am not mistaken. And I don’t know why but it didn’t work at all for me. My comments kept reloading the whole page while sent. A couple of months ago I decided it was time to do it myself.

The plan

My original plan was simple: when the user hits the submit button, send the comment to the server, save it and add it to the DOM at the end of the comment list. This looked simple, but it had a few thing to deal with. I didn’t want to mess with Drupal’s comment module neither modify any line of Drupal’s core. So I decided to take an alternative path.

Saving a comment

Saving a comment doesn’t require much information. Since the user has to be logged in, the only information needed is basically the node Id and the comment itself. Drupal has a very nice hook_menu that allows you to create pages with a specific URL. These pages can process data, serve data or both. The only drawback, if so, was that hooks only works on modules, so I had to create my own. I decided to add it to my custom site module.

  1. function spaniards_menu($may_cache) {
  2.   $items = array();
  3.   if (!$may_cache) {
  4.     // Add comment
  5.     $items[] = array(
  6.       'path' => 'add/comment',
  7.       'title' => 'Add Comment',
  8.       'callback' => 'spaniards_add_comment',
  9.       'callback arguments' => array(),
  10.       'access' => user_access("post comments"),
  11.       'type' => MENU_CALLBACK,
  12.     );
  13.   }
  14. }
  15.  
  16. function spaniards_add_comment() {
  17.    global $user;
  18.    $text = $_REQUEST['text'];
  19.    $comment = array();
  20.    $comment['uid'] = $user->uid;
  21.    $comment['nid'] = $_REQUEST['nid'];
  22.    $comment['pid'] = 0;
  23.    $comment['subject'] = substr($text, 0, 20);
  24.    $comment['comment'] = $text;
  25.    // Add comment to DB
  26.    $cid = comment_save($comment);
  27.  
  28.    // Mark the node as viewed by the user
  29.    node_tag_new($comment['nid']);
  30.  
  31.    // Get all comments since the last one
  32.    $fromcid = isset($_REQUEST['lastcid'])? $_REQUEST['lastcid'] : 0;
  33.    print _spaniards_get_latest_comments($comment['nid'], $fromcid);
  34.  
  35.    exit;
  36. }

Just with those two functions we can bypass Drupal’s comment submission system and add new comments to the database. As long as we use the comment_save function, the actual process involved in saving a comment is safe (hooks get called, watchdog logging works, etc). Also, hook_menu guarantees only requests with the right access will be able to add a comment (in this case the user_access function verifies the user has “post comments” permissions granted).

Once the comment is saved, the node is marked as viewed by the user, to remove all “new” labels from the new comments added.

Finally the comment is rendered and returned to the user. But wait! What if another user has posted a comment in between? During a conversation between multiple users this will happen very often. The solution: return all comments added since the last comment visible when the user visited the thread.

We will see how to do that next.

Series:
Ajax comments in Drupal 5: How I made it – Part I
Ajax comments in Drupal 5: How I made it – Part II
Ajax comments in Drupal 5: How I made it – Part III

See the ajax comments in action

Please, note this is a WordPress based blog. The Drupal website where I have implemented the ajax comments is www.spaniards.es

7 responses so far

Highlighting elements with Firebug console

Dec 02 2008 Published by Eneko Alonso under uncategorized

Today I had an issue with an element was breaking a page layout, making the page width wider than 960px. I tried to find it with Firebug, looking at the layout properties of some divs and elements, but I couldn’t find it. I knew it had to be there, so I decided to highlight all div elements in the page with a red border. And it worked! I found it right away.

Here is the code I used to highlight the fields (I had to do it in prototype only):

Prototype JS:

  1. <img src="http://l.wordpress.com/latex.php?latex=%28%27div%27%29.each%28function%28item%29%7B%20%20%24%28item%29.setStyle%28%7B%20border%3A%20%27solid%201px%20red%27%20%7D%29%3B%7D%29%3B%3C%2Fpre%3EjQuery%3A%3Cpre%20lang%3D%22javascript%22%3E%24%28%27div%27%29.css%28%27border%27%2C%20%27solid%201px%20red%27%29%3B%3C%2Fpre%3EMootools%3A%3Cpre%20lang%3D%22javascript%22%3E&bg=0D324F&fg=FFFFFF&s=1" title="('div').each(function(item){  $(item).setStyle({ border: 'solid 1px red' });});

jQuery:

$('div').css('border', 'solid 1px red');

Mootools:

" style="vertical-align:-20%;" class="tex" alt="('div').each(function(item){  $(item).setStyle({ border: 'solid 1px red' });});

jQuery:

$('div').css('border', 'solid 1px red');

Mootools:

" />('div').setStyle('border', 'solid 1px red');

PS: Note that by adding a 1px border to all divs in the page, the page layout will be affected and some items may display improperly. Of course, you can use other colors than red :)

No responses yet

Injecting javascript with Firebug

Nov 30 2008 Published by Eneko Alonso under uncategorized

Sometimes you may want to include a Javascript file on a live web page to see how things will work or to try new things. Usually you would do this by editing the source code including the new file but there is a fastest way if you have Firefox with Firebug. Just run the following code in the Javascript console:

  1. var headID = document.getElementsByTagName("head")[0];
  2. var newScript = document.createElement('script');
  3. newScript.type = 'text/javascript';
  4. newScript.src = 'http://enekoalonso.com/lib/jquery-1.2.6.min.js';
  5. headID.appendChild(newScript);

Here I am loading the jQuery library from my own server, but I could load any Javascript from any server on the net. The best is this will work on any website, whenever you have access to the server or not. Once you have the Javascript loaded you can use it right away. For example, here on this blog I could use jQuery to check how the header will look if its height was only 100px by running the next command on the console:

  1. jQuery('#header').css('height',100);

Yep, it won’t look very good, right? Try it! You have to see it by yourself. Firefox is great. Firebug is awesome. And Firebug’s Javascript console is the best thing ever!
PS: In case you are wondering, the Javascript file injected will exist only on the current browser session.

No responses yet

JS.Class: a very nice object oriented approach

Nov 29 2008 Published by Eneko Alonso under uncategorized

I have been using jQuery and Mootools in the last few months at work, among other libraries. So far, jQuery was my favorite because it is very fast and encapsulated (it doesn’t interfere with other librearies, etc). But I love Mootools too because of its object oriented approach and beautiful things like the Class class which lets you create JS classes very easily. Every time I use jQuery I miss that from Mootools.

Well, a copuple of days ago I have discovered JS.Class which I think is the best thing I have seen so far in object oriented Javascript. It is awesome! Very well designed and documented and very well encapsulated.

JS.Class is not a library for DOM manipulation but just a framework to build object oriented code. And I love that! I think it is way better to focus on a single task, instead of trying to cover it all. This makes JS.Class a very lightweight library too :)

Copy-constructor test class in Mootools:

var myClass = new Class({
  1.   text: '',
  2.   say: function() {
  3.     console.log(this.text);
  4.   }
  5. });

Copy-constructor test class in JS.Class:

var myClass = new JS.Class({
  1.   text: '',
  2.   say: function() {
  3.     console.log(this.text);
  4.   }
  5. });

As you can see, the way of creating classes is almost the same. So what makes JS.Class better than Mootools? Where, both allow inheritance and extending classes with other class-helpers, but JS.Class does this better. I’ll work on some example code to prove this :)

PS: Don’t take me wrong, I still love Mootools and I’ll still use it on web development. But I try to use JS.Class as much as possible.

No responses yet