Hi,

I've been stucked on problem since two weeks.
Basically, what i'am trying to do is:
1) Create a page, in which there is an link ("Click me"),
When i click on it, a form is fetched from another page and added to the current page.
The form is correctly fetched, but it seems any javascript/ajax related to the form is lost.
I have a custom Ajax submit callback for this form, on the page of the form(submit button has class ajax-processed), it is working correctly but when fetched by Ajax(submit button does not has the class ajax-processed), it doesn't.

Below is my code:

<?php

function custom_preprocess_page(&$vars){
   
  drupal_add_library('system', 'drupal.ajax');
  drupal_add_library('system', 'drupal.form');
    drupal_add_js(drupal_get_path('module','custom').'/custom.js', 'file');
}

/**
 * Implements hook_menu
 * @return string
 */
function custom_menu() {

    $items['my-test/my_form'] = array(
        'title' => 'Ajax test',
        'description' => 'Page containing form',
        'page callback' => '_custom_retrieve_webform',
        'access callback' => TRUE,
    );

    $items['my-test/retrieve_form'] = array(
        'title' => 'Ajax test',
        'description' => 'Page where form should be loaded by ajax',
        'page callback' => '_custom_retrieve_html',
        'access callback' => TRUE,
    );
    return $items;
}
function _custom_retrieve_html(){
    return '<div id="justadiv"><a class="clickme">'. t('click me'). '</a></div>';
}
function _custom_retrieve_webform(){
  
   $node = node_load(12);
   $submission = (object) array();
   $enabled = TRUE;
   $preview = FALSE;
   $form= drupal_get_form('webform_client_form_12', $node, $submission, $enabled, $preview);
  return '<div id="myform">'.drupal_render($form).'<div>';
}




function custom_form_alter(&$form, &$form_state, $form_id) {
    if ($form_id == 'webform_client_form_12') {
        $nid = $form['#node']->nid;
        $form['submitted']['gender']['#default_value'] = 'M';
        $form['actions']['submit']['#ajax'] = array(
            'callback' => 'mymodule_webform_js_submit',
            'wrapper' => 'webform-client-form-' . $nid,
            'method' => 'replace',
            'effect' => 'fade',
        );
        
       //$form['#theme']=array('custom_web_form');
        
       array_unshift($form['#theme'], 'custom_web_form');
    }
}

function mymodule_webform_js_submit($form, $form_state) {
      // define the $sid variable (submission id from webform)
      $sid = $form_state['values']['details']['sid'];
      // if we have a sid then we know the form was properly submitted, otherwise, we'll just return the existing $form array
      if ($sid) {

        dsm($sid);

        $confirmation = array(
          '#type' => 'markup',
          '#markup' => 'sucess',
        );
        // return the confirmation message
        return $confirmation;
      }
      else {
        // return the form
        return $form;
      }
    }

My js:

(function($){
    
    Drupal.behaviors.my_custom= {
        
        attach:function(context,settings){
            $('a.clickme').click(function(e){
                $('#justadiv').load('/d7/my-test/ajax_loaded #webform-client-form-12', function(response, status, xhr) {
                    
                    Drupal.attachBehaviors('#webform-client-form-12');

                    
                });
                Drupal.attachBehaviors($('#webform-client-form-12')[0]);
            });

        }
        
    }
        
})(jQuery)

I badly need assistance.

Comments

nevets’s picture

Have you tried the Ajax module?

panditvarun20’s picture

He want to with custom code not any module.

Varun Kr Pandey

Jaypan’s picture

You are on the right track, but there are a few issues with your code. First, lets look at your ajax callback function, _custom_retrieve_webform():

function _custom_retrieve_webform()
{
  $node = node_load(12);
  $submission = (object) array();
  $enabled = TRUE;
  $preview = FALSE;
  $form= drupal_get_form('webform_client_form_12', $node, $submission, $enabled, $preview);

  return '<div id="myform">' . drupal_render($form) . '</div>';
}

The function you've mapped to your AJAX callback path is _custom_retreive_webform(). At the end if your function, you've returned some HTML.

The code that you return from this function is then wrapped in the page HTML, which includes blocks, content, scripts, css files - everything for an entire page load. The content for that page is div#myform, which contains the form that you've rendered. Next, we'll look at your JavaScript:

$('a.clickme').click(function(e) {
  // Note - following code adjusted for readablity
  $('#justadiv').load('/d7/my-test/ajax_loaded #webform-client-form-12');
}

In the above code, when a.clickme is clicked, the jQuery $.load() function requests data from the path /d7/my-test/ajax_loaded. The jQuery selector that is attached is then used as a filter, and everything outside this div is stripped off, and thrown away.

The problem as you can see, is that a lot of unnecessary HTML is generated on the server, as it is just thrown away by the javascript. To fix this, instead of returning the HTML from your page callback function, you can kill the script right there, as you already have everything you need:

  // Was: return '<div id="myform">' . drupal_render($form) . '</div>';

  // New code:
  die(render($form));

The render() function will render the form and send the HTML to the browser, which means the remainder of the page is not loaded or rendered, so less HTML is sent to the browser, meaning your AJAX responses will be faster as well.

Then, in your JavaScript, you can do the following:

$('a.clickme').click(function(e) {
  $('#justadiv').load('/d7/my-test/ajax_loaded');
});

Because your PHP function only returns the form, there is no need to filter anything from the URL you've called. So you can load the entire contents of the response into div#justadiv.

Next, lets look at your usage of Drupal.attachBehaviors():

$('a.clickme').click(function(e){
  $('#justadiv').load('/d7/my-test/ajax_loaded #webform-client-form-12', function(response, status, xhr) {
    Drupal.attachBehaviors('#webform-client-form-12');
  });
  Drupal.attachBehaviors($('#webform-client-form-12')[0]);
});

First, you don't need to pass anything to Drupal.attachBehaviors(). You can, and should, call it with no arguments:

Drupal.attachBehaviors();

This will allow all modules to attach any behaviors on the new HTML that has been added. Some other module may be specifically watching form elements, and since you have now added form elements, you want to allow all modules to do whatever they want.

The other issue is that you are calling Drupal.attachBehaviors() twice - once when your $.load() function has completed, and once on the $.click() handler you've set on a.askme. This means that every time you click that button, Drupal.attachBehaviors() is called. But you only want to call it when new content has been added, so you should remove your second call to Drupal.attachBehaviors().

One final point on your JavaScript, your $.click() handler will be attached every time Drupal.attachBehaviors() is called. This is no good, as you will end up having your code called multiple times each time the a.askme is clicked. To get around this, you can use Drupal 7's $.once() function. This function ensures that the contained code will only be executed one time. This is achieved by tracking html elements using a custom class name in the HTML (in the following case, .my-custom). Here is a bit of a rewrite of your javascript:

(function($)
{
  Drupal.behaviors.myCustom= {
    attach:function() {
      $('a.clickme').once("my-custom", function() {
        // $(this) refers to a single instance of a.clickme
        $(this).click(function() {
          $('#justadiv').load('/d7/my-test/ajax_loaded', function() {
            Drupal.attachBehaviors();
          });
        });
      }
    }
  };
}(jQuery));

Finally, lets return back to your menu callback function, as there is one more step that needs to happen for the AJAX on your form to work. You need to generate the javascript settings for the form, and send them to the browser to be merged with the existing settings. Without doing this, any AJAX on your form will not work. Here is the adjusted callback function:

function _custom_retrieve_webform(){
 
  $node = node_load(12);
  $submission = (object) array();
  $enabled = TRUE;
  $preview = FALSE;
  $form= drupal_get_form('webform_client_form_12', $node, $submission, $enabled, $preview);

  // Generate the settings:
  $settings = FALSE;
  $javascript = drupal_add_js(NULL, NULL);
  if(isset($javascript['settings'], $javascript['settings']['data']))
  {
    $settings = '<script type="text/javascript">jQuery.extend(Drupal.settings, ';
    $settings .= drupal_json_encode(call_user_func_array('array_merge_recursive', $javascript['settings']['data']));
    $settings .=  ');</script>';
  }

  // Return the rendered form and the settings
  die(drupal_render($form) . $settings);
}

The above merges the returned settings for your form, with the original settings, so that when Drupal.attachBehaviors() is called, your form can be #ajaxified.

Good luck! Code not tested, so there may be typos.

j.b’s picture

Thanks for your reply,

The form is returned correctly, but still no javascript related to the form is loaded.
So form is not ajaxified.

here is my ajax response:

<form class="webform-client-form" enctype="multipart/form-data" action="/d7/my-test/ajax_loaded" method="post" id="webform-client-form-12" accept-charset="UTF-8"><div>wafdfdfdfdfdfdfdfdf<fieldset class="webform-component-fieldset collapsible form-wrapper" id="webform-component-rwar"><legend><span class="fieldset-legend">rwar</span></legend><div class="fieldset-wrapper"><div class="form-item webform-component webform-component-textfield" id="webform-component-rwar--age">
  <label for="edit-submitted-rwar-age">age </label>
 <input type="text" id="edit-submitted-rwar-age" name="submitted[rwar][age]" value="" size="60" maxlength="128" class="form-text" />
</div>
<div class="form-item webform-component webform-component-textfield" id="webform-component-rwar--fname">
  <label for="edit-submitted-rwar-fname">fname <span class="form-required" title="This field is required.">*</span></label>
 <input type="text" id="edit-submitted-rwar-fname" name="submitted[rwar][fname]" value="" size="60" maxlength="128" class="form-text required" />
</div>
</div></fieldset>

  
  <div class="gender">
      GENDER
    <div class="form-item webform-component webform-component-radios" id="webform-component-gender">
  <label for="edit-submitted-gender">Gender </label>
 <div id="edit-submitted-gender" class="form-radios"><div class="form-item form-type-radio form-item-submitted-gender">
 <input type="radio" id="edit-submitted-gender-1" name="submitted[gender]" value="M" checked="checked" class="form-radio" />  <label class="option" for="edit-submitted-gender-1">Mr </label>

</div>
<div class="form-item form-type-radio form-item-submitted-gender">
 <input type="radio" id="edit-submitted-gender-2" name="submitted[gender]" value="Mrs" class="form-radio" />  <label class="option" for="edit-submitted-gender-2">Mrs </label>

</div>
</div>
</div>
      
  </div>
  <div class="WAA">
      <div class="form-actions form-wrapper" id="edit-actions"><input type="submit" id="edit-submit" name="op" value="Submit" class="form-submit" /></div>  </div>
 <input type="hidden" name="details[sid]" value="" />
<input type="hidden" name="details[page_num]" value="1" />
<input type="hidden" name="details[page_count]" value="1" />
<input type="hidden" name="details[finished]" value="0" />
<input type="hidden" name="form_build_id" value="form-Wz46KoIAsK-6nnQUsKrHr3ZMku3tg5XQxSNR98kR8cA" />
<input type="hidden" name="form_token" value="3LUGHZcw_X5J4HvOkZYNHb8W8ebm2zEb8sGDt3vjsKc" />
<input type="hidden" name="form_id" value="webform_client_form_12" />
</div></form><script type="text/javascript">jQuery.extend(Drupal.settings, {"basePath":"\/d7\/","pathPrefix":"","ajaxPageState":{"theme":"bartik","theme_token":"8Jxg6DHpvtQ3xoXK22EKpeeN3Zd0JprAp1jyQ8B6g1g"}});</script>
Jaypan’s picture

Sorry, I had a mistake in my code. I cannot edit it now.

This:

  $form= drupal_get_form('webform_client_form_12', $node, $submission, $enabled, $preview);

Should be this:

  $form= render(drupal_get_form('webform_client_form_12', $node, $submission, $enabled, $preview));

And this:

  die(drupal_render($form) . $settings);

Should be this:

  die($form . $settings);
j.b’s picture

It seems to be working,
I probably just need to change the id of my submit button as there is another form in a block on the same page whose submit button has the same id #edit-submit

j.b’s picture

Great,
It works fine
You are a life saver Jaypan.

Thanks a lot !

Anonymous’s picture

Hi

I have an error: "jQuery is not defined" for the line:

$settings = '<script type="text/javascript">jQuery.extend(Drupal.settings, ';
$settings .= drupal_json_encode(call_user_func_array('array_merge_recursive', $javascript['settings']['data']));
$settings .=  ');</script>';

Why is jQuery not defined? The whole page works with jQuery.

I added this to my template.php

function mytheme_preprocess_page(&$variables) {
  drupal_add_library('system', 'drupal.ajax');
  drupal_add_library('system', 'drupal.form');
}

The whole code of my Webform

<div id="webform-ajax-wrapper-17"><form class="webform-client-form" enctype="multipart/form-data" action="/de/admin/commentreport" method="post" id="webform-client-form-17" accept-charset="UTF-8"><div><div  class="form-item webform-component webform-component-textarea" id="webform-component-grund">
  <label for="edit-submitted-grund">Grund <span class="form-required" title="Diese Angabe wird benötigt.">*</span></label>
 <div class="form-textarea-wrapper resizable"><textarea id="edit-submitted-grund" name="submitted[grund]" cols="60" rows="5" class="form-textarea required"></textarea></div>
 <div class="description">Warum soll dieser Kommentar entfernt werden?</div>
</div>
<div  class="form-item webform-component webform-component-textfield" id="webform-component-kommentar-link">
  <label for="edit-submitted-kommentar-link">Kommentar Link </label>
 <input type="text" id="edit-submitted-kommentar-link" name="submitted[kommentar_link]" value="" size="60" maxlength="50" class="form-text" />
</div>
<div  class="form-item webform-component webform-component-email" id="webform-component-e-mail">
  <label for="edit-submitted-e-mail">E-Mail </label>
 <input class="email form-text form-email" type="email" id="edit-submitted-e-mail" name="submitted[e_mail]" size="60" />
</div>
<input type="hidden" name="details[sid]" />
<input type="hidden" name="details[page_num]" value="1" />
<input type="hidden" name="details[page_count]" value="1" />
<input type="hidden" name="details[finished]" value="0" />
<input type="hidden" name="form_build_id" value="form-PVn2fKq9RQZz92YZferL6jTmZC7mE-ZjD7t-vVek5rI" />
<input type="hidden" name="form_token" value="heEyKq7PT7hBPTydBLOs7f_7QY2gdOPzWT__fY17k5c" />
<input type="hidden" name="form_id" value="webform_client_form_17" />
<input type="hidden" name="webform_ajax_wrapper_id" value="webform-ajax-wrapper-17" />
<div class="form-actions form-wrapper" id="edit-actions"><input type="submit" id="edit-webform-ajax-submit-17" name="op" value="Absenden" class="form-submit" /></div></div></form></div><script type="text/javascript">jQuery.extend(Drupal.settings, {"basePath":"\/","pathPrefix":"de\/","ajaxPageState":{"theme":"seven","theme_token":"wMvPlrljZ1kSrVmKF7uO4SsbtMnLP-D50SljR11Et8I"},"photoswipe":{"options":{"allowUserZoom":false,"autoStartSlideshow":false,"allowRotationOnUserZoom":false,"backButtonHideEnabled":true,"captionAndToolbarAutoHideDelay":5000,"captionAndToolbarFlipPosition":false,"captionAndToolbarHide":false,"captionAndToolbarOpacity":0.8,"captionAndToolbarShowEmptyCaptions":true,"cacheMode":"normal","doubleTapSpeed":300,"doubleTapZoomLevel":"2.5","enableDrag":true,"enableKeyboard":true,"enableMouseWheel":true,"fadeInSpeed":250,"fadeOutSpeed":250,"imageScaleMethod":"fit","invertMouseWheel":false,"jQueryMobile":null,"jQueryMobileDialogHash":"\u0026ui-state=dialog","loop":true,"margin":20,"maxUserZoom":"5.0","minUserZoom":"0.5","mouseWheelSpeed":500,"nextPreviousSlideSpeed":0,"preventHide":false,"preventSlideshow":false,"slideshowDelay":3000,"slideSpeed":250,"swipeThreshold":50,"swipeTimeThreshold":250,"slideTimingFunction":"ease-out","zIndex":1000}},"ajax":{"edit-webform-ajax-submit-17":{"callback":"webform_ajax_callback","wrapper":"webform-ajax-wrapper-17","progress":{"message":"","type":"throbber"},"event":"click","url":"\/de\/system\/ajax","submit":{"_triggering_element_name":"op","_triggering_element_value":"Absenden"}}}});</script>

Please can you help me??

Jaypan’s picture

$settings = '<script type="text/javascript">jQuery.extend(Drupal.settings, ';
$settings .= drupal_json_encode(call_user_func_array('array_merge_recursive', $javascript['settings']['data']));
$settings .=  ');</script>';

What is the context of this?

nevets’s picture

It is also easier to use drupal_add_js() to add settings.

Jaypan’s picture

Well, the code shown is used when returning AJAX forms to the browser, so that the ajax-loaded form is ajaxified. But it won't put up an error about jQuery not being loaded, so the poster has done something wrong. But without more code, it's impossible to know what.

nevets’s picture

If they used a renderable array they could use #attached to add the javascript (similar to drupal_add_js())

Jaypan’s picture

The code (s)he is using is sent to the browser in custom ajax callbacks, and has to be run after all content has been rendered. It creates a <script> tag containing all the Drupal.settings for the content rendered in the ajax callback. This script tag then gets appended to the DOM, at which point it merges the new settings with the existing Drupal.settings object. The browser then can call Drupal.attachBehaviors() , and all the new content returned in the ajax callback will have its JavaScript attached through the Drupal.behaviors.MODULENAME.attach().

Core uses this code in ajax_render() for #ajax responses.

Because this code has to be run after all content has been rendered, it cannot be attached using drupal_add_js() or as a render array, as all JavaScript added using these methods has already been rendered, generating the settings that this code then retrieves, generating the <script> tag.

nabajit’s picture

I used the above steps to show my login form when user hover over the login link. good thing is that is works perfectly.

Thanks Jaypan

AlxVallejo’s picture

Why would you need to load the form via AJAX in that case?

dragos.han’s picture

The form is not submitted via AJAX. Any help would be appreciated.

Here is my PHP code:

function mymodule_menu(){
$items['get-new-item-form'] = array(
    'title' => 'Get form',
    'description' => 'Get form',
    'page callback' => 'get_form',
    'access callback' => 'mymodule_access',
    'access arguments' => array('get-form'),
    'type' => MENU_NORMAL_ITEM, 
  );
}

function get_form(){
  $unrendered_form = drupal_get_form('new_item_form');
  $form = render( $unrendered_form );
	
  // Generate the settings:
  $settings = FALSE;
  $javascript = drupal_add_js(NULL, NULL);
	
  if(isset($javascript['settings'], $javascript['settings']['data']))
  {
    $settings = '&lt;script type="text/javascript"&gt;jQuery.extend(Drupal.settings, ';
    $settings .= drupal_json_encode(call_user_func_array('array_merge_recursive', $javascript['settings']['data']));
    $settings .=  ');&lt;/script&gt;';
  }

  // Return the rendered form and the settings
  print $form . $settings;
  
  drupal_exit();
}



function new_item_form( $form, &$form_state ){
  $form = array();
	
  $form['map_id'] = array(
    '#type' => 'hidden', 
    '#value' => 1
  );
	
	
  $form['name'] = array(
    '#type' => 'textfield', 
    '#title' => t('Name:'), 
    '#required' => TRUE,
    '#attributes' => array('class' => array('name-input')),
  );
	
	
  $form['submit'] = array(
    '#type' => 'submit', 
    '#value' => t('Save'),
    '#attributes' => array(
      'class' => array('save-new-item-submit-button')
    ),
    '#ajax' => array(
        'callback' => 'new_item_form_ajax_submit',
        'wrapper' => 'save-new-item-detail-form-wrapper',
	'effect' => 'fade', 
	'name' => 'save-new-item-submit-button', 
	'method'=>'replace',
    )
  );
	
  $form['#prefix'] = '&lt;div id="save-new-item-detail-form-wrapper"&gt;';
  $form['#suffix'] = "&lt;/div&gt;";
	
  return $form;	
}

and the JS code:

jQuery('#add-new-item-form-wrapper').load('/get-new-item-form', function(){
    Drupal.attachBehaviors();
}); 

roi’s picture

Hi, Jaypan, your solution helped me a lot, thanks. I was wondering if it's better to use die() in order to output only the necessary HTML, or is it better to use delivery callback in our hook_menu(). I chose the latter, I think it's cleaner. I used it to add drupal_get_js() to the output, without which my ajaxed html didn't function.

See https://www.drupal.org/node/2046693

Roi

Jaypan’s picture

I was wondering if it's better to use die() in order to output only the necessary HTML, or is it better to use delivery callback in our hook_menu(). I chose the latter, I think it's cleaner.

I agree. I've started using that method in the time since I wrote my post.

roi’s picture

H