I'm building a CTools Panels Content Type (i.e., that thing you insert into panels when adding content, not a type of node) and I am trying to use a form item's #ajax attribute to set some default values. See code below.

This is all inside of the content type's my_module_content_type_edit_form($form, &$form_state) call, by the way.

  $form['link_type'] = array(
    '#type' => 'radios',
    '#title' => t('Link Type'),
    '#ajax' => array(
      'callback' => 'my_module_set_target'
    '#default_value' => empty($conf['link_type']) ? '_blank' : $conf['link_type'],
    '#options' => array('none'=>t('No Link'), 'internal'=>t('Internal Link'), 'external'=>t('External Link'), 'document'=>t('Document Link')),

My callback is the following.

function my_module_set_target($form, $form_state) {
  watchdog("Test", "Testing callback", array(), WATCHDOG_ALERT);
  $form['link_target']['#default_value'] = '_parent';

  return $form['link_target']['#default_value'];

Regardless of whether the return I'm suggesting would actually work, watchdog() doesn't even work.

I know CTools does some weird stuff with AJAX, but it can't be that weird. Do you have any idea on how I'd do what I'm wanting to do?


Alternatively: How do I set a default value based on the value of a previous form option?

I figured out how to do this, but it's a bit hacky -- you create new form fields for each dependency fork. You can then merge the values together in hook_content_type_edit_form_submit(), using whichever one corresponds to the value chosen for the component that initially forked everything.

I'm leaving the question open because I (and, frankly, every programmer I'm working with) is really wanting a good way to use AJAX inside these Panel content type edit forms.

Update: It appears you can't do stuff with #attached, either.

$form['link'][$i] = array(
  '#type' => 'fieldset',
  '#title' => t('Link #@num', array('@num' => $i)),
  '#collapsible' => TRUE,
  '#collapsed' => TRUE,
  '#attached' => array(
    'js' => array(
      'alert("Yay.");', 'inline'
  • Being a "Panels for everything" type of Drupal developer, I imagine I could find use for this in the future as well, so I added a bounty, lets see what happens.
    – Letharion
    Commented Aug 17, 2012 at 9:09
  • Wow, that bounty came and went without even a comment (Thanks BTW, Letharion). Is what I'm asking impossible or something?
    – aendra
    Commented Aug 31, 2012 at 18:20
  • Should be worth noting that I've been able to successfully add Javascript using ctools_add_js(); or drupal_add_js(); at the end of hook_content_type_edit_form();. If you're just doing simple UI-related stuff, seems like that might be the best call (At least until somebody properly answers this question).
    – aendra
    Commented Sep 29, 2012 at 15:36

3 Answers 3


Short answer: you should use #ajax['path'].

Long answer:

Having a ajax callback does not help as ctools creates it's forms differently afaik. The callback done by system/ajax cannot find the complete form definition thus cannot find an element to process the ajax request for. Using #ajax[path] just triggers a menu item.

You can check for yourself by dumping the form when using #ajax[callback]

function ajax_form_callback() {
  list($form, $form_state) = ajax_get_form();
  drupal_process_form($form['#form_id'], $form, $form_state);

I've modified the simplecontext_content_type_edit_form by adding the user autocomplete widget and your field which both work :)

function simplecontext_content_type_edit_form($form, &$form_state) {
  $conf = $form_state['conf'];

  $form['owner_name'] = array(
    '#type' => 'textfield',
    '#title' => t('Username'),
    '#default_value' => 'admin',
    '#autocomplete_path' => 'user/autocomplete',
    '#size' => '6',
    '#maxlength' => '60',
    '#description' => '$description',

  $form['link_type'] = array(
    '#type' => 'radios',
    '#title' => t('Link Type'),
    '#ajax' => array(
      'path' => 'my_module_set_target'
    '#default_value' => empty($conf['link_type']) ? '_blank' : $conf['link_type'],
    '#options' => array('none' => t('No Link'), 'internal' => t('Internal Link'), 'external' => t('External Link'), 'document' => t('Document Link')),

As you now use #ajax path you need to add a menu item as I did like


function my_module_menu() {
  $items = array(
    'my_module_set_target' => array(
      'title' => 'AJAX Example',
      'page callback' => 'my_module_set_target',
      'access callback' => TRUE,
      'expanded' => TRUE,
  return $items;

function my_module_set_target() {
  drupal_json_output( array('data' => "ABC"));

A nittpick about #attached[js]: the inline js should be key => value like:

'#attached' => array(
  'js' => array(
    'alert("Yay.");' => 'inline',

I've used Firebug to check for the resulting value not the resulted effect. So I hope this helps to fix your problem.

  • Wow. I am so stoked to finally have an answer to this question. Will try it out in a bit. Thanks also for the note on the #attached bit, that's actually what I had initially (But changed it because I figured that couldn't be right).
    – aendra
    Commented Oct 3, 2012 at 12:32
  • So, any success? Would be interesting to know before the bounty is over :)
    – Letharion
    Commented Oct 5, 2012 at 22:22
  • I hope I do get my bounty :p Commented Oct 6, 2012 at 15:16
  • Sorry for the delay with this, having a bit of a time getting it working -- when I try to return the $form array, it tells me my validate function isn't defined. It is, and when I try to re-declare it in my module (as opposed to content type plugin file), it tells me I'm redeclaring it. I'm guessing that's not what I need to do, but I can't figure out how to structure drupal_json_output so it outputs something that my form will interpret as a meaningful response. Any help? A better example callback would help a LOT. Thanks!
    – aendra
    Commented Oct 7, 2012 at 10:16
  • I've missed the tiny little part of your question which is about the return value: return $form['link_target']['#default_value']; which is your trying to return a drupal form to a html/javascript context. That will not work. Have you seen similar thing what you want in the panels UI? (I myself disable JavaScript now and then with views UI just to see the non js workflow) (edited this comment a zillion times ... awkward) Commented Oct 10, 2012 at 6:50

I've had a similar problem, where I wanted to include a Media element type into a CTools content type plugin, that also uses ajax for selecting an image.

It uses it's own ajax 'path' setting, instead of the 'callback' setting, but when selecting an image, the form was rebuilt without the media element entirely.

I traced this to the fact that drupal_rebuild_form couldn't find neither the CTools form wrapper function, nor the actual settings form function. So I fixed it by adding these lines of code to the ctools settings form:

function custom_module_my_content_plugin_content_type_edit_form($form, &$form_state) {

$background_image = isset($conf['background_image']) ? $conf['background_image'] : array();
  $form['background_image'] = array(
    '#title' => t('Background image'),
    '#default_value' => $background_image,
    '#type' => 'media',
    '#input' => TRUE,
    '#extended' => TRUE,
    '#tree' => TRUE,
    '#media_options' => array(),

  // The two function calls below are necessary if we want to use a media
  // element type, because it causes ajax requests, which in turn call
  // drupal_form_rebuild(), and without the below includes, Drupal will
  // not be able to rebuild the form.

  // Include the CTools content type plugin file, because it provides
  // the ctools_content_configure_form_defaults() function, which is needed
  // when rebuilding the form, because of an ajax action, like selecting
  // a media element.
  ctools_form_include($form_state, 'content');

  // Include this plugin file as well, so that when the form is rebuilt, it
  // can successfully retrieve the settings form.
  ctools_form_include($form_state, 'my_content_plugin', 'custom_module', 'plugins/content_types/my_content_plugin');


Maybe I'm missing something obvious why the include files are not loaded, but including them manually fixed the issue for me.


on my side, I had to write a form wrapper function in .module file and to include the pane manually (where original form content is defined) like that:

function mymodule_form($form, &$form_state) {
  // include mymodule/panes/mypane.inc
  ctools_include('mypane', 'mymodule', 'panes');
  return mymodule_form_content($form, $form_state);

Now, during ajax call, Drupal is able to retrieve my form so I can use the standard AJAX form API.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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