Hi

I have a simple use case and I cannot figure it out how to achieve this with ctools.

- Base is a simple form with some textfields.
- The user enter some numbers and presses "Submit".
- The fields are multiplied and the result is displayed below the submit button.

How can I change the form, that the "Submit" is done by Ajax (form validation and submission) and that after sucessfull validation the result ist displayed by javascript in the DOM?

I want to run through the normal submit / validate handler.


function my_calculator_simple_form($form_state) {

  //Invitation Form
  $form = array();

  $form['size'] =  array(
    '#type' => 'textfield',
    '#title' => t('Size'),
    '#default_value' => '',
  );

  $form['weight'] =  array(
    '#type' => 'textfield',
    '#title' => t('Weight'),
    '#default_value' => '',
  );

   
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Calculate'),
    '#weight' => 11,  
  );
  return $form;

}

function my_calculator_invite_form_validate($form, &$form_state) {
  
  $size = explode(' ', $form_state['values']['size']);
  
  if (!is_numeric($size)) {
    form_set_error('size', t('You have to enter at numeric value'));
  }


}


function my_calculator_invite_form_submit($form, &$form_state)  {

  $result = $size * $weight;

  //Display the result below the submit button using AJAX
  //But how?
  


}
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

ayalon’s picture

Does anyone have an idea how to do this? This would be very helpful.

ayalon’s picture

I still have no clue, how to do this. Does anyone have a hint for me?

merlinofchaos’s picture

Have you looked at the ctools_ajax_sample module? It contains a piece of code that does something like this, though without the form part actually involved.

ayalon’s picture

I have studied the code. The problem is, that forms can only be handlet "modal" or in a wizard way. My goal was to handle a form without a dialog and display the result in the DOM of the current page.

There is no example with a form, that allows me to submit the form (submit handler) and to render some result on the same page. Imagine a Vote form, you press vore and the form is hidden and the result is displayed in place of the form.

merlinofchaos’s picture

Ok, I see what you mean.

You can operate more or less how the wizard works, but use ctools_build_form() instead of the modal form wrapper. You test $form_state['executed'] to see if the form was submitted, and behave appropriately based upon $form_state['clicked_button'].

Let me know how far that gets you.

merlinofchaos’s picture

Hm. I'm not sure the above is clear.

This is pseudocode:


$commands = array();

$output = ctools_build_form('form_id', $form_state);
if (!empty($form_state['executed'])) {
  // Let's say that a particular button wanted us to render new data to a particular area.
  $commands[] = ctools_ajax_command_replace('#div-id-to-use', $content);
}
else {
  // otherwise, render our output in the form area
  $commands[] = ctools_ajax_command_html('#div-of-form-area', $output);
}

ctools_ajax_render($commands);

It's slightly more complicated than that if you want to operate both with and without a javscript context, since you need to detect $js and answer appropriately.

ayalon’s picture

Dear merlinofchaos

Thank you very much for your response. I was not aware of the $form_state['executed'] variable and I will try to extend my example in the way you described. I will post my finding, as soon I was able to get a working example.

merlinofchaos’s picture

It's something ctools_build_from() added somewhere along the way because it was getting increasingly difficult to tell if a form had been successfully submitted or not.

ayalon’s picture

Hi merlinofchaos

I tried to implement a simple example, but unfortunatley I was not successful. It's a small BMI Calculator. You enter your weight/height, press submit and then the form should validate. If not, display some errors. If it validates, replace the form with the results of the calculation.

bmi_calculator.module


function bmi_calculator_menu() {

  $items = array();

  $items['bmi/test'] = array(
    'title' => 'BMI Form Test',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('bmi_calculator_simple_form'),
    'access arguments' => array(),
    'type' => MENU_CALLBACK,
  );

  return $items;

}

function bmi_calculator_simple_form($form_state) {

  $form = array();

  //Display Sender Data for anonymous user
  $form['size'] =  array(
    '#type' => 'textfield',
    '#title' => t('Your size in centimeters'),
    '#default_value' => '',
    '#required' => TRUE,
    //'#access' => $access,
    //'#disabled' => $disabled, 
  );

  $form['weight'] =  array(
    '#type' => 'textfield',
    '#title' => t('Your current weight'),
    '#default_value' => '',
    '#required' => TRUE,
    //'#access' => $access,
    //'#disabled' => $disabled, 
  );
  
  //on the submit-button I addes some ctools magic to make AJAXify it
  
  $form['submit'] = array(
    '#type' => 'submit',
    '#attributes' => array('class' => 'ctools-use-ajax'),
    '#value' => t('BMI berechnen'),
    '#weight' => 11, 
  );

  return $form;

}

function bmi_calculator_simple_form_validate($form, &$form_state) {
  
  $size = $form_state['values']['size'];
  
  if (!is_numeric($size)) {
    form_set_error('size', t('You have to enter at numeric value'));
  }
  
}


function bmi_calculator_simple_form_submit($form, &$form_state)  {

  ctools_include('form');
  ctools_include('ajax');

  $output = ctools_build_form ( $form['form_id']['#value'], $form_state );

  $content = 'Result of calculation';
  
  if (! empty ( $form_state ['executed'] )) {
		// Let's say that a particular button wanted us to render new data to a particular area.
		$commands [] = ctools_ajax_command_replace ( '#bmi-calculator-simple-form', $content );
	} 
	else {
		// otherwise, render our output in the form area
		$commands [] = ctools_ajax_command_html ( '#bmi-calculator-simple-form', $output);
  }

  ctools_ajax_render($commands);

}


As you can see, I added your code in the submit handler. But this is probably not the right place.

2 problems occur:
- The form validation throws an error (don't know why)
- The submit-handler is recursively executed and throws server error
- $form_state ['executed'] is always empty

Maybe you could explain me, where to add the pseudocode you wrote above. The problem is, that I have to start with the drupal_get_form() in the menu item to get the form displayed. The next action should occur, when pressing the submit button. This is the reason, why I put your code in the submit handler but probably this is wrong.

merlinofchaos’s picture

You don't add your code to the submit you add it after the call to ctools_build_form().

In my world, the _submit handler deals ONLY with the input parsing of the form, usually to copy raw form values into something that can be used later.

This means you can't use 'drupal_get_form' for your page callback, either. You'll need to add another callback.

Let me fiddle with this code, I bet I can get a working example fairly quickly.

merlinofchaos’s picture

Here is a functioning example.

<?php
/**
 * @file
 *
 * A simple BMI calculator to demonstrate AJAX and forms using CTools.
 */

/**
 * Implementation of hook_menu()
 */
function bmi_calculator_menu() {
  $items = array();

  $items['bmi/test'] = array(
    'title' => 'BMI Form Test',
    'page callback' => 'bmi_calculator_simple_page',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );

  return $items;
}

function bmi_calculator_simple_page() {
  // See if this request was sent via javascript so we can degrade.
  $js = !empty($_REQUEST['js']);

  $form_state = array(
    'ajax' => $js,
    'no_redirect' => TRUE,
  );

  ctools_include('form');
  // Have to do this or form submit won't work right.
  drupal_add_js('misc/jquery.form.js');
  ctools_add_js('ajax-responder');
  $output = ctools_build_form('bmi_calculator_simple_form', $form_state);

  // If the form was submitted, cope.
  if (!empty($form_state['executed'])) {
    ctools_include('ajax');
    $commands = array();
    $content = 'Result of calculation';

    // Remove any pre-existing status messages, from a validation failure:
    $commands[] = ctools_ajax_command_remove('.messages');
    // Let's say that a particular button wanted us to render new data to a particular area.
    $commands[] = ctools_ajax_command_html('#bmi-calculator-result', $content);
    ctools_ajax_render($commands);
  }

  if ($js) {
    // The form is only submitted via .js. This means there was a validation
    // and we have to rerender the form. This will go the extra mile:
    ctools_include('ajax');
    $commands = array();
    // If there are messages for the form, render them.
    if ($messages = theme('status_messages')) {
      $output = $messages . $output;
    }

    // Remove any pre-existing status messages, from a validation failure:
    $commands[] = ctools_ajax_command_remove('.messages');
    // And replace the form.
    $commands [] = ctools_ajax_command_replace('#bmi-calculator-simple-form', $output);
    ctools_ajax_render($commands);
  }

  return $output;
}

/**
 * BMI Calculator form
 */
function bmi_calculator_simple_form($form_state) {

  $form = array();

  //Display Sender Data for anonymous user
  $form['size'] =  array(
    '#type' => 'textfield',
    '#title' => t('Your size in centimeters'),
    '#default_value' => '',
    '#required' => TRUE,
    //'#access' => $access,
    //'#disabled' => $disabled,
  );

  $form['weight'] =  array(
    '#type' => 'textfield',
    '#title' => t('Your current weight'),
    '#default_value' => '',
    '#required' => TRUE,
    //'#access' => $access,
    //'#disabled' => $disabled,
  );

  //on the submit-button I addes some ctools magic to make AJAXify it

  $form['submit'] = array(
    '#type' => 'submit',
    '#attributes' => array('class' => 'ctools-use-ajax'),
    '#value' => t('BMI berechnen'),
  );

  // Put a place to put the result so we don't just erase the form.
  $form['result'] = array(
    '#value' => '<div id="bmi-calculator-result" class="clear-block">&nbsp;</div>',
  );

  return $form;
}

function bmi_calculator_simple_form_validate($form, &$form_state) {
  $size = $form_state['values']['size'];

  if (!is_numeric($size)) {
    // form_error() always preferable to form_set_error()
    form_error($form['size'], t('You have to enter at numeric value'));
  }
}

I would recommend doing the actual calculation in the submit. Something like:

$form_state['bmi'] = $form_state['values']['weight'] / ($form_state['values']['height'] * $form_state['values']['height']);

And probalby using number_format and then outputing it via t().

merlinofchaos’s picture

BTW, assuming a correct formula, can I turn this into an example for the examples module?

tic2000’s picture

Starting from the pseudo code in #6 I achieved the same result in an uglier way.

But what issue I have with both mine and that in #11 is that if the form doesn't validate it shows the red border around the form. If you submit correct values I found no way to get rid of that. If I try to the same command as when getting a validation error the form just disappears. If I try to do a ctools_build_form again when I submit the form with correct values the throbber will go on and on and I will get an error with an empty response.

Any solution for this?
If there is no easy solution I could build an ajax command to remove the error class from the inputs and add that in the submit (after $commands[] = ctools_ajax_command_html('#bmi-calculator-result', $content);)

PS. Using this bmi example or just a simple multiplying form, yes, I think it would be great to add it to the example module.

merlinofchaos’s picture

FileSize
7.83 KB

But what issue I have with both mine and that in #11 is that if the form doesn't validate it shows the red border around the form. If you submit correct values I found no way to get rid of that. If I try to the same command as when getting a validation error the form just disappears.

I don't understand. This is what I get when the form fails to validate:

tic2000’s picture

Yes, my comment wasn't very clear.

From the image above. If you enter correct values and submit again the first field will still display the red border even if the value is valid and you get your result displayed.

First step
Second step

merlinofchaos’s picture

AH!

Ok, that's because it only sends the result back, it doesn't rerender the entire form.

Add 'rerender' => TRUE to the $form_state (right under the no_redirect => TRUE) and send the form back:

    $commands[] = ctools_ajax_command_replace('#bmi-calculator-simple-form', $output);

Do that *before* the replace on the calculation since we put that in the form. Meaning the calculation result area should probably be outside the form.

tic2000’s picture

Now it works like a charm.

merlinofchaos’s picture

Title: Ajax Form Submit and replace some DOM elements with the results » Add an AJAX form example to the CTools ajax sample module
Category: support » task

Changing to a task to integrate this into the CTools ajax sample.

It definitely needs a bunch of cleanup. Also the right formula would be handy so it actually calculates.

AS an example it would be great to show both the modal and non-modal versions, as that would allow a nice side by side comparison.

tic2000’s picture

You can see the formula at http://www.whathealth.com/bmi/formula.html if you want to use the BMI example.

ayalon’s picture

Hi merlinofchaos, hi tic2000!

Thank you very much for your help. I think drupal is the only CMS, where a "god-like" developer helps a beginner in such a patient way. I will try to convert this example including the correct form in to the AJAX sample module. It seems, that more than only two people are interested in handling forms in such a brilliant way *g*.

Give me some time to do dis task.

tic2000’s picture

One more thing. In this example the id of the form to replace it's hardcoded. It works if this is the only form of this type on the page. But if we do a vote form for example and display it on node teasers then the id will always point to the first form on the page.

My fix was to add 'want form' => TRUE to the form state array.
Then I replace

$output = ctools_build_form('bmi_calculator_simple_form', $form_state);

with

$form = ctools_build_form('bmi_calculator_simple_form', $form_state);
$output = drupal_render_form('bmi_calculator_simple_form', $form);

and

$commands[] = ctools_ajax_command_replace('#bmi-calculator-simple-form', $output);

with

$commands[] = ctools_ajax_command_replace('#'. $form['#id'], $output);

Now my question is, given the fact that ctools is used a lot with ajax, wouldn't be useful to add a line in ctools_build_form() where we do $form_state['dom id'] = $form['#id'] so we have that id always available without extra lines of code?
If you consider it useful and if I can get my head around the new git stuff needed to write a patch, I will open a new issue and provide a patch for this.

merlinofchaos’s picture

If I'm ever in a situation where I don't KNOW the #id of the form, I will usually wrap the form in another id that I can be sure of, or add classes to the form via $form['#attributes'] in order to perform operations on it.

You can also store whatever other data you need in $form_state, so if there's some variable in the form #id that you need, you can do that as well.

I find it's really rare to have multiple forms of the same type on a page (Drupal has troubles with this in general) so I think the 'want form' parameter is a perfectly good way of handling it.

drurian’s picture

Thanks for the provided example. Just wanted to notice though that it doesn't work with the javascript turned off. How would you fix it?

tic2000’s picture

Modify the line

if (!empty($form_state['executed'])) {

to

if (!empty($form_state['executed']) && $js) {

Then of course add the same condition with !$js instead of $js were you code your output for the non js page.
That condition will make sure that you won't receive a json string as output if js is not enabled.

PS: For what I did using this code as a starting point and it also degrades see http://d6.miidecuvinte.ro/node/1

drurian’s picture

Thanks, do you mind posting your code somewhere?

tic2000’s picture

FileSize
6.43 KB

Here it is.
Note that it also requires comment_bonus_api module for it to work. And of course ctools.

drurian’s picture

For whatever reason, using

if (!empty($form_state['executed']) && !$js) {

didn't work for no js (missing from the $form_state I guess). However, I was able to simply append some stuff to $output and it got rendered on submit.

drurian’s picture

Ok, got another question. I tried to combine this example with the one from here. It goes like this: a user chooses from one of the radio buttons, on submit the map of US+Canada is going to load with links to another page with the maps. Depending on their choice, the map of the selected country is loaded. The last step is where I get ajax loading errors. No js works fine.

My code looks like this:


function eval_interface_menu() {
  $items = array();
  
  $items['eval'] = array(
      'title' => 'Find your Sales Engineer',
      'page callback' => 'eval_interface_form_page',
      'access arguments' => array('access content'),
      'type' => MENU_CALLBACK,
  );
  $items['eval/regions_map/%ctools_js/%'] = array(
      'title' => 'Find your Sales Engineer',
      'page callback' => 'eval_interface_ajax_regions',
      'page_arguments' => array(2),
      'access arguments' => array('access content'),
      'type' => MENU_CALLBACK,
  );
  
  return $items;
}

function eval_interface_form_page(){
  eval_interface_redirect();
  // Check if this request was sent via javascript so we can degrade.
  $js = !empty($_REQUEST['js']);

  $form_state = array(
    'ajax' => $js,
    'no_redirect' => TRUE,
    'rerender' => TRUE,
  );

  ctools_include('form');
  drupal_add_js('misc/jquery.form.js');
  ctools_add_js('ajax-responder');
  $output = ctools_build_form('eval_interface_map_form', $form_state);

  //if (!empty($form_state['executed']) && !js) {
           
  //}

  if (!empty($form_state['executed']) && $js) {
    ctools_include('ajax');
    $commands = array();
    $content = $form_state['countries_map'];   
    // Remove any pre-existing status messages, from a validation failure:
    $commands[] = ctools_ajax_command_remove('.messages');
    // Render the map to a specified element
    $commands[] = ctools_ajax_command_html('#imagemap', $content);
    ctools_ajax_render($commands);
  }

  if ($js) {
    // The form is only submitted via .js. This means there was a validation
    // and we have to rerender the form. This will go the extra mile:
    ctools_include('ajax');
    $commands = array();
    // If there are messages for the form, render them.
    if ($messages = theme('status_messages')) {
      $output = $messages . $output;
    }
    // Remove any pre-existing status messages, from a validation failure:
    $commands[] = ctools_ajax_command_remove('.messages');
    // And replace the form.
    $commands[] = ctools_ajax_command_replace('#eval-interface-map-form', $output);
    ctools_ajax_render($commands);
  } else {
    // if js disabled
    if (!empty($form_state['countries_map'])) {
      $output .= $form_state['countries_map'];
    }
  }


  return $output;
}

function eval_interface_map_form($form_state) {
  $form = array();
  $form['number_of_users'] = array(
        '#type' => 'radios',
        '#title' => t('Choose the number of users'),
        '#required' => TRUE,
        '#options' => array(
            'smb1' => t('1-500 users'),
            'smb2' => t('500 - 1500 users'),
            'smb3' => t('1501+ users')),
    );
  // ajaxify submit button
  $form['submit'] = array(
    '#type' => 'submit',
    '#attributes' => array('class' => 'ctools-use-ajax'),
    '#value' => t('Submit'),
  );
  // Put a place to put the result so we don't just erase the form.
  $form['result'] = array(
    '#value' => '<div id="imagemap" class="clear-block">&nbsp;</div>',
  );
  return $form;
}

function eval_interface_map_form_submit($form, &$form_state) {
  $countries_map = theme('countries_map', $form_state['values']['number_of_users']);
  
  $form_state['countries_map'] = $countries_map;
  
  if (!$form_state['ajax']) {
  //$form_state['rebuild'] = TRUE;
  $form_state['executed'] = TRUE;
  $js = FALSE;
  //dprint_r($form_state);
  }
}
function eval_interface_ajax_regions($js = FALSE) {
  
  $output = theme('regions_map');

  if ($js) {
    ctools_include('ajax');

    $commands = array();
    $commands[] = ctools_ajax_command_replace('#countries-map', $output);

    ctools_ajax_render($commands);
    // above command will exit().
  }
  else {
    return $output;
  }
}

function eval_interface_theme(){
  $path = drupal_get_path('module', 'eval_interface');

  return array(
      'countries_map' => array(
          'arguments' => array('number_of_users' => NULL),
          'template' => 'countries-map',
          'path' => "$path/theme",
      ),
      'regions_map' => array(
        'arguments' => array(),
        'template' => "regions-map",
        'path' => "$path/theme",
      )
  );
}

function eval_interface_preprocess_regions_map(&$vars) {
  $vars['country'] = arg(3);  
}

countries-map.tpl.php

<h3>Choose your country</h3>
<div id="countries-map">
  <?php
  // to do something with users
  print $number_of_users; ?>
  
  <a href="eval/regions_map/nojs/ca" class="ctools-use-ajax">Canada Map</a>
  <a href="eval/regions_map/nojs/us" class="ctools-use-ajax">US MAP</a>
  
</div>

regions-map.tpl.php

<h3>Choose Your Region</h3>
<div id="regions-map">
  <?php
  if ($country == 'ca') {
    print t('Canada map');
  } elseif ($country == 'us') {
    print t('Map of US');
  }
  ?>
</div>
tic2000’s picture

I have one more question regarding this.
If I use $form_state['rerender'] = TRUE the form will be retrieved from the cache and will have the last values I entered.
What can I do to create a clean form after the submit, like the first time I opened the page? Looking in form.inc I saw that $form_state['input'] is used to store some info about the form. I tried to create a new form inside if (!empty($form_state['executed']) { ... } but this also stops the form from submitting correctly. I tried to this with a node form and doing that the node uid is lost.

merlinofchaos’s picture

For that you actually need to rebuild the form. I recommend using ctools_build_form() a second time to get a completely clean form, and make sure $form_state['input'] = array() so it doesn't pull values from $_POST.

tic2000’s picture

OK, disregard that.
It worked with 'input' = array(). My error was that I was loading the node to display it and then forgot to create a new "empty" one for the new form.

ayalon’s picture

Could you please post your code so we can include it in the sample? Thanks

tic2000’s picture

@merlinofchaos
That was exactly what I did. But like I said, I wanted to render the node and when rebuilding the form I used the same variable, which of course had all the node data. So I created a new empty variable for a new node.
The function used is actually this

function livingclassic_profile_add_status() {
  global $user;
  // See if this request was sent via javascript so we can degrade.
  $js = !empty($_REQUEST['js']);
  $node = array('uid' => $user->uid, 'name' => (isset($user->name) ? $user->name : ''), 'type' => 'status', 'language' => '');
  $form_state = array(
    'ajax' => $js,
    'no_redirect' => TRUE,
    'rerender' => TRUE,
    // This are the arguments required by the form
    'args' => array($node),
  );

  ctools_include('form');
  // Have to do this or form submit won't work right.
  drupal_add_js('misc/jquery.form.js');
  ctools_add_js('ajax-responder');
  ctools_add_js('livingclassic_profile', 'livingclassic_profile');
  module_load_include('inc', 'node', 'node.pages');
  $output = ctools_build_form('status_node_form', $form_state);
  // If the form was submitted, cope.
  if (!empty($form_state['executed']) && $js) {
    ctools_include('ajax');
    $commands = array();
    // Single node view.
   // I think the nide is also stored in $form_state['nid'], but getting it from the db works too.
    $nid = db_result(db_query("SELECT nid FROM {node} WHERE type = 'status' AND uid = %d ORDER BY created DESC LIMIT 1", $user->uid));
    $node = node_load($nid);
    $node->build_mode = 'activity';
    $content = node_view($node, TRUE, FALSE);
    $new_node = array('uid' => $user->uid, 'name' => (isset($user->name) ? $user->name : ''), 'type' => 'status', 'language' => '');
    $form_state1 = array(
      'ajax' => $js,
      'no_redirect' => TRUE,
      'rerender' => TRUE,
      'input' => array(),
      // This are the arguments required by the form
      'args' => array($new_node),
    );
    $clean_form = ctools_build_form('status_node_form', $form_state1);*/

    // Remove any pre-existing status messages, from a validation failure.
    $commands[] = ctools_ajax_command_remove('.messages');
    // If there are messages render them.
    if ($messages = theme('status_messages')) {
      $commands[] = ctools_ajax_command_prepend('#status-form-wrapper', $messages);
    }
    $commands[] = ctools_ajax_command_replace('#status-form-wrapper form', $clean_form);
    $commands[] = ctools_ajax_command_after('#status-form-wrapper', $content);
    $commands[] = ctools_ajax_command_remove_messages(6000);
    ctools_ajax_render($commands);
  }
  if ($js) {
    // The form is only submitted via .js. This means there was a validation
    // and we have to rerender the form. This will go the extra mile:
    ctools_include('ajax');
    $commands = array();
    // If there are messages for the form, render them.
    if ($messages = theme('status_messages')) {
      $output = $messages . $output;
    }

    // Remove any pre-existing status messages, from a validation failure:
    $commands[] = ctools_ajax_command_remove('.messages');
    // And replace the form.
    $commands[] = ctools_ajax_command_html('#status-form-wrapper', $output);
    ctools_ajax_render($commands);
  }

  return '<div id="status-form-wrapper">'. $output .'</div>';
}

The node type is "status". ctools_add_js('livingclassic_profile', 'livingclassic_profile'); is a js file where I added some custom ctools ajax commands (like the one I use later ctools_ajax_command_remove_messages(6000); to remove the status messages after 6 seconds.

The only problem I had left is that if the node has attachments (like an image field), after the first save which works fine, on the second, uploading a file will throw a validation error saying the form is not in cache. If I try to save the second node without a file, everything works.

I remember I read an issue about this, but there was no solution if I remember correctly.

PS. Next week I get back at home and if no one provides a patch I will try to make one (a good way to learn how to use git now).

drurian’s picture

Follow-up: correcting the typo took care of my problem. However, I needed to bind Ajax behavior to area tags for my image maps, so I added this javascript to my module:

(function ($) {

  Drupal.CTools = Drupal.CTools || {};
  Drupal.CTools.AJAX = Drupal.CTools.AJAX || {};
  Drupal.CTools.AJAX.commands = Drupal.CTools.AJAX.commands || {};

Drupal.behaviors.evalInterface = function(context) {

$('area.ctools-use-ajax:not(.ctools-use-ajax-processed)', context)
 .addClass('ctools-use-ajax-processed')
 .click(Drupal.CTools.AJAX.clickAJAXLink);

}
})(jQuery);

Is this the proper way to do it?

mpaler’s picture

Version: 6.x-1.x-dev » 6.x-1.8
Status: Needs review » Active

Hi,

I'm running the example posted in #7 above and it works nicely. Thankyou!

However, I'm definitely observing the behavior described in #830382: Unwanted / duplicated temporary css files if CSS Optimization is enabled.. Many stylesheets are being injected into the bottom of my page like this:

<link class="ctools-temporary-css" type="text/css" rel="stylesheet" media="all" href="/modules/system/system.css?f">

Can we rule out this example being the culprit? Should I be looking elsewhere?

I'm using Zen theme. Thanks for any insights.

mpaler’s picture

Back to the original topic at hand: ctools ajax documentation...I thought I would pass on a tip that may or not be obvious, nevertheless, it was a very pleasant discovery to me.

All the examples in the ctools_ajax_sample module and the examples above use menu callbacks that render a complete page. I was looking for a solution that would put an ajaxified form directly within all nodes of a certain type. It turns out you can render the menu callbacks into a node template variable and have them render AND work as expected.

For example, using the example above of the BMI calculator, you can put the following in your template.php file:

function zen_preprocess_node(&$vars, $hook) {
  $vars['bmi'] = bmi_calculator_simple_page(); 
}

then, anywhere in your node template you can render the form fully ajaxified using

print $bmi;
tic2000’s picture

Version: 6.x-1.8 » 6.x-1.x-dev
Status: Active » Needs review
FileSize
8.64 KB

OK, here's a patch. I hope it's the correct format.

I made sure it works with and without javascript enabled. The only difference between the 2 is that the validation error are displayed above the form in the javascript form.

Now, I also added a "chart" for the results. If it adds too much complexity it can be removed, but I think it's also a nice example of how to use ctools to alter a page through ajax.

mpaler’s picture

Version: 6.x-1.8 » 6.x-1.x-dev
Status: Active » Needs review

Just tried this patched demo. When I run it with core css aggregation turned on, all the css files are loaded at the bottom of the page thus overriding my theme in horrible ways. The js files are reloaded into the . Could this be related to the code in the demo?

[edit]

I fixed this by adding

  ctools_include('ajax');

to the top of ctools_ajax_sample_bmi_page()

as follows:

function ctools_ajax_sample_bmi_page() {
  // See if this request was sent via javascript so we can degrade.
  $js = !empty($_REQUEST['js']);

  $form_state = array(
    'ajax' => $js,
    'no_redirect' => TRUE,
    // Re render the form after submit. This is used if you want the form to
    // remeber the values after submission
    'rerender' => TRUE,
  );
  ctools_include('ajax'); //<=== 
  ctools_include('form');
  // Have to do this or form submit won't work right.
  drupal_add_js('misc/jquery.form.js');
  ctools_add_js('ajax-responder');
zamir’s picture

If someone have an example for drupal 7?I think the above code can not be executed correctly in d7,at least the function 'ctools_build_form' do not exsit.

Raf’s picture

Seems like Drupal core took some of Ctools' functionality in D7. I just found this link, with all upgrade changes you have to make in your code in order to make it work with D7.

Specific to ctools_build_form, it says the following:

- ctools_build_form is very close to being removed. In anticipation of this,
all $form_state['wrapper callback']s must now be
$form_state['wrapper_callback']. In addition to this $form_state['args']
must now be $form_state['build_info']['args'].

NOTE: Previously checking to see if the return from ctools_build_form()
is empty would be enough to see if the form was submitted. This is no
longer true. Please check for $form_state['executed']. If using a wizard
check for $form_state['complete'].

shantanu1’s picture

Write your first ajax form submit.....

$items['testForm'] = array(
		'title'           => t('testForm'),
		'page callback'   => 'drupal_get_form',
		'page arguments'  => array('node_list_add_to_list_form'),
		'access callback' => TRUE,
		'type'            => MENU_CALLBACK,
);
//////////////////////////////////////////////////////////////

function node_list_add_to_list_form($form, &$form_state, $nid) {
  
  // Wrapper for entire form
  $form['#prefix'] = '<div id="all-fields">';
  $form['#suffix'] = '</div>';  
  
  // Test button
  $form['test'] = array(
    '#type' => 'textarea',
   
     
  );   
  
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  		'#ajax' => array(
  				'callback' => 'node_list_add_to_list_form_submit_commands',
  				
  		),
  );      
  
  return $form;
}
//////////////////////////////////////////////////////////////


function node_list_add_to_list_form_submit_commands($form, $form_state) {

	if (!form_get_errors()) {
		$commands = node_list_add_to_list_form_submit($form_state);
		return array('#type' => 'ajax', '#commands' => $commands);
	}

	// Reload form if it didn't pass validation.
	return $form;
}


//////////////////////////////////////////////////////////////////////////////////


function node_list_add_to_list_form_submit($form, $form_state) {
  
	ctools_include('ajax');
	ctools_include('modal');
	
	//write your INSER/UPDATE DB function.
	
	//message
	$title = t('Message added successfully');
	$message .= 'Message added successfully ';
	drupal_set_message(check_plain(t($message)));
	$commands[] = ctools_modal_command_display($title, theme('status_messages'));
	
	print ajax_render($commands);
	exit;
}

Thanks
Shantanu Karmakar
email:shantanu2683@gmail.com

andypost’s picture

This needs to join forces with #1662570: CTools exportable example and find proper home for a new module

dhineshkumar’s picture

Nice stuff
It working. Thank you.