Ajax-paged tables with graceful degradation in Drupal 7

24 Jul 2013
Posted by Kiran
I was recently developing a Drupal module that required having multiple tables on the same page. No issues right? Wrong! As soon as I put multiple tables with pagers on the same page, I found that the pagers on the tables just stopped working.

Flummoxed, I started searching for a solution and finally stumbled upon Rahul Singla's blog explaining how to implement table pagers using ajax. However, Rahul's script did not handle graceful degradation, which was important for me. Hence, using Rahul's approach & script as a starting-point template and reading up a bit more about the PagerDefault and TableSort query extenders in Drupal, this is what I came up with:

How it works

The reason multiple pagers don't work on the same webpage is because Drupal needs some way to differentiate which pager to refresh. Though Drupal 7 does have a solution for this using the PagerDefault query extender (more on this later), using ajax for paging tables circumvents this problem by making each page-request a separate ajax request to a very specific server-side function.

The webpage initially loads blank and then separate ajax calls are made to refresh the various paged tables on the webpage. However, this approach breaks down when client-side Javascript is disabled. After loading the blank webpage, the client is unable to initiate the ajax calls to populate the table content.

We get around this problem by loading the webpage in a way that would work without Javascript. On the client-side we then use Javascript to set it up to work using ajax. Then ajax calls will only be used if Javascript is enabled.

The Server-side

On the server side, we use a separate function to render the content into the paged table. This function can be called either directly within the module code or can be called via a menu callback through the menu router system.

The initial rendering is done into the table on the server side when the form render array is generated, and subsequent ajax calls make use of the menu callback to receive other pages of the table.

Since the content of the paged table is initially rendered on the server side itself, we also attach the CSS class 'table-refreshed' to the container to indicate that the content is already in place. This tells the client side script to just attach the ajax behaviors and not to initiate an ajax request on page-load for this container.

The Drupal module code

  1. <?php
  2.  
  3. /**
  4. * Generic function to add the JS files required to enable Ajax Pager
  5. * functionality
  6. */
  7. function ajaxpager_add_js($ajaxpager_settings) {
  8.   drupal_add_library('system', 'drupal.ajax');
  9.   drupal_add_js(drupal_get_path('module', 'ajaxpager') . '/jquery.url.js', 'file');
  10.   drupal_add_js(drupal_get_path('module', 'ajaxpager') . '/ajaxpager.js', 'file');
  11.   drupal_add_js($ajaxpager_settings, 'setting');
  12. }
  13.  
  14.  
  15. /**
  16. * Implementation of hook_menu() for this example module
  17. */
  18. function ajaxpager_menu() {
  19.   //In addition to the menu link for the page that contains
  20.   //the paged tables, we also create additional menu callbacks
  21.   //for each paged table.
  22.   //
  23.  
  24.   $items = array();
  25.  
  26.   //The main page where the multiple pagers will be displayed
  27.   $items['ajaxpager'] = array(
  28.     'title' => 'Ajax Pager page',
  29.     'type' => MENU_NORMAL_ITEM,
  30.     'page callback' => 'drupal_get_form',
  31.     'page arguments' => array('ajaxpager_initial_page'),
  32.     'access arguments' => array('access content')
  33.   );
  34.  
  35.   //Menu callback for ajax response for the first pager
  36.   $items['ajaxpager/node_pager'] = array(
  37.     'title' => 'Ajax callback for listing Nodes',
  38.     'type' => MENU_CALLBACK,
  39.     'page callback' => 'ajaxpager_ajax_node_callback',
  40.     'access arguments' => array('access content'),
  41.   );
  42.  
  43.   //Menu callback for ajax response for the second pager
  44.   $items['ajaxpager/user_pager'] = array(
  45.     'title' => 'Ajax callback for listing Users',
  46.     'type' => MENU_CALLBACK,
  47.     'page callback' => 'ajaxpager_ajax_user_callback',
  48.     'access arguments' => array('access content'),
  49.   );
  50.  
  51.   return $items;
  52. }
  53.  
  54. /**
  55. * The page callback to be used to generate the initial form
  56. * using Form API
  57. */
  58. function ajaxpager_initial_page($form, $form_state) {
  59.   //The settings array is passed to the Client side using
  60.   //drupal_add_js. This array provides a list of all pagers
  61.   //on this page along with details of the Menu callbacks to
  62.   //be used for each pager
  63.   //
  64.   //Format of the array is:
  65.   //  Array
  66.   //  (
  67.   //    ['ajaxpagers'] => Array
  68.   //                      (
  69.   //                        ['#<ContainerName1>'] => Array
  70.   //                                                (
  71.   //                                                  'divName' => '#<ContainerName1>',
  72.   //                                                  'url' => '<Menu Callback URL for Ajax response 1>/nojs',
  73.   //                                                ),
  74.   //                        ['#<ContainerName2>'] => Array
  75.   //                                                (
  76.   //                                                  'divName' => '#<ContainerName2>',
  77.   //                                                  'url' => '<Menu Callback URL for Ajax response 2>/nojs',
  78.   //                                                ),
  79.   //                        ...
  80.   //
  81.   //                        ['#<ContainerNameN>'] => Array
  82.   //                                                (
  83.   //                                                  'divName' => '#<ContainerNameN>',
  84.   //                                                  'url' => '<Menu Callback URL for Ajax response N>/nojs',
  85.   //                                                ),
  86.   //                      ),
  87.   //  )
  88.   //
  89.   //The ContainerName MUST be prefixed by '#' in this array and must match with the Id tag for the
  90.   //corresponding container in the Form render array.
  91.   //
  92.   //The URL must have a suffix of 'nojs' as the last path component.
  93.   //
  94.   $ajaxpager_settings = array(
  95.     'ajaxpagers' => array(
  96.       '#node_pager' => array(
  97.         'divName' => '#node_pager',
  98.         'url' => 'ajaxpager/node_pager/nojs',
  99.       ),
  100.       '#user_pager' => array(
  101.         'divName' => '#user_pager',
  102.         'url' => 'ajaxpager/user_pager/nojs',
  103.       ),
  104.     ),
  105.   );
  106.  
  107.   $output['nodes'] = array(
  108.     '#type' => 'fieldset',
  109.     '#title' => t('Nodes'),
  110.     '#collapsible' => TRUE,
  111.     '#collapsed' => FALSE,
  112.   );
  113.  
  114.   $output['nodes']['table'] = array(
  115.     '#type' => 'container',
  116.     '#id' => 'node_pager', //Don't prefix with # here.
  117.     '#attributes' => array('class' => array('table-refreshed')), //Add table-refreshed class as we are rendering
  118.                                                                  //the content on the server side itself
  119.   );
  120.  
  121.   $output['nodes']['table']['content'] = array(
  122.     '#type' => 'markup',
  123.     '#markup' => ajaxpager_ajax_node_callback(),
  124.   );
  125.  
  126.   $output['users'] = array(
  127.     '#type' => 'fieldset',
  128.     '#title' => t('Users'),
  129.     '#collapsible' => TRUE,
  130.     '#collapsed' => FALSE,
  131.   );
  132.  
  133.   $output['users']['table'] = array(
  134.     '#type' => 'container',
  135.     '#id' => 'user_pager', //Don't prefix with # here.
  136.     '#attributes' => array('class' => array('table-refreshed')), //Add table-refreshed class as we are rendering
  137.                                                                  //the content on the server side itself
  138.   );
  139.  
  140.   $output['users']['table']['content'] = array(
  141.     '#type' => 'markup',
  142.     '#markup' => ajaxpager_ajax_user_callback(),
  143.   );
  144.  
  145.   ajaxpager_add_js($ajaxpager_settings); //Add the settings along with other required
  146.                                          //JS files into the form.
  147.  
  148.   return $output;
  149. }

In the code above you will see that the functions ajaxpager_ajax_node_callback and ajaxpager_ajax_user_callback are used to generate the content for the two containers node_pager and user_pager respectively. These functions are also setup as page callbacks for the Ajax response in the hook_menu() implementation for the module.

This allows these functions to be called from within the module code as well as make a direct call to them through the menu router system. The actual implementation for these functions is provided below.

The callback functions for ajax response

  1. <?php
  2.  
  3. /**
  4. * Function to invoke the Ajax Pager custom command to render the paged table
  5. *
  6. * Drupal provides a set of Ajax commands that can be used while providing responses
  7. * to ajax requests from the client. However, in this case we have created a custom
  8. * ajax command on the client side and we ask Drupal to invoke that command.
  9. */
  10. function ajaxpager_command_render_table($selector, $data, $method = 'nojs') {
  11.   //The $method indicates whether it is a response to an ajax request
  12.   //or a normal call.
  13.   //
  14.   if ($method == 'ajax') {
  15.     //If $method is 'ajax' then it is a response to an ajax request and we
  16.     //invoke the custom renderTable command on the client side to ensure
  17.     //that the response is written into the table on the client side using
  18.     //the selector provided.
  19.     //
  20.     if (is_array($data)) {
  21.       $data = drupal_render($data);
  22.     }
  23.  
  24.     //If it is an ajax response, the response structure will need to include
  25.     //the command name, the selector that should be written into as well as
  26.     //the data to be written.
  27.     $commands[] = array(
  28.       'command' => 'renderTable',
  29.       'selector' => $selector,
  30.       'data' => $data,
  31.     );
  32.  
  33.     $response = array('#type' => 'ajax', '#commands' => $commands);
  34.     ajax_deliver($response);
  35.   }
  36.   else {
  37.     //If $method is 'nojs', then it means that javascript is disabled on the
  38.     //client side and normal HTTP response is desired. In this case, the data
  39.     //is returned as is to allow rendering through the Form API
  40.  
  41.     return $data;
  42.   }
  43. }
  44.  
  45.  
  46. /**
  47. * The page callback used to generate content for the node_pager
  48. * container within the form.
  49. *
  50. * This function is called either directly to populate the markup
  51. * direction in the form render array, or to provide an ajax response
  52. * to ajax calls made to the 'ajaxpager/node_pager' menu callback.
  53. */
  54. function ajaxpager_ajax_node_callback($method = 'nojs') {
  55.   //The $method is assumed to be 'nojs' to support graceful degradation
  56.  
  57.   $node_header = array(
  58.     array('data' => 'Title', 'field' => 'title', 'sort' => 'asc'),
  59.     array('data' => 'Type', 'field' => 'type'),
  60.     array('data' => 'Created', 'field' => 'created'),
  61.     array('data' => 'Published'),
  62.   );
  63.  
  64.   //We query the nodes in the system and attach the query extenders
  65.   //for paging - PagerDefault - and sorting - TableSort - the table.
  66.   //The query is limited to 10 records per page and sorted by the
  67.   //$node_header by default.
  68.   //The query is marked with the element 0, indicating that it is
  69.   //the first pager on the page.
  70.   $node_rows = db_select('node', 'n')
  71.     ->condition('status', 1)
  72.     ->extend('PagerDefault')
  73.     ->extend('TableSort')
  74.     ->element(0)
  75.     ->limit(10)
  76.     ->orderByHeader($node_header)
  77.     ->fields ('n')
  78.     ->execute();
  79.  
  80.   foreach ($node_rows as $node) {
  81.     $node_data[] = array(
  82.       $node->title,
  83.       $node->type,
  84.       format_date($node->created),
  85.       $node->status
  86.     );
  87.   }
  88.  
  89.   //If no content has been created, then provide a user-friendly message.
  90.   if (empty($node_data)) {
  91.     $node_data[] = array(
  92.       array('data' => t('No content has been created as yet.'), 'colspan' => count($node_header)),
  93.     );
  94.   }
  95.  
  96.   $node_table  = theme('table', array('header' => $node_header, 'rows' => $node_data));
  97.   $node_table .= theme('pager', array('element' => 0)); //Use the same element which was used in the query
  98.  
  99.   return ajaxpager_command_render_table('#node_pager', $node_table, $method); //Use the same selector as that used in the form for this container.
  100. }
  101.  
  102.  
  103. /**
  104. * The page callback used to generate content for the user_pager
  105. * container within the form.
  106. *
  107. * This function is called either directly to populate the markup
  108. * direction in the form render array, or to provide an ajax response
  109. * to ajax calls made to the 'ajaxpager/user_pager' menu callback.
  110. *
  111. * The function is exactly similar to ajaxpager_ajax_node_callback
  112. * and can be generalized to handle different queries.
  113. */
  114. function ajaxpager_ajax_user_callback($method = 'nojs') {
  115.   //The $method is assumed to be 'nojs' to support graceful degradation
  116.  
  117.   $user_header = array(
  118.     array('data' => 'Name', 'field' => 'name', 'sort' => 'asc'),
  119.     array('data' => 'Email', 'field' => 'mail'),
  120.     array('data' => 'Created', 'field' => 'created'),
  121.     array('data' => 'Login', 'field' => 'login'),
  122.     array('data' => 'Status', 'field' => 'status'),
  123.   );
  124.  
  125.   //We query the users in the system and attach the query extenders
  126.   //for paging - PagerDefault - and sorting - TableSort - the table.
  127.   //The query is limited to 10 records per page and sorted by the
  128.   //$user_header by default.
  129.   //The query is marked with the element 1, indicating that it is
  130.   //the second pager on the page.
  131.   $user_rows = db_select('users', 'u')
  132.     ->extend('PagerDefault')
  133.     ->extend('TableSort')
  134.     ->element(1)
  135.     ->limit(10)
  136.     ->orderByHeader($user_header)
  137.     ->fields ('u')
  138.     ->execute();
  139.  
  140.   foreach ($user_rows as $queried_user) {
  141.     $user_data[] = array(
  142.       $queried_user->name,
  143.       $queried_user->mail,
  144.       format_date($queried_user->created),
  145.       format_date($queried_user->login),
  146.       $queried_user->status
  147.     );
  148.   }
  149.  
  150.   //If no content has been created, then provide a user-friendly message.
  151.   if (empty($user_data)) {
  152.     $user_data[] = array(
  153.       array('data' => t('There are no users in this system.'), 'colspan' => count($user_header)),
  154.     );
  155.   }
  156.  
  157.   $user_table  = theme('table', array('header' => $user_header, 'rows' => $user_data));
  158.   $user_table .= theme('pager', array('element' => 1)); //Use the same element which was used in the query
  159.  
  160.   return ajaxpager_command_render_table('#user_pager', $user_table, $method); //Use the same selector as that used in the form for this container.
  161. }

The functions ajaxpager_ajax_node_callback and ajaxpager_ajax_user_callback make use of a common function ajaxpager_command_render_table to return the content they generate; this common function checks whether or not an ajax response is needed and structures the response accordingly. If an ajax response is desired, the function invokes a client-side command called renderTable to render the content into the right container, as defined by the selector provided in the response. If a normal HTTP response is desired, the data is returned untouched.

The Client-side

On the client-side, we make use of Drupal's powerful Javascript and Ajax framework along with some jQuery magic to complete the ajax cycle.

Here we implement the custom Drupal ajax command renderTable and pin it on to Drupal's ajax framework. This is the command that is invoked from the server-side code as part of each ajax response. Every time the user clicks on a link to go to a different page, Drupal's ajax framework is invoked, which in turn makes a call to the appropriate menu callback on the server. Once the ajax response is received, the client-side renderTable command is invoked, which then renders the new content into the container.

The Javascript code

  1. (function ($) {
  2.  
  3. /**
  4. * We store all Javascript functions and objects within the AjaxPager object
  5. */
  6. Drupal.AjaxPager = {};
  7.  
  8. /**
  9. * Drupal Ajax Custom Command to refresh the HTML content of the Context Selector
  10. */
  11. Drupal.AjaxPager.renderTable = function(ajax, response, status) {
  12.     $(response.selector).html(response.data);
  13.     $(response.selector).addClass('table-refreshed');
  14.     Drupal.attachBehaviors($(response.selector));
  15. };
  16.  
  17. /**
  18. * Adding a function to the Drupal ajax prototype. This function helps in
  19. * initial refresh of the table if it hasn't been rendered from the server
  20. * side.
  21. */
  22. Drupal.ajax.prototype.initialRefresh = function() {
  23.     //This function simply takes the ajax options that are already
  24.     //loaded into the Drupal ajax object and intiates the ajax request
  25.     var ajaxer = this;
  26.  
  27.     if(!ajaxer.ajaxing) {
  28.         try {
  29.             $.ajax(ajaxer.options);
  30.         }
  31.         catch (e) {
  32.             alert("Error occurred while processing ajax request to " + ajaxer.options.url);
  33.             return false;
  34.         }
  35.     }
  36.     else {
  37.         return false;
  38.     }
  39. };
  40.  
  41. /**
  42. * Adding the custom ajax command into the Drupal.ajax prototype
  43. */
  44. Drupal.ajax.prototype.commands.renderTable = Drupal.AjaxPager.renderTable;
  45.  
  46. /**
  47. * This is the main function that attaches behaviors to all links within the
  48. * ajaxed table.
  49. */
  50. Drupal.behaviors.ajaxPager = {
  51.     attach: function(context, settings) {
  52.         // This function may be call in different contexts
  53.         //  1. During the initial load of the page with the table already rendered
  54.         //     on the server side
  55.         //  2. During the initial load of the page with the table not rendered
  56.         //     from the server side
  57.         //  3. For re-attaching the behaviors after an ajax page request
  58.         //
  59.  
  60.         if(context.selector !== undefined) {
  61.             //This is a ajax page request since there is a context (Scenario 3)
  62.  
  63.             //Retrieve the URL and other settings from the settings provided at the server
  64.             //for that particular context.selector
  65.             div_settings = settings.ajaxpagers[context.selector];
  66.             load_settings = div_settings;
  67.  
  68.             if($(context.selector).hasClass('table-refreshed')) {
  69.                 //If the content has already been rendered, only then
  70.                 //we can attach the behaviors to the links
  71.  
  72.                 load_settings.progress = {};
  73.                 load_settings.progress.type = 'throbber';
  74.  
  75.                 i = 0;
  76.                 $(context.selector + ' ul.pager li a')
  77.                 .add(context.selector + ' th a')
  78.                 .once('pager-ajax').each(function() {
  79.  
  80.                     //Select the individual links for the pager as well as
  81.                     //the sorted header links
  82.  
  83.                     load_settings.url = this.href;
  84.                     load_settings.event = 'click tap';
  85.  
  86.                     element_link = $(this);
  87.  
  88.                     //An element id is required for Drupal.ajax but the pager doesn't
  89.                     //provide an id to the pager links. We must manually create them
  90.                     element_id = 'link-' + i++ ;
  91.                     element_link.attr('id', element_id);
  92.  
  93.                     //Add a Drupal.ajax to each link
  94.                     Drupal.ajax[context.selector + '-' + element_id] = new Drupal.ajax(element_id, element_link, load_settings);
  95.                 });
  96.             }
  97.         }
  98.         else {
  99.             //The context.selector will be undefined only during the initial page load
  100.             //Subsequent ajax page requests will always have a context.selector
  101.  
  102.             //In case of the initial page load, we will have to loop through
  103.             //all available pagers on the page and ensure they are refreshed
  104.             //and behaviors attached to all links
  105.             //
  106.             //The settings.ajaxpagers object will have the list of all
  107.             //pagers on this page. This settings object is set on the server
  108.             //side with each page.
  109.             for (var i in settings.ajaxpagers) {
  110.                 //Looping through all ajaxpagers for this page
  111.  
  112.                 div_settings = settings.ajaxpagers[i];
  113.                 load_settings = div_settings;
  114.  
  115.                 if(!$(div_settings.divName).hasClass('table-refreshed')) {
  116.                     //(Scenario 2)
  117.                     //If the server has not rendered the content, it will not have
  118.                     //the table-refreshed class. In this case we call the initialRefresh
  119.                     //function to make the ajax call and render the content for this
  120.                     //pager
  121.  
  122.                     load_settings.url = Drupal.settings.basePath + '?q=' + div_settings.url; //This should work for both Clean URLs as well as Unclean URLs
  123.                     load_settings.event = 'onload'; //Even will be onload of the body
  124.                     load_settings.keypress = false;
  125.                     load_settings.prevent = false;
  126.                     load_settings.progress = {}; //We don't need a progress indicator in this case
  127.  
  128.                     //By passing a null id and the document body itself as the object, we link the ajax to the document itself
  129.                     Drupal.ajax[div_settings.divName] = new Drupal.ajax(null, $(document.body), load_settings);
  130.                     Drupal.ajax[div_settings.divName].initialRefresh();
  131.                 }
  132.                 else {
  133.                     //(Scenario 1)
  134.                     //If the server has already rendered the content, it will have
  135.                     //the table-refreshed class. In this case we simply
  136.                     //attach the ajax behaviors
  137.  
  138.                     load_settings.progress = {};
  139.                     load_settings.progress.type = 'throbber';
  140.  
  141.                     new_url = '';
  142.                     clean_enabled = false;
  143.  
  144.                     //In this scenario, the server will have loaded the content into the pager
  145.                     //assuming that JavaScript has been disabled. Hence, all URLs will refer
  146.                     //directly to the base page URL and not the specific Ajax callback URL.
  147.                     //
  148.                     //This ensures that in case JavaScript is disabled, the table can still be
  149.                     //paged by refreshing the entire page.
  150.                     //
  151.                     //If this script runs, then JavaScript is enabled on the client. We must
  152.                     //manually change all the URLs to the ajax URLs
  153.  
  154.                     //To do this, we take just one URL using the .first jQuery call
  155.                     //and parse that URL to derive the basepath for the URL
  156.                     //We then use the the Ajax URL provided in the div_settings
  157.                     //and derive the ajax callback to be used for this link
  158.                     $(div_settings.divName + ' ul.pager li a')
  159.                     .add(div_settings.divName + ' th a')
  160.                     .first().each(function() {
  161.                         base_string = $.url(this.href).attr('base'); //The basepath from the URL
  162.                         query_string = $.url(this.href).attr('query'); //The Query String from the URL
  163.  
  164.                         if(query_string.substr(0,2) == 'q=') {
  165.                             //Clean URLs aren't enabled
  166.                             new_url = base_string +'/?q=' + div_settings.url;
  167.                         }
  168.                         else {
  169.                             //Clean URLs are enabled
  170.                             clean_enabled = true;
  171.                             new_url = base_string +'/' + div_settings.url;
  172.                         }
  173.                     });
  174.  
  175.                     //Once the URL callback format has been derived, the same can be applied
  176.                     //to all URLs
  177.  
  178.                     i = 0;
  179.                     $(div_settings.divName + ' ul.pager li a')
  180.                     .add(div_settings.divName + ' th a')
  181.                     .once('pager-ajax').each(function() {
  182.                         elem_query = $.url(this.href).attr('query');
  183.  
  184.                         //The Ajax callback URL format will be different when Clean URLs are enabled
  185.                         //
  186.                         //If Clean URLs are enabled then the querystring will only contain the page,
  187.                         //sort and order
  188.                         //
  189.                         //If Clean URLs aren't enabled the querystring will contain the entire path
  190.                         //itself - everything after the ?q= in the URL
  191.                         load_settings.url = new_url + (clean_enabled ? '?' + elem_query : '&' + elem_query.substr(elem_query.indexOf('&') + 1));
  192.                         load_settings.event = 'click tap';
  193.  
  194.                         element_link = $(this);
  195.  
  196.                         //An element id is required for Drupal.ajax but the pager doesn't
  197.                         //provide an id to the pager links. We must manually create them
  198.                         element_id = 'link-' + i++ ;
  199.                         element_link.attr('id', element_id);
  200.  
  201.                         //Add a Drupal.ajax to each link
  202.                         Drupal.ajax[div_settings.divName + '-' + element_id] = new Drupal.ajax(element_id, element_link, load_settings);
  203.                     });
  204.                 }
  205.             }
  206.         }
  207.     }
  208. };
  209.  
  210. })(jQuery);
Graceful degradation

What happens if the user has client-side Javascript disabled? Since we need to degrade gracefully in such scenarios, all content generated from the server initially assumes that client-side Javascript is in fact disabled. Hence, all the links in the content — for different pages and for sorting the table using the header — will trigger a full page refresh and not an ajax request.

The Drupal Ajax framework

You will notice that the server side function ajaxpager_command_render_table delivers an ajax response only if the variable $method is set to 'ajax'. You will probably also notice that the method is always set to 'nojs' and never explicitly changed to 'ajax'. How will this work then?

The answer to that is in the Drupal Ajax framework. When a URL containing the path segment nojs is passed into the Drupal Ajax framework, it automatically replaces that path segment to ajax. This means that a URL such as ajaxpager/node_pager/nojs will be automatically converted by the framework to ajaxpager/node_pager/ajax. Since the last segment of the path is passed into the page callback functions as a parameter, the $method variable in our two callbacks receives a value of 'ajax'.

Since this conversion of nojs to ajax is done by the client-side Drupal Ajax framework, it only happens when client-side Javascript is enabled. Otherwise, the original URL with the nojs segment continues to be used and the server responds with a full HTTP response.

PagerDefault Query Extender

Another aspect of implementing graceful degradation lies in the query extender PagerDefault. This extender makes use of an attribute called element which helps us differentiate the various pagers on a single Drupal webpage.

In the above example, I have used the element(0) for the node_pager and an element(1) for the user_pager. This helps differentiate the two pagers and ensures that even in the absence of an ajax callback, the correct pager gets refreshed.

Limitation

The only limitation that I am aware of with the above approach is to do with the TableSort query extender. Unlike PagerDefault, TableSort does not support elements. This means that if you have two tables on the same webpage and both tables have columns with the same title, then clicking on one will result in both tables getting sorted by the column of that name.

A workaround for this is to perhaps always ensure that the columns in your tables have different names.

Here is an example module that contains all the code that I have described above. You can install this on your Drupal installation to see it working. Do let me know if you found this write up useful. If there are questions, leave a comment and I will do my best to answer.

Hi, thanks for the module.

Hi, thanks for the module. This is exactly what I've been looking for. I've downloaded and installed your module but I've got the below error message:

An AJAX HTTP error occurred.
HTTP Result Code: 404
Debugging information follows.
Path: http://localhost/ajaxpager/node_pager/ajax?page=1
StatusText: Not Found
ResponseText:
Object not found!
The requested URL was not found on this server.

Any help is appreciated. Thanks.


Reply to comment | Kiran J. Holla

I went over this website and I believe you have a lot
off good info, bookmarked (:.

My wweb blog :: free mixtape downloads,rap mixtapes,street mixtapes,hip hop music, new mixtapes


Reply to comment | Kiran J. Holla

Remаrkable! Its truly amazing pіeϲe oof writing,I
have got much ϲlear idea concerning from this post.

my page My Booty Kit


Reply to comment | Kiran J. Holla

Standard holdewrs positgion thhe document adjacent too the monitor,
nonetheless We recommend models that let the user to locaation numerous
things straight in front of the body, in between your keyboard and monitor, avoiding awkward neck postures and maximizing productivity.

My blog - office depot desk chair


Reply to comment | Kiran J. Holla

I feel that is among the most important info for me.

And i'm satisfied studying your article. But should observation on some
normal things, The free mixtape site [Katie] style is ideal, the articles
is actually excellent : D. Good activity, cheers


Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options