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

Comments

Stalski’s picture

I am trying to write a js script to update all flag links on the same node.
for instance, a wishlist.
Suppose we are on a node detail of a product and it shows a flag link "add to wishlist". Clicking it, a right sidebar block updates and the node appears (views block loads via ajax on after update event) in the wishlist. This wishlist shows the remove flag link as well. So at that moment there are two flag links for the same node. I click "remove from wishlist" in de wishlist itself, meaning that block will update, removing that node from the wishlist. Problem here is that the other flag link in full node is still "remove from wishlist".
So how could we do a search or body context replace of all affected flag links?
Any ideas will be helpfull

Stalski’s picture

Drupal.orderlist.getShoppingCart = function(event, data) {
 
  $.ajax({
    type: "POST",
    url: Drupal.orderlist.path,
    cache: false,
    dataType: "json",
    success: function(html, status) {
     
      var link = $(data.link);
      var newLink = $(data.newLink);
      var targetClass = '';
      
      // Find the class of the current clicked flag-link 
      // E.g. flag unflag-action flag-link-toggle flagged flaglink-1843 flag-processed
      var classes = link.find('a').attr("class").split(" ");
      jQuery.each(classes, function() {
        var class = String(this);
        if(!class.indexOf('flaglink-')) {
          targetClass = class;
        }
      });
      
      if (targetClass != '') {
      
        // Replacement link, anchor inside newLink
        var reversedLink = newLink.find('a');
	      
        // Update all identical flag links on the page,
        // except the one that was clicked (flag already did it)
        $('.' + targetClass + '').not(link.find('a')).each(function() {
          var span = $(this).parent();
          span.find('a').remove();
          span.append(reversedLink);
	});
      }
      
      // Refresh the view/block with the orderlist
      $("#" + Drupal.orderlist.block_div + " .content-inner").html(html.view);

      Drupal.flagLink();
    }
  })    
}

And a link alter to add the classes

function orderlist_preprocess_flag(&$variables) {
  if ($variables['content_id']) {
    $variables['flag_classes'] .= ' flaglink-' . $variables['content_id'];
  }
}

To embed the view (menu callback to return json)


/**
 * Callback function to fetch a embedded view
 * @return json string
 */
function orderlist_ajaxcall() {
  drupal_json(array('status' => TRUE, 'data' => orderlist_shoppingbasket(), 'view' => views_embed_view(ORDERLIST_FLAG_NAME, ORDERLIST_BLOCK_AJAX_UPDATE_NUM)));
}
drupalina’s picture

I'm trying to implement community-moderated content with Flag and FlagAbuse and Actions-- basically the same functionality as we see on Digg.com with "X Bury" button.

Expected behavior: you click a flag and the teaser of that node instantly vanishes as if it was never there.

I've been searching for this "instantly-vanishing" functionality for more than a year, and I can see that many people (especially with Drigg sites) would love to have it.

How can we implement such a javascript with Flag module?

mooffie’s picture

1. Add a special CSS class to buried nodes. This page explains how.

2. In your stylesheet hide any portion you don't want shown of a "buried" node. Example:

.node-flagged-bury-self .body {
  display: none;
}

(Naturally you don't want to hide the title or the flag links (because you want to allow people to unbury a node)).

3. Add JavaScript to hide/remove this special CSS class upon flagging/unflagging:

$(document).bind('flagGlobalAfterLinkUpdate', function(event, data) {
  if (data.flagName == 'bury') {
    $(data.link).parents('.node')[data.flagStatus == 'flagged' ? 'addClass' : 'removeClass']('node-flagged-bury-self');
  }
});

(I haven't checked this code. Expect typos.)

yngens’s picture

I've found a solution to my problem, described on #963578: How to list users who flagged a comment in the body of the comment itself?. The following piece of code, placed in comment.tpl.php is working very well:

  $query="SELECT name, users.uid AS uid FROM users INNER JOIN flag_content flag_content_users ON users.uid = flag_content_users.uid AND flag_content_users.fid = 3 AND flag_content_users.content_id = $comment->cid";
  $result=db_query($query);
     while ($row = db_fetch_array($result)) { print '<a href="/user/'.$row['uid'].'">'.$row['name'].'</a>, ';   } 

However, I would like a user, who has just flagged a comment, to be seen in the body of the comment immediately and not after reloading the whole page. If I place the above code in between, let's say,

, how can I load that division via The Flag API (JavaScript) just after flag has been clicked on?

FMB’s picture

The flagGlobalAfterLinkUpdate event is no longer fired clientside, but with a patch you can call AJAX commands in a PHP event subscriber.