ideacode accurately identified all our needs and helped us to successfully track the information in the format that was required. During the development and testing phase, ideacode quickly and effectively addressed our issues. The net result was a successful product that met our needs.

919-341-5170

help@ideacode.com

 

Blazing a trail to better facilities management.

The definitive guide to powerhouse Dojo dialog forms


Dojo 1.8 lets you create dynamic, template-driven forms in a dialog -- complete with validation and internationalization.  And it's easy, once you understand the architecture and requirements Dojo places on you.  This article shows you the code you need, how to organize it, and the important caveats to adding powerhouse dialogs to your Dojo application.


The new GIS interface in our AERES software has a lot of fancy form dialogs, the choose-your-own-adventure kind that reacts to one input by exposing/enabling/validating other inputs.

Dojo 1.8 has mixins to make these dialogs, and I thought it'd take only an hour or two per dialog.  Wrong.  Getting it to work the way I wanted on the first dialog took me two days.  Two, whole, days.  But... now that I've got it figured out, the rest should be easy.  If you find yourself needing templated, dynamic, validating, and international forms in a dojo 1.8 dialog, this article's for you.

Our goal in this article is to build a dialog like this:

Screen shot showing a dynamic, template-based dialog with validation

To create this, in a way that lets us add more forms easily and logically, we'll need to make four files:

  1. Your driver file, the entry point into the code.  Think "index.html".
  2. A resources file, which holds the strings to use for each language you support.
  3. A template file, which holds the HTML for your form and variables that'll be substituted with the language-appropriate strings.
  4. A custom widget, which will hold your form logic and glue the template and resources together.

We're going to look at these one at a time, line-by-line.  If you're in a rush, download the files and experiment or try it for yourself online.

The driver file ("index.html")

So this is the file that starts it all, the entry point.  It could be index.html or buried somewhere in your application.

<!DOCTYPE html>
<html lang='en'>
    <head>
        <meta charset='utf-8' />
        <title>The definitive Dojo 1.8 templated, dynamic, validating, internationalized form in a Dialog</title>
        <link href='http://ajax.googleapis.com/ajax/libs/dojo/1.8.0/dijit/themes/nihilo/nihilo.css' media='all' rel='stylesheet' type='text/css'>
        <script src='http://ajax.googleapis.com/ajax/libs/dojo/1.8.0/dojo/dojo.js'></script>
    </head>
    <body class='nihilo'>
        <script>
        require(
            { packages:[ { name:'lib', location:location.pathname.replace(/\/[^/]+$/, '') + '/lib' } ] },
            [
                'lib/TemplatedInternationalizedForm',
                'dojo/text!lib/templates/TemplatedInternationalizedForm.html',
                'dojo/i18n!lib/nls/resources',
                'dijit/Dialog',
                'dojo/domReady!'
            ],
            function (TemplatedInternationalizedForm, template, resources, Dialog) {
                new Dialog({
                    content:new TemplatedInternationalizedForm({templateString:template, resources:resources}),
                    title:resources.titleForDialog
                }, 'dialog').show();
            }
        );
        </script>
        <div id='dialog'></div>
    </body>
</html>

Lines 1-5, 8, 10, 28, 30-11 are your boilerplate HTML 5.  Nothing to see, move along.

Lines 6, 7, and 9 pull the relevant Dojo code from the Google CDN and arrange to use the Nihilo theme.

Lines 11-28 are where the magic begins to happen.  Line 12 configures the Dojo loader to pull packages prefixed by "lib/" from a sub-directory of this file's directory called "lib".  Imagine this file is at http://www.example.com/index.html.  Because line 14 begins with "lib/" and because we defined a package called "lib", dojo will look for http://www.example.com/lib/TemplatedInternationalizedForm.js.  Line 15 and 16 do the same thing: they are packages in "lib/", so dojo is going to find them for us.

Line 15 and 16 are a little different than the others, because they have that bang ("!") in them.  That signifies these are plug-ins, and they're doing some magic.  The dojo/text plugin on line 15 says "go fetch me the file that follows after the !".  The dojo/i18n plugin on line 16 says "go find me the file that follows after the !" (just like dojo/text), but then "parse it as a resources file".

The bottom line is that lines 14, 15, and 16 collectively give us access to the next three files we need: resources, template, and custom module.

Lines 17 and 18 load the dijit Dialog and wait until the browser has loaded the DOM.  Then the work begins.

Line 20 passes in the required modules from lines 14-18.  Note there is a 1:1 correspondence between names, like the "resources" variable matches up with the result of "dojo/i18n!lib/nls/resources".

Line 21 begins creating our dialog.

Line 22 sets the dialog content to an instance of our custom module.  Notice that we're handing our custom module two pieces of information: the template to use and the string translation resources.  Warning!  The "templateString" variable name is important.  The dojo mixin we'll use to get template requires that variable be named "templateString".  The other variables you can name as you please.

Line 23 sets the title of our dialog, but notice: the title comes from our resources!  The dojo/i18n plugin has already resolved the user's browser's locale and merged in the right string, so "titleForDialog" will be whatever is appropriate for the browser or your global default.  And on that note, let's look at the resources file.

The resources

This file defines the locale-specific strings you want to use.  The dojo/i18n plugin defines its structure and usage, so you should also read the dojo i18n reference guide.

define({
    root:{
        titleForDialog:'An awesome dialog title',
        labelForNameField:'Your name',
        labelForURLField:'Your website',
        labelForSubmitButton:'Ok',
        errorNotAURL:'Please enter a valid URL, beginning with http:// or https://',
        submitYourForm:'Congratulations, I like everything in your form.  I will submit it now.'
    }
});

Lines 1 and 10 wrap the definitions in a define.  This is just how it works.

Lines 2 and 9 wrap the root definitions, which you can think of as the default for this language.  Now this particular file is in a location that clues the dojo/i18n plugin that these are the global defaults.  The defaults for other languages are in sub-directories relative to this one, as explained in the dojo i18n reference guide.

Lines 3-8 map a variable name with the string to use for this language (again, this file is the global default language).  I like my variable names to be long-ish for these, so that they express the semantic intent of the string.  I believe that helps the translators when approaching a resource file, gives them more information about how the developer meant to use them -- which may be different than how the Ui designer decided to word it.

Anyway, that's a separate discussion.  Bottom line you can put whatever and however many variable name to string entries in this file as you want.  You can use these in your javascript (as we saw in the driver file above, with the dialog title) or you can use them in the template.  Which is now a good time to see.

The template

This file defines the HTML that will go into your form.  While generally I like to programmatically build my widgets, I find it much easier to write forms in HTML then have dojo parse them.

<form data-dojo-attach-point='containerNode'>
    <ol>
        <li>
            <label for='name'>${resources.labelForNameField}</label>
            <input id='name' name='name' type='text'
                data-dojo-type='dijit/form/ValidationTextBox' data-dojo-props='required:true'
                observer='reactor'
            />
        </li>
        <li data-dojo-attach-point='urlField' class='dojoFormValue'>
            <label for='url'>${resources.labelForURLField}</label>
            <input id='url' name='url' type='text'
                data-dojo-type='dijit/form/ValidationTextBox'
                data-dojo-props='regExp:dojox.validate.regexp.url,invalidMessage:"${resources.errorNotAURL}"'
            />
        </li>
        <li>
            <button id='submit' data-dojo-type='dijit/form/Button' name='submit' type='submit'>${resources.labelForSubmitButton}</button>
        </li>
    </ol>
</form>

So in this form, I choose to arrange my widgets with an ordered list.  You can use a table or div or whatever other containers suit you.  The important points are these.

The first line, line 1, must be the <form> element and the last line (21 in this example) must be the </form> element.  You must not have any other top-level elements -- a common mistake is apparently putting an HTML comment above the form.  That would make for two top-level elements, which you must not have.  So if you want a comment, stick it inside the form.

You must also have data-dojo-attach-point='containerNode' or all heck will break loose.  This fact is buried in the API documentation as an inscrutable note, so take heed.  Don't deviate, just use it.

Lines 4, 11, 14, and 18 substitute values into the template.  Looking very PHP like, you wrap a dollar brace brace ("${}") around the variable to substitute.  In this example, you'll see that we're pulling from the resources member variable we passed into the object in the driver file (two sections previous).

Lines 6, 13, and 18 declare the dijit type of our form elements via data-dojo-type: dijit/form/ValidationTextBox and dijit/form/Button.  These are dijit forms, so you must use dijit widgets, but beware!  These are not auto-loaded.  You will have to load these in the custom class, which I'll talk about soon.

Lines 6 and 14 pass the properties onto our dijit widgets via data-dojo-props.  Line 6 tells the Validation Text Box that the name field is required.  Line 14 gets a little fancier and instructs Validation Text Box to use a URL regular expression from the dojox validate module.  As with dijit widgets, the dojox.validate module is not auto-loaded, you will have to do that.

Line 10 does something unusual: it assigns a name to the whole list item.  Here's why: the logic I want in my form is that when name is given, show them the optional URL field and hide the URL field until then.  Our custom module will mixin a dojo form helper to show and hide elements and this is a convenient way to handle showing this for both the label and the input.  But since this is a DOM node, not a form element, we have to tell the manager to pay attention to it.  We do that by giving it a class of "dojoFormValue' and a name that we can reference with data-dojo-attach-point.

So far in this template file, we've covered templating proper, internationalization, and validation.  Finally, we get to the dynamic part on line 7.  For every form element that participates in dynamic response add an observer attribute, whose value is a method on your custom widget.  In this example, whenever the state changes on my name input the reactor() method will get called.  More on that soon, but two cautions before moving ahead.

First, you cannot declare the observer method in this template.  Normally a <script type='dojo/method'> would do it, but in templates <script> tags are not processed.  Second, you only need to put it on one form element with a given name.  Radio buttons, for example, have many instances with the same name="..." attribute.  Only one of these needs an observer attribute.

The custom widget

Finally, the last piece of the puzzle.  The custom widget is like the Force, surrounding and binding all of this together.

define(
    [
        'dojo/_base/declare',
        'dijit/form/Form', 'dijit/_WidgetsInTemplateMixin',
        'dojox/form/manager/_Mixin', 'dojox/form/manager/_NodeMixin', 'dojox/form/manager/_FormMixin', 'dojox/form/manager/_DisplayMixin',
        'dijit/form/ValidationTextBox', 'dojox/validate',
        'dijit/form/Button'
    ],
    function (
        declare,
        Form, WidgetsInTemplateMixin,
        FormMgrMixin, FormMgrNodeMixin, FormMgrFormMixin, FormMgrDisplayMixin,
        ValidationTextBox, Validate,
        Button
    ) {
        return declare([Form, WidgetsInTemplateMixin, FormMgrMixin, FormMgrNodeMixin, FormMgrFormMixin, FormMgrDisplayMixin], {
            templateString:null,
            resources:null,
            dialog:null,
            constructor:function (options) {
                declare.safeMixin(this, options);
                this.inherited(arguments)
            },
            postCreate:function () {
                this.inherited(arguments);
                this.hide(['urlField']);
            },
            reactor:function (value, field) {
                if ('name' == field && 0 < value.length) {
                    this.show(['urlField']);
                } else {
                    this.hide(['urlField']);
                }
                this.dialog && this.dialog.resize();
            },
            onSubmit:function () {
                this.inherited(arguments);
                this.validate();
                if (this.isValid()) {
                    alert(this.resources.submitYourForm);
                }
                return false;
            }
        });
    }
);

Lines 3-7 and 10-14 instruct dojo to load our dependencies and assign them to variables.  Let's talk about these dependencies in detail:

  • dijit/form/Form and dijit/_WidgetsInTemplateMixin are the base class and mixin we need to get the template and internationalization part of our form.  You don't need to include WidgetBase or TemplatedMixin.
  • dojox/form/manager/_Mixin gets us the base stuff for dynamic forms.  You must have this.
  • dojox/form/manager/_NodeMixin lets non-form elements participate in the dynamic form.  As per my requirements, I wanted to turn off a <li> under certain circumstances, and this mixin lets me do that.
  • dojox/form/manager/_FormMixin adds hooks to form submission.  Trust me, it's required because we're using a template surrounded by <form>.
  • dojox/form/manager/_DisplayMixin adds methods to show and hide "form" elements.  (And remember nodes are treated like form elements, because of the _NodeMixin.)  Since my requirement is to show/hide a field, I need this mixin.

There are other mixins you can leverage, too, based on your needs.

Lines 6 and 7 load the widgets we use in the template, because as I said, they're not auto-loaded.  Forget to do this and you'll get an exception raised.

Line 16 declares our custom modules pedigree.  It's based on Form, then mixes in templates and dynamicism.  Order matters, so mind it!

Lines 17 - 19 are reminders that these are variables we're expecting:

  • templateString is required by the template mixin (inherited via Form) to have templates.  Yes, you could build the domNode yourself, but the whole point of this exercise is to have templates separate from the code.
  • resources is the language-specific strings we'll substitute in, which are used in the templateString as ${resources.whatever}
  • dialog is the dialog in which the form exists, if any.  Strictly not required, but if present we need to do some magic.  Read on.

Lines 20 - 23 create our custom module and simply put passed in options into our object.

Lines 24 - 27 initialize our form.  Do whatever you want here to get the form into its intial state.  Here's a really important tip.  In postCreate(), call this.setValues({}) to set the values for your form.  When you use setValues(), all the dynamic functions built into your form will fire, so you don't have to do any extra work!  setValues takes a hash of field name to value.

Lines 28 - 35 are the "reactor" method marked as being the "observer" for the <input name="name"> in the template.  Every time the name input changes state, this method is called with two arguments: the value of the field and the name of the field changing.  The name of the field helps you when you have one observer handling multiple inputs.  Inside reactor, the code checks if the name field is longer than 0 and if so, shows the URL field.  If not, the URL field is hidden.  As mentioned earlier, "urlField" is actually a <li>.  The NodeMixin makes it easy to treat large sections of the DOM neatly: doing it form element by element would be tedious!

Note the use of this.dialog && this.dialog.resize().  When the dialog first fires up, it's centered and sized for the original content.  But as more fields come into view, the dialog size changes.  To keep it looking neat, we want that resized dialog to move to center.  That's what the resize method does for us, and since it applies only to dialogs, we first check if that's how we're invoked.

Finally, lines 36 - 43 react to the user submitting the form (clicking a button, pressing enter).  The first thing to do is call validate to check the fields, then we can react to the validity.  By returning false, we prevent submission back to the server.

Summary

We're done!

There's a lot going on here, but the devil is in the details.  So here are some summary reminders:

  • In your driver:
    • The templateString member variable must contain your template.
  • In your template:
    • The first and last lines of your template must be <form> and </form> and nothing else before or after them.
    • You must have data-dojo-attach-point='containerNode' in your <form> element.
    • There is no auto-loading in your template.  You must manually load all requirements.
    • Use the special observer attribute to bind a form element state change to a method in your custom widget.
    • Do not put the method in your template file with <script type='dojo/method'>, as it will be ignored.
    • Mark non-form elements that participate in the dynamic nature of the form with class='dojoFormValue' and name them with data-dojo-attach-point.
  • In your custom module:
    • Use this.setValues({field1:'whatever'}) to initialize your form, which will react to this and invoke all your dynamic behavior.
    • Ensure you call this.inherited(arguments) in overridden methods like constructor, postCreate, and onSubmit.
    • In onSubmit, return false to cancel submission -- useful for XHR submission.  Also, note that onSubmit() takes no arguments.

Keep these in mind and you'll have templated, dynamic, validating, and internationalized forms in dialogs in no time!

You can download these files for your own experimentation, or try it for yourself online.

If you're using Dojo 1.7 or earlier, head over to SitePen for how to create these kinds of dialogs in Dojo 1.7.

Trackback URL for this post:

http://ideacode.com/trackback/60

The best tutorial on the topic

This explanation gave a wonderful insight into the working of dojox/form/Manager. Many thanks for the effort.

Best Dojo dialog example

Hi

Congratulations...

I have searched all over the internet looking for a good example of how to create a custom dojo dialog, Now I found it.

There are a lot people theorizing on the internet about how make a custom widget on dojo, but not body hit the point as you do.

Thanks

Thanks for the feedback, Miguel!

Our contact form was busted and I didn't have an email address for you after you wrote. Did you get that store question answered? If not, contact us again -- we got that figured out.

This is indeed the definitive

This is indeed the definitive guide. It should be in the official documentation. Thanks so much for writing such a clear guide.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Syntax highlight code surrounded by the {syntaxhighlighter SPEC}...{/syntaxhighlighter} tags, where SPEC is a Syntaxhighlighter options string or "class="OPTIONS" title="the title".
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.