Author image
Senior Developer

Make a link use ajax in Drupal 7 (it's easy)

Part 1: Make the link work without javascript

In this example we have a table listing log entries of some kind. We want to put a delete link against each log entry allowing the administrator to delete the log entry:

The following code is responsible for printing out each link onto the page:

$query = array(
  'tok' => drupal_get_token('delete_log_item' . $flid),
) + drupal_get_destination();
$output[] = l(t('Delete'), 'admin/my-custom-log/delete/' . $flid, array('query' => $query));

The callback of which is defined by the following hook_menu item:

  $items['admin/my-custom-log/delete/%'] = array(
    'page callback' => 'my_custom_log_entry_delete',
    'page arguments' => array(3),
    'access arguments' => array('permission name'),
    'type' => MENU_CALLBACK,
  );

And the callback code is:

function my_custom_log_entry_delete($flid) {
 
  if (empty($_GET['tok']) || !drupal_valid_token($_GET['tok'], 'delete_log_item' . $flid)) {
    return MENU_ACCESS_DENIED;
  }
 
  db_delete('my_custom_log')
    ->condition('flid', $flid)
    ->execute();

    drupal_set_message(t('Deleted 1 message'));
    drupal_goto();
}

Part 2: Ajaxify it

Three simple steps:

  1. Make the link be requested with ajax.

  2. Identify on the server-side when the link has been requested by ajax.

  3. Decide what we want to do with the page when responding to an ajax request.

One Simply add the class 'use-ajax' to the link and it will ajax. Also add in 'nojs' into the url. This will be rewritten automatically by Drupal JS as 'ajax' when it is being requested using ajax:

// Make sure Drupal Ajax framework javascript is around
drupal_add_library('system', 'drupal.ajax');

$query = array(
  'tok' => drupal_get_token('delete_log_item' . $flid),
) + drupal_get_destination();
$output[] = l(t('Delete'), 'admin/my-custom-log/delete/nojs/' . $flid, array('attributes' => array('class' => 'use-ajax'), 'query' => $query));

Two Change our hook_menu item to include a 'nojs' and 'ajax' version of the path. We can use the same callback:

  $items['admin/my-custom-log/delete/nojs/%'] = array(
    'page callback' => 'my_custom_log_entry_delete',
    'page arguments' => array(3, 4),
    'access arguments' => array('permission name'),
    'type' => MENU_CALLBACK,
  );
  $items['admin/my-custom-log/delete/ajax/%'] = array(
    'delivery callback' => 'ajax_deliver',
  ) + $items['admin/my-custom-log/delete/nojs/%'];

Three Make our callback respond with something sensible when on an ajax request:

function my_custom_log_entry_delete($ajax, $flid) {
 
  $is_ajax = $ajax === 'ajax';
 
  // Since clicking this link updates the database, we used drupal_get_token() for security.
  if (empty($_GET['tok']) || !drupal_valid_token($_GET['tok'], 'delete_log_item' . $flid)) {
    return MENU_ACCESS_DENIED;
  }
 
  db_delete('my_custom_log')
    ->condition('flid', $flid)
    ->execute();
 
  if ($is_ajax) {
    $commands = array();
   
    // Perhaps we could remove the table row we just deleted?
    $commands[] = ajax_command_remove('.my-custom-log tr.flid-' . $flid);
   
    return array(
      '#type' => 'ajax',
      '#commands' => $commands,
    );
  }
  else {
    drupal_set_message(t('Deleted 1 message'));
    drupal_goto();
  }
}

Easy peasy. And for that last bit you can return any number of Drupal ajax commands you want.

Comments

Can't wait to try it out. Thanks for the post!

Great post. Just a small correction required for the hook_menu() in the ajaxified version: the page arguments should there be array(3, 4), otherwise the id parameter ist missing

Actually Drupal is smart enough to pass in any extra elements in the path as additional arguments to the page callback, so in this case we don't need to specify the argument explicitly.

Though I think it's clearer to specify them explicitly in this example, so I've updated the post.

Nice tip James,
Maybe in the future Drupal will include the CSRF token validation in the menu hook thttp://drupal.org/node/755584
Even easier

Nicely written up tutorial. Not alot on how to use drupal_get_token and drupal_validate_token to prevent CRSF.

Not a lot written or available was what I meant.

I have followed the above instructions, however my links always come out with "nojs" and the misc/ajax.js library file is not included. I cant seem to find the code within Drupal which detects when it's needed/when it needs to be added or, indeed, what actually adds it!

Ah ha!

Might be worth adding to the tutorial above that ajax.js only gets added automatically if you use the <code>#ajax</code> property on a form element. For the purposes of this example, you need to manually add it using
<?php
drupal_add_library('system', 'drupal.ajax');
?>

I got this tip from the API:
http://api.drupal.org/api/examples/ajax_example%21ajax_example_misc.inc/...

Yes, good tip - I've added the line to the article.

Hey there.

I tried to use your code on a different scenario (panels and views), but I keep getting a Page not found.

The link is correct and the paths are defined in my custom module. I believe it's not even recognizing the path in my hook_menu but I can't figure out what may be the reason.

Any clue of what I could be missing?

Thanks.

Thanks to share this. Its really a good article.

Page not found sounds like Drupal isn't picking up your menu callback. Some common things that have caught me out before: Is your custom module enabled? Have you remembered to return the "$items" array from your hook_menu implementation? Have you cleared all the caches? Have you put some access on your menu callback which the currently logged in user doesn't have the correct permission for?

Comments on this article are now closed, if you want to give us feeback you can use our contact form instead.