4

What is the way to trigger a Drupal form via JavaScript, and pass new values to $form_state while doing so?

I have a main form, which has an element that triggers a jQuery UI dialog with a form in it. When submitting the dialog form, I want to bring the values back into $form_state. I want the main form to display and save the values from both forms. Both Ctools Ajax example and MonoDialog show examples that just manipulate the DOM of the main page/form after submitting a form in a dialog.

One thing I could maybe do is populate a hidden form field with the values from the dialog form in JSON format, and then trigger an ajax button with jQuery. I rather not do this as it doesn't sound like a clean solution.

What I try to achieve is kind of like the views ui interface, which has a form with form in dialogs. Submitting the dialog forms, updates the main form.

Code example

<?php
function example_main_form($form, &$form_state) {
  // The main form is defined here. Includes a button which triggers a
  // callback function (example_main_form_callback) to open up a new form
  // in a dialog popup. Upon this ajax trigger, it also populates $form_state
  // with some values to pass to the dialog form.
}

function example_main_form_callback($form, &$form_state) {
  $commands = array();
  // open dialog
  $commands[] = ajax_command_invoke('#dialog', 'dialog', array('open'));
  // dialog title
  $title = t('Configure:') . ' ' . $form_state['dialog']['values']['title'];
  $commands[] = ajax_command_invoke('#dialog-title', 'html', array($title));
  // dialog body
  $body = drupal_render(drupal_get_form('example_dialog_form', $form_state['dialog']));
  $commands[] = ajax_command_invoke('#dialog-body', 'html', array($body));
  return array('#type' => 'ajax', '#commands' => $commands);
}

function example_dialog_form($form, &$form_state) {
  // include this file in build_info
  form_load_include($form_state, 'inc', 'example', 'example.forms');
  // The popup dialog form is defined here.
  // the submit button has an ajax callback, which calls 
  // example_dialog_form_callback below.
}

function example_dialog_form_callback($form, &$form_state) {
  // This callback closes the dialog, but I also want to populate the
  // form_state of the main form with the values of this saved form. The values
  // are available in this callback's $form_state, but I'm not sure what the
  // best way is to pass them on to the main form. Is it possible to trigger
  // another form programmatically using jQuery?
  $commands = array();
  // close dialog
  $commands[] = ajax_command_invoke('#dialog', 'dialog', array('close'));
  return array('#type' => 'ajax', '#commands' => $commands);
}

I think I could attach the dialog form values to the Drupal.ajax.example_element.options.data object. In this way, when the main form is programmatically triggered for an Ajax callback, the values in options.data will be added to the main form $form_state['input']. That's the cleanest approach I found so far, but still not straightforward. A separate function is required to populate options.data. One options is to extend options.beforeSerialize(), but in my case the Ajax element is dynamic. (See How to extend or "hook" Drupal Form AJAX?) Thus this approach would work better:

In the dialog form callback:

<?php
function example_dialog_form_callback($form, &$form_state) {
  $dialog_id = 'dialog-id';
  $commands = array();
  // close dialog
  $commands[] = ajax_command_invoke('#dialog', 'dialog', array('close'));
  // Call function which attaches the dialog form values to Drupal.ajax.{dialog-id}.options.data.
  // After triggering the dialog ajax callback, the dialog values will be added to the $form_state of
  // the main form
  $data = array(
    'id' => $dialog_id,
    'values' => $form_state['input'], // for example, but could be cleaned up.
  );
  $commands[] = ajax_command_invoke(NULL, 'dialogValues', array($data));
  // trigger dialog button
  $commands[] = ajax_command_invoke('#' . $dialog_id, 'trigger', array('click'));
  return array('#type' => 'ajax', '#commands' => $commands);
}

The jQuery function:

  $.fn.dialogValues = function(data) {
    // dialog ajax id
    var dialogAjaxId = data.id;
    // add values as JSON, so we can pass multi dimensional arrays
    Drupal.ajax[dialogAjaxId].options.data._dialog_values = JSON.stringify(data.values);
};

Is there a better solution?

  • Good explanation for ajax_command_invoke. Thanks :) – kalidasan Mar 17 '15 at 13:12
0

I can think of two ways that might work for you:

  1. (a) In example_dialog_form_submit() (not in your question), get the submitted values from the values key of $form_state (values from the dialog form). Lets say we call this a record. You can either save the record using the $_SESSION super global or the CTools object cache.

    (b) Now, when the main form is being validated, use the values from the record in the validate callback, say example_main_form_validate(). On failed validation, ignore. On successful validation, add values from the record to the $form_state of the main form using form_set_value(). Now, your main form submit handler can always assume that it'll have the values from the dialog form as well. (somewhere in its own $form_state tree)

  2. An alternative way by which you can structure your forms is:

    (a) Instead of loading your dialog form using AJAX, you can make it a part of the main form itself as a set of elements. #limit_validation_errors can help you define the boundary for the dialog "form" within the main form.

    (b) On submit inside the dialog, you can just do a $form_state['rebuild'] = TRUE to allow the form to rebuild. In the AJAX callback, close the dialog and refresh the main form.

    (c) In this approach, the form submit handler in the main form will always have the values from the dialog and it is easier to debug. Only caveat is if the form inside the dialog has a lot of elements in which case an AJAX load (like you are doing already) might be better.

  • As for 1. I'd like to avoid dependency on CTools or a $_SESSION global, but I do like keeping the forms separate. I think that'd be the best approach for when I create a 'no javascript' fallback. – Danny_Joris Nov 11 '12 at 2:45
  • I updated the original post with another approach. – Danny_Joris Nov 12 '12 at 1:40
0

FWIW/others: Rather than extending jQuery you can extend the Drupal.ajax commands, and thereby the commands you send back from Drupal to the JavaScript.

javascript:

Drupal.ajax.prototype.commands.mymodule_command = function(ajax, response, status) { 
  console.log('mymodule_command', ajax, response, status);
  console.log('arguments', response.arguments);
};

php:

$commands[] = array(
  'command' => 'mymodule_command',
  'arguments' => array(...),
);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.