Jan 15 2017
Jan 15
I've already written about how to use native Drupal ajax mechanism on front-end side in this post. There I've described how to send requests immediately or by clicking (or by any other event) on any DOM element. It works when javascript is enabled in user's browser. But how to make your menu callback work both with ajax and GET (when js is disabled) requests?

1. Menu router. Have to contain additional argument %ctools_js:
<?php

/**
 * Implements hook_menu().
 *
 * Just for example we will render a node form.
 */
function module_menu() {
  $items = [];

  $items['some/%node/%ctools_js/path'] = [
    'title' => 'Test router',
    'page callback' => 'module_test_router_callback',
    'page arguments' => [1, 2],
    // For ajax requests first.
    'delivery callback' => 'ajax_deliver',
    'type' => MENU_CALLBACK,
  ];

  return $items;
}
2. Menu callback:
<?php

/**
 * Menu callback for test router.
 *
 * Just for example we will render a node form
 * and if it's ajax request then replace body element
 * with a form. Otherwise reload page and show
 * node form.
 */
function module_test_router_callback($node, $ajax) {
  // Don't forget to include node specific file
  // for an example.
  module_load_include('inc', 'node', 'node.pages');
  $form = drupal_get_form($node->type . '_node_form', $node);

  // If it's ajax request then $ajax will be equals 1.
  if ($ajax) {
    // Set up command for replacing body content
    // with a rendered form.
    $commands[] = ajax_command_html('body', render($form));

    return [
      '#type' => 'ajax',
      '#commands' => $commands,
    ];
  }
  else {
    // If it's not ajax request just deliver
    // html markup.
    drupal_deliver_html_page($form);
  }
}
3. Link with a use-ajax class:
<?php

// Make sure you've added ajax library
// before printing a link.
drupal_add_library('system', 'drupal.ajax');

// Main point here is 'use-js' class for a link.
// Also take a look at 'nojs' argument. If js is
// enabled in a browser then this string will
// be replaced with 'ajax' and link will be look
// like 'some/NID/ajax/path'.
print l(t('Get node form'), 'some/NID/nojs/path', [
  'attributes' => [
    'class' => [
      'use-ajax',
    ],
  ],
]);
So what's happening here?

  • First. Clicking by the link with 'some/NID/nojs/path' href and 'use-ajax' class. If js is enabled in user's browser then 'nojs' substring will be replaced by 'ajax'.
  • Second. You need to find out what is %ctools_js. Ctools module provides a function ctools_js_load() that determines if it's ajax request or not ('ajax' or 'nojs' string passed as an argument to page callback).
  • Third. Page callback takes argument $ajax which equals 1 if ajax request and 0 otherwise (thanks to ctools_js_load() function). We're returning ajax commands for ajax requests and delivering render array with drupal_deliver_html_page() function otherwise. Since we've defined delivery callback for menu router as ajax_deliver we need to handle non ajax requests with drupal_deliver_html_page() function manually.

Key notes:

Jan 08 2017
Jan 08
Throbber is an element with a message that shows us that something is running in background. Drupal renders this element each time when ajax request is running (for example when you submit form via ajax). Sometimes we need to disable throbber at all or at least remove/change message. That's how you can do this.

Disable throbber at all:
<?php

// Add 'progress' settings into '#ajax'
// element of a submit button. Define progress
// type as 'none'.
$form['actions']['submit']['#ajax']['progress'] => [
  'type' => 'none',
];
Remove/change throbber message:
<?php

// Add 'progress' settings into '#ajax'
// element of a submit button. Change progress
// message to NULL (delete message) or any other
// string (change message).
$form['actions']['submit']['#ajax']['progress'] => [
  'message' => NULL,
];
If you  send ajax requests as described in this post you should modify settings variable and add progress property. Disable throbber:
var $link = $('#link-element', context);

new Drupal.ajax($link.attr('id'), $link, {
  url: '/ajax/callback/path',
  event: 'click',
  progress: {
    type: 'none'
  }
});
Remove/change throbber message:
var $link = $('#link-element', context);

new Drupal.ajax($link.attr('id'), $link, {
  url: '/ajax/callback/path',
  event: 'click',
  progress: {
    type: 'throbber',
    // Change progress message to NULL
    // (delete message) or any other
    // string (change message).
    message: null
  }
});

Key notes:

Jan 08 2017
Jan 08
If you are using jQuery.ajax() or jQuery.post() for background requests you should have a look at native Drupal ajax implementation. Drupal provides a js object Drupal.ajax that performs all needed actions for you (like processing ajax commands that came from back-end side). All that you need to do is to create instance of this object and configure it.

Send ajax request by event triggering of a DOM object:
// By clicking on this link we will
// send an ajax request.
var $link = $('#link-element', context);

new Drupal.ajax($link.attr('id'), $link, {
  url: '/ajax/callback/path',
  event: 'click',
  // Pass some data to back-end side
  // if you need.
  submit: {
    some_data: 'some_data_value'
  }
});
Send ajax request immediately:
// If we want to send request immediately
// we can omit first two parameters.
var ajax = new Drupal.ajax(false, false, {
  url: '/ajax/callback/path'
});

// To send request call this function.
// You shouldn't think how to process
// response. Be sure that all commands
// will be processed correctly.
ajax.eventResponse(ajax, {});

Key notes:

Jan 07 2017
Jan 07
Drupal 7 offers a lot of default ajax commands which are the part of Drupal Ajax Framework. Furthermore, ctools module extends a set of ajax commands and provides own commands. But what if you need some specific command? First, you need to write a wrapper function which returns ajax command array. Should looks like this one:
<?php

/**
 * Ajax command for rendering canvas data.
 *
 * @param array $data
 *   Settings array.
 *
 * @param mixed $some_other_parameter
 *   Some other parameter.
 *
 * @return array
 *   Ajax command array.
 */
function specific_ajax_command(array $data, $some_other_parameter) {
  return [
    // Name of a js function.
    'command' => 'specificAjaxCommand',

    // Data will be available in js function.
    'specific_data' => $data,

    // You can define as many parameters
    // for your command as you want.
    'some_other_parameter' => $some_other_parameter,
  ];
}
Second step is js command function implementation :
(function ($) {
  'use strict';

  // Ensure that Drupal.ajax object is set up.
  if (Drupal.ajax) {
    Drupal.ajax.prototype.commands.specificAjaxCommand = function (ajax, response, status) {
      // Check response status.
      if (status == 'success') {
        // Get data from back-end side.
        var specificData = response.specific_data,
            someOtherParameter = response.some_other_parameter;
        
        // Perform specific actions dependent on a data.
      }
    };
  }

}(jQuery));
Third, ensure that js file contains code above is added to a page before returning ajax command. When it's done you are ready to go with a new ajax command:
<?php

/**
 * Ajax page callback.
 *
 * @return array
 *   Array contains Drupal ajax commands.
 */
function module_ajax_page_callback() {
  $some_data = [];
  $some_other_parameter = 'Some other parameter value';
  $commands[] = specific_ajax_command($some_data, $some_other_parameter);
 
  return [
    '#type' => 'ajax',
    '#commands' => $commands,
  ];
}

Key notes:

Jan 07 2017
Jan 07
If you want to submit node form (or any form) via ajax request you need to follow next steps:
  1. Alter needed form: add container for validation messages and ajax callback for submit action.
  2. Implement ajax callback that returns ajax commands.
<?php

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function module_form_node_form_alter(&$form, &$form_state, $form_id) {
  // Add container for validation messages.
  $form['#prefix'] = '
';

  // Add ajax callback for submit action.
  $form['actions']['submit']['#ajax'] = [
    'callback' => 'module_node_form_ajax_submit',
  ];

  // Don't forget to include node.pages.inc file. Without it Drupal will not be
  // able to find and call node_form_validate() function.
  form_load_include($form_state, 'inc', 'node', 'node.pages');
}

/**
 * Form ajax submit callback.
 *
 * @return array
 *   Array contains Drupal ajax commands.
 */
function module_node_form_ajax_submit() {
  // Remove all status messages from $_SESSION['messages'] variable
  // because we want to render only error messages.
  drupal_get_messages('status');

  // Check form for possible validation messages.
  if (form_get_errors()) {
    // Validation failure: return rendered validation messages.
    $commands[] = ajax_command_html('#validation-messages', [theme('status_messages')]);
  }
  else {
    // Validation success: do something, for instance redirect user.
    ctools_include('ajax');
    $commands[] = ctools_ajax_command_redirect('some/page');
  }

  return [
    '#type' => 'ajax',
    '#commands' => $commands,
  ];
}

Key notes:

May 20 2015
May 20

Boost the performance of your Drupal 7 website, improve usability and help with SEO by making content load via AJAX in just a few steps.

Drupal AJAX is a cinch to implement—as it should be since core is loaded with it. In just a few of steps I’ll show you how to use jQuery and Drupal’s hook_menu() function to quickly build a AJAX function to return anything from HTML to a JSON array.

Simple Drupal AJAX JSON Implementation

/**
 * Implementation of hook_menu().
 * @see https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_menu/7
 */
function moduleName_menu() {

  // Setup a URL to retrieve the JSON array.
  $items['node/%/moduleName/pageviews'] = array(
    'page callback'     =&gt; 'moduleName_get_pageviews',
    'page arguments'    =&gt; array(3),
    'type'              =&gt; MENU_CALLBACK,
    'access arguments'  =&gt; array('access content'),
    'delivery callback' =&gt; 'moduleName_ajax'
  );

  return $items;
}

/**
 * Returns the number of pageviews for a node ID.
 */
function moduleName_get_pageviews($nid) {

  // Some fancy function that returns pageviews.
  return fancyPageviews($nid);
}

/**
 * Renders a JSON array.
 */
function moduleName_ajax($pageviews) {

  // Tell the browser to expect JSON data.
  // @see https://api.drupal.org/api/drupal/includes!bootstrap.inc/function/drupal_add_http_header/7
  drupal_add_http_header('Content-Type', 'application/json');

  // Output the JSON result
  // @see https://api.drupal.org/api/drupal/includes!common.inc/function/drupal_json_output/7
  print drupal_json_output(array('pageviews', $pageviews));

  // Perform end-of-request tasks.
  // @see https://api.drupal.org/api/drupal/includes!common.inc/function/drupal_page_footer/7
  drupal_page_footer();
}

With the above code (after clearing cache), visit yoursite.com/node/55/moduleName/pageviews to return a JSON array with the number of pageviews for node ID 55. More information on the hooks and functions used can be found below:

You can then use this URL and jQuery.getJSON() to return the data via AJAX.

;( function( $ ) {
  &quot;use strict&quot;;

  // When document is ready...
  $( function() {

    // Get the JSON result.
    $.getJSON( &quot;node/55/moduleName/pageviews&quot;, function( data ) {

      // Add the number of pageviews to the page.
      $( &quot;#pageviews&quot; ).text( data.pageviews );
    });
  });

})( jQuery );

Or if you need to pass data to the AJAX script:

;( function( $ ) {
  &quot;use strict&quot;;

  // When ready...
  // @see http://www.benmarshall.me/drupal-behaviors/
  Drupal.behaviors.myModule = {

    // Arguments to pass to the AJAX script.
    vars arguments = { status: 'awesome' };

    // Send the arguments &amp; return the JSON data.
    $.getJSON( &quot;node/55/moduleName/pageviews&quot;, arguments ).done( function( data ) {

      // Add the number of pageviews to the page.
      $( &quot;#pageviews&quot; ).text( data.pageviews );
    });
  };

})( jQuery );

For more information on $.getJSON(), see http://api.jquery.com/jquery.getjson/.

You’ll also notice it’s using something called Drupal.behaviors. This is essentially a document ready for all content, including new content that get’s added to the page after load. For instance, if a modal is added to the page via AJAX and a function that’s already been initialized needs to run on the modal, normally you’d have to force event delegation. For a quick introduction to Drupal behaviors, see http://www.benmarshall.me/drupal-behaviors/.

A Closer Look

Like with everything in Drupal, there’s a number of ways to implement AJAX, but which is best? Let’s dive a little deeper into Drupal AJAX and take a look at how, why and when you can use this powerful feature.

AJAX outside the Form API is new in Drupal 7. It allows back and front-end developers the ability to leverage high performance and solid JSON and HTML responses. You can use this feature to update blocks, nodes, pages and any other element with better performance on the client and server-side vs. loading AHAH page fragments.

Drupal AJAX with jQuery

jQuery provides a few different AJAX commands, depending on your exact requirements. Here is the simplest Ajax call you can make with jQuery:

$( &quot;#someDiv&quot; ).load( url );

What this is saying is “Find the div with the id of ‘someDiv’ and load the html that you find at the location ‘url’ into this div”.

The jQuery utility functions below provide for the extra flexibility you need when dealing with data coming back from the server that needs to be parsed.

// https://api.jquery.com/jquery.get/
$.get( url, parameters, callback );

// http://api.jquery.com/jquery.post/
$.post( url, parameters, callback );

// http://api.jquery.com/jquery.ajax/
$.ajax( options, settings );

// http://api.jquery.com/jquery.getjson/
$.getJSON( url, data, success );

// http://api.jquery.com/load/
$.load( url, data, complete )

The only difference between $.get and $.post is the HTTP request method used to send your parameters (an array passed as the second argument) to the server. Very often in Drupal, you won’t need to send any parameters because the url you will be calling will be a menu callback you have set up as, for example, ‘ajax/get/node_details’ taking one argument, ‘nid’ and so you would simply make the call to ‘ajax/get/node_details/123’ and not need to send your nid parameter as a parameter in the second argument.

Additional Resources:

Like this:

Like Loading...

Author: Ben Marshall

Red Bull Addict, Self-Proclaimed Grill Master, Entrepreneur, Workaholic, Front End Engineer, SEO/SM Strategist, Web Developer, Blogger

May 15 2015
May 15

In this article, I am going to show you a clean way of using the Drupal 8 Ajax API without writing one line of JavaScript code. To this end, we will go back to the first custom form we built for Drupal 8 in a previous article and Ajaxify some of its behaviour to make it more user friendly.

Drupal 8 logo

An updated version of this form can be found in this repository under the name DemoForm (the demo module). The code we write in this article can also be found there but in a separate branch called ajax. I recommend you clone the repo and install the module in your development environment if you want to follow along.

DemoForm

Although poorly named, the DemoForm was very helpful in illustrating the basics of writing a custom form in Drupal 8. It handles validation, configuration and exemplifies the use of the Form API in general. Of course, it focuses on the basics and has nothing spectacular going on.

If you remember, or check the code, you’ll see that the form presents a single textfield responsible for collecting an email address to be saved as configuration. The form validation is in charge of making sure that the submitted email has a .com ending (a poor attempt at that but enough to illustrate the principle of form validation). So when a user submits the form, they are saving a new email address to the configuration and get a confirmation message printed to the screen.

In this article, we will move the email validation logic to an Ajax callback so that after the user has finished typing the email address, the validation gets automagically triggered and a message printed without submitting the form. Again, there is nothing spectacular about this behaviour and you will see it quite often in forms in the wild (typically to validate usernames). But it’s a good exercise for looking at Ajax in Drupal 8.

Ajax form

The first thing we need to do is move the email validation logic from the general validateForm() to a method that handles only this aspect:

/**
 * Validates that the email field is correct.
 */
protected function validateEmail(array &$form, FormStateInterface $form_state) {
  if (substr($form_state->getValue('email'), -4) !== '.com') {
    return FALSE;
  }
  return TRUE;
}

As you can notice, we’ve also changed the logic a bit to make sure the email address ends with a .com.

Then, we can defer to this logic from the main validation method to make sure our existing behaviour still works:

/**
 * {@inheritdoc}
 */
public function validateForm(array &$form, FormStateInterface $form_state) {
  // Validate email.
  if (!$this->validateEmail($form, $form_state)) {
    $form_state->setErrorByName('email', $this->t('This is not a .com email address.'));
  }
}

This way even if our form gets somehow submitted (programatically or otherwise), the validation will still be run.

Next, we need to turn to our form definition, specifically the email field, and make it trigger ajax requests based on a user interaction. This will be the act of a user changing the value of the field and removing focus from it:

$form['email'] = array(
  '#type' => 'email',
  '#title' => $this->t('Your .com email address.'),
  '#default_value' => $config->get('demo.email_address'),
  '#ajax' => [
    'callback' => array($this, 'validateEmailAjax'),
    'event' => 'change',
    'progress' => array(
      'type' => 'throbber',
      'message' => t('Verifying email...'),
    ),
  ],
  '#suffix' => '<span class="email-valid-message"></span>'
);

What we did new here is add the #ajax key to the array with some of the relevant keys. Additionally, we added a little markup after the form element as a wrapper for a short message regarding the validity of the email.

The callback inside the #ajax array points to a method inside our form class (validateEmailAjax()) while the event adds a javascript binding to this form element for the jQuery change event. Alternatively, you can also specify a path key instead of a callback, but in our case it would mean having to also set up a route and a controller which seems redundant. And we don’t want the wrapper key because we do not intend to fill up an area with returned content but want to fine grain the actions that result from the callback. For that, we will use Ajax commands.

To learn more about all of this, I encourage you to consult the Ajax API page or the Form API entry for Ajax. There are a handful of other options you can use to further customize the Ajax behavior of your form elements.

Now it’s time to write the callback method inside of our form class. This receives the $form array and $form_state object as arguments coming from the form that triggered the Ajax request:

/**
 * Ajax callback to validate the email field.
 */
public function validateEmailAjax(array &$form, FormStateInterface $form_state) {
  $valid = $this->validateEmail($form, $form_state);
  $response = new AjaxResponse();
  if ($valid) {
    $css = ['border' => '1px solid green'];
    $message = $this->t('Email ok.');
  }
  else {
    $css = ['border' => '1px solid red'];
    $message = $this->t('Email not valid.');
  }
  $response->addCommand(new CssCommand('#edit-email', $css));
  $response->addCommand(new HtmlCommand('.email-valid-message', $message));
  return $response;
}

Simply put, in this method, we perform the validation and return an Ajax response with multiple commands that differ depending on the validation result. With the CssCommand we apply some css directly to the email form element while with the HtmlCommand we replace the contents of the specified selector (remember the suffix from our form element?).

These commands pretty much map to jQuery functions so they are quite easy to grasp. You can find a list of all available commands on this page. And since we are using three new classes inside this method, we must remember to also use them at the top:

use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\CssCommand;
use Drupal\Core\Ajax\HtmlCommand;

And that is pretty much it. If you clear the cache and reload your form, typing into the email field and removing focus will trigger the callback to validate the email address. You’ll notice the little throbber icon there (which can be changed in the definition) and the short message we defined as well. A correct email address should highlight the field in green and print the OK message while on the contrary the color red is used with an opposite message.

If we had specified a wrapper in the form element definition, we could have returned some content (or render array) which would have been placed inside that selector. So you have the option of choosing between returning content or Ajax commands but I recommend the latter for most cases because they offer a more flexible (and consistent) behavior.

Conclusion

In this article we’ve seen an example of using Ajax to improve our form and make it more friendly to end users. And we have written exactly zero lines of javascript to accomplish this.

In our case, it really is a matter of preference or fancification. But if you are dealing with a 20 field form which has validation on multiple fields similar to this, using Ajax really makes sense. It doesn’t annoy users with having to submit the form only to realize their input is invalid.

Although forms are the main area where you’ll see Ajax in Drupal 8, there are a couple of other ways you can leverage it without writing JavaScript.

Once nice way is to add the use-ajax class on any link. This will have Drupal make an Ajax request to the URL in the href attribute whenever the link is clicked. From the callback you can return Ajax commands and perform various actions as needed. But do keep in mind that jQuery and other core scripts are not loaded on all pages for anonymous users (hence Ajax will gracefully degrade to regular link behaviour). So make sure you include these scripts for anonymous users if you need this behavior.

Apr 20 2015
Apr 20

In this article, we are going to look at how we can create a Drupal module which will allow your users to like your posts. The implementation will use jQuery to make AJAX calls and save this data asynchronously.

logo_drupal

Creating your Drupal like module

Let’s start by creating the new Drupal module. To do that we should first create a folder called likepost in the sites\all\modules\custom directory of your Drupal installation as shown below:

Initial folder structure

Inside this folder, you should create a file called likepost.info with the following contents:

name = likepost
description = This module allows the user to like posts in Drupal.
core = 7.x

This file is responsible for providing metadata about your module. This allows Drupal to detect and load its contents.

Next, you should create a file called as likepost.module in the same directory. After creating the file, add the following code to it:

/**
 * @file
 * This is the main module file.
 */

 /**
 * Implements hook_help().
 */
function likepost_help($path, $arg) {

    if ($path == 'admin/help#likepost') {
        $output = '<h3>' . t('About') . '</h3>';
        $output .= '<p>' . t('This module allows the user to like posts in Drupal.') . '</p>';
        return $output;
    }
}

Once you have completed this you can go to the modules section in your Drupal administration and should be able to see the new module. Do not enable the module yet, as we will do so after adding some more functionality.

Creating the schema

Once you have created the module file, you can create a likepost.install file inside the module root folder. Inside, you will define a table schema which is needed to store the likes on each post for each user. Add the following code to the file:

<?php

/**
* Implements hook_schema().
*/
function likepost_schema() {
    $schema['likepost_table_for_likes'] = array(
        'description' => t('Add the likes of the user for a post.'),
        'fields' => array(
            'userid' => array(
                'type' => 'int',
                'not null' => TRUE,
                'default' => 0,
                'description' => t('The user id.'),
            ),

            'nodeid' => array(
                'type' => 'int',
                'unsigned' => TRUE,
                'not null' => TRUE,
                'default' => 0,
                'description' => t('The id of the node.'),
                ),

        ),

        'primary key' => array('userid', 'nodeid'),
    );
    return $schema;
}

In the above code we are are implementing the hook_schema(), in order to define the schema for our table. The tables which are defined within this hook are created during the installation of the module and are removed during the uninstallation.

We defined a table called likepost_table_for_likes with two fields: userid and nodeid. They are both integers and will store one entry per userid – nodeid combination when the user likes a post.

Once you have added this file, you can install the module. If everything has gone correctly, your module should be enabled without any errors and the table likepost_table_for_likes should be created in your database. You should also see the help link enabled in the module list next to your likepost module. If you click on that you should be able to see the help message you defined in the hook_help() implementation.

Help Message

Creating a menu callback to handle likes

Once we have enabled the module, we can add a menu callback which will handle the AJAX request to add or delete the like. To do that, add the following code to your likepost.module file

/**
* Implements hook_menu().
*/
function likepost_menu() {
    $items['likepost/like/%'] = array(
        'title' => 'Like',
        'page callback' => 'likepost_like',
        'page arguments' => array(2),
        'access arguments' => array('access content'),
        'type' => MENU_SUGGESTED_ITEM,
    );
    return $items;
}


function likepost_like($nodeid) {
    $nodeid = (int)$nodeid;
    global $user;

    $like = likepost_get_like($nodeid, $user->uid);

    if ($like !== 0) {
        db_delete('likepost_table_for_likes')
        ->condition('userid', $user->uid)
        ->condition('nodeid', $nodeid)
        ->execute();
        //Update the like value , which will be sent as response
        $like = 0;
    } else {
        db_insert('likepost_table_for_likes')
        ->fields(array(
        'userid' => $user->uid,
        'nodeid' => $nodeid
        ))
        ->execute();
        //Update the like value , which will be sent as response
        $like = 1;
    }

    $total_count = likepost_get_total_like($nodeid);
    drupal_json_output(array(
        'like_status' => $like,
        'total_count' => $total_count
        )
    );

}

/**
* Return the total like count for a node.
*/
function likepost_get_total_like($nid) {
    $total_count = db_query('SELECT count(*) from {likepost_table_for_likes} where nodeid = :nodeid',
    array(':nodeid' => $nid))->fetchField();
    return (int)$total_count;
}

/**
* Return whether the current user has liked the node.
*/
function likepost_get_like($nodeid, $userid) {
    $like = db_query('SELECT count(*) FROM {likepost_table_for_likes} WHERE
    nodeid = :nodeid AND userid = :userid', array(':nodeid' => $nodeid, ':userid' => $userid))->fetchField();
    return (int)$like;
}

In the above code, we are implementing hook_menu() so that whenever the path likepost/like is accessed with the node ID, it will call the function likepost_like().

Inside of likepost_like() we get the node ID and the logged in user’s ID and pass them to the function likepost_get_like(). In the function likepost_get_like() we check our table likepost_table_for_likes to see if this user has already liked this post. In case he has, we will delete that like, otherwise we will insert an entry. Once that is done, we call likepost_get_total_like() with the node ID as a parameter, which calculates the total number of likes from all users on this post. These values are then returned as JSON using the drupal_json_output() API function.

This menu callback will be called from our JQuery AJAX call and will update the UI with the JSON it receives.

Displaying the Like button on the node

Once we have created the callback, we need to show the like link on each of the posts. We can do so by implementing hook_node_view() as below:

/**
 * Implementation of hook_node_view
 */
function likepost_node_view($node, $view_mode) {
    if ($view_mode == 'full'){
        $node->content['likepost_display'] =  array('#markup' => display_like_post_details($node->nid),'#weight' => 100);

        $node->content['#attached']['js'][] = array('data' => drupal_get_path('module', 'likepost') .'/likepost.js');
        $node->content['#attached']['css'][] = array('data' => drupal_get_path('module', 'likepost') .'/likepost.css');
    } 

}

/**
* Displays the Like post details.
*/
function display_like_post_details($nid) {

    global $user;
    $totalLike =  likepost_get_total_like($nid);
    $hasCurrentUserLiked = likepost_get_like($nid , $user->uid);

    return theme('like_post',array('nid' =>$nid, 'totalLike' =>$totalLike, 'hasCurrentUserLiked' => $hasCurrentUserLiked));
    
}
/**
* Implements hook_theme().
*/
function likepost_theme() {
    $themes = array (
        'like_post' => array(
            'arguments' => array('nid','totalLike','hasCurrentUserLiked'),
        ),
    );
    return $themes;
}

function theme_like_post($arguments) {
    $nid = $arguments['nid'];
    $totalLike = $arguments['totalLike'];
    $hasCurrentUserLiked = $arguments['hasCurrentUserLiked'];
    global $base_url;
    $output = '<div class="likepost">';
    $output .= 'Total number of likes on the post are ';
    $output .= '<div class="total_count">'.$totalLike.'</div>';

    if($hasCurrentUserLiked == 0) {
        $linkText = 'Like';
    } else {
        $linkText = 'Delete Like';
    }

    $output .= l($linkText, $base_url.'/likepost/like/'.$nid, array('attributes' => array('class' => 'like-link')));

    $output .= '</div>'; 
    return $output;
    
}

Inside likepost_node_view() we check for when the node is in the full view mode and we add the markup returned by the function display_like_post_details(). We also attached our custom JS and CSS file when the view is rendered using the attached property on the node content. In function display_like_post_details() we get the total number of likes for the post and whether or not the current user has liked the post. Then we call the theme function which will call the function theme_like_post() which we have declared in the implementation of ‘hook_theme’ but will allow the designers to override if required. In theme_like_post(), we create the HTML output accordingly. The href on the link is the $base_url and the path to our callback appended to it. The node ID is also attached to the URL which will be passed as a parameter to the callback.

Once this is done, add a file likepost.css to the module root folder with the following contents:

.likepost {
    border-style: dotted;
    border-color: #98bf21;
    padding: 10px;
}

.total_count {
    font-weight: bold;
}

.like-link {
    color:red;
}

.like-link:hover {
    color: red;
}

Now if you go to the complete page of a post you will see the Like post count as shown below.

Adding the jQuery logic

Now that we see the like link displayed, we will just have to create the likepost.js file with the following contents:

jQuery(document).ready(function () {

    jQuery('a.like-link').click(function () {
        jQuery.ajax({
            type: 'POST', 
            url: this.href,
            dataType: 'json',
            success: function (data) {
                if(data.like_status == 0) {
                    jQuery('a.like-link').html('Like');
                }
                else {
                    jQuery('a.like-link').html('Delete Like');
                }

                jQuery('.total_count').html(data.total_count);
            },
            data: 'js=1' 
        });

        return false;
    });
});

The above code binds the click event to the like link and makes an AJAX request to the URL of our callback menu function. The latter will update the like post count accordingly and then return the new total count and like status, which is used in the success function of the AJAX call to update the UI.

Updated UI with Like count

Conclusion

jQuery and AJAX are powerful tools to create dynamic and responsive websites. You can easily use them in your Drupal modules to add functionality to your Drupal site, since Drupal already leverages jQuery for its interface.

Have feedback? Let us know in the comments!

Dec 15 2014
Dec 15

Angular.js is the hot new thing right now for designing applications in the client. Well, it’s not so new anymore but is sure as hell still hot, especially now that it’s being used and backed by Google. It takes the idea of a JavaScript framework to a whole new level and provides a great basis for developing rich and dynamic apps that can run in the browser or as hybrid mobile apps.

logo_drupal

In this article I am going to show you a neat little way of using some of its magic within a Drupal 7 site. A simple piece of functionality but one that is enough to demonstrate how powerful Angular.js is and the potential use cases even within heavy server-side PHP frameworks such as Drupal. So what are we doing?

We are going to create a block that lists some node titles. Big whoop. However, these node titles are going to be loaded asynchronously using Angular.js and there will be a textfield above them to filter/search for nodes (also done asyncronously). As a bonus, we will also use a small open source Angular.js module that will allow us to view some of the node info in a dialog when we click on the titles.

So let’s get started. As usual, all the code we write in the tutorial can be found in this repository.

Ingredients

In order to mock this up, we will need the following:

  • A custom Drupal module
  • A Drupal hook_menu() implementation to create an endpoint for querying nodes
  • A Drupal theme function that uses a template file to render our markup
  • A custom Drupal block to call the theme function and place the markup where we want
  • A small Angular.js app
  • For the bonus part, the ngDialog Angular module

The module

Let us get started with creating a custom module called Ang. As usual, inside the modules/custom folder create an ang.info file:

name = Ang
description = Angular.js example on a Drupal 7 site.
core = 7.x

…and an ang.module file that will contain most of our Drupal related code. Inside this file (don’t forget the opening <?php tag), we can start with the hook_menu() implementation:

/**
 * Implements hook_menu().
 */
function ang_menu() {
  $items = array();

  $items['api/node'] = array(
    'access arguments' => array('access content'),
    'page callback'     => 'ang_node_api',
    'page arguments' => array(2),
    'delivery callback' => 'drupal_json_output'
  );

  return $items;
}
/**
 * API callback to return nodes in JSON format
 *
 * @param $param
 * @return array
 */
function ang_node_api($param) {

  // If passed param is node id
  if ($param && is_numeric($param)) {
    $node = node_load($param);
    return array(
      'nid' => $param,
      'uid' => $node->uid,
      'title' => check_plain($node->title),
      'body' => $node->body[LANGUAGE_NONE][0]['value'],
    );
  }
  // If passed param is text value
  elseif ($param && !is_numeric($param)) {
    $nodes = db_query("SELECT nid, uid, title FROM {node} n JOIN {field_data_body} b ON n.nid = b.entity_id WHERE n.title LIKE :pattern ORDER BY n.created DESC LIMIT 5", array(':pattern' => '%' . db_like($param) . '%'))->fetchAll();
    return $nodes;
  }
  // If there is no passed param
  else {
    $nodes = db_query("SELECT nid, uid, title FROM {node} n JOIN {field_data_body} b ON n.nid = b.entity_id ORDER BY n.created DESC LIMIT 10")->fetchAll();
    return $nodes;
  }
}

In hook_menu() we declare a path (api/node) which can be accessed by anyone with permissions to view content and which will return JSON output created in the callback function ang_node_api(). The latter gets passed one argument, that is whatever is found in the URL after the path we declared: api/node/[some-extra-param]. We need this argument because of we want to achieve 3 things with this endpoint:

  1. return a list of 10 most recent nodes
  2. return a node with a certain id (api/node/5 for example)
  3. return all the nodes which have the passed parameter in their title (api/node/chocolate for example, where chocolate is part of one or more node titles)

And this is what happens in the second function. The parameter is being checked against three cases:

  • If it exists and it’s numeric, we load the respective node and return an array with some basic info form that node (remember, this will be in JSON format)
  • If it exists but it is not numeric, we perform a database query and return all the nodes whose titles contain that value
  • In any other case (which essentially means the lack of a parameter), we query the db and return the latest 10 nodes (just as an example)

Obviously this callback can be further improved and consolidated (error handling, etc), but for demonstration purposes, it will work just fine. Let’s now create a theme that uses a template file and a custom block that will render it:

/**
 * Implements hook_theme().
 */
function ang_theme($existing, $type, $theme, $path) {
  return array(
    'angular_listing' => array(
      'template' => 'angular-listing',
      'variables' => array()
    ),
  );
}

/**
 * Implements hook_block_info().
 */
function ang_block_info() {

  $blocks['angular_nodes'] = array(
    'info' => t('Node listing'),
  );

  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function ang_block_view($delta = '') {

  $block = array();

  switch ($delta) {
    case 'angular_nodes':
      $block['subject'] = t('Latest nodes');
      $block['content'] = array(
        '#theme' => 'angular_listing',
        '#attached' => array(
          'js' => array(
            'https://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js',
            'https://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular-resource.js',
            drupal_get_path('module', 'ang') . '/lib/ngDialog/ngDialog.min.js',
            drupal_get_path('module', 'ang') . '/ang.js',
          ),
          'css' => array(
            drupal_get_path('module', 'ang') . '/lib/ngDialog/ngDialog.min.css',
            drupal_get_path('module', 'ang') . '/lib/ngDialog/ngDialog-theme-default.min.css',
          ),
        ),
      );
      break;
  }

  return $block;
}

/**
 * Implements template_preprocess_angular_listing().
 */
function ang_preprocess_angular_listing(&$vars) {
  // Can stay empty for now.
}

There are four simple functions here:

  1. Using hook_theme() we create our angular_listing theme that uses the angular-listing.tpl.php template file we will create soon.
  2. Inside the hook_block_info() we define our new block, the display of which is being controlled inside the next function.
  3. Using hook_block_view() we define the output of our block: a renderable array using the angular_listing theme and which has the respective javascript and css files attached. From the Google CDN we load the Angular.js library files, inside ang.js we will write our JavaScript logic and in the /lib/ngDialog folder we have the library for creating dialogs. It’s up to you to download the latter and place it in the module following the described structure. You can find the files either in the repository or on the library website.
  4. The last function is a template preprocessor for our template in order to make sure the variables are getting passed to it (even if we are actually not using any).

As you can see, this is standard boilerplate Drupal 7 code. Before enabling the module or trying out this code, let’s quickly create the template file so Drupal doesn’t error out. Inside a file called angular-listing.tpl.php, add the following:

        <div ng-app="nodeListing">
        
           <div ng-controller="ListController">
        
             <h3>Filter</h3>
             <input ng-model="search" ng-change="doSearch()">
        
              <ul>
                <li ng-repeat="node in nodes"><button ng-click="open(node.nid)">Open</button> {{ node.title }}</li>
              </ul>
        
             <script type="text/ng-template" id="loadedNodeTemplate">
             <h3>{{ loadedNode.title }}</h3>
             {{ loadedNode.body }}
             </script>
        
            </div>
        
        </div>

Here we have some simple HTML pimped up with Angular.js directives and expressions. Additionally, we have a <script> tag used by the ngDialog module as the template for the dialog. Before trying to explain this, let’s create also our ang.js file and add our javascript to it (since the two are so connected):

angular.module('nodeListing', ['ngResource', 'ngDialog'])

  // Factory for the ngResource service.
  .factory('Node', function($resource) {
    return $resource(Drupal.settings.basePath + 'api/node/:param', {}, {
      'search' : {method : 'GET', isArray : true}
    });
  })

  .controller('ListController', ['$scope', 'Node', 'ngDialog', function($scope, Node, ngDialog) {
    // Initial list of nodes.
    $scope.nodes = Node.query();

    // Callback for performing the search using a param from the textfield.
    $scope.doSearch = function() {
      $scope.nodes = Node.search({param: $scope.search});
    };

    // Callback to load the node info in the modal
    $scope.open = function(nid) {
      $scope.loadedNode = Node.get({param: nid});
      ngDialog.open({
        template: 'loadedNodeTemplate',
        scope: $scope
      });
    };

}]);

Alright. Now we have everything (make sure you also add the ngDialog files as requested in the #attached key of the renderable array we wrote above). You can enable the module and place the block somewhere prominent where you can see it. If all went well, you should get 10 node titles (if you have so many) and a search box above. Searching will make AJAX calls to the server to our endpoint and return other node titles. And clicking on them will open up a dialog with the node title and body on it. Sweet.

But let me explain what happens on the Angular.js side of things as well. First of all, we define an Angular.js app called nodeListing with the ngResource (the Angular.js service in charge communicating with the server) and ngDialog as its dependencies. This module is also declared in our template file as the main app, using the ng-app directive.

Inside this module, we create a factory for a new service called Node which returns a $resource. The latter is in fact a connection to our data on the server (the Drupal backend accessed through our endpoint). In addition to the default methods on it, we define another one called .search() that will make a GET request and return an array of results (we need a new one because the default .get() does not accept an array of results).

Below this factory, we define a controller called ListController (also declared in the template file using the ng-controller directive). This is our only controller and it’s scope will apply over all the template. There are a few things we do inside the controller:

  1. We load nodes from our resource using the query() method. We pass no parameters so we will get the latest 10 nodes on the site (if you remember our endpoint callback, the request will be made to /api/node). We attach the results to the scope in a variable called nodes. In our template, we loop through this array using the ng-repeat directive and list the node titles. Additionally, we create a button for each with an ng-click directive that triggers the callback open(node.nid) (more on this at point 3).
  2. Looking still at the template, above this listing, we have an input element whose value will be bound to the scope using the ng-model directive. But using the ng-change directive we call a function on the scope (doSearch()) every time a user types or removes something in that textfield. This function is defined inside the controller and is responsible for performing a search on our endpoint with the param the user has been typing in the textfield (the search variable). As the search is being performed, the results populate the template automatically.
  3. Lastly, for the the bonus part, we define the open() method which takes a node id as argument and requests the node from our endpoint. Pressing the button, this callback function opens the dialog that uses a template defined inside of the <script> tag with the id of loadedNodeTemplate and passes to it the current scope of the controller. And if we turn to the template file, we see that the dialog template simply outputs the title and the body of the node.

Conclusion

You can see for yourself the amount of code we wrote to accomplish this neat functionality. Most of it is actually boilerplate. A very fast node query block that delivers results asynchronously with all of its benefits. And if you know Angular.js, you can imagine the possibility of enhancing the Drupal experience further.

Now, are you interested to learn more about the love between Angular.js and Drupal? Would you have done anything differently? Let us know in the comments below!

Dec 24 2011
Dec 24

http://www.watchseika.com/ The web and celebrities go jointly like Ga Ga along with glamour. From its beginnings, the Internet has played an integral role [url=http://www.watchseika.com/]腕時計 レディース[/url]in making in addition to breaking countless famous these people :, from politicians to celebrities to sports figures.

http://www.findwatchjp.com/ But the influence flows in both directions. since the high point of her party years, but together with your ex cohorts, Hilton and Winehouse and also Britney Spears, Lohan set the common for fast youth in the online age. Lohan's [url=http://www.findwatchjp.com/]ブルガリ 時計[/url] impact on the net may not become a good thing to get her professionally, but it has only helped a booming Online gossip business.

http://www.watchbasis.com/ Twitter has created on the list of strangest intersections on online, a junction where celebrities not only tweet the details in their daily lives for numerous regular [url=http://www.watchbasis.com/]腕時計 メンズ[/url]
folks to read, but occasionally read what the off the shelf folks are

http://www.cathkidstonbaggu.com/ Without the computer along with software innovation of mega developers like Jobs and Bill Gates, there would be zero thriving[url=http://www.cathkidstonbaggu.com/]キャスキッドソン 店舗[/url] computer infrastructure which we could use the internet. But Jobs' Apple travelled further, helping to revolutionize the way we live online.

Dec 25 2010
mg
Dec 25

Sure there are modules for getting an auto complete textfield in Drupal. But where would the fun be then :)

Here I will show you how to write your own auto complete AJAX textfield. It is surprisingly easy and we will walk through all the steps from defining a text box to writing a SQL query to fetch the results to display.

Basically there are following steps to the whole process:

1. Define a textfield which will act as an auto complete

2. Define a menu item which will be invoked by our textfield(form element)

3. Call a function (handler function) which will hit a SQL query and fetch the results.

1. Define the form element - textfield:

$form['item'] = array(
'#type' => 'textfield',
'#title' => t('Select the entry'),
'#autocomplete_path' => 'nodes/autocomplete',
);

Drupal offers an #autocomplete_path attribute for textfields. This tells Drupal to look at the menu which handles the url and react accordingly. So next we define the menu which will handle this URL

2. Menu item to handle the URL

$items['nodes/autocomplete'] = array(
'page callback' => 'nodes_autocomplete',
'access arguments' => user_access('access example autocomplete'),
'type' => MENU_CALLBACK,
);

This entry in the hook_menu() will forward the request to the function 

nodes_autocomplete()

3. Guts of the autocomplete

In the function, nodes_autocomplete() you will implement the code the return the list of items to be shown in the textfield. In this case, we can fetch a node of type my_type

function nodes_autocomplete($string){
$items = array();
$result = db_query("SELECT nid, title FROM {node} WHERE status = 1 AND
type='my_type' AND title LIKE LOWER ('%s%%')", $string) ;
while($obj = db_fetch_object($result)) {
$items[$obj->nid] = check_plain($obj->title);
}
drupal_json($items);
}

This function makes the SQL query to retrive all nodes of type my_type and searches the title for autocomplete. drupal_json() formats the data in JSON format. So the $items will be returned as JSON which our javascript will be able to handle and show the results.

This is all you need and you should be on your way to having your own autocomplete textfield in Drupal. Drupal has all the built in support for these kinds of calls.

Jul 15 2010
Jul 15

Ajax form validation is one of those features typically tasked to a Drupal Developer. Here is how it is accomplished in Drupal 6.

Let's say we were adding Ajax form validation to the user registration page. You need to create a jQuery event listener in a javascript file.

$(document).ready(function(){   
  $("#edit-name").blur(function () {
    $.post('modulename/validate', { name: this.value }, function(data) {
      $('#edit-name-wrapper .description').append(data);
    });
  });
});

What this does is when the user clicks away from the username field it takes the value of that field and does an HTTP POST to modulename/validator (we will actually set up this url in a minute). If the name isn't valid, or we need to display some sort of error message, we append this to the description, or you could just define a new div to place stuff into.

Now, let's set up our custom module. You need to create a modulename directory in sites/all/modules then inside that directory create 2 files modulename.module and modulename.info with a little bit of information about your module. See http://drupal.org/node/206756 for reference. Now open up your .module file and let's create the url.

/**
* Implementation of hook_menu().
*/
function modulename_menu () {
  $items['modulename/validate'] = array(
    'page callback' => "modulename_ajax_validate",
    'page arguments' => array(2),
    'type' => MENU_CALLBACK,
  );
 
  return $items;
}

function modulename_ajax_validate ($string = '') {
  if ($string) {
    //do your validation here...say we didn't want to allow underscores in the username
    if(strstr($string,"_")) {
      $errors[] = t("Underscores in your username aren't allowed.");
    }
    foreach ($errors as $error) {
      print "$error <br />";
    }
  }
}

What this does is creates a path for the validation (modulename/validate) and then calls a function (there's nothing special about this function name, you could have called it foo_validateme) passing it the string parameter parameter. Then if the validation fails, it adds a string to the errors array. If there are errors, loop through the errors and print them out. This string gets passed back to our jQuery handler we wrote above as the variable name: data and gets printed out and appended to the description of the username field.

You could try this example, but it also might be prudent to have a look at the Ajax Project on Drupal.org.

Dear fellow Drupal Developer, you are now armed with the awesomeness of Ajax. Happy validating!

UPDATE: Several readers have recommended using the jQuery Validation plugin. This is a great plugin and have used it many times. I suggest you check it out. Also, there is a good example of doing AHAH in the examples module on drupal.org. Another reader commented that it would be a great idea to use the same validation function name so that the form would be validated by the same code using Ajax or just normal form submission. For this to work, you would need to get the name right using the Drupal Form API. Here is an example of how to do that.

Feb 10 2010
Feb 10

Ajax is becoming a standard, oft asked for feature in our Drupal sites. Clients like it because it makes their sites look 'slick'. We don't like it because it causes a few PITA's especially when it came time for us to integrate with Drupal's FAPI (Form API). Not so anymore. After needing to dig in and make a couple custom AJAX modules for a clients site, we got pretty familiar with the ins and outs of what is appropriately called AHAH. Now we're sharing it with you.

In this tutorial we'll show you a simple example of the anatomy of an AJAX module. Hopefully it will help you get started on your own AJAX fueled custom module(s). We'll assume you're somewhat comfortable with getting your hands dirty with a Drupal site

What is AHAH?
AHAH (Asynchronous HTML And HTTP) let you update web pages dynamically without refreshing the whole page. The advantage of it is that it decreases wait time for the user and requires less server resources.

You can download the example module used in this tutorial. To try it out, un-zip it to your /sites/all/modules folder and enable the module. Then go to Site Building -> Blocks and place the "AHAH Example" to the desired region of your site.

Step 1 - Create your .info file

name = AHAH Example
description = A module for demonstrating how to implement AHAH in Drupal.
version = "6.x-1.0"
core = "6.x"
project = "ahah_example"

Step 2 - Create your .module file
i.e. ahah_example.module

Step 3 - Define your menu callback function

<?php
/**
 * Implementation of hook_menu().
 */
function ahah_example_menu() {
	$items = array();
	$items['ahah_example/add'] = array(
		'page callback' => 'ahah_example_add',
		'access arguments' => array('access content'),
		'type' => MENU_CALLBACK,
	);
	return $items;
}
?>

In the above code we're doing two things. The first is to tell Drupal what URL is going to handle the AHAH request. In the example it's the $item arrays key (ahah_example/add). The second is to tell Drupal what function within our module is going to process the incoming request. In this case it's ahah_example_add.

Step 4 - Create a block

<?php
/**
 * Implementation of hook_block().
 */
function ahah_example_block($op = 'list', $delta = 0, $edit = array()) {
	global $user;
 
	switch ($op) {
		case 'list':
			$blocks[0] = array(
				'info' => t('AHAH Example'),
			);
			return $blocks;
		case 'view': default:
			switch ($delta) {
				case 0:
					$block['subject'] = t('AHAH Example - My Recent Pages');
					$block['content'] = drupal_get_form('ahah_example_form');
					break;
			}
			return $block;
	}
}
?>

Step 5 - Create your form
This is where you use Drupal's form API to define which form elements you'd like on your page.

<?php
/**
 * Implementation of hook_form().
 * A form to allow the user to create a new page node and show the 5 most recent pages created by the currently logged in user.
 */
function ahah_example_form($form_state) {
	global $user;
 
	// create a DIV to show the output of the AHAH request
	$form['new_row_wrapper'] = array(
		'#type' => 'markup',
		'#value' => '<br><div id="ahah-example-new-row-wrapper" style="clear: both;"></div>',
	);
 
	// show links to the latest 5 page nodes
	$sql = "SELECT n.nid, n.title
			FROM {node} AS n
			WHERE n.uid = '%d'
				AND n.type = 'page'
			ORDER BY n.nid DESC
			LIMIT 5";
	$db_result = db_query($sql, $user->uid);
	while ($row = db_fetch_object($db_result)) {
		$form['node_'.$row->nid] = array(
			'#type' => 'markup',
			'#value' => '<div><a href="'.url('node/'.$row->nid).'">Node '.$row->nid.' - '.$row->title.'</a></div>',
		);
	}	
 
	// create a form to allow the user to enter the new node's title and body
	$form['new_title'] = array(
		'#type' => 'textfield',
		'#title' => "Title",
		'#size' => 40,
		'#required' => TRUE,
	);
	$form['new_body'] = array(
		'#type' => 'textarea',
		'#title' => "Body",
		'#required' => TRUE,
	);
	$form['submit'] = array(
		'#type' => 'submit',
		'#value' => t('Add Node'),
		'#submit' => array(),
		'#ahah' => array(
			'path' => 'ahah_example/add',
			'wrapper' => 'ahah-example-new-row-wrapper',
			'method' => 'prepend',
			'effect' => 'fade',
			'progress' => array('type' => 'bar', 'message' => t('Please wait...')),
		),
	);
	return $form;
}
?>

The most important part of the above code is:

<?php
		'#ahah' => array(
			'path' => 'ahah_example/add',
			'wrapper' => 'ahah-example-new-row-wrapper',
			'method' => 'prepend',
			'effect' => 'fade',
			'progress' => array('type' => 'bar', 'message' => t('Please wait...')),
		),
?>

The path tells Drupal where to send this form post. In this case it's the menu item we setup in Step 1 of this tutorial. When the user clicks "Add Node" the form values will be passed to our menu item, then onto the function we specified (ahah_example_add).

Wrapper is used to define the HTML element that we want the returned AJAX data to show up in. In our example it points to a <div> with an id of "ahah-example-new-row-wrapper".

Method defines the behavior of the returned HTML. In our case we chose 'prepend' which will add the data at the beginning of the <div>'s contents. Other possible values include: 'replace' (default), 'after', 'append', 'before', 'prepend'.

Effect defines the jquery effect the new data will be brought into view with. Possible values: 'none' (default), 'fade', 'slide'.

The progress option let's you choose from one of two default Drupal progress indicators. The typical throbber or if you're expecting your AJAX call to take a bit longer (e.g. file uploads) you can choose a progress bar instead.

Step 6 - Create your function to handle the AHAH request
Depending on what values you're expecting from the form post, you'll need to change the error checking logic and the logic that handles the submitted data, but if you're just following along these should sufficiently cover the basics.

<?php
function ahah_example_add() {
	global $user;
 
	$output = '';
 
	//error checking
	if (!$user->uid) {
		$output .= '<div class="messages error ahah-example-msg">Access denied.</div>';
	}
	if ($_REQUEST['new_title'] == '') {
		$output .= '<div class="messages error ahah-example-msg">Please enter a title</div>';
	}
	if ($_REQUEST['new_body'] == '') {
		$output .= '<div class="messages error ahah-example-msg">Body field is required.</div>';
	}
 
	//no error, save node
	if (!$output) {
		$node = new StdClass();
		$node->type = 'page';
		$node->status = 1;
		$node->uid = $user->uid;
		$node->title = $_REQUEST['new_title'];
		$node->body = $_REQUEST['new_body'];
		node_save($node);
		if ($node->nid) {
			$output = '
			<div class="messages status ahah-example-msg">Successfully saved your page.</div>
			<script type="text/javascript">
			$("#edit-new-title").val("");
			$("#edit-new-body").val("");
			</script>
			<div><a href="'.url('node/'.$node->nid).'">Node '.$node->nid.' - '.$node->title.'</a></div>';
		} else {
			$output = '<div class="messages error ahah-example-msg">An error occurred, we cannot add your page at this time.</div>';
		}
	}
 
	//remove status message after 10 seconds
	$output .= '
		<script type="text/javascript">
		$("#edit-new-title").val("");
		$("#edit-new-body").val("");
		setTimeout("$(\'.ahah-example-msg\').remove();", 10000);
		</script>';
 
	//send output back to browser
	drupal_json(array('status' => TRUE, 'data' => $output));
}
?>
Jan 15 2009
Jan 15

When the Quick Tabs module was first conceived, it was meant as a space saving device that would replicate a feature becoming quite common on news websites: the little "Most emailed / Most popular" block, where you'd have two tabs, each showing about 5 items (node titles with links to the nodes), and that would be that. Of course I wasn't so short-sighted as to limit it to two tabs (I limited it to 10), but I didn't imagine there'd be much more people could want out of it. Well, my issue queue soon proved me very wrong. Among the many features subsequently requested by users, one kept cropping up again and again: AJAX-loaded tabs. The original offering (and what the Drupal 5 version is still unfortunately limited to) simply provided a show/hide mechanism for the tabs, so that all of the content was loaded at once, on page load, and simply shown or hidden as different tabs were clicked. As people began adding more and more views to their tabs, thereby essentially loading several views simultaneously on one page, they soon started to be concerned about performance. Once I saw how people were actually using the module, I started to realise how valuable a feature this could be: quicktabs wouldn't necessarily be confined to sidebars but could form the main content of a page, pulling in large amounts of Views data across several tabs. I honestly had never thought about it that way before.

So after dragging myself around to a bigger view of the module I finally had a go at implementing this ajax feature back in November. I completely separated the code for this new type of quicktab out from the original code for fear of breaking something in the parts that were already working fine and ended up presenting the user with two completely separate quicktabs creation forms - one for the ajax version, where you could choose either a node or a view for each tab, and one for the original type with blocks and views. Of course this proved to involve brutally unnecessary duplication of code as was pointed out to me soon enough, but it served my purposes fine for just figuring the thing out. After I got the basic functionality working - a couple of ajax callbacks for pulling the content (one for nodes and one for views) and sticking it into the quicktabs container - I felt very pleased with myself and committed it, fully expecting it to be received with rapturous appreciation by those who had been awaiting it. To my complete deflation what I got instead were bug reports. You see, I hadn't thought that people would be so demanding as to actually want things like paging and table-sorting to work in their ajax-loaded views, that just seemed like a ridiculously tall order to me. This was my initial reaction anyway, and in fact I did respond by saying something like "Oh well actually this probably won't work with paged views - sorry!" But of course it niggled and niggled at me until I finally gave in and decided to see why exactly the ajax paging in the views was not working inside my shiny new ajaxified quicktabs blocks.

That's when I started delving into the voodoo that is the Views ajax code, contained mainly (I think) in includes/ajax.inc and js/ajax_view.js. I tinkered around enough to discover the following useful facts:
1. there's no need to reinvent the wheel when it comes to loading a view via ajax: Views itself does this all the time with its exposed filters, paging links, etc
2. you can't load a view via ajax unless Drupal.settings already knows about it
3. it's one thing to wish to load multiple views to the same page (i.e. using the same instantiation of Drupal.settings) via ajax, but it's quite another to expect to load the same view twice (or three times, or four!) via ajax, passing different arguments or whatever, and have everything work swimmingly
4. when dealing with code from the likes of nedjo and merlinofchaos it's actually ok to expect things like #3

What #1 meant for me was that there was no need for my ajax view callback function at all - with the right javascript I could use the ajax callback function already built in to Views for sending back a JSON-formatted version of the requested view. Which brings us to point #2: "the right javascript" means ensuring that the Drupal.settings.views object contains the ajax_path (the path to the Views callback function) and also contains the ajaxViews object which in turn contains objects with the name, display id, arguments and path for every view that your page could conceivably want to load via ajax. Then it was simply a matter of copying the behaviour defined for the pager links and adapting it for use in my tab links. There was one snag though: if you did have a quicktabs block with two or more tabs loading the same view for whatever reason (most likely to pass different arguments each time), the pager links in the second and subsequent ones would end up returning the first version of the view. This was because it was referring to Drupal.settings.views.ajaxViews and finding the details for the first view with that name even though there were multiple objects for that particular view. But a recent change to ajax_view.js seems to have fixed this: the view object to be passed to the ajax callback gets initialised from what's in Drupal.settings but its properties are overridden with the values found in the querystring of the link - and in the pager links these will always be the correct details for that "version" of the view.

So I think it's all working nicely now - as long as you're using the Views code from HEAD. But the story of Quick Tabs' aspiration to awesomeness does not end there - not by a long shot. There have been massive improvements to the code as well as several great new features and usability improvements for which I sadly cannot take credit. No, that goes to Pasqualle, my new co-maintainer who has really transformed the module into an extremely powerful tool. He ripped out my ridiculously code-heavy "two forms for two different quicktab types" idea and replaced it with one form that allows you to choose whether your new (or existing) quicktab block should be ajax driven or not, and presents you with four (FOUR no less!) options of content for each tab: block, view, node or ... wait for it... quicktab. Yes, you can now nest a quicktab inside a quicktab. And yes, when I put this on the demo site a curious user did try the crazy idea of nesting the same quicktab within itself and yes, this did result in a giant black hole leading to the implosion of the entire site. So I have since added some validation to protect against that. Oh and you can now create quicktabs programmatically and you can have a different style for individual quicktabs blocks rather than one style for all and you can clone quicktabs and there is RTL support and there are more styles to choose from and... and... and... All thanks to Pasqualle.

There's an RC1 release up which is fairly stable, though it doesn't contain that all-important anti-black-hole protection and some other improvements (e.g. progress bar for ajax-loading content) have also been made subsequently. I'm not sure when the next ultra ultra stable release will come out but I really just wanted to get the word out there about this in the hopes that more people will give it a spin and provide some feedback. The next major feature request to be tackled is Panels integration (Panels 3 I guess, right? hmmm, I have a steep learning curve ahead of me...) Anyway, you can see Quick Tabs 2.0 in action here.

Aug 21 2008
Aug 21

I received in the mail my pre-ordered copy of Pro Drupal Development Second Edition yesterday.

The book is a brick. 667 pages with the index.

I am a little disappointed already... To be fair, I had high expectations. The treatment JavaScript and Ajax would receive was the first thing I'd look into.

AHAH is covered but the example provided is lame. It does not cover modifications to a form... Well it does, but you only inject some text in a field of type markup, so you do not need to fiddle with the cached form, or even read anything from it in the callback function. (The callback function is the PHP function that 'writes' the JSON that's returned to the browser...).

The chapter on jQuery provides one additional example module, besides PlusOne. That other module only attaches an additional JavaScript file to the page, using HOOK_init() and a registered theme function. No Ajax.

PlusOne still doesn't does degrade in this Drupal 6 version (see comments below). The book Learning Drupal 6 Module Development provides an example that does degrade. Meaning: without JavaScript, things won't look broken, as they do with the PlusOne module.

The chapter on jQuery explains how to add a function to Drupal.behaviors, but it does not explain how to pass a variable from Drupal PHP to the client-side Drupal.settings namespace. Learning Drupal 6 Module Development does explain this. It becomes important to use drupal_add_js to pass PHP variables over to our JavaScript to always avoid hard-coding certain values in our scripts.

We learned that in Drupal 6 our JavaScript is themable. Example, please? The book provides none.
(Any module's JavaScript file that produces HTML content must now provide default theme functions in the Drupal.theme.prototype namespace.)

The book does not even show one case where Drupal.t() is used. That function should have been mentioned at least.

Coverage of JavaScript best coding practices (such as... use camelCase for 'variable' and function names, etc.) should have found its place somewhere in that second edition, because the Drupal community and this very book are pushing hard for coding standards.

The book Learning Drupal 6 Module Development has its own shortcomings in its treatment of JavaScript due to the timing of its publication probably. By the way, the author of Learning Drupal 6 Module Development, Matt Butcher, has encouraged Drupal.org to update its handbook JavaScript coding standards page, to extend it based on a more thorough definition. Kuddos to him. JavaScript and Ajax are that important. Contributed modules developer (I am part of them) need to follow some conventions.

Last edited by Caroline Schnapp about 5 years ago.

Jan 14 2007
Jan 14

The upgrade went quite nice, even though I had to upgrade several modules and port quite a bunch of custom hacks I had on my old (Drupal 4.6) site. I first upgraded to 4.7, then to 5.0 (as is the recommended procedure) on a test-site, and after figuring out how to fix or work-around all the issues that appeared, I upgraded the live site.

I password-protected the site during the upgrade, that's why it wasn't available for a while today (and caused some problems on Planet Debian it seems, sorry for that!).

New features you might enjoy:

  • Every blog post, podcast entry, and photo/image has an AJAX-enabled voting/rating box now, thanks to the nice jRating module. Feel free to rate any content over here, I'm eager to know what you think.
  • The Service links module provides those tiny images in each post, which allow you to submit the post to del.icio.us, Digg, etc. etc. with a single click.
  • I now use the great new standard Drupal-Theme Garland with a custom color map. I really like it.
  • Tons of small changes here and there, removing custom hacks which are now obsoleted with the new Drupal release, shuffling some menus around etc. etc.

If you notice any bugs or problems with the site, please let me know.

May 01 2006
May 01

Wheee! Drupal 4.7.0, the new stable release of the CMS, has been released. After more than one year of development, we are now finally able to enjoy all the goodies which this new release brings.

For German speaking people we can now proudly claim to have a completed translation of the user interface, thanks to all the contributors! If you don't speak German, you can choose from one of the other 40+ translations for your site.

Read the release announcement for more information, I'm off upgrading a couple of sites now ;)

Oct 06 2005
Oct 06

The annual report from usability expert Jakob Nielsen:

  1. Legibility Problems
  2. Non-Standard Links
  3. Flash
  4. Content That's Not Written for the Web
  5. Bad Search
  6. Browser Incompatibility
  7. Cumbersome Forms
  8. No Contact Information or Other Company Info
  9. Frozen Layouts with Fixed Page Widths
  10. Inadequate Photo Enlargement

I agree with all of them, especially number 3 (Flash). I'm starting to like most of the AJAX sites popping up around me, but I have yet to find a Flash site which I really like.

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web