I have got a module, where, when a user clicks on a link, I am loading a new form in a dialog, which has an Ajax-enabled submit button. In my dialog, I don't want the entire drupal header and footer, etc., to display. All I am displaying is, the contents of that new form. So my form is outputted via json like this:

  $json['html'] = drupal_render(drupal_get_form('my_form'));
  drupal_json_output($json);  

Which works like I want, and only the form contents are displayed. However, when this happens, the submit button in that form, which is ajax-enabled, doesn't submit through ajax. Instead, it submits like a normal button and takes me to the same form on another page.

If I have the form being outputted and displayed normally, with the header and footers, like this:

  $output = drupal_get_form('my_form');
  return $output;

everything works just fine, and my submit button is 'ajax-enabled'.

I thought, maybe it was because I was loading the new content and the behaviors were not being attached to the these new form contents. So in my js file, I did this as well:

$('a.my_link', context).click(function() {
//processing done here
$('#my_modal_dialog').html(jsondata['html']);
}, "json");
Drupal.attachBehaviors($('#my_modal_dialog'));

That little addition, didn't fix the problem either. I am wondering as to why, if the form is outputted normally the button works as an ajax button, and when, only the form content is displayed, the ajax doesn't work.

Hoping someone will have the answer...Thanks in advance.

Comments

darrylmabini’s picture

Try to put Drupal.attachBehaviors() inside of the click event. And try to remove first the context argument in the drupal.attachbehaviors. Not sure but maybe.

shabana.navas’s picture

I tried that as well, but no luck. Is it something to do with the Drupal.settings, am I not applying the behaviors/settings correctly here?

Shabana Navas
snavas@acromedia.com
Software Developer
Acro Media
https://www.acromedia.com
1-800-818-4564
Skype: super.shaba

Jaypan’s picture

To send the settings back to the browser, you can use this:

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

Append $settings to the end if the rendered content in your ajax callback. You will also need to call Drupal.attachBehaviors().

shabana.navas’s picture

Could you please clarify as to how we would add it at the end of the rendered content in the callback function? Right now, my callback for rendering the form looks like this:

  $json['html'] = drupal_render(drupal_get_form('my_form'));
  drupal_json_output($json);  

And in my js file, I do this bit to display it in a dialog:

$.get( url , {} , function(jsondata){
          //Add the html to the div
          $('#my_modal_dialog).html(jsondata['html']);
          Drupal.attachBehaviors('#my_modal_dialog);
        }, "json");

Where are we technically adding the settings?

Shabana Navas
snavas@acromedia.com
Software Developer
Acro Media
https://www.acromedia.com
1-800-818-4564
Skype: super.shaba

Jaypan’s picture

  $json['html'] = drupal_render(drupal_get_form('my_form')) . $settings;
shabana.navas’s picture

Thanks so much!!! Yea!!!! It's working great now! I just need to add my css stuff now and I think I'll have my form functioning like I want it to. Thanks again, was struggling with it for a few days...

Shabana Navas
snavas@acromedia.com
Software Developer
Acro Media
https://www.acromedia.com
1-800-818-4564
Skype: super.shaba

edulterado’s picture

Including the javascript settings in the JSON didn´t work for me.

I´m trying to accomplish something very similar. I´m loading a form in a jQuery UI dialog, which has an ajax-enabled submit button. The form is successfully rendered in a dialog when a user clicks on a link, but the submit button has lost its ajax capabilities.

Here it´s what I´m doing:

1) My menu item:

<?php

/**
* Implements hook_menu().
*/

function custom_menu() {

  $items['quote/form'] = array(
    'title' => 'Form json',
    'type' => MENU_CALLBACK,
    'page callback' => 'custom_load_form',
    'access arguments' => array('access content'),
  );

  return $items;

}

?>

2) The menu item page callback:

<?php

/**
* Form loader
*/

function custom_load_form() {

  //Get the form
  $form = drupal_get_form('custom_form');
  
  $javascript = drupal_add_js(NULL, NULL);
  $settings = FALSE;
  
  if(isset($javascript['settings'])) {
  
    $settings = '<script type="text/javascript">jQuery.extend(Drupal.settings, '. drupal_json_encode(call_user_func_array('array_merge_recursive', $javascript['settings']['data'])) .');</script>';
  
  }
  
  $json['html'] = drupal_render($form) . $settings;
    
  return drupal_json_output($json);
  
}

?>

3) And the Javascript code:


(function($){
  Drupal.behaviors.get_quote = {
    attach: function (context, settings) {

      function ajaxCompleted (data) {
            jQuery('#form-dialog', context).html(data['html']);
      }
      
      jQuery('a#load-form').click(function() {
      
        jQuery('#form-dialog').html('Loading form...');
      
        jQuery('#form-dialog').dialog({
            modal: true,
            width: 700,
            height: 'auto',
            title: "Title" });  
      
        jQuery.ajax({
            type: 'POST',
            url: '/quote/form',
            dataType: 'json',
            success: ajaxCompleted,
        });
        
        Drupal.attachBehaviors(jQuery('#form-dialog'));
      
        return false;
      
      });

    }
  };
})(jQuery);

Clicking the submit button redirects the page to the contents returned by the menu item page callback, which outputs the JSON html:

{"html":"\u003Cdiv class=\u0022messages error\u0022\u003E\n\u003Ch2 class=\u0022element-invisible\u0022\u003EError message\u003C\/h2\u003E\n \u003Cul\u003E\n \u003Cli\u003EYour name field is required.\u003C\/li\u003E\n \u003Cli\u003EYour e-mail field is (...)

Any insights on what´s going on are welcome.

Jaypan’s picture

You need to move your call to Drupal.attachBehaviors() into your ajax callback. Right now you are calling it before the new content has loaded.

edulterado’s picture

I have placed Drupal.attachBehaviors() here:

function ajaxCompleted (data) {
  jQuery('#form-dialog', context).html(data['html']);
  Drupal.attachBehaviors(jQuery('#form-dialog'));
}

But it still doesn´t work. I don´t know if I´m doing it correctly.
I have also removed the 'context' argument with the same results.

shabana.navas’s picture

Try rendering the form first (notice where I am doing drupal_render), and then adding the javascript bit. I had to tweak it a bit like this:

//Retrieve the HTML generated from the $form data structure.
  $html = drupal_render(drupal_get_form('myform'));
  
  $javascript = drupal_add_js(NULL, NULL);
  $settings = FALSE;
  
  if(isset($javascript['settings']))
  {
    $settings = '<script type="text/javascript">jQuery.extend(Drupal.settings, ' .
      drupal_json_encode(call_user_func_array('array_merge_recursive', $javascript['settings']['data'])) .
      ');</script>';
  }

  //Add the settings to the form
  $html .= $settings;

  //Drupal output would produce
  print $html;

If everything in the jquery is okay, then, I am hoping this little change will work for you.

Shabana Navas
snavas@acromedia.com
Software Developer
Acro Media
https://www.acromedia.com
1-800-818-4564
Skype: super.shaba

edulterado’s picture

Hey! That worked! I was missing completely that point.

Thank you all!

orgnsm’s picture

Hi, I've read through all this and put it into my code and still no success, my notes are here if anyone would be able to help: https://drupal.org/node/2142355

Thanks

grant_taylor’s picture

I've got this working in Jquery-ui Tabs, sort of.

I can load multiple forms in the tabs fine, each form is given a unique form_id via hook_forms and a random string is added to the url in $.Post which is then handled as a wildcard argument in hook_menu.

However only the first tab will save by an ajax submit. Trying to submit on any other form other then the first added will redirect to a page with json on it: {"html":"\u003Cform enctype=\u0022mult.... etc

This is the same result whether I use Drupal.attachBehaviors on the div of the tab or the document it's self.

Anyone have any ideas? I've been stuck on this for a while, and can't think of what to do.

danSamara’s picture

Trick with send the settings back to the browser didn´t work for me: i've get message An error occurred while attempting to process /system/ajax: ajax.form.ajaxSubmit is not a function.
I wrote mini module for demonstrate the problem: https://www.dropbox.com/s/5mls878wnd3aycm/ajax_forms.tar.gz

landor’s picture

I used methods described here, but had issues that took me a long time to figure out, so I will post my complete solution here.

Specifically, the issue was that the form was not using ajax to post. The solution required adding the proper javascript libraries and not reusing html ids in the form generation.

My goal was to open an ajax-submitting form into a modal dialog, triggered by clicking on a link.

It look me a long time to figure out that I was missing the drupal.ajax and jquery.form libraries in order to make the loaded form ajaxy.
I want to be able to use this functionality anywhere on the site. You may choose to be more selective on where you include these libraries.

function hook_process(&$variables, $hook) {
  if ($hook == 'page') {
    // required for jquery ui dialog
    drupal_add_library('system', 'ui.dialog');
    // required for the loaded form to be ajaxy
    drupal_add_library('system', 'drupal.ajax');
    drupal_add_library('system', 'jquery.form');
  }
}

In hook_menu(), add the menu callback to load the form:

  $items['share-with-contacts-dialog'] = array(
    'title' => 'Share with Contacts Dialog',
    'type' => MENU_CALLBACK,
    'page callback' => 'menu_share_with_contacts_dialog',
    'access arguments' => array('access content'),
  );

The menu callback, using methods described in previous comments:

function menu_share_with_contacts_dialog() {
  $form = drupal_render(drupal_get_form('share_with_contacts_dialog'));
  
  $javascript = drupal_add_js(NULL, NULL);
  $settings = FALSE;

  if(isset($javascript['settings'])) {
    $settings = '<script type="text/javascript">jQuery.extend(Drupal.settings, '. drupal_json_encode(call_user_func_array('array_merge_recursive', $javascript['settings']['data'])) .');</script>';
  }
  
  $html = $form . $settings;
  echo $html;
}

The form generation:

function share_with_contacts_dialog($form, &$form_state) {
  // Add your form elements here.
  
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Send'),
    '#ajax' => array(
      'callback' => 'share_with_contacts_dialog_callback',
      'wrapper' => 'share-with-contacts-dialog',
      'method' => 'replace',
    ),
  );
  
  $form['cancel'] = array(
    '#type' => 'button',
    '#value' => t('Cancel'),
    '#attributes' => array(
      'onclick' => "jQuery('#share-with-contacts-dialog-container').dialog('close');return false;",
    ),
  );
  
  return $form;
}

Ajax submit callback:

function share_with_contacts_dialog_callback($form, &$form_state) {
  // $form_state['show_thanks'] is set in the form submit handler to trigger displaying this output.
  // otherwise it just returns the whole form
  if (! empty($form_state['show_thanks'])) {
    $thanks = array(
      'thanks' => array(
        '#type' => 'markup',
        '#markup' => '<div class="form-item">Thank you for sharing with your friends!</div>',
      ),
      // I'm reusing the cancel button for the close button
      'close' => $form['cancel'],
    );
    $thanks['close']['#value'] = t('Close');
    
    $form = $thanks;
  }
  return $form;
}

Form validate handler:

function share_with_contacts_dialog_validate($form, &$form_state) {
  // add your form validation here
  
  // implicitly set that we are not to show the thanks message
  $form_state['show_thanks'] = false;
}

Form submit handler:

function share_with_contacts_dialog_submit($form, &$form_state) {
  // your form submit magic goes here
  
  // trigger display of thanks message
  $form_state['show_thanks'] = true;
}

Now on to the javascript:

jQuery(document).ready(function($) {
  // create dialog div
  jQuery('<div id="share-with-contacts-dialog-container"></div>').appendTo('body');
  
  // ajax completed callback
  function ajaxCompleted (data) {
     jQuery('#share-with-contacts-dialog-container').html(data);
     Drupal.attachBehaviors(jQuery('#share-with-contacts-dialog-container'));
     jQuery('#share-with-contacts-dialog-container').dialog({
         height: 'auto',
     }); 
  }
  // add click trigger to links
  jQuery('a.share-with-contacts').click(function(e) {
    e.preventDefault();
    jQuery('#share-with-contacts-dialog-container').html('');
    jQuery('#share-with-contacts-dialog-container').dialog({
      modal: true,
      width: 500,
      height: 400,
      title: 'Email a Friend',
      draggable: false,
      resizable: false,
    });  

    var url = '/share-with-contacts-dialog/';
    
    // ajax_html_ids are necessary to post so that the form generation doesn't assign used html ids to form elements
    var ajax_html_ids = [];
    $('[id]').each(function () {
      ajax_html_ids.push(this.id);
    });
    
    jQuery.ajax({
      type: 'POST',
      url: url,
      success: ajaxCompleted,
      data: {'ajax_html_ids[]': ajax_html_ids},
    });

  });
});

Here is an example of what the triggering link looks like:

<a href="#" class="share-with-contacts">share with contacts</a>

Thanks to previous comments.
I hope this helps!

Jaypan’s picture

I just found a really difficult to debug issue that causes this issue even after using the solutions on this page.

First, I was reusing a form multiple times on a page. I was using hook_forms() to allow for this, and setting an index on each form, so that they did not conflict. First, let's look at the hook_forms() setup:

function mymodule_forms($form_id, $args)
{
  $forms = array();

  // First check if the form ID matches my dynamic form IDs:
  if(strpos($form_id, 'mymodule_some_form_') === 0)
  {
    $forms[$form_id] = array
    (
      'callback' => 'mymodule_some_form',
      'callback arguments' => $args,
    );
  }
  return $forms;
}

function mymodule_some_form($form, &$form_state)
{
  // Form definition skipped as irrelevant to this example. Only submit
  // button is relevant:
  $form['submit'] = array
  (
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  return $form;
}

With the above code, I can call the same form multiple times on a single page, by appending an arbitrary number onto the end of the form ID. I had set up a function that keeps track of the index and appends it each time the form is called as follows:

function some_form_index()
{
  $index = &drupal_static(__FUNCTION__);
  if(!is_int($index))
  {
    $index = 0;
  }
  $index++;

  return $index;
}
function some_page_callback()
{
  $form_index = some_form_index(); // Equal to 1
  $form1 = drupal_get_form('mymodule_some_form_ ' . $form_index);
  $form_index = some_form_index(); // Equal to 2
  $form2 = drupal_get_form('mymodule_some_form_ ' . $form_index);
  $page = array
  (
    'form1' => $form1,
    'form2' => $form2,
  );
  return $page;
}

With the above code, the same form is used twice, once after the other. The first time it is called, it is called with the key 'mymodule_some_form_1' and the second time it is called with 'mymodule_some_form_2'. This will allow these forms to both work properly, and ajax buttons within the forms work correctly.

The issue comes in my AJAX callback when I am loading the same form using AJAX. The second time the form loaded, it wasn't working. I was at a loss, and it actually took me about a full day of total time to fully debug the issue. I was trying attachBehaviors() in my JS. I was passing the settings back in the JS. I was overriding the action on the form to ensure it was submitting properly, but no matter what I did, the AJAX button on the AJAX loaded form would not work.

To solve the issue, we need to look at the process:
1) Load the form(s) using AJAX. Each form has an ascending integer appended to the form key, starting with 1
2) The form is submitted using AJAX
3) The system regenerates the form using the form key, but since the AJAX page load is separate from the original page load, the key starts from 1 again.

This means that the AJAX loaded form has the same form key as the first original form. The system already thinks that it has appended the AJAX to that form button, so no AJAX is attached to the button!

The solution was to change my submit button from:

  $form['submit'] = array
  (
    '#type' => 'submit',
    '#value' => t('Submit'),
  );

To

  $form_index = some_form_index();
  $form['submit_' . REQUEST_TIME . '_' . $form_index] = array
  (
    '#type' => 'submit',
    '#value' => t('Submit'),
  );

This ensures that the #id of the button is always unique for every form on the page load, as well as unique between page loads (since the REQUEST_TIME of the page generation is used within the button ID).

And now I have multiple versions of the same form on the same page, and these forms can be loaded through AJAX.

Very convoluted, so hopefully it can help someone who runs into the same issue in the future.

er.pushpinderrana’s picture

Very nice explanation.

Thanks Jaypan for sharing this tricky issue with solution with us.

Pushpinder Rana #pushpinderdrupal
Acquia Certified Drupal Expert

pjc’s picture

Hi Jaypan,
I think I've run into a similar issue involving ajax-loaded user forms (login, registration and password reset). I began by displaying the user-login block on the front page. In a form_alter, I added links to the form just above the 'log in' submit button that when clicked fetch either a register form or a password reset form.

User login block form alter (omitted others for clarity)

/**
 * Implements hook_form_form_id_alter()
 */
function my_module_form_user_login_block_alter(&$form, &$form_state, $form_id) {
  $form['login_links'] = array(
    '#type' => 'container',
    '#attributes' => array(
      'class' => array(
        'user-login-links'
      )
    ),
  );

  $form['login_links']['sign_up'] = array(
    '#type' => 'button',
    '#name' => 'btn-signup',
    '#value' => t('Create new account'),
    '#limit_validation_errors' => array(),
    '#ajax' => array(
      'callback' => 'my_module_registration_form',
      'wrapper' => 'user-login-form',
      'method' => 'replace',
      'effect' => 'fade'
    ),
    '#attributes' => array(
      'class' => array(
        'inline-btn',
      ),
    )
  );

  $form['login_links']['reset_pass'] = array(
    '#type' => 'button',
    '#name' => 'btn-reset-pass',
    '#value' => t('Forgot password?'),
    '#limit_validation_errors' => array(),
    '#ajax' => array(
      'callback' => 'my_module_pass_reset_form',
      'wrapper' => 'user-login-form',
      'method' => 'replace',
      'effect' => 'fade'
    ),
    '#attributes' => array(
      'class' => array(
        'inline-btn'
      ),
    ),
  );
}

Ajax callback that returns a form (registration form only, omitted others for clarity)

/**
 * Helper function to build a user registration form
 */
function my_module_registration_form($form, &$form_state) {
  global $user;
  module_load_include('inc', 'user', 'user.pages');
  $new_form = drupal_get_form('user_register_form');
  $html = drupal_render($new_form);
  return $html;
}

I'm able to go back and forth between forms without a problem, so it seems that all the necessary files/js settings are loaded. However, the submit button only works for the form that was loaded with the initial page load. Submit buttons belonging to ajax-fetched forms don't work at all. Any ideas?

Jaypan’s picture

I'm guessing the submit button has the same ID on both forms. After it has been ajaxified on the first form, it won't re-ajaxify it on the second form, since the system thinks it has already been ajaxified. Try changing the key for the submit button from $form['submit'] to $form['submit_pass'] or something.

pjc’s picture

Hi Jaypan, thanks for your response. I tried changing the submit key but had no luck. I noticed that the $form['#action'] on ajax-fetched forms is set to system/ajax. If I submit the form with valid field values, I'm redirected to system/ajax with the raw json response. If I then click the browser back button and click refresh, I can see the status messages -- one that keeps showing up: Undefined index: #ajax in ajax_form_callback()

phponwebsites’s picture

Having same problem...
Is any other solution for this problem?
How to handle '#ajax' for 2 forms in a page?