jQuery UI Widgets, Drag and Drop (London Drupal Drop In Dec 2011)
jQuery UI Widgets, Drag and Drop (London Drupal Drop In Dec 2011)
Another great Drupal London event last night!
Chris (matason on Drupal.org) gave a great presentation of the new Drupal 7 Workbench module. It was great to see it in action and one I can see using myself a lot in the future. John and Rob demonstrated their Survey Builder module, a new backend for the excellent Form Builder tool. This looks like it has a lot of potential, so one to keep an eye on for the future I think. Vamory gave a really useful guide to making multi-step forms in FAPI, and then I got to talk about one of my pet favourite subjects, jQuery!
I showed the Active Tags module, and demonstrated adding some of the jQuery UI widgets to a Drupal 7 site, then showed how to build up a more complex interaction by adding Drag and Drop functionality to a couple of Drupal Views, then hooking up a Rules backend linked together by Page Manager. In case you missed it, or wanted more details, read on...
You can get the full set of slides (minus the screen cast video demos) from my slideshare account by clicking the above title image.

Progressive enhancement is a method for adding nice interaction and widgets to your Drupal site while maintaining compatibility for non-javascript browsers, and accessibility. It basically means you generate the HTML for your widget then insert it into the HTML DOM object using Javascript. Then you hide the old widget. When a user interacts with your widget you have to complete the value in the old widget, so for example, if the old widget was a text box and you have replaced it with a slider, when the users moves the slider you have a Javascript function attached to the change event that takes the new position of the slider and puts it into the text box. This means that users without javascript just see the old widget, but most users get the better experience of using the jQuery widgets.

Active tags is a Drupal module that offers a replacement widget for the Drupal core tagging field. Instead of textbox requiring a comma separated list of tags, you insert the tags one at a time and press enter or click the add button. Behind the scenes this builds up the comma separated list in Drupal's own textfield, so it doesn't affect how Drupal sees the tags.

I talked for a bit about various ways of including Javascript code in your Drupal site, ways for themers to include it in Drupal Themes, and lots of ways for module developers to use drupal_add_js(). I pointed out that most widget and interface stuff might be better put in a module so it can persist if you change themes on the site.


I mentioned drupal_add_js(), hook_library() and drupal_add_library() as ways to include Javascript code, and gave some simple examples of attaching jQuery UI Widgets to Drupal Forms. I wont go through the examples here, if you want to you can get the slides and look. It was just some simple code examples:

Turn the login block into a dialog box

Change a set of radio buttons into jQuery UI buttons
There was also an example to make a textarea automatically resize to fit the content as you type, using the jQuery autoResize plugin.

I explained how Drupal Behaviors are used as a replacement for the $(document).ready() function in Drupal. There's more information about this in the Drupal documentation page Managing Javascript in Drupal 7.

Now we get to building a more complex user interaction using jQuery UI and Drupal. In this example I create two views. One where the items become Draggable, and another view that becomes the Droppable target. This fires off a drop event, which triggers an AJAX call back to Drupal. Page manager picks up this request and passes it on to Rules where it is processed. I posted a brief video demo of it in action at the top of this post.

This site has a content type called Meeting, and a Relation (called attendee) that links users to meetings. The interaction we are building creates a relation item when a user is dropped on the meeting.

The two views are created using Views in the usual way. I've chosen to make one a simple unformatted list, and one as a grid view, but the drag and drop interactions can be attached to any DOM element. In the first view (Staff members) I have overridden the output to put the .dproj-draggable class on each item. This is
what will be used to attach the draggable interaction. In the second view I have added the .dproj-droppable at the View level ('CSS classes' in advanced options in View) so the whole view can be given the jQuery UI droppable interaction.

When the draggable is dropped on the droppable an event is fired. This event is assigned a callback function that sends a request back to Drupal via AJAX. In order to do this is must have a callback path that includes arguments to indicate what was dropped where. This is why we build a callback from two parts, one from the dragged item and one from the dropped item. These two arguments are generated as part of the views, as a span wrapped in a callback class. This allows us to hide them from the user using CSS and extract their values in the jQuery drop event.

Here is the code that powers this:

The draggable is just given one option. The revert option is set to 'invalid' which means that if a draggable is dropped anywhere other than a valid droppable area it reverts back to it's original position.
The droppable takes a few more options. The most important being the 'drop' function that is called when a draggable is dropped on the droppable. You can access the droppable element using 'ui.droppable' as passed in to the drop function. In this function I am doing a few things:
I hide the original droppable (you might want to revert it if you want to allow it to be dropped again elsewhere), then fade out the droppable slightly to give some visual feedback that something is happening.
Then I work out the ID of the view that has been dropped on, this is because I will later need to extract this view from the response and replace the current version with the new version of the view from the AJAX response.
I then calculate the callback path using the hidden callback element from the dragged item and the droppable. Then make an AJAX call using $.ajax to this callback path. The ajax takes a success function that it calls when the ajax response comes back. This function parses the new version of the view from the data that is returned, replaces the version in the page with this new version, then fades the view back to full.

Page manager (part of ctools) is used to register the callback function with Drupal. This allows you to specify a path with arguments. As you can see above I have specified two arguments, the user and the node, i.e. the dragged item and the item it was dropped on.

Here the arguments are converted into a context, you can load up the related entities, making them available for Rules.

You need the Rules Bonus Pack for this. It provides a module called 'Miscellaneous' which includes a really cool bit of functionality for linking Page Manager to Rules. You can see this in the above image. It creates a reaction that triggers an event in Rules.

You can then use Rules to process the request. Set up a rule to trigger on the event you created in Page manager. Then add the following actions:
- Create a new variable of type 'list of entities'
- Add the user entity to the list
- Add the meeting node entity to the list
- Create a new Relation entity, using the list as it's value
- Force a redirect to a page that just includes the updated view matching the droppable.
I hope that explains everything. If you have any questions, drop me a line or leave a comment and I'd be happy to try an explain further.

Comments
I thought this was a really interesting presentation. We've been using jquery_ui recently and have been impressed with how easy it is it use. Wasn't aware of the rules / page manager integration - neat!
Oh, and thanks for making an example of me :)
John
- reply
John Griffin (not verified)Thanks, I'm going to try and make use of this in the next site I build.
- reply
Eddy (not verified)Its not my first time to see this web site, thanks for the information.
- reply
Scott Staples (not verified)The drag and drop functionality you have shown looks great, and I think it opens up drupal to a world of exciting capabilities.
What is the chance of getting a step-by-step screencast posted? The outline above is helpful but is still slightly difficult to folllow in full for a new drupal user. Thanks
- reply
Robert (not verified)Thank you for showing us how jquery can be used within drupal. I too wasn't aware of the page manager integration. I also agree with the post above that a screencast could be very useful.
Pete
- reply
Pete (not verified)As soon as I've stopped celebrating the festival period I'll get a screencast recorded.
- reply
DarrenThanks Darren, appreciate it. Hope you enjoy your break.
- reply
Robert (not verified)I'm looking forward to the screencast as well.
- reply
Carson (not verified)A screencast would be hugely helpful. Thanks.
- reply
Howard (not verified)Are we any closer to getting a screencast? I am really looking forward to this one. Thanks in advance
- reply
Pete (not verified)Yes, closer. I wrote a script for it, I just have to find a couple of hours to sit down and record it!
- reply
DarrenDarren, thanks so much for moving forward with the screencast. I'm eager to watch it and learn!
- reply
Carson Weber (not verified)Still keen on seeing the screencast ;)
- reply
Robert (not verified)Any word on the screencast? *smile*
- reply
Carson Weber (not verified)Can't wait to see the screencast! Thanks, Darren!
- reply
Steve (not verified)how nice is this way to add a relation... i would love to see the Screencast
- reply
alxis suarez (not verified)greate!
thanks.
- reply
Guest (not verified)Will this implementation work with flags as well? Do you have to use the Relation module to make it work?
Example would be a node is flagged with RED
User drags to a "list"(view) of nodes flagged with BLUE.
User drags to a "list"(view) of nodes flagged with GREEN.
And Visa Versa.
These are not taxonomy terms.
You used relation which makes perfect sense for your implementation but I have a much different use case.
My assumption is that you should be able to do this between multiple blocks, views or panels.
I there any way you could post a link to a feature of this implementation or your module code?
Looking forward to the screen cast.
Thanks,
Will
- reply
Will (not verified)Yes, it could be configured to work with flags too. In fact, it will work with any workflow you can build with Rules module. You link up your front end jQuery code with Rules via page manager.
- reply
DarrenI have looked through the slide share at least 10 times hoping to discover something I missed. All I can figure is that what I am missing isn't there.
Unfortunately the drupal learning curve has reared it's ugly head again in my life. So in an effort to end the suffering I beg of you to please post the code to this technique somewhere that others can see it.
Tutorials are great but they take time and resources. Posting the code will allow myself and others to learn it much quicker.
I am a much better reader than a video watcher which is why I am frustrated.
Thanks.
- reply
Will (not verified)Hi Will, I've uploaded the code to GitHub. I'll write a short blog about it now...
- reply
DarrenWhen creating the page with page manager, are you selecting the HTTP response, and if so, is it 301, with no redirect filled in? The option seems to be to pick a layout, and your screenshot doesnt show layout tabs on the variant.
- reply
Scott (not verified)How do I get a remove bottun next to the attendees name?
- reply
Nicklas (not verified)I also need to know how to remove attendees
- reply
Andrew K (not verified)