Create an ajax control in drupal 7

We know that in drupal you can attach #ajax property to an existing ajax ready control to have ajax functionality, such controls like ‘button’,’textarea’,’select’,’radio’ etc, to find the whole list  see system_element_info() in which all the controls that #process function includes ‘ajax_process_form’ are ajax read controls.

The question comes when you want an ajx control which is not in the system_element_info(), in my case I want to use bootstrap dropdownbutton, which renders as


<button><span></span></button>

<ul>

<li>item1<li>

<li>item2<li>

</ul>

The ajax action should happen on the item selection, not on the button. So both the layout and ajax functionality of this control asks for a completely new customized control with ajax support

Basics of ajax

Drupal ajax can be wired in to a form or not with a form, the formal is the most likely the case, behind the heavy handed drupal ajax processing, drupal ajax is using jquery.form.ajaxsubmit with serialized form values, you can also pass none form data to server using $element[#ajax][submit][key]=value, this is used by system to store trigger element name and value as well.

In some cases, we might not find an environment of form, like a node view, how can we find a form environment where ajax can be used in this scenario? The solution is create a form by calling drupal_get_form() where we want to use  ajax.

In hook_node_view


$build = drupal_get_form("example_form",  $form_args);

$additions['example_form'] = $build;

return $additions;

This technique is used by comments module to add form to node view. Within the example_form we can place any kind of ajax controls.

Declare the new element type

In hook_element_info, declare the type


$types['example_dropdownbutton'] = array(

'#input'=>true, // to make it trigger element

'#process' => array('example_dropdownbutton_process','ajax_process_form'),

'#theme_wrappers' => array('example_dropdownbutton'),

);

Setting #input to true is very important, as it will give it a #name to make it trigger element,

Add ajax_process_form to #process, as it is where #ajax of an element is processed.

Before ajax_process_form is our own process function, the reason it should be added before ajax_process_form is that all ajax controls within the element can be processed, and are not being missed out.

example_dropdownbutton_process

As it is said earlier it is the form that is actually submitted back to server, so in this element I embed a hidden input to store the selected value from ul.

Can I attach an #ajax to this hidden input to trigger the ajax? The answer is no,  unless you rewrite the system hidden control, drupal hidden type does not output id, which is a must-have for ajax ready control, as all ajax settings in client side work around the id of the control as the trigger,  the trigger must have an id, when a control #input is set to true, even if no id is set for the control, system will generate #id like edit-parent-control for it.

So we have to make our hybrid element as ajax trigger, the hybrid element is virtual object at this stage, it is not a button, not an input text, not a select, so what kind of event we can use to trigger ajax? Fake one, so I faked a ‘itemselected’ event of our made up control, as it is not a dom event, so I have to fire this event on the made up control which is a div, therefor  in the li select event I  trigger this faked event using jquery.trigger , #ajax processing by drupal makes sure that ajax submit is bound to this faked event, amazing. As the code in ajax.js indicated


$(ajax.element).bind(element_settings.event, function (event) {

return ajax.eventResponse(this, event);

});

Here is the code for  example_dropdownbutton_process

function example_dropdownbutton_process($element, &$form_state, $form) {

 // always having a tree structure for UI
 $element['#tree'] = true;

$element['selecteditem'] = array(
 '#type'=>'hidden',
 '#default_value' =>'0',
 '#attached'=>array(
 'js' => array(drupal_get_path('module', 'example') . '/example_dropdownbutton.js'=>
 array('group' => JS_DEFAULT, 'weight'=>500),
 array('type' => 'setting',
 'data' => array('dropdownbuttonids'=>array($element['#id'])),
 ), // telling the client javascript the id of this element
 ),

 ),

);

if(!empty($element['#ajax']))
 {
 //customize this triggering event
 // this is important if no event set, it will not be marked as ajax element
 unset($element['#ajax']['event']);
 $element['#ajax']['event']='itemselected';

}
 return $element;
}

The selecteditem is the hidden input to store the selection of li.

Instead of hardcoding id in javascript, or in case  this control having multiple instances, so I set the element id into javascript settings array under key of  dropdownbuttonids.

The id management of ajax element in drupal is very complicated, the short story is all input control ids within the wrapper will change after round trip to server.  So the javascript cannot rely on the hardcoded id, it is either not using id in javascript at all or storing id in a setting variable to keep up with the id change. The later is exactly how durpal does to wire up ajax settings to a control on front end.

Here is the code for   example_dropdownbutton.js

</pre>
(function($) {

// declare a global object which is initialized by attach
 Drupal.example = Drupal.ccpuriri || {};
 Drupal.example.dropdownbutton = Drupal.example.dropdownbutton || {};

Drupal.behaviors.DROPDOWNBUTTON = {
 attach : function(context, settings) {
 if (settings.dropdownbuttonids){
 for (var base in settings.dropdownbuttonids) {
 // binding the select event to every li select
 $('#'+settings.dropdownbuttonids[base]+'-div > ul.dropdown-menu > li ').bind('click',function(){
 // set the value to hidden input
 $('#'+settings.dropdownbuttonids[base]+'-div > input[type="hidden"]').val($(this).val());

 // trigger the ajax event
 if($(this).val()>0)
 {
 // deviders having id as 0, no trigger
 $('#'+settings.dropdownbuttonids[base]).trigger('itemselected');
 }
 });


 }

 }


 },

};

})(jQuery);
<pre>

So in this behavior, li select handler is created to update the value of hidden control and trigger the ajax event on the target.

Theme_example_dropdownbutton

This function will just render the control that is a boot strap dropdown button.

The use of custom element


$form['list_block']['addbutton']= array(

'#type' => 'example_dropdownbutton',

'#value' => t('Add to List'),

'#icon' =>'icon-plus',

'#attributes'=>array(),

'#colorcode' =>'info',

'#items' => $toaddlists,

'#ajax' =>array(

'wrapper'=>'list-block',

'callback'=> 'example_list_addremove_form_callback',

),

);

The above is the example of how I use the control, ignore #icon and #colorcode, #items is an array for dropdown list, there is no event defined in #ajax here, in theory all custom ajax controls must have event set, otherwise it will not be treated as ajax, in this case event of ajax is set by the controls process function.

Tags:

This entry was posted on Wednesday, October 23rd, 2013 at 12:33 am and is filed under PHP. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

Leave a Reply

*