People have been asking how to refresh a part of the page after something has been flagged. (example.)

While refreshing part of the page is the task of some other rmodule, not of ours, our module at least has the duty to notify contributed code that "I've just flagged something".

This patch addresses this problem. It introduces a "flagSuccess" event contributed code can subscribe to.

For example, a user can add the following to her theme's javascript:

$(document).bind('flagSuccess', function(event, data) {
  if (data.contentType == 'node') {
    alert('Node number ' + data.contentId + ' has been ' + data.flagStatus + ' using flag ' + data.flagName);
    // ...refresh some block on the page...
  }
});
CommentFileSizeAuthor
#7 flag_events.diff2.34 KBmooffie
flagSuccess.diff2.13 KBmooffie

Comments

mooffie’s picture

Comments:

  1. The new link element too is made available to the event handler (through data.link) in case the user wants to to something with that area of the page. (Incidentally, users are asking: "How can I remove the message popup after 5 seconds?" Now there's the means to do this.)
  2. The patch also fixes a documentation bug. The "crash" comment was in the wrong place. I'm aware that the Style Police won't approve of the new place, but our documentation has to be correct.
smitty’s picture

That's really good news!

I applied this patch to the 5.x-1.0-beta6 version and at least it seems to cause no problems.

But now I don't know where to put the js-code and what to fill in to "// ...refresh some block on the page...".

Sadly I have only very little experience in js and I don't know the first thing about ajax.

I'm using the garland theme and the block is based on views (it's almost the same, which you delivered as flags_bookmarks. The only differences are: less fields and it’s a block-view instead of a page-view).

Perhaps you could give me a hint, how to go on with this or where I should look for more information? Thanks a lot!

mooffie’s picture

Smitty, the ultimate intention of this patch is that a handbook page be written and in it answers for all your questions.

I have an early draft of this page. There are still some peripheral tasks to do (like porting the 'component' module to D6, which I've just done).

quicksketch’s picture

This looks pretty good. I wonder if it would be better to call the "flagSuccess" event on the link rather than on the entire document. This would give some contextual information to the flagging event about which link was actually clicked.

mooffie’s picture

I wonder if it would be better to call the "flagSuccess" event on the link rather than on the entire document.

I don't see how this can be implemented for D5: if links are updated, or new links are brought in, the event has to be re-attached to them. D6 has Drupal.behaviors, but D5 hasn't.

This would give some contextual information to the flagging event about which link was actually clicked.

That's why I put the link in 'data.link', so the event handler can see it.

[...] rather than on the entire document.

The event isn't attached to the "entire" document. I happened to attach it to the document element, because it's a safe one: ajax snippets aren't likely to overwrite it.

What the $.event.trigger('flagSuccess', [data]) line does is "broadcast" the event to all 'flagSuccess' handlers, on any element. (This isn't inefficient, because jQuery keeps track of the handlers.)

BTW, calling bind() doesn't remove the previous event handler, so we don't have to worry lest different snippets all doing $(document).bind(... will clash.

quicksketch’s picture

Status: Needs review » Reviewed & tested by the community

Thanks, I'm not entirely sure how the binding of custom events works so this is new territory to me. I didn't notice the data.link = updateLink(element, data.newLink); line, which is exactly the concern I had regarding finding the "context" of which link was actually clicked.

This looks like a great approach (way better than what I currently have in Fivestar, which offers something similar), so let's put it in. :)

mooffie’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new2.34 KB

This is a revised version. I splitted this event into two: "beforeLinkUpdate" and "afterLinkUpdate" (the actual namse have a "flagGlobal" prefix).

I wrote a handbook page explaining this:

Triggering JavaScript actions

mooffie’s picture

(When this feature gets committed I think I'll "wontfix" the #312241: Refreshing a group of links feature request, because that feature could be implemented by refreshing part(s) of the page, as demonstrated in the handbook page.)

mooffie’s picture

Nathan, are you ok with the new patch?

quicksketch’s picture

Status: Needs review » Reviewed & tested by the community

Yep, sorry I was contemplating the namespace choice of calling the method "flagGlobalAfterLinkUpdate", which seemed a bit long-winded. However after looking at the docs you put up, I think it's a reasonable name choice considering the event occurs on the entire document but actually references a single link. So, all in all, looks ready to me!

mooffie’s picture

Status: Reviewed & tested by the community » Fixed

Committed.
http://drupal.org/cvs?commit=159199
http://drupal.org/cvs?commit=159200

The "Global" in the names is in case we have per-element events in the future.

Yeah, I don't like these long names either ...and they remind me of Win32 API.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for two weeks with no activity.

witzel’s picture

Component: Code » Documentation

I'm using Drupal 6. I think the handbookpage describes exactly what i need, but I haven't really understood, how to do it : I'm using Panels 3 and want to refresh the flag-view-block in my right column, when someone clicks the flag-link in my left column.

1. Do I have to install the component module?
2. What do I have to change in this handbook-page-code:

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


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

  function refreshBlock(module_name, block_id) {
    var dom_id = '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.
      $('#'+dom_id).after(newHtml).remove();
    });
  }

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

I just found these classes in my css: view-display-id-block_1 and view-dom-id-1. Is that the block ID, that i have to replace in the code and which module name do i have to fill in the code?

Sorry, you see I dont really understand what the code means, but i'd like to get it working. Thanks

ju.ri’s picture

I have exactly the same problem, just can't get it to work. can someone help with a little walk-through? I'm not even sure if my theme picks up the script correctly..

turbanov’s picture

Yes, you do need to install the component module. The component module provides a way to access the specific content via an url, which we then use to replace the existing content. I'm sure there's a better way to do this all but since I don't really understand anything about javascript, this is what I managed to hack together for Drupal 6 (put this in you page.tpl.php or a separate .js file and remember to include this in your theme's info-file):

<script type="text/javascript">
	
	var base_url = (Drupal.setting && Drupal.setting.base_path);
	
	$(document).bind('flagGlobalAfterLinkUpdate', function(event, data) {

	  function blockExistsOnPage(module_name, block_id) {
		var dom_id = 'block-' + module_name + '-' + block_id;
		return $('#'+dom_id).length != 0;
	  }
	  
	  function viewExistsOnPage(view_id) {
		var dom_class = view_id;
		return $('.'+dom_class).length != 0;
	  }
	
	  function refreshBlock(module_name, block_id) {
		var dom_id = '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.
		  $('#'+dom_id).after(newHtml).remove();
		});
	  }
	  
	  function refreshView(view_id) {
		var dom_class = 'view-' + view_id;
		$.get(base_url + '?q=component/view/' + view_id , null, function(newHtml) {
		  // Replace the old view with the new one.
		  $('.'+dom_class).after(newHtml).remove();
		});
	  }
	
	  if (data.flagName == 'varaa') {
		  refreshView('vapaat_keikat');
		  refreshBlock('views', 'poimitut_keikat-block_1');
		  location.reload(true);
	  }
	});
	
	</script>

First of all, notice the Drupal.setting.base_path, there's an error in the example here. What I was trying to achieve is a live front page where users pickup items which are then added to their list of items (displayed in a block) and subsequently removed from the main view. Based on the example provided, I derived the url the Component-module provides for individual views and added the function to refresh the view.

Additionally I added the full page refresh at the end with the location.reload(true). I had a problem where the content would get loaded correctly after setting the flag once, but the second time around I would get redirected to the url from the Component module, for whatever reason. However refreshing the page in between would fix this. Thus I added the refresh call.

I've also ignored the check for whether or not the block/view exists at this point.

As you can see, I'm no javascript developer and admittedly do not fully understand what is actually going on here. However, after hours and hours of pondering I managed to achieve what I set out for, so I figured I'd share it with you. Hope it's of some help to you.