Improving user experience: using Dialogs for profile edit in modal forms

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.

And that is the result of UI improvements:
Modal 1

and after clicking “Edit” near “Hobby” item…
Modal 1

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:

<?php
function mymodule_menu() {
  return array(
   
'node/%node/edit_story/%dialog_js' => 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.

<?php
/**
* Retrieves the form and makes it work via dialog
*/
function mymodule_dialog_story_form_handler($node, $ajax) {
 
// if user has js enabled...
 
if ($ajax) {
   
dialog_display(TRUE);
   
   
$content = drupal_get_form('mymodule_dialog_story_form_builder', $node);
   
$title = t('Edit your story');
   
   
   
    if (
dialog_display()) {       
     
$output[] = dialog_command_display($content, array('title' => $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:

<?php
drupal_add_library
('dialog','dialog');
?>

<a href="/node/11/edit_story/nojs" class="use-ajax use-dialog">Edit node body</a>

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.

Комментарии

Great write I ever seen in Drupal World

13 января, 2012

You should have a look at the aloha integration we've been working on.

You should be able to edit any text field just by clicking into the field and typing. Those modals do look really pretty though, and you'd need them for the drop downs and images etc.

Here's the link to the sandbox we're working from

http://drupal.org/sandbox/iler/1403780

13 января, 2012

It looks cool! But I think it’s harder to implement in real-world websites - in terms of css bugfixing and in use cases when you need to apply filtering for rendering.

14 января, 2012

Great stuff! I hope you are considering to contrib it as a module?

14 января, 2012

Well, I don’t see the point in releasing such a module - there are already good examples in Dialog module package.

15 января, 2012

Awesome! This kind of articles make people start digging in module development and more complex stuff than the usual site building

14 января, 2012

Thanks! :)

15 января, 2012

Hi Anton,

Great post and interesting info. I kept reading about editing body with Ajax.
From UX point, I'm thinking if the method you chose actually proves itself?!

It could have be great to be able to test the different methods for editing user profile info.
For instance, I'm worried that in your approach users will leave more fields empty than in a common profile edit page.
On other hand in websites like LinkedIn, it feels like the edit mode contains way too many fields.

Well...if you have related info to share, it can be a great post to continue this one :-)
Thanks.

15 января, 2012

I think your concerns on empty fields are fair enough, but this issue impact can be minimized by encouraging people to fill in their profile (e.g. fancy bars of profile filling progress, with percents).

At the same time, the advantage of the method is that visitors don’t have to think of fields they don’t want to fill in right now. That’s an interesting topic, indeed :)

15 января, 2012

I have been exploring for a little for any high-quality articles or weblog posts in this sort of
house . Exploring in Yahoo I eventually stumbled upon this web site.
Studying this info So i'm glad to show that I have a very good uncanny feeling I discovered just what
I needed. I so much indubitably will make sure to
don?t overlook this website and provides it a look on a continuing basis.

Have a look at my web site :: building's width

21 октября, 2014

Thanks for the writeup.

What I missed is a more generic solution to edit defined fields. You could have used the _load() callback for your module like this

function mymodule_menu() {
return array(
'node/%node/edit-part/%mymodule_edit_node_part/%dialog_js' =
...

function mymodule_edit_node_part_load($config) {
...

then a path like node/12/edit-part/story-body/nojs triggers to load config for 'edit-part'

This is what I did for a full edit on D6 using ctools http://build2be.com/content/using-ctools-modal-forms-node-edit where I swapped the menu path order.

I still am not sure whether merging with node/%node/my-generic-extension is wise as one can encounter path clashes with revision, diff and the like.

Btw comment preview is not working so just submitted as is :(

15 января, 2012

Hi Clemens,
thanks for a good idea!
I’ve simplified the code provided in the post so it’s not overloaded with things not directly related to Dialogs, but in production, we still use separate menu item per each dialog - that allows us to provide different access callbacks for each menu item (and that’s more readable than injecting access checks into menu helper _load() function), since in our case, it’s more complex than just node_access() check.

So, we have something like

<?php
function mymodule_menu() {
...
'node/%node/edit_field/%dialog_js' => array(
     
'title' => 'Edit body',
     
'page callback' => 'mymodule_dialog_form', // general function used for all website dialogs
     
'page arguments' =>  array(1, 'specific_field_form', 3), // specific field dialog form id is specified here
     
'access callback' => 'our_fancy_access_checker',
     
'access arguments' => array(1),
     
'file' => 'mymodule.dialogs.inc'
   
),    
...
?>

and in specific_field_form function, which is a form builder function, we also have
build_dialog_form(‘field_id’) helper which loads node form, hides all fields except specified, etc.

16 января, 2012

An interesting discussion is definitely worth comment. I believe that you need to write more on this subject,
it may not be a taboo matter but usually folks don't speak
about these topics. To the next! Many thanks!!

18 июля, 2014

You are the man

03 февраля, 2012

I am also saying "Great write I ever seen in Drupal World", as the first comment..
I need your help man,i followed your tutorial and everything went well..Just to mention i am new to Drupal module development.
So i created a module which allow users to submit a number after clicking on a link which will open up on a modal as in ur tutorial,the form opens well.
what i need is do validation on the submitted input and return a message to user on the same modal,whether its Success or Error!
but i just cant do it.. ((
Below is my module code

<?php
/**
* Implementation of hook_menu()
*/
function raha_ajax_menu() {
$items = array();
$items['test/%ctools_js/go'] = array(
'page callback' => 'raha_test_form_handler',
'page arguments' => array(1),
'access arguments' => array('access content'),
);
return $items;
}

function raha_test_form_handler($ajax){

if ($ajax) {
dialog_display(TRUE);

$content = drupal_get_form('raha_test_kupon_form_builder');
$title = t('Process the raha');

if(dialog_display()) {
$output[] = dialog_command_display($content, array('title' => $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('raha_test_kupon_form_builder');
}

}

function raha_test_kupon_form_builder($form,$form_state){
$form = array();
$form['raha_qty']=array(
'#type'=>'textfield',
'#id'=>'coupon_qty',
'#size'=>10,
'#default_value'=>0,
);
$form['product_id']=array(
'#type'=>'hidden',
'#id'=>'product_id',
'#default_value'=>$nodeid,
);

$form['submit']=array(
'#type'=>'submit',
'#id'=>'submit_raha',
'#value'=>t('Get Raha'),
);

$form['submit']['#attributes'] = array('class' => array('use-ajax-submit'));

$form['#process'][] = 'dialog_process_ajax_form';
$form['#submit'][] = 'raha_test_kupon_form_submit';
return $form;

}

function raha_test_kupon_form_submit($form,&$form_state){

$productid=$form_state['values']['product_id'];
$submitted_coupon=$form_state['values']['coupon_qty'];

if (!is_numeric($submitted_coupon)) {
form_set_error('raha_qty',t('You must enter a number for the Raha'));
}

//submit values to database
if(submit_your_raha()){ ///this function returns true if the value was inserted to db
drupal_set_message(t('your Raha was submitted'));
}else{
drupal_set_message(t('Error! occured'));
}

}
?>
As you can see the function raha_test_kupon_form_submit($form,&$form_state) is where the submission and validation happens... so please help me at this point.
Sorry for my english.

04 февраля, 2012

i forgot to include the link
Submit Raha

04 февраля, 2012

Sorry, it’s hard for me to tell you where is the bug, looking at your code without proper formatting and syntax highlighting. As far as I understand, the dialog opens without any issues, but then you have issues when submitting the form?

25 февраля, 2012

yes the form opens in modal but I have solved the issue!
i found out whenever i pass a variable directly in drupal_set_message() is when the problems happens.
but after using place holders everything works well. something like
drupal_set_message(t('you are now looking at @u profile,array('@u'=>$user->name)'));

27 февраля, 2012

Nice writup however I found Dialog very limiting. Check http://drupal.org/project/ctools_automodal especially with my patch http://drupal.org/node/1420534#comment-5553552.

Conversion from normal form to modal form could be as easy as:

<?php
/**
* Implements hook_modal_paths().
*/
function eco_user_modal_paths() {
$paths = array();

$paths['user/register'] = array(
'style' => 'eco-user-signup',
'redirect' => 'user',
'close' => TRUE,
);

return $paths;
}
?>

14 февраля, 2012

That looks really promising, indeed. The idea is brilliant, but currently the whole module looks not very flexible comparing to Dialog, correct me if I’m wrong.
I’m talking about stuff like updating part of page after dialog sumbit - is it possible now?

25 февраля, 2012

That is not built-in right now, but AFAIK the Dialog doesn't have this implemented too - you need to handle these things manually in the callbacks.

In CTools Modals with the hook_modal_paths() implementation you can:

1. set specific modal style (e.g. size)
2. redirect after form submit
3. reload page after form submit
4. display confirmation page in the modal

The update of the particular place at the original page is not implemented right now but I can imagine some kind of simple implementation of that e.g. by specifying the CSS selector in the hook_modal_paths().

25 февраля, 2012

Are you sure that specifying CSS selector is a way to go? You can end up with loads of possible config params. May be it’s better to think of specifying custom processing callbacks in hook_modal_paths()?

26 февраля, 2012

At the same time, css selector can be a really popular feature that still should be exposed to config. I was just trying to say that lots of advanced techniques will still require custom callbacks. Cheers!

26 февраля, 2012

Yeah, with the CSS selectors I want to satisfy the word "auto" in the CTools Automodals name ;-) I was using Popups API in Drupal 6 and CSS selectors worked amazingly well here. Actually I'm still using the Popups API on one intranet site. The selector is applied there even in the backend because page is rendered in a usual way and Popups API just picks the needed chunk from the rendered page and replace the original content. On the other hand I was thinking about the custom callbacks too to allow switch from "auto" to "manual" gearbox for some specific tasks. With the hook_modal_paths() the possiblities are wide. Hopefully Dave Reid - the module maintainer - will like my concept.

03 марта, 2012

The line:

$form['submit']['#weight'] = 100;

was causing my Drupal 7.12 install to throw an

Unsupported operand types error in forms.inc.

I think forms.inc expects the value to be provided in an array (haven't tested).

Anyhow, hopefully that might save someone out there some trouble.

01 апреля, 2012

That’s weird. ‘#weight’ is expected to be an integer. Are you sure you are not mistaken - may be it’s a different line of code which causes the trouble?

17 октября, 2012

Hi Anton Sidashin, my modal opens and i can process the form as you explained..
I have one problem though.. i just can find a way of including javascript on the dialog.. for example i have field email address.. the field has a default text "write your email". My requirement is when the field clicked ...the default text to dissapear..
simply,i want to use this jquery plugin to my input forms http://digitalbush.com/projects/watermark-input-plugin/
how can i do this?

19 апреля, 2012

You can attach js to dialog elements just like you do it for any form.
 http://api.drupal.org/api/drupal/developer!topics!forms_api_reference.html/7#attached

10 мая, 2012

Yes ... the design is clearly needed to be changed :)
The dark green color would fit perfectly xD

17 мая, 2012

Can I ask which patches you found necessary to apply?

01 августа, 2012

Sorry, I don’t remember since that were just example modules. Things are probably improved now and I hope most of fixes are already committed.

27 августа, 2012

Thanks for the great article.

I have been trying to do this for User profiles.
Have one form for Basic profile info such as name etc.
One for change Profile image.

The problem for me has been the profile image. I can get the modal with the form to save the profile image, but the current image doesn't show in the Modal. It shows the path to the existing image but not image itself.

I was wondering if you had the issue to show the current profile image in the modal.

27 августа, 2012

nope, never had an issue like this.

27 августа, 2012

> After some issue queues reading it was clear to me that ctools dialogs are not so mature as jquery.ui.dialogs
Can you point to these issues?

Thanks for the article.

05 сентября, 2012

You saved lot of time for me. Thanks for the article

19 сентября, 2012

Thankx for the dialogs, it helped me alot with my previous project.
Now am doing a project, where user profile has got lots of form elements as you explained in the Description above.
I want to be to able to separate it into small forms, form for photo, basic informations,Hobbies and so on.
Please share with me how you achieved this)
Thank you again.

20 ноября, 2012

How do this for Drupal 6?
Please, help me! It's very important to me!

19 июля, 2013

I have done the same updrade with my Drupal 7. It's very convenient and natural. I followed your easy-to-follow step very carefully as the coding requires attention-details.

01 сентября, 2013

Hey, thanks for writing this up. I've been looking at several blogs to find something like this. Do you by any chance have a contact email that you could share? I would like to contact you directly.

17 октября, 2013

First thing I get is:
"undefined variable: ajax"

Could you please explain?

15 апреля, 2014

I have recently come across a new driving app for android, its name is Real Race: Asphalt Road Racing and it's nearly like a racing sim with arcade mechanics. Driving around desert dunes and steppes, asphalt and lanes, it is like Need for Speed Carbon or Most Wanted, and as a matter of fact the latest edition, NFS: Rivals.
On a scale of 1 to 10 I'd give it a strong 8. List of features is almost overwhelming, and so is its replayability. Outrunning the leaderboards really entangles you and you are instantaneously pulled in. It trully reminded me of Asphalt Overdrive. Above all it's a great, challenging free app.
There are no chases with law enforcement, but perhaps it is for the best as multiplayer asphalt feature gives ample opportunity to demonstrate your unsurpassed underground drag racing prowess.
On-screen immense velocity gives an illusion of literally being there and racing with top drivers as seen in TV. Stress spikes are not far and few.
It has some really state-of-the-art Artificial Inteligence and I'm playing it already nine times in a row or so and it is still enjoyable and interesting. It's kinda cool to drive and beat other racers on 6 or so tracks. Game feels similar to race with GT cars like lamborghini or dodge viper.

21 октября, 2014

I have recently come across a new driving app for android, its name is Real Race: Asphalt Road Racing and it's nearly like a racing sim with arcade mechanics. Driving around desert dunes and steppes, asphalt and lanes, it is like Need for Speed Carbon or Most Wanted, and as a matter of fact the latest edition, NFS: Rivals.
On a scale of 1 to 10 I'd give it a strong 8. List of features is almost overwhelming, and so is its replayability. Outrunning the leaderboards really entangles you and you are instantaneously pulled in. It trully reminded me of Asphalt Overdrive. Above all it's a great, challenging free app.
There are no chases with law enforcement, but perhaps it is for the best as multiplayer asphalt feature gives ample opportunity to demonstrate your unsurpassed underground drag racing prowess.
On-screen immense velocity gives an illusion of literally being there and racing with top drivers as seen in TV. Stress spikes are not far and few.
It has some really state-of-the-art Artificial Inteligence and I'm playing it already nine times in a row or so and it is still enjoyable and interesting. It's kinda cool to drive and beat other racers on 6 or so tracks. Game feels similar to race with GT cars like lamborghini or dodge viper.

21 октября, 2014

On many occasions you could receive a light at the earliest possible moment.
Therefore, before you even punished by the police need for an MVD hearing.
Be it the attention you are charged with a blood test performed
on the drivers blood. Police officers do not know of a restricted
license application granted. When you need to do is to mommy makeover try
his best to get for such persons.

Feel free to surf to my web blog; read this article

22 октября, 2014

Отправить комментарий

Содержание этого поля является приватным и не предназначено к показу.
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Разрешённые HTML-теги: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Строки и параграфы переносятся автоматически.

Подробнее о форматировании текста

Note for potential spammers: all links in your comment will not be indexed by search engines.

Anton Sidashin

Anton Sidashin senior developer, Pixeljets co-founder

I'm a web developer specializing in PHP and Javascript, and Drupal, of course. I'm building Drupal projects since 2005, and I was working as full-time senior engineer in CS-Cart for a while, building revolutionary e-commerce software. In my free time, I enjoy playing soccer, building my body in gym, and playing guitar.

Drupal.org ID: restyler
Drupal association member