Labs

Trigger Drupal managed AJAX calls at any time with Drupal 7

By John Ennew | 23rd February 2012

In a previous article I described how to trigger a CTools AJAX callback at any time, not just on an onclick event or form submission. This will only work with Drupal 6.  In Drupal 7, much of this functionality has now been brought into Drupal Core, which is great. Unfortunately, it also appears to have been changed considerably.  In this article I give a demonstration on how to achieve the same functionality with Drupal 7.

In this example you will create your own custom module again, called 'mymodule'.

Here is the contents of the mymodule.info file:

	name = mymodule
	description = My Module
	core = 7.x
	scripts[] = js/mymodule.js

In your mymodule file, let's start by defining the hook_menu which will define the path the ajax request will be made to.

/**
 * Implements hook_menu().
 */
function mymodule_menu() {
  $items = array();
 
  $items['mymodule/refresh-elements'] = array(
    'title' => 'AJAX callback - refresh the elements on the page',
    'page callback' => 'mymodule_refresh',
    'page arguments' => array(1),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );
 
  return $items;
}

If you are comparing this with the previous Drupal 6 tutorial with CTools you will see there is no need to define the %ctools_js item here.  Next, we define the actual callback function which will do the processing and send the page change requests back to the browser. Add the following to mymodule.module file.

/**
 * Menu callback.
 */
function mymodule_refresh($action, $type) {
  if ($type != 'ajax') {
    // This is js only.
    return 'Oh well';
  }
 
  $commands = array();
  $commands[] = ajax_command_replace('.some-class', date('G:i:s'));
  $page = array('#type' => 'ajax', '#commands' => $commands);
  ajax_deliver($page);
}

The arguments to this function are each path element past the first one.  If our site was stored at http://www.example.com then the hook_menu defines a path at http://www.example.com/mymodule/refresh-elements.  When the mymodule_refresh function is called, the $action variable will be the string 'refresh-elements'.  If a third element was added to the path, then the $type variable would get filled with this. http://www.example.com/mymodule/refresh-elements/ajax - $type will be the string 'ajax'.  This will be the actual url we will use.

We also need to make use of the drupal.ajax library which we will include in the hook_init for this tutorial. Put this in the mymodule.module file.

/**
 * Implements hook_init().
 */
function mymodule_init() {
  drupal_add_library('system', 'drupal.ajax');
}
 

Now make a js folder in your mymodule folder and create a file called mymodule.js in it. This will contain our custom javascript.  This file is going to be included on every page of the site when the module is enabled. We specified this in the info file by defining the javascript file with the scripts[].  The javascript file contents look like this:

(function($){
 
  /**
   * Add an extra function to the Drupal ajax object
   * which allows us to trigger an ajax response without
   * an element that triggers it.
   */
  Drupal.ajax.prototype.specifiedResponse = function() {
    var ajax = this;
 
    // Do not perform another ajax command if one is already in progress.
    if (ajax.ajaxing) {
      return false;
    }
 
    try {
      $.ajax(ajax.options);
    }
    catch (err) {
      alert('An error occurred while attempting to process ' + ajax.options.url);
      return false;
    }
 
    return false;
  };
 
  /**
   * Define a custom ajax action not associated with an element.
   */
  var custom_settings = {};
  custom_settings.url = '/mymodule/refresh-elements/ajax/';
  custom_settings.event = 'onload';
  custom_settings.keypress = false;
  custom_settings.prevent = false;
  Drupal.ajax['custom_ajax_action'] = new Drupal.ajax(null, $(document.body), custom_settings);
 
  /**
   * Define a point to trigger our custom actions. e.g. on page load.
   */
  $(document).ready(function() {
    Drupal.ajax['custom_ajax_action'].specifiedResponse();
  });
 
})(jQuery);                               

To make this work we define an ajax action and then execute the action.

In the definition, the items you will need to change for your own purposes will be the custom_settings.url for your own module and path. The other settings will be left alone.  If you want to do more than one ajax request, duplicate the bits from var custom_settings = {}; to Drupal.ajax['custom_ajax_action'] ... changing each url for each action and also changing 'custom_ajax_action' string so it is unique for each action.

To trigger the action you need to run Drupal.ajax['custom_ajax_action'].specifiedResponse(); at the right time in your own javascript code. Above I have run it at page load.

Now, to see this work, crete a node add add something with a class of some-class and watch it be replaced with the current time.

	<p class="some-class">Initial text</p>

For some general information about using javascript in Drupal 7, check out: http://drupal.org/node/756722. For general tutorial on Drupal 7's ajax features then checkout this page: http://api.drupal.org/api/drupal/includes%21ajax.inc/group/ajax/7

Comments

A couple notes I learned after following this tutorial, if you're using AJAX to update a field, Drupal provides field level #ajax that work simply by setting the #ajax property on a form like so: http://www.gregboggs.com/drupal-7-ajax-forms/

If you're going to use AJAX on a widget that already defines it's own AJAX then you'll need to use the method detailed here except you'll want to trigger your AJAX based on AJAX complete instead of Document.ready() like so:

    /**
     * Define a point to trigger our custom actions. e.g. on ajax complete
     */
 
    $(document).ajaxComplete(function() {
      $('#edit-taxonomy-vocabulary-1-und-hierarchical-select-dropbox-add').click(function() {
        console.log ('click');
        ....
      }
Jeroen VB's picture
Jeroen VB
Jeroen VB

Now this is exactly what i was trying to figure out. Being able to trigger ajax on an object that already has its own field-attribute ajax set.

I'm wondering why Drupal didn't allow to have multiple #ajax entries set (of course there is the commands attribute, which isn't as flexible with callback functions)

Thanks for this. I was foregoing using Drupal.ajax altogether because of it being event based, but then after a core update, Drupal.dettachBehaviors/attachBehaviors starting misbehaving with certain contrib modules. This solution is not only cleaner, it WORKS!

Adam Bramley's picture
Adam Bramley
Adam Bramley

Great post, worked very well. One thing that tripped me up though is your implementation of the ajax page callback and hook_menu definition. If you are using this for (for example) a block on a page, you need to add 'theme callback' => 'ajax_base_page_theme', to your hook_menu item. This means the page won't be rendered under your site's theme so you will just get the ajax response rather than the full page delivered.

HARRI's picture
HARRI
HARRI

You have really nice theme in your site! Is it opensource or created by you?

Manu's picture
Manu
Manu

HI,

Thank you for the article ! it helped me a lot understanding Ctools ajax features. However, I found out to make it work, i had to remove the first trailing slash in the js file in the custom.settings.url. so '/mymodule/refresh-elements/ajax/' become 'mymodule/refresh-elements/ajax/'.

Hope this helps !

Cheers

twitter : @Manu_Neny

David Thomas's picture
David Thomas
David Thomas

Thanks so much! This is _exactly_ what I was looking for. Ajax loading content without some user interaction was hard to fathom. Your clear post has elucidated the solution nicely. Cheers!

venky's picture
venky
venky

nice explanation, I am brought here by link in views auto refresh,
I was wondering, can some one help me.I am looking for readily writable node using ajax , I am looking for this to be used in Advanced forum, where there is button "newpost" on click user is taken to content create page, What i wanted is, if we have a readily writable box for title and body, which will have share button , user simply types the title and and body and click share or save. can some one help

Chaz's picture
Chaz
Chaz

@John Ennew , You are the man,thank you alot.
now ts working like a charm.

Add new comment