Improving user experience: using Dialogs for profile edit in modal forms
Table of Contents
We are now developing big Drupal 7 project where users have profiles with lots of fields.
They also have a node tied to them ("My story") which is created during registration.
Initially, these were "edit my profile" and "edit my story" links which were leading to a huge forms where user was scared by amount of elements.
So, basically, user has separate small form for photo upload, separate form for about field, and separate form for small details.
I can tell you that filling in the profile in this way feels more natural, and is much more easy and convenient . And another cool thing is that all form validation errors appear without form reload, via ajax - and you don't have to do anything to achieve that - you just need to add "use-ajax-submit" class to form submit button! To avoid misunderstanding: ajax submit is the core functionality of Drupal 7, not directly related to modal forms, but it really shines when used with them!
Research
When we realized that we need some sort of dialogs, I decided to use Colorbox module since it provides similar user experience.
But after some evaluation I realized that forms integration is not working in the right way in Colorbox. (That's also what the project page says: "The attempt to support opening various forms in a Colorbox was in hindsight never a good idea. It will most likely be completely removed from the module. Form error handling and form redirects are complicated, a lot of code would be needed to do it correctly.") - it suggests to use Modal Forms module instead.
After some issue queues reading it was clear to me that ctools dialogs are not so mature as jquery.ui.dialogs library which is included (surprise, huh?) in Drupal 7 core.
After more research I found "PHP API" for jQuery dialogs - Dialog API module.
Interesting thing is that Dialog API project page mentions CTools, too - but I couldn't find any code that uses CTools in dialog module (tell me if you find it).
So, I decided to use Dialog API module in this project. It's not very stable so I had to apply some patches from issue queue to make example modules work.
Implementation
The basic idea of making some form appear in ajax is that we create new menu item (using hook_menu) which is "ajax-aware" menu item, and then create a special link to that menu item.
So, you need your own module, and you need to know how forms are working in Drupal, to create something interesting.
Example:
Let's imagine that we want to open part of node edit form (e.g. only node body field) in dialog.
Here is the demo (I've allowed anonymous to use the link):
http://analytic7.pixeljets.com/node/11
So, in our module, we write:
array( 'title' => 'Edit your story', 'page callback' => 'mymodule_dialog_story_form_handler', 'page arguments' => array(1, 3), // we pass node object and dialog ajax marker to our new function 'access callback' => 'node_access', // We allow to edit this field if user has permissions to edit node 'access arguments' => array('update', 1), ) ); } ?>
And here is the code that gets regular node form, hides all fields except node body, and prepares the form for ajax submit.
$title, 'draggable' => false, 'resizable' => false, )); } else { $output[] = dialog_command_reload(); } ajax_deliver(array('#type' => 'ajax', '#commands' => $output)); } else { // User has js disabled return drupal_get_form('mymodule_dialog_story_form_builder', $node); } } /** * Builds the form */ function mymodule_dialog_story_form_builder($form, $form_state, $node) { $form = array(); form_load_include($form_state, 'inc', 'node', 'node.pages'); $form = node_form($form, $form_state, $node); // basically, we hide everything except 'body' field foreach (element_children($form) as $key) { if ($key != 'body') { $form[$key]['#access'] = FALSE; } } $form['submit'] = $form['actions']['submit']; $form['submit']['#weight'] = 100; $form['submit']['#attributes'] = array('class' => array('use-ajax-submit')); $form['#process'][] = 'dialog_process_ajax_form'; $form['#submit'][] = 'mymodule_dialog_story_form_submit'; return $form; } function mymodule_dialog_story_form_submit($form, &$form_state) { // Tell Dialog that we want to close it after form submit drupal_static_reset('dialog_display'); // We also don't want to redirect the form needlessly. The redirected page // would have loaded in the dialog. We'll be dismissing that dialog. If a // destination was specified we will handle that with a dialog_command. $form_state['no_redirect'] = true; } ?>
That's it!
Now we need to create a link to that page, but to make it appear in dialog, we also need to include dialog files and add special class to the link:
Caveats
image upload via Drupal ajax form is not working properly so I had to disable ajax-submit for image field dialog for now. http://drupal.org/node/1328988
More advanced examples
The Dialog module provides several example modules for testing.
We used Dialogs for some advanced things like Field-collection edit/add links, it 's a bit more complicated but it still works good. You may notice that we reload the page entirely after the dialog submit button is clicked - but it's not too hard to modify the code so it refreshes only part of page - so it will be completely ajax-powered editing.