Darren Mothersele

Software Developer

Warning: You are viewing old, legacy content. Kept for posterity. Information is out of date. Code samples probably don't work. My opinions have probably changed. Browse at your own risk.

jQuery UI Widgets, Drag and Drop (London Drupal Drop In Dec 2011)

Dec 13, 2011

web-dev

UPDATE (Feb, 2014):This post offers a simple way to update entities using a module from the Rules Bonus Pack that allows you to trigger a Rule from a Panels page variant. This is not a good idea. The HTTP protocol states that you should not implement any state change via HTTP GET. If you do this, you open yourself up to CSRF vulnerabilities. A better approach would be to use the Rules Link module that allows you to use tokenized links, or confirmation pages.

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:

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.