Pages

Friday, August 30, 2013

Drupal behaviors: A quick how to

If you’re adding JavaScript to your custom module it is very easy and tempting to simply add it like this:
jQuery(document).ready(function($){ alert(‘hot dog flavored water’); }); 
Now this code works perfectly fine but what if your JavaScript needs to be executed on page load and after an AJAX request? Imagine you have a view that uses “Views Infinite Scroll” and you want add a CSS class to every result like this:
jQuery(document).ready(function($){ $('.view-display-id-page .views-row').addClass('fancy-pants'); }); 
This will work for the results that are displayed initially but for all the results that are loaded by Infinite Scroll's AJAX call the class is not added. That’s where Drupal behaviors come in handy.  The behaviors will be executed on every request including AJAX requests, so let's do the equivalent of the code above but this time using this method:
Drupal.behaviors.infiniteScrollAddClass = { attach: function (context, settings) { $('.view-display-id-page .views-row').addClass('fancy-pants'); } }; 
I admit that was quick - so here are some explanations:
  • infiniteScrollAddClass: This is your namespace and should be unique. For example, this is typically the name of your module, but it isn't mandatory.
  • context: This is actually really really cool, on page load the context will contain the entire document and after an AJAX request will have all the newly loaded elements. This way you can treat content that is loaded in via AJAX differently than others.
  • settings: This contains information passed on to JavaScript via PHP, it is similar to accessing it via Drupal.settings. For further comprehension I recommend this source.
There obviously are cases where some functionality should not be executed on every request. In such a case its great to use jQuery's .once() method. So let's say we want to give all the initially loaded results in our view an additional class, for something like this we would proceed like so:
Drupal.behaviors.infiniteScrollAddClass = { attach: function (context, settings) { // these are the elements loaded in first $('.view-display-id-page').once(function(){ $(this).find('.views-row').addClass('i-was-here-first'); });  // everybody $('.view-display-id-page .views-row').addClass('fancy-pants'); } }; 
This will add the class “i-was-here-first” to all the view results present on page load, everybody else joining in via AJAX will just get the “fancy-pants” class.
So that’s a quick look at Drupal behaviors, if you haven’t used it do use it!
If you are looking for additional theoretical insight into this topic I can recommend these two sources for further reading:

Drupal 7 modules: The bare necessities

A while ago we wrote about the Drupal 6 modules you really need. Since that post a few things have changed, on one hand Drupal 7 has moved a bunch of those modules to its core, while on the other hand some were replaced by better alternatives.
Of course every Drupal site we build has different requirements, which makes our choice of contributed modules unique for every project. Nevertheless here is our current selection of trusted Drupal modules we don't want to miss. The bare necessities, if you will.
Internationalization & Entity translation
Working in a country, that has four officially recognized languages and English being the Internet's lingua franca, we install these modules without thinking anymore. Multilingual solutions have come on leaps and bounds since previous Drupal releases and despite not being perfect yet they make our lives much easier.
Pathauto
Regardless of how much attention you want to give to search engine optimization in a project - getting the basics right is essential. Pathauto enables you to create URL patterns for all entity types. The Pareto principle will thank you.
Views
This is by far the most powerful module in the Drupal ecosystem. Built on the Chaos tool suite (ctools), it gives the full control to display any kind of entity in the manner you want it. No wonder it will be part Drupal 8's core.
Devel
If you are a developer this one can help you out of the odd pickle. It allows you to gather information on what is happening in Drupal's background. Just remember to disable it on productive environments.
Wysiwyg & CKEditor
Until the day arrives where everybody is a HTML wizard, WYSIWYG editors will remain a feature, which just belong to every Drupal installation.
Google Analytics
The quickest and easiest way to make your websites visits quantifiable. You can only manage what you can measure.
Panels
Need a complex layout on the quick? Panels will be your trusted friend. You can define layout templates and define in which contexts they should appear.
Display Suite / Panelizer
Depending on the level of complexity of our brief we will opt for one of these two masters of data display control. Although both have their pros and cons we tend to use Panelizer more often, since it's benefits can be leveraged on large and complex websites.
Rules
An amazing module, built upon the Entity API, which allows you to define workflows and actions while you barely have to write any code. It integrates into many modules witch makes it a powerful and useful resource.
Webforms
You can argue that allowing visitors to create nodes with appropriate field permissions can act a contact forms - we have seen this approach in the wild... In our opinion using the Webforms module is the better option for the majority of use cases where a form is needed. You don't want your content overview screen cluttered with spam that got through Mollom's net, right?
What do you think? What are your bare necessities?

How Drupal "Views Auto-Refresh" really works

Views is a great module and as you'll probably know it is the most downloaded module in Drupal's history. The possibilities which Views provides you with are almost endless but there is one limitation. The generated output of a view is by default static. What if you want to have a dynamic activity stream as you know it from Twitter or Facebook?
Allow Drupal's unofficial slogan to answer that question: There is a module for that! The module in question is "Views Hacks" and contains another module called "Views Auto-Refresh". There is a blog post about how to implement this module in order to get it to work as you want to, but it seems like this post doesn't cover all of the aspects that are implemented in the "dev"-version. A follow up blog post offers a little more insight, but still not everything I needed.
On a side note: In this tutorial we will use the "dev"-version of "Views Hacks", because the alpha was buggy at the time, and the implementation of the JavaScript part is not in the Drupal way anymore.

Preparation

Views autorefreshb
Let's get started with the implementation. I assume that you have downloaded, installed and activated Views, Views UI, Views Hacks and Views Auto-Refresh. Further I assume that you have created a view for which you want to use the auto-refresh feature.
I will demonstrate the implementation of Views Auto-Refresh by showing screenshots of the actual project.

Implementation

Views autorefresh
Make the following configurations:
  • First of all, you need to duplicate your actual view as a view page. Give it a semantic name like "autorefresh".
  • Give the page a unique path as displayed in the screenshot above.
  • Also give an easy-to-remember machine name.
  • Make sure that the view is using AJAX.
  • Add the Content: Post date (with operator) or any other timestamp as a contextual filter (we used the "Content: Updated date"). Views Auto-Refresh will provide the timestamp needed.
Now head over to your view and add a "Global: Text area" with the text format "PHP Code" and add following code:
<?php print theme('views_autorefresh', array('interval' => '30000', 'incremental'=> array( 'view_base_path' => 'frontpage/autorefresh', 'view_display_id' => 'autorefresh', 'view_name' => 'articles', 'sourceSelector' => '.view-content', 'targetSelector' => '.view-content', 'firstClass' => 'views-row-first', 'lastClass' => 'views-row-last', 'oddClass' => 'views-row-odd', 'evenClass' => 'views-row-even', ))); ?>
Views autorefresh
 
As you can see, there is a base path and a display ID.
The base path equals your defined page path, and the display ID equals the Machine name of the auto-refresh View.
"interval" defines how often the auto-refresh View is being called while "view_name" is the machine name of the actual view.
The additional settings are the selectors and classes which will be addressed by the JavaScript of Views Auto-Refresh. I don't want to dig too deep since they should be self describing.
Now you might think that everything is done. But no, wait, we have to add the same code to the header of your original view. So do the same thing there as I have described it above. Please make sure that this view also uses AJAX, else it wouldn't work.

Further possibilities

In our project we use the jQuery library Isotope which sorts all the posts dynamically on loading or resizing. But you have to trigger the re-layout of the page if the Views Auto-Refresh has delivered some new posts. This is really simple and straight forward. You just have to add a Drupal behavior in your JavaScript like this:
/** * Add functionality to trigger reloadItems after an autorefresh */ Drupal.behaviors.triggerIsotopeAfterAutorefresh = { attach: function(context, settings){ $('.view-id-articles').bind('autorefresh.incremental', function() { //getting the content/context $isotope = $('.view-id-articles .view-content'); //reload all items by original order $isotope.isotope( 'reloadItems' ).isotope({ sortBy: 'original-order' }); }); } }
As you can see, you can just bind the event 'autorefresh.incremental' to execute your own code. 'autorefresh.incremental' is fired every time the Views Auto-Refresh module loads the designated view.

Conclusion



After a few trial and error attempts, I finally figured out how Views Auto-Refresh really worked. There is a lot more to this module that isn't documented. So it is much more powerful than I can describe in only one blog post. So go on and give it a go.

Update 9.5.2013: As pointed out by Phil Dodd in his comment the Views Auto Refresh module has been moved to its own home athttp://drupal.org/project/views_autorefresh.

Saturday, August 10, 2013

Example: Block to show "Latest News" related to current page

This recipe shows how to apply the Contextual View idea to create a block that shows news headlines related to the current page being displayed. It assumes you have two node types, which I'll call "primary" (this is the node type that contains the base information) and "news" (this is a node type that defines a story, announcement, or whatever). In our case, the "news" stories are often directly related to one of the "primary" nodes on the site, and we wanted to show all the "latest news" related to each primary node in a block on that node's page.
Here's the recipe:
  1. Add a CCK "nodereference" field to your "news" type node.
  2. Use a "Select List" type, with a title like "This news relates to";
  3. Select the "primary" node type as the "Content Types that can be referenced" (and/or set up a View to restrict which nodes to list further, by user id, taxonomy, or whatever).
  4. (You might even choose to set this to be visible in the "Display Fields" settings, as this will provide a link back to the "primary" node it relates to.)
  5. In Views, create a Block view with a title like "latest news", set to display 5 nodes (say, with a more link). Use a List view.
  6. Select the Node:Title Field (as links), so you get links to the "headlines".
  7. Filter based on Node Type is "news" (and Published, of course)
  8. Add the Contextual View Block magic, to tell the view to pull in the news stories that have a reference to the current page! Add the argument: "Node Reference:This news relates to" argument, with "Use Empty Text" as a default. Add the following Argument Code:
          if (arg(0) == 'node' && is_numeric(arg(1))) {
              $args[0] = arg(1);
          }
          return $args;
  1. Sort in reverse chronological order (so latest news goes on top);
  2. Go to admin/blocks and configure the block to show up wherever you like. Set the block to only display on pages that will display your primary type CCK nodes.
Done. Wherever you have enabled this block, it should show only news stories that are related to (have a reference to) the current page!