I have a regular (non-modal) drupal form... when the user hits "submit," I want to open a modal dialog...

I've set up a ctools modal callback, and it's working correctly:

  $items['download-manager/%ctools_js/download/%'] = array(
    'title' => 'Go to the download form for a particular node.',
  	'page callback' => 'download_manager_wizard',
  	'page arguments' => array(1, 3), // %ctools_js, %nid
  	'access callback' => TRUE,
    'type' => MENU_CALLBACK,
    'file' => 'download_manager_wizard.inc',
  );

The modal opens fine using:

ctools_modal_text_button(t('Download'), 'download-manager/nojs/download/' . $nid, t('Download this asset'), 'download-button');

But from within my form this does not work:

$form['#action'] = url('download-manager/ajax/download/122');

That just gets me an ajax waterfall dump... what should I be doing to open the modal from form submit?

Comments

jordanmagnuson’s picture

I've also tried adding the "ctools-use-modal" class to my form's submit button, but no luck. A modal begins to popup, but instant error messages:

  $form['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Download'),
      '#attributes' => array(
          'class' => array('ctools-use-modal'),
      ),      
  );  
merlinofchaos’s picture

Hmm. That really ought to work. Perhaps there's a bug.

Have you tried with CTools -dev?

jordanmagnuson’s picture

Version: 7.x-1.0-rc1 » 7.x-1.x-dev

Thanks a lot for the reply Merlin! I really appreciate your willingness to help.

Okay, just tried the dev, and still having the same issue. If you want to scan my code, here are github links to download_manager.module and download_manager_wizard.inc.

Or for limited reference, here's my complete form callback:

function download_manager_download_form($form, &$form_state, $nid) {
  
  $node = node_load($nid);
  
  // ---------------------------------------------------------------------------
  // Get license options
  $licenses = array();
  foreach ($node->field_license['und'] as $l) {
    $licenses[] = $l['value'];
  }  
  
  // Define options array
  $license_options = array();
  
  // Personal use
 	if (in_array(PERSONAL_USE_LICENSE_ID, $licenses)) {
 	  $personal_use_price = $node->field_personal_use_price['und']['0']['value'];
 	  if ($personal_use_price == 0) {
 	    $personal_use_price_text = '<span>FREE</span>';
 	  }
 	  else {
 	    $personal_use_price_text = '$' . $personal_use_price;
 	  } 	  
 	  $license_options[PERSONAL_USE_LICENSE_ID] = $personal_use_price_text . ' | Personal Use Unlimited';
 	} 
 	
 	// Commmercial use
 	if (in_array(COMMERCIAL_USE_LICENSE_ID, $licenses)) {
 	  $commercial_use_price = $node->field_commercial_use_price['und']['0']['value'];
 	  if ($commercial_use_price == 0) {
 	    $commercial_use_price_text = '<span>FREE</span>';
 	  }
 	  else {
 	    $commercial_use_price_text = '$' . $commercial_use_price;
 	  }
 	  $license_options[COMMERCIAL_USE_LICENSE_ID] = $commercial_use_price_text . ' | Commercial Use Unlimited';
 	}
 	
 	// Remove personal use if same price as commercial
 	if ($personal_use_price == $commercial_use_price) {
 	  unset($license_options[PERSONAL_USE_LICENSE_ID]);
 	}
 	
 	// Default value
 	$default_value = key($license_options);
 	
 	// ---------------------------------------------------------------------------
 	// Build form	
  $form['license'] = array(
      '#type' => 'radios',
      '#options' => $license_options,
      '#default_value' => $default_value,      
      '#required' => TRUE,
  );  
  
  $form['personal_use_price'] = array(
      '#type' => 'value',
      '#value' => $personal_use_price,
  );
  
  $form['commercial_use_price'] = array(
      '#type' => 'value',
      '#value' => $commercial_use_price,
  );
  
  $form['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Download'),
      '#attributes' => array(
          'class' => array('ctools-use-modal'),
      ),      
  );  
  
  $form['#action'] = url('download-manager/nojs/download/122');

  return $form;
}

And here's my modal wizard callback that the form's action links to:

function download_manager_wizard($js = NULL, $nid = NULL, $step = NULL) {
  
  // $user_id is used to create $download_object_id
  global $user;
  
  // Check for js
  if ($js) {
    ctools_include('modal');
    ctools_include('ajax');
  }  
  
  // Set up the ctools wizard
  $form_info = array(
    'id' => 'download_manager',
    'path' => "download-manager/" . ($js ? 'ajax' : 'nojs') . "/download/$nid/%step",
    'show trail' => TRUE,
    'show back' => TRUE,
    'show return' => FALSE,
    'next callback' => 'download_manager_wizard_next',
    'finish callback' => 'download_manager_wizard_finish',
    'cancel callback' => 'download_manager_wizard_cancel',
    'order' => array(
      'choose-license' => t('Choose License'),
  		'confirm-spending' => t('Confirm Spending'),
      'start-download' => t('Start Download'),
    ),
    'forms' => array(
      'choose-license' => array(
        'form id' => 'download_manager_wizard_choose_license_form',
      ),
      'confirm-spending' => array(
        'form id' => 'download_manager_wizard_confirm_spending_form',
      ),      
      'start-download' => array(
        'form id' => 'download_manager_wizard_start_download_form',
      ),
    ),
  );
  
  // Create our unique object id, for caching.
  $download_object_id = $nid . '_' . $user->uid;
  
  // TODO: This automatically gets defaults if there wasn't anything saved.
  $download_object = download_manager_wizard_cache_get($download_object_id);  
  if (!isset($download_object)) {
    // Create a default object.
    $download_object = new stdClass;
    $download_object->nid = $nid;    
    $download_object->license = 'unknown';
    $download_object->price = 'unknown'; 
  }
  
  if (empty($step)) {
    // We reset the form when $step is NULL because that means they have
    // for whatever reason started over.
    download_manager_wizard_cache_clear($download_object_id);
    $step = 'choose-license';
  }  
 
  // Set the form state
  $form_state = array(
    'ajax' => $js,
    // Put our download_object and ID into the form state cache so we can easily find it.
    'download_object_id' => $download_object_id,
    'download_object' => &$download_object,
  );
  
  // Send this all off to our form. This is like drupal_get_form only wizardy.
  ctools_include('wizard');
  $form = ctools_wizard_multistep_form($form_info, $step, $form_state);  
  $output = drupal_render($form);
  
  // Render the form in modal if js.
  if ($js) {
    // If javascript is active, we have to use a render array.
    $commands = array();
    if (!empty($form_state['cancel'])) {
      // If cancelling, return to the activity.
      $commands[] = ctools_modal_command_dismiss();
    }
    else {
      $commands = ctools_modal_form_render($form_state, $output);
    }
    print ajax_render($commands);
    exit;
  }
  else {
    if (!empty($form_state['cancel'])) {
      $original_node_path = 'node/' . $nid;
      drupal_goto($original_node_path);
    }
    else {
      return $output;
    }
  }  
}

I'm using this call to render the form:
print drupal_render(drupal_get_form('download_manager_download_form', $node->nid));

And like I said, I've tried ctools_modal_text_button, and that works fine to generate the modal:

print ctools_modal_text_button(t('Download'), 'download-manager/nojs/download/122', t('Download this asset'), 'download-button');
jordanmagnuson’s picture

StatusFileSize
new275.8 KB

Here's a screenshot of the error I'm getting (attached).

jordanmagnuson’s picture

Oops... realized that I left in the form_submit callback code for the error screenshot I attached previously (not sure if that really matters or not). Removed the submit callback, and the AJAX HTTP error message now reads as follows (looks like it's basically just spitting out the page content):

An AJAX HTTP error occurred.
HTTP Result Code: 200
Debugging information follows.
Path: 
StatusText: OK
ResponseText: 
Euro Stamp Brad 1 | Pixel Scrapper
@import url("http://dev.pixelscrapper/modules/system/system.base.css?m13rw3");
@import url("http://dev.pixelscrapper/modules/system/system.menus.css?m13rw3");
@import url("http://dev.pixelscrapper/modules/system/system.messages.css?m13rw3");
@import url("http://dev.pixelscrapper/modules/system/system.theme.css?m13rw3");
@import url("http://dev.pixelscrapper/modules/contextual/contextual.css?m13rw3");
@import url("http://dev.pixelscrapper/modules/comment/comment.css?m13rw3");
@import url("http://dev.pixelscrapper/modules/field/theme/field.css?m13rw3");
@import url("http://dev.pixelscrapper/modules/node/node.css?m13rw3");
@import url("http://dev.pixelscrapper/modules/search/search.css?m13rw3");
@import url("http://dev.pixelscrapper/modules/user/user.css?m13rw3");
@import url("http://dev.pixelscrapper/sites/all/modules/views/css/views.css?m13rw3");
@import url("http://dev.pixelscrapper/sites/all/modules/ctools/css/ctools.css?m13rw3");
@import url("http://dev.pixelscrapper/sites/all/modules/ctools/css/modal.css?m13rw3");
@import url("http://dev.pixelscrapper/sites/default/modules/node_list/css/node-list.css?m13rw3");
@import url("http://dev.pixelscrapper/sites/all/modules/admin_menu/admin_menu.css?m13rw3");
@import url("http://dev.pixelscrapper/sites/all/modules/admin_menu/admin_menu.uid1.css?m13rw3");
@import url("http://dev....sis. Nunc vel imperdiet sem. Pellentesque consectetur egestas felis, id porta elit scelerisque et. Nullam a mauris dolor. Fusce leo augue, facilisis ut cursus nec, congue a augue. Nulla fermentum tristique nulla nec ullamcorper. 				
Follow Me
Blog
Facebook
Flickr
Pinterest
Twitter
YouTube
RSS Feed
RSS by Email
Configure block
Execute PHP Code
PHP code to execute 
Enter some code. Do not use &lt;?php ?&gt; tags.
Configure block
Search form
Search 
Configure block
Colors
tan, black
Tags
europe, stamp	
File Info
png image, 0.5mb
600 x 600 pixels (1.7 x larger than shown)
2 x 2 inches
Description
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam sollicitudin sapien non ipsum eleifend mattis. Donec sed turpis dolor. Sed rutrum lobortis ante, vehicula malesuada erat tincidunt id.
Download
FREE | Commercial Use Unlimited 
Learn about our liberal licenses		
Stats
15 Downloads
2 Lists
0 Comments
0 people heart this
Template File	
Make your own assets like this one!
Get the template 
Make your own assets like this!-->
jpg image, 0.1mb
500 x 500 pixels
Recent content
Configure block
Euro Stamp Brad 1Marisa Lerineditdelete 
test templatemagjoreditdelete 
Emmamagjoreditdelete 
Bobmagjoreditdelete 
Blue Corner Frame PaperMarisa Lerineditdelete 
Jackmagjoreditdelete 
test templatemagjoreditdelete 
Red PaperMarisa Lerineditdelete 
Busan Beach 1 - ribbonMarisa Lerineditdelete 
Busan Beach PaletteMarisa Lerineditdelete 
More  
Configure block
Powered by Drupal  
jordanmagnuson’s picture

Any chance of getting further help on this?

Tony Stratton’s picture

I'm running into the same issue. I need to pass data from a form into a modal dialog, I took the same approach as you and getting the same error.

Any success yet?

jordanmagnuson’s picture

I got somewhat sidetracked from this issue, but no, still no luck. I'll post back here if I have an update, and would be grateful if you would do the same :)

alduya’s picture

StatusFileSize
new511 bytes

I encountered this issue while making a form, in which the email address is given, that opens another form in a modal, with the previously entered email already entered.

I found that in the file modal.js, in Drupal.behaviors.ZZCToolsModal, on line 224 findURL is used to get the url to use for the modal based on the button of the form.
This button however doesn't have the url information.

I don't know if this is the correct sollution, but if I add this check, that gets the action value of the parent form if no url was found. Patch is attached.

         // AJAX submits specified in this manner automatically submit to the
         // normal form action.
         element_settings.url = Drupal.CTools.Modal.findURL(this);
+        if (element_settings.url == '') {
+          element_settings.url = $this.parents('form').attr('action');
+        }
         element_settings.event = 'click';

I also found a possible workaround. findURL tries to get a url based on the name of the item it is given. I added a hidden field to the form with a specific class and the url as value.
My submit butten name is submit, so the extra hidden field must have a class of edit-submit-url.

<?php
$form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
    '#attributes' => array('class' => array('ctools-use-modal')),
  );
  $form['hidden_url'] = array(
    '#type' => 'hidden',
    '#value' => url('modal_url/nojs/parameter'),
    '#attributes' => array('class' => array('edit-submit-url')),
  );
?>
alduya’s picture

Category: support » bug
Status: Active » Needs review
jordanmagnuson’s picture

Title: How to open modal on form submission? » Open modal on form submission is broken

Thank you very much Sander for the extremely helpful reply--both of your solutions worked for me!

Regarding the patch:

  • I think it's for a slightly old version of ctools?
  • $this should have parenthesis around it: $(this) (your patch did not work for me until I changed this).
  • I think using $(this).closest('form') is faster and makes more sense than $(this).parents('form')...?

So my version reads:

if (element_settings.url == '') {
  element_settings.url = $(this).closest('form').attr('action');
}
jordanmagnuson’s picture

Hey Sander, this is slightly off topic, but can you tell me how you managed to pass data from your form to the modal (you say you passed an email address)?

jordanmagnuson’s picture

Component: Code » Modal
jordanmagnuson’s picture

Any chance on getting further help with this? alduya, are you still there?

jordanmagnuson’s picture

Status: Needs review » Reviewed & tested by the community

Have been using #11 for some time without any problems. Any chance of getting this patched in dev?

andypost’s picture

+1 to commit, really annoing

merlinofchaos’s picture

Status: Reviewed & tested by the community » Needs review

I'm a little unsure about this patch. While it will AJAX to the form action, is it actually going to properly submit anything? Since it's acting on 'click' and not 'submit' I don't think it's going to submit form values, is it?

Plus, it's not RTBC if the actually attached patch isn't the patch that's been reviewed.

jordanmagnuson’s picture

Merlin: that's a good question about the form actually submitting anything. I've just been using this to pass values from a form to an ajax modal (just grabbing the values from $_POST)... haven't actually needed to submit the form, per say.

andrewbelcher’s picture

Here is the patch from #11 as a patch. It solves the problem for me where we're submitting a VBO form, so I think it is find for submission, but wouldn't hurt to have some more testing...

andypost’s picture

Also there's a question to properly process when form submission ends with redirect

andrewbelcher’s picture

Yes, it varies depending on what's happening, so we deal with that in the submit handlers using the various core/ctools ajax commands.

jordanmagnuson’s picture

Status: Needs review » Reviewed & tested by the community

If the form is being submitted properly, then I think this is RTBC, as that's the only question that was holding it back...

podarok’s picture

Assigned: Unassigned » merlinofchaos

#19 looks good
assigning it to @merlinofchaos

RTBC

andrewbelcher’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new633 bytes

Turns out there was one other thing missing, if you have multiple submit buttons, the triggering element doesn't get recognised as op isn't sent. This is simply a case of telling Drupal.ajax to set the op based off the clicked element. I've updated the patch for it.

HarshalDhumal’s picture

You can open modal on form submit by converting that form submit into ajax submit.

function test_form_menu() {
  $items['test_form'] = array(
    'page callback' => 'test_form_callback',
    'access arguments' => array('access content'),
    'title' => 'Test form',
  );
  return $items;
}

function test_form_callback(){
       return drupal_render(drupal_get_form('test_form'));
}

function test_form($form, $form_state){
       ctools_include('ajax');
       ctools_include('modal');
       ctools_modal_add_js();

	$form['test_field'] = array(
		'#type' => 'textfield',
		'#title' => t('Test field'),
	);
	$form['submit'] = array(
		'#type' => 'submit',
		'#value' => 'Show modal',
		'#attributes' => array('class' => array('ctools-use-modal')),
		'#ajax' => array(
			'callback' => 'test_form_ajax_submit',
		),
	);

    return $form;
}

function test_form_ajax_submit($form, $form_state){
	ctools_include('ajax');
        ctools_include('modal');
	ctools_modal_render('Modal on form submit',  $form_state['values']['test_field']);
	drupal_exit();
}

jordanmagnuson’s picture

@HarshalDhumal: thanks for the example. Using your method, how do we degrade gracefully if javascript is not enabled?

Much appreciated if you could provide an example with graceful degradation.

jordanmagnuson’s picture

To answer my own question, I think the best way to handle graceful degradation for the method in #25 is to do something similar to the method used in the ajax graceful degradation example module: have two submit buttons on the form, one for ajax, one for non-ajax (with its own non-ajax submit callback), and then use css and js to hide/show the appropriate button depending on whether the user has js enabled or not. Then you can react to each situation independently (e.g. open a modal for the js user, send the non-js user to a form page via drupal_goto).

japerry’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community

Looks good to me, I'm including it with the ctools 1.4 sandbox for testing.

japerry’s picture

japerry’s picture

Status: Reviewed & tested by the community » Fixed

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.