Tutorial 1: Creating new Javascript widgets

Last modified: January 24, 2006 - 18:34

If you want to implement functionality not already available, you'll create a new Javascript file and then write PHP calls that add to the page what's needed for the Javascript to work.

So, to come up with a simple if fairly useless example, say we want to write a module that allows users to click on specific words and get a message with further information. Here's how we might do it.

  • Use CSS class selectors to identify the elements to add the Javascript to.

    You might be familiar with the approach of adding Javascript to an element through parameters, like this:

    <span onclick="doSomething()">click me</span>

    In Drupal, we consider such hard-coding inelegant and also error-prone. Instead, we provide a way to identify elements that Javascript can be attached to selectively, after the page loads. Anything that can identify an element will do (e.g., an id attribute), but we tend to use CSS class selectors. So, instead of the above, we would output something like:

    <span class="clickInfo">click me</span>

    Often you'll need to pass information to your scripts. In this case, we want to pop up a message--but of what? The simplest way to pass the information is to use another attribute of the element you're giving the class to--in our case, a <span> element. Candidates are id and title attributes. Example:

    <span class="clickInfo" title="Hello world">click me</span>

    Of course, rather than manually outputting the class and other attributes, you'll want to write a PHP function that adds them. In our case, in a click_info.module file, we might use something like the following:

    <?php
    function click_info_make($text, $words) {
      foreach (
    $words as $word => $message) {
       
    $text = str_replace ($word, '<span class="clickInfo" title="' . $message . '">' . $word . '</span>', $text);
      }
      return
    $text;
    }
    ?>

    Now we can add the needed elements to a particular string through a call to that function. So, if we want to make every occurrence of the word "Drupal" clickable, with the message being "rocks!", we could use a _nodeapi hook:

    <?php
    /**
    * Implementation of hook_nodeapi().
    */
    function click_info_nodeapi(&$node, $op, $teaser, $page) {
      switch (
    $op) {
        case
    'view':
         
    $words = array('Drupal' => 'rocks!');
         
    $node->body = click_info_make($node->body, $words);
      }
    }
    ?>

  • Put your functionality in a new Javascript file.

    In most or all cases, you'll want to create a new Javascript file for your new functionality.

    In the file, you'll need at the very least an "autoattach" function. The purpose of this is to attached the desired behaviours to page elements. Note that function and variable names in Drupal Javascript are written in "camel case". Call your javascript file, and its "autoattach" function, after the behaviour. So in our case, we would create a clickInfo.js file, and include in it a clickInfoAutoAttach function.

    The first thing you need to do in your autoattach function is answer the question "What elements do I attach this behaviour to?" Typically, in your autoattach function you will first select all elements of a given node type, then iterate through them and see if they have right class. In our case, we're looking for (a) span elements that (b) have classset to clickInfo. When we find one, we want to attach a particular behaviour to it: that, when clicked, it will pop an alert up, with the data we've encoded in an attribute called info. Just to be fancy, we're going to make the onclick action trigger a custom function. So our autoattach function will look like this:

    function clickInfoAutoAttach() {
      var spans = document.getElementsByTagName('span');
      for (var i = 0; span = spans[i]; i++) {
        if (span && hasClass(span, 'clickInfo')) {
          // Read in the message from the 'title' attribute
          span.message = span.getAttribute('title');
          // Set the title to NULL, so no tooltip will display
          span.removeAttribute('title');
          span.onclick = function() {
            alert(this.message);
          }
        }
      }
    }

    Now all we need to do is ensure this code is run when it should be. To do this, we add a call at the beginning of our script using two more drupal.js functions:

    if (isJsEnabled()) {
      addLoadEvent(clickInfoAutoAttach);
    }

    This snippet (a) tests if appropriate Javascript support is present, and, if so (b) registers our autoattach function to be called when the page has loaded (and, therefore, all the page elements are available to have behaviours attached to them).

  • Add style (optional)
    In many cases, but not all, you'll want to change the display of the elements you're working with. This is best done through an included .css file. In our case, we want to let users know that specific words/phrases are clickable. We can do this through a .css file, click_info.css:

html.js .clickInfo {
  background: yellow;
  cursor: pointer;
}

Note the html.js selector: it selects the class 'js' on the <html> tag. Drupal adds this class through JavaScript, so it means this style will only be used if JavaScript is enabled.

  • Send the needed files

    We've got all the needed elements, so all that remains is to send them to the user. We do this with drupal_add_js(). Ideally, we'll add the Javascript only when we know it's needed on a page--i.e., when we've created target elements. In this case, since we add those elements in a _nodeapi() hook, we can add calls to that function.

    <?php
    /**
    * Implementation of hook_nodeapi().
    */
    function click_info_nodeapi(&$node, $op, $teaser, $page) {
      switch (
    $op) {
        case
    'view':
         
    $path = drupal_get_path('module', 'click_info');
         
    drupal_add_js($path . '/click_info.js');
         
    theme_add_style($path . '/click_info.css');
         
    $words = array('Drupal' => 'rocks');
         
    $node->body = click_info_make($node->body, $words);
      }
    }
    ?>

    Note that we don't need a separate call for adding drupal.js; it's added automatically with the first drupal_add_js() call. Similarly, we don't need to worry about scripts being added twice, as a static variable in drupal_add_js() ensures that already-added files are skipped.

  • See the completed module. To use it:

    • Install and enable the module
    • Create a new node (e.g., a 'story'), and include the word Drupal.
    • View the page. The word Drupal should be highlighted. Click on it to get the alert 'rocks'.

    Drupal 5.1 Changes

    rho_ - February 8, 2007 - 20:53

    There are only a couple of changes to make this work in Drupal 5.
    Firstly you will have to add the .info file to allow Drupal to recognise this as a module. So you would need to add a new file called click_info.info containing something like,

    ; $Id: click_info.info,v 0.5 2007/2/2 16:13:46 ryan Exp $
    name = click_info
    description = Javascript testing in D 5.1
    package = Click_Info - optional
    version = VERSION

    This will allow you to see the module in the module listing and be able to activate it.

    Secondly, as mentioned above by tic2000 you will have to change the line in click_info.module that reads,

    <?php
    theme_add_style
    ($path . '/click_info.css');
    ?>

    to read,

    <?php
    drupal_add_css
    ($path . '/click_info.css');
    ?>

    And lastly, there has been a change in the way nodeapi handles content. You can no longer change $node->body directly and have it change the way content is displayed, you will have to change $node->content['body']['#value']. So the line that reads,

    <?php
    $node
    ->body = click_info_make($node->body,$words);
    ?>

    will change to,

    <?php
    $node
    ->content['body']['#value'] = click_info_make($node->content['body']['#value'],$words);
    ?>

    After these changes have been made, everything should be working fine. For more info on how to update things to Drupal 5, check out the Converting 4.7 to 5.0 article in the Drupal Handbook.

    Peace,
    Ryan Oles


    Pare Technologies
    info at paretech dot com

    www.paretech.com

     
     

    Drupal is a registered trademark of Dries Buytaert.