Ajax modal windows, the easy way

Using the CTools Modal Framework

Last week I wrote about the awesomeness that is the CTools ajax framework. If you're anything like me, your mind immediately started racing about all the cool possibilities this opens up. One of those cool possibilities is yet another hidden CTools gem, the modal framework. If you've ever used panels, then you've seen CTools modals in action. In this post, I'll show you how to use modals, in the same way that panels does.

First things first...

If you missed my last post, chances are that this one will not make any sense. I highly suggest reading it before continuing with this one.

We will be building a very simple module with two pages. The first simply holds the link to the modal window, the second is the page that will be displayed in the modal. Lets start with some basic code to set up our test module. This is almost identical to the ajax module we built last week. The biggest difference here, is that the class on the link has been changed to 'ctools-use-modal' and we are adding the javascript using ctools_modal_add_js().

<?php
/**
* Implementation of hook_menu().
*/
function example_menu() { 
 
$items = array();
 
$items['test'] = array(
   
'title' => 'Ajax Test',
   
'page callback' => 'example_test_main',
   
'access arguments' => array('access content'),
  );
 
$items['test/%ctools_js/go'] = array(
   
'page callback' => 'example_test_modal_callback',
   
'page arguments' => array(1),
   
'access arguments' => array('access content'),
  );
  return
$items;
}

/**
* The main page that holds the link to the modal.
*/
function example_test_main() {
 
// Load the modal library and add the modal javascript.
 
ctools_include('modal');
 
ctools_modal_add_js();

 
$output = l('Load modal content', 'test/nojs/go', array(
   
'attributes' => array('class' => 'ctools-use-modal')));

  return
$output;
}
?>

All that is left is to define the modal callback. Since we are using the %ctools_js wildcard, this same callback will be responsible for the content in both modal and non-modal states. Remember that the %ctools_js wildcard will be translated to a boolean value in which TRUE signals that we are in a javascript context.

<?php
function example_test_modal_callback($js = FALSE) {
 
$output = t('<p>Lorem ipsum dolor sit amet...</p>');
 
$title = t('Modal example');
  if (
$js) {
   
ctools_include('ajax');
   
ctools_include('modal');
   
ctools_modal_render($title, $output);
   
// above command will exit().
 
}
  else {
   
drupal_set_title($title);
    return
$output;
  }
}
?>

The code above is about as simple as it gets with modal windows. It simply outputs some text and a title in a modal window. The modal library provides a nice utility function for this, ctools_modal_render(). The function builds the necessary ajax command object and passes it to the browser using ctools_ajax_render().

But now for something not so trivial. Arguably the best use case for modal windows is for displaying forms. This is where the modal library really excels. In this example, we will show the user login form in a modal. Your first impulse might be to just send the output of drupal_get_form() to the modal. While this would display the form in the modal, it would not handle all of the submission and validation properly. Normally Drupal form submissions end in a redirect, which would break our ajax callbacks. Luckily, CTools has an answer for this situation. In the ajax context, we use ctools_modal_form_wrapper() to build the form. The one tricky part here, is that we must evaluate the return value of this function. This function returns an array, that may or may not be populated with ajax commands. If the form submission was not completed for any reason, such as validation errors, then the array will have the commands needed to re-display the form, with errors, in the modal window. If the array is empty, then we can assume that the form was submitted properly. In this case, we add one or more ajax commands to the array to let the user know that the form submitted successfully. In the case of our login form, we do that by redirecting the user to their account page.

One thing I forgot to mention, is that ctools_modal_form_wrapper() expects you to pass in a form_state array. drupal_get_form() allows you to pass in additional arguments after the form id, but the ctools form functions expect all arguments to be passed through the form_state array. In the case of modal forms, the form_state must contain at least an 'ajax' and a 'title' element.

<?php
function example_test_modal_callback($js = FALSE) {
  if (
$js) {
   
ctools_include('ajax');
   
ctools_include('modal');
   
$form_state = array(
     
'ajax' => TRUE,
     
'title' => t('Login'),
    );
   
$output = ctools_modal_form_wrapper('user_login', $form_state);
    if (empty(
$output)) {
     
$output[] = ctools_modal_command_loading();
     
$output[] = ctools_ajax_command_redirect('user');
    }
   
ctools_ajax_render($output);
  }
  else {
    return
drupal_get_form('user_login');
  }
}
?>

Hopefully, I've explained enough so that you can understand what is going on in the above function.

Dialog API

So here is where I will diverge a little bit. While using the CTools modals, I kept wishing that I could use the jQuery UI Dialog widget as the front-end for my modals. So I took the time to build it. Dialog API aims to be functionally equivalent to the modal library, except that it uses jQuery UI. The ajax commands that it exposes are all nearly identical to their modal equivalents, except that the display command allows you to pass an array of options to the Dialog widget. This allows you to control things like height and width from the ajax callback.

And thanks to the insane work of Rob Loach, Dialog API already has a Drupal 7 port. Very exciting stuff.

Posted at 10:39 pm on December 16, 2009 — Tags: ajax | ctools | dialog | drupal

20 Responses

Very very cool. I will definitely be using the Dialog API in the future. Thanks for all your (and Merlin's of course!) hard work!

1
Dec 17, 2009 10:32 am

This is really great. I followed through both of your ctools examples and was very impressed. Gonna try your Dialog API module now :)

2
Dec 17, 2009 11:21 am
Mayk Brito

Men! so good!
but I don't know... this wildcard (%ctools_js) is'nt work with me.. when I put the full url (test/nojs/go) works well.

(sorry for my bad english.. i'm brazilian)

3
Dec 20, 2009 10:05 am

@mayk: make sure you are only using the wildcard in your hook_menu() declaration. When you output the links, use 'nojs'.

4
Dec 20, 2009 10:44 am
Mayk Brito

SOORRRYY BUDY!!!
very sorry..
I was with old version of ctools (1.0).
it's working like a charm with update 1.2!!
WONDERFUL MODULE.. thanks again!!!

5
Dec 20, 2009 11:46 am
Mayk Brito

@roger i'm not a drupal expert.. so.. i'm trying to put other form like node/add page you know.. and I can't figure out how to do this.. please.. send me some guide some help some light! lol

thanks

6
Dec 20, 2009 1:58 pm
Mayk Brito

I'm getting tired.. so far .. I found this error "Missing argument 2 for node_form()".
Well I think that this thing:
$output = ctools_modal_form_wrapper ('blog_node_form', $form_state); is the problem..

'couse otherwise to get form I would passing 3 args:
$output = drupa_get_form('blog_node_form', $node, $form_state); right?

If you already have the solution for this I will appreciate.

Thanks

7
Dec 20, 2009 4:39 pm

@mayk: the ctools form processor doesn't allow you to pass arguments directly to the form builder function. But, you can use $form_state['args'] to have ctools pass these on for you. In your case, this would look like:

<?php
  $form_state
= array(
   
'ajax' => TRUE,
   
'title' => t('My title'),
   
'args' => array($node),
  );
  
$output = ctools_modal_form_wrapper ('blog_node_form', $form_state);
?>

Hope this helps.

8
Dec 20, 2009 5:39 pm
Mayk Brito

@roger thank you so much i'll try this soon.. now I will get some football game.. (i need some rest)
THANKS again.. and I'll be back to gave you some feedback
later

9
Dec 20, 2009 5:46 pm
Mayk Brito

@roger this worked so well.. some issues like fieldset does not open.. but we will discover it soon..
Thank you again.. my brain can't do this, but you're good at this =]

How can you do this I don't know but I really appreciate your talent!

I'm waiting more posts about this ctools.
Thanks.
See you.

10
Dec 21, 2009 12:26 am

Hi Mayk,

Regarding the fieldsets not opening, it's because the fieldset JavaScript behaviors don't exist. You're going to have to load up additional new JavaScript to the client that's on the page. What Dialog will need is xLazyLoader, along with a xLazyLoader command to load up those missing JavaScript behaviors. If you create an issue in the queue, I'm sure we'll be able to stick it in there eventually.

Thanks for helping out with this!

Also, Roger, http://drupal.org/project/ctools_modal_login . I see Druplicate code here! That explicitly uses CTools Modal, while Dialog API uses jQuery UI.

11
Dec 21, 2009 6:30 am
Mayk Brito

Hey Rob and Roger

Thanks for your wonderful help.
How can I open an issue in the queue, where, what put on that issue.. I'm brand new with drupal but I wanna be like you (rob and roger) =] lol

Other thing that I notice is that don't have scrollbar for content in the dialog module. It must be something that I've been missed (sure). =D

( I made myself clear? I need to study more english language..but I'm trying )

12
Dec 21, 2009 12:13 pm

The Dialog issue queue is at http://drupal.org/project/issues/dialog. Feel free to create an issue for any bugs or inconsistencies you find.

13
Dec 21, 2009 12:34 pm
Mayk Brito

@roger it's ok.. I opened 2 issues over there..

Thanks again for your help!

14
Dec 21, 2009 1:01 pm

Hi Roger,

Great tutorial.
Is there a way to load a node (more specifically some areas of page eg. content + right side bar regions) in a modal window with ctools ?
I wish to replace the Lightbox iframe approach by a modal window like the cTools one.

Thanks

Laurent

15
Jan 7, 2010 5:02 pm
Babs

Hi,
I really am very happy you put this module out here at this time. I'm almost done with this website but they wanted a modal login like you have. I really like the idea of this module using ctools and jquery that I already have. But right now its not working.

I thought maybe it was the issue that was mentioned above with the out of date c-tools but I upgraded that to 1.2 and no difference. It seems like all the pieces are there, but they're just not connecting. For instance, when I click on the dialog navigation and hover over the test link, I see it added the nojs addition to the link url. Than I click on the test link and the modal window will appear for a sec, and than disappear and the the test page will be show, but it is shown in the website and not in the modal window.

I thought maybe it was my theme, since I am using custom templates for the login and register pages, but even changing out the theme shows no difference.

Any tips from anyone would be VERY appreciated.

16
Jan 8, 2010 8:13 pm

Nice work Roger! Thanks for writing this up and sharing. I think Dialog API is going to be the new method Skinr uses. :-)

17
Jan 20, 2010 7:46 pm

I've been trying to use your code here as a basis for a modal window that displays a view of images and their titles, but all I get is a generic "An error occurred while attempting to process /my/url".

First, here is the menu call:

$items['feature_package/%ctools_js/go'] = array(
'page callback' => 'feature_package_modal_callback',
'page arguments' => array(1),
'access callback' => TRUE,
);

and the link I'm using to call the modal:

l('Browse for image', 'feature_package/nojs/go', array('attributes' => array('class' => 'ctools-use-modal')));

And finally, the callback:

function feature_package_modal_callback($js = FALSE) {
ctools_include('modal');
ctools_modal_add_js();

$output = views_embed_view('feature_package','page_3');

return $output;
}

Now whenever I just have text in the modal (your first example), it works fine, but any time I try to embed anything like a form or a view, I get this error.

There are a number of instances in ajax-respsonder.js of this error, but I tracked it down to the first one:

var url = $(this).attr('href');
var object = $(this);
$(this).addClass('ctools-ajaxing');
try {
url = url.replace(/nojs/g, 'ajax');
$.ajax({
type: "POST",
url: url,
data: '',
global: true,
success: Drupal.CTools.AJAX.respond,
error: function() {
alert("An error occurred while attempting to process " + url);
},
complete: function() {
object.removeClass('ctools-ajaxing');
},
dataType: 'json'
});
}

Is there something I'm missing?

Thanks.

18
Jan 28, 2010 2:15 am

@wonder95:

It seems you are mixing up (or just mixing) the ajax callback, and the callback to display the link.

The callback for the page to display the link should call ctools_modal_add_js(), but the ajax callback doesn't need it. This is illustrated in example_test_main() above.

The ajax callback should use one of the ctools render functions (such as ctools_modal_render), as illustrated in example_test_modal_callback() from above. Notice how that function tests the $js variable and returns the string output only if the $js variable is false.

Hope that helps. If you need more help, you can find me on irc in #drupal.

19
Jan 29, 2010 10:05 pm
Simon

Hi Roger,

this is pretty nice work you do here. It fills a gap, especially for those starting with drupal and do not have the time to read through miles of code to understand how such a powerful module like ctools works.

I used your code above as well as the one posted in December. The December code worked all fine, but the code above threw an error message (see for details http://drupalbin.com/13562).

I have no clue where the error belongs to....my intutition tells me that something went wrong with the user picture....

I hope you can give me some advice here.

Simon

20
Feb 16, 2010 9:23 am

Post new comment

  • You can use some basic HTML in your posts
  • I will never publish your email address.
  • If you have a Gravatar associated with your email address, it will be used.
  • If you are putting spammy links in your posts, the entire post WILL be deleted.