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:
- 
Make the link be requested with ajax. 
- 
Identify on the server-side when the link has been requested by ajax. 
- 
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.

