Ajax without Javascript

Notes from my DrupalCampNYC 7 presentation

Ajax is nothing new.  And especially since Drupal's adoption of jQuery, ajax has certainly become much easier.  Generally, ajax requests in Drupal involve

  1. PHP code (usually in a module) to generate the necessary markup for the originating page.
  2. Javascript code to handle the ajax response.
  3. Some form of initialization code, usually in a Drupal behavior, to connect the markup to the behaviors.
  4. PHP to generate the response, both for the ajax request and for the non-javascript page. (Oh, and it's up to you to decide how to tell the difference!)

Let the Chaos begin

There's nothing inherently wrong with the above method, but there's definitely room for improvement.  With the introduction of CTools (a.k.a Chaos tool suite), ajax development has become much simpler.  From the CTools project page on Drupal.org,

"AJAX responder -- tools to make it easier for the server to handle AJAX requests and tell the client what to do with them."

CTools introduces the concept of an ajax command.  A command is a javascript function within the Drupal.CTools.AJAX.commands namespace, which can be invoked as an ajax response.  The server-side callback returns an object representation of a command, and this object contains everything necessary to run the command on the client-side.  The easiest way to explain this is with an example.

We will be building an example module, which I will call "example".  This module will display a page with an ajax link that will reveal more content when clicked.

The first thing that is needed is a hook_menu() implementation to define two new paths.  The first is the page that will hold the link, and the second defines the ajax callback.  Take note of the %ctools_js in the second entry.  This is how we will determine if the call is being made from an ajax call or not.  More on that when we get to the callback code.

<?php
/**
* Implementation of hook_menu().
*/
function example_menu() {
 
$items = array();
 
$items['test'] = array(
   
'title' => 'Ajax Test',
   
'page callback' => 'example_test_main',
   
'access arguments' => array('access content'),
  );
 
$items['test/%ctools_js/go'] = array(
   
'page callback' => 'example_test_ajax_callback',
   
'page arguments' => array(1),
   
'access arguments' => array('access content'),
  );
  return
$items;  
}
?>

Now for the main page callback.  The only output on this page is a link to the second path that we defined.  The link has two things to take note of.  First, the path of the link is test/nojs/go.  The 'nojs' portion of the path will be replaced with 'ajax' when an ajax call is made.  This distinction is how we detect if the callback is being called from an ajax request or not.  The second thing to note is that we add a class of 'ctools-use-ajax' to the link.  This tells the ajax-responder javascript that this link should be processed by the ajax responder.  And finally, we must include the ajax-responder javascript.

<?php
function example_test_main() {
 
$output = l('Load more content', 'test/nojs/go', array(
   
'attributes' => array('id' => 'test-ajax-link', 'class' => 'ctools-use-ajax')));

 
ctools_add_js('ajax-responder');
  return
$output;
}
?>

Last but not least, the ajax callback.  Notice how the function takes a boolean parameter for $js.  CTools takes care of turning the strings ('nojs' or 'ajax') into a boolean, so we have a very clean way to determine how to respond.  We will be returning the same content, in both conditions, to maintain accessibility for non-javascript enabled browsers (for progressive enhancement as well as SEO).

<?php
function example_test_ajax_callback($js = FALSE) {
 
$output = t('<p>Lorem ipsum dolor sit amet...</p>');

  if (
$js) {
   
ctools_include('ajax');

   
$commands = array();
   
$commands[] = ctools_ajax_command_after('#test-ajax-link', $output);
   
$commands[] = ctools_ajax_command_remove('#test-ajax-link');

   
ctools_ajax_render($commands);
   
// above command will exit().
 
}
  else {
    return
$output;
  }
}
?>

In the javascript branch of the conditional, we construct an array of command objects.  Luckily for us, CTools offers a complementary php function for each javascript command, so creating the commands array is simple.  The particular set of commands that we are using will add the output after the link, then remove the link.  You can add as many commands as needed.  The last thing to do, is to pass the commands array through ctools_ajax_render, which will output the commands array as JSON and exit.  From that point on, the ajax-responder javascript on the first page takes over, and executes the commands in the order they are received.

Tada!

And that's it.  No javascript, just commands.  CTools provides many commands for most of the basic javascript actions, from which you can build compound actions to do almost anything.  Obviously, this set of commands cannot possibly cover every possible option, but I'll leave that for another day. Until then, here is the list of ajax commands provided by CTools.

  • replace (ctools_ajax_command_replace)
    • selector: The CSS selector. This can be any selector jquery uses in $().
    • data: The data to use with the jquery replace() function.
  • prepend (ctools_ajax_command_prepend)
    • selector: The CSS selector. This can be any selector jquery uses in $().
    • data: The data to use with the jquery prepend() function.
  • append (ctools_ajax_command_append)
    • selector: The CSS selector. This can be any selector jquery uses in $().
    • data: The data to use with the jquery append() function.
  • after (ctools_ajax_command_after)
    • selector: The CSS selector. This can be any selector jquery uses in $().
    • data: The data to use with the jquery after() function.
  • before (ctools_ajax_command_before)
    • selector: The CSS selector. This can be any selector jquery uses in $().
    • data: The data to use with the jquery before() function.
  • remove (ctools_ajax_command_remove)
    • selector: The CSS selector. This can be any selector jquery uses in $().
  • changed (ctools_ajax_command_change)
    • selector: The CSS selector. This selector will have 'changed' added as a clas.
    • star: If set, will add a star to this selector. It must be within the 'selector' above.
  • alert (ctools_ajax_command_alert)
    • title: The title of the alert.
    • data: The data in the alert.
  • css (ctools_ajax_command_css)
    • selector: The CSS selector to add CSS to.
    • argument: An array of 'key': 'value' CSS selectors to set.
  • attr (ctools_ajax_command_attr)
    • selector: The CSS selector. This can be any selector jquery uses in $().
    • name: The name or key of the data attached to this selector.
    • value: The value of the data.
  • settings (ctools_ajax_command_settings)
    • argument: An array of settings to add to Drupal.settings via $.extend
  • data (ctools_ajax_command_data)
    • selector: The CSS selector. This can be any selector jquery uses in $().
    • name: The name or key of the data attached to this selector.
    • value: The value of the data. Not just limited to strings can be any format.
  • redirect (ctools_ajax_command_redirect)
    • url: The url to be redirected to. This can be an absolute URL or a Drupal path.
  • reload (ctools_ajax_command_reload)
  • submit (ctools_ajax_command_submit)
    • selector: The CSS selector to identify the form for submission. This can be any selector jquery uses in $().


Posted at 10:16 pm on December 7, 2009 — Tags: ajax | ctools | drupal | drupalcamp

13 Responses

This is damn awesome stuff! You're missing a link to your crazy ass Dialog module.

1
Dec 8, 2009 1:52 am

OMG! This is so rad!  If only I had known this sooner.

2
Dec 8, 2009 2:31 am

You had me fooled by the title of this article. But after reading your post, you've shown me that not a single line of JS was needed to do an ajax call. That's amazing!Dialog Module is excellent!

3
Dec 8, 2009 3:06 am
merlinofchaos

Technicly JavaScript was used but no custom js needed to be written. We did manage to get this framework into core for d7 so we will see lots of code using this stuff in the future.

4
Dec 8, 2009 3:19 am
slip

Thanks for taking the time to write this! Very cool, easy to understand and thorough.

5
Dec 8, 2009 11:26 am

Woo! This was the highlight of DrupalCampNYC for me. I'll petition Mr. Miles to include something like this as a sample in ctools by default:http://drupal.org/node/655764

6
Dec 9, 2009 6:35 pm

Great write up of an excellent presentation!

7
Dec 11, 2009 3:06 pm
threecheese

Does one always need to decorate the source link with the `ctools_use_ajax` class?  Shoundl't the fact that we're invoking the '%ctools_js' menu callback be enough information for ctools, to realize we want to use it's ajax capabilities?Just curious - this looks very interesting.

8
Dec 12, 2009 12:56 am

@threecheese: the 'ctools_use_ajax' class is what ctools uses to trigger the ajax on each link. By the time the link hits the DOM, the %ctools_js is actually the string 'nojs'. Without this class, ctools would need to scan the DOM for all links that have an href with a substring of 'nojs'.

9
Dec 15, 2009 10:17 pm

Awesome Stuff! thanks for sharing!

10
Dec 20, 2009 2:45 am

Someone in IRC pointed out that a downside to this is that example/nojs/go will be indexed by Google. I'd say however that any link that provides useful degradation to a modal window probably doesn't have content that Google needs to index, and could have rel = "nofollow" set, otherwise indexing would not be a problem.

11
Dec 21, 2009 1:04 pm

Michael,

The whole point of progressive enhancement is to provide an "upgraded" experience to browsers that support it. Take the case of a user login form, the /nojs/ link should take you to the same (or similar) login form as the /ajax/ link, but with a different experience. With that being said, I don't see the problem of having alternate content indexed.

12
Dec 21, 2009 2:59 pm
Gen

How come i am not knowing that... thanks for your info.
good posting.

13
Feb 27, 2010 8:07 am

Post new comment

  • You can use some basic HTML in your posts
  • I will never publish your email address.
  • If you have a Gravatar associated with your email address, it will be used.
  • If you are putting spammy links in your posts, the entire post WILL be deleted.