The Flag API (JavaScript)

Sometimes we wish to carry out a JavaScript action after some content has been flagged (or unflagged).

For example, we may wish to refresh a block on the page, via AJAX. This could happen if, for example, this block lists bookmarked content. We wouldn't want to have a stale block on the page.

The Flag module provides us with two "pseudo" events to which we can attach our JavaScript handlers. These events are both triggered after content has been successfully flagged (or unflagged).

The two events

The flagGlobalAfterLinkUpdate event
This event is triggered immediately after a flag link has been updated. (Flag links appear in two flavors: "Bookmark this!" and "Unbookmark this!", and when we speak of "update" we mean this change in appearance).
The flagGlobalBeforeLinkUpdate event
This event is triggered just before a flag link is updated. As will be explained later, the existence of this event is for cosmetic reasons only: it allows us to carry out actions while the throbber () is still visible.

Example

The following handler, attached to the flagGlobalAfterLinkUpdate event, will pop up an alert box with the details of the flagging operation.

$(document).bind('flagGlobalAfterLinkUpdate', function(event, data) {
  alert('Object #' + data.contentId + ' (of type ' +
    data.contentType + ') has been ' + data.flagStatus +
    ' using flag "' + data.flagName + '"');
});

Where should you put this code? In a file. And make Drupal "load" this file.

For example, Drupal 6 users could put it in a 'my_script.js' file in their theme's folder and add "scripts[] = my_script.js' to their theme's .info file. Then they should clear Drupal's cache or else Drupal's theming system won't notice this new file. For Drupal 5 users the instructions are a bit more complex: they should use a drupal_add_js() call somewhere in their 'template.php' file.

Several interesting observations about our code:

  • We attach the handler to some arbitrary object (the document object) and not to the flag link itself. That's why we nickname this event a "global event". When content is flagged, or unflagged, the event is "broadcasted" to all listening elements. This scheme is used mainly because Drupal 5 doesn't support the new 'Drupal.behaviors' scheme.
  • Our handler receives two parameters: the first is the obligatory event object, and the second is an object containing some information about the flagging operation (You may wish to peek at the flag_page() PHP function to learn about all the available fields).

Example: Making "popup" messages vanish after 5 seconds

Avid users of the Flag module must have noticed that the "popup" messages shown after flagging content aren't removed from the page. This is a glitch that should be fixed, of course, but in the meantime here's how to fix the problem:

$(document).bind('flagGlobalAfterLinkUpdate', function(event, data) {
  window.setTimeout(function() {
    $('.flag-message', data.link).hide();
  }, 5*1000);
});

The data object contains a data.link field, which points to the link's DOM element.

Example: Refreshing a block on the page, via AJAX

In this case we need to fetch a block's HTML from the server. The Flag module cannot do this. This isn't its purpose. We need to use some other module for this. I've chosen the component module for this demonstration. That module provides a ?q=component/block/MODULE/BLOCKID URL where blocks' HTML can be fetched.

$(document).bind('flagGlobalAfterLinkUpdate', function(event, data) {

  // The following line is needed for compatibility with Drupal 5,
  // which doesn't have 'Drupal.settings.basePath'. Drupal 5 users
  // must update the '/' to match their bash path.

  var base_url = (Drupal.settings && Drupal.settings.basePath) ||  '/';

  function blockExistsOnPage(module_name, block_id) {
    var domId = 'block-' + module_name + '-' + block_id;
    return $('#'+domId).length != 0;
  }

  function refreshBlock(module_name, block_id) {
    var domId = 'block-' + module_name + '-' + block_id;
    $.get(base_url + '?q=component/block/' + module_name + '/' + block_id, null, function(newHtml) {
      // Replace the old block with the new one.
      var domElement = $(newHtml).insertAfter('#'+domId);
      $('#'+domId).remove();
      if (Drupal.attachBehaviors) {
        Drupal.attachBehaviors(domElement);
      }
    });
  }

  //
  // Note:
  //
  // Change the 'flag_bookmarks-block_1' below to match
  // your block's ID.
  //

  if (data.flagName == 'bookmarks') {
    if (blockExistsOnPage('views', 'flag_bookmarks-block_1')) {
      refreshBlock('views', 'flag_bookmarks-block_1');
    }
  }
});

Example: Refreshing a view on the page, via AJAX

You can alter the code above to work with views. But there's now a module to do exactly this: Views Flag Refresh.

The data.link field

A previous example showed that the data object contains a data.link field pointing to the flag link's DOM element. This makes it possible for us to learn about the context of the flagging operation. Examples:

if ($(data.link).parents('.block').length) {
  // The link clicked was inside a block
}
if ($(data.link).parents('.node').length) {
  // The link clicked was under a node
}

Aesthetics: the flagGlobalBeforeLinkUpdate event

When the flagGlobalAfterLinkUpdate event is triggered, the link shown on screen is the new one, and, also, the throbber is gone already. If we're now to carry out some lengthy operation --e.g. an AJAX operation-- we won't enjoy a throbber to notify the user of the wait.

The solution is to cancel the update of the link. We do this by triggering our code in the flagGlobalBeforeLinkUpdate event and in it notifying the Flag module of our wish: by setting data.preventDefault to true. It's now our responsibility to update the link, so this method is mainly useful in AJAX scenarios where we refresh the section of the page where the flag link appears in.

(Technical note: It's more common to prevent default actions form taking place by calling event.preventDefault(), or by doing return false in our handler; However, we resort to data.preventDefault = true because the antique jQuery libraries shipped with Drupal prevent us from "doing it the right way".)

Example: Hiding a view row when unflagging

A question:

Refreshing a view when a flag link is clicked.

The originally submitted idea is faulty. Someone please update with new code.

Guide maintainers

mooffie's picture