Search code examples
javascriptdojojs-amd

Dojo loader not loading widget's dependencies used in callbacks?


I am having a real problem with Dojo's loader. I find it really hard to paste code here, as it's a problem that involves the application as a whole. I created a widget called LoginForm which starts like this:

define([
"dojo/_base/declare",
"app/globals",
"app/stores",
"dijit/form/Form",
"dijit/_WidgetBase",
"dijit/_TemplatedMixin",
"dijit/_WidgetsInTemplateMixin",
"dijit/layout/TabContainer",
"dijit/layout/StackContainer",
"dijit/layout/ContentPane",
"dijit/layout/BorderContainer",
"dijit/form/Button",
"app/widgets/ValidationPassword",
"app/widgets/ValidationEmail",
"app/widgets/AlertBar",
"app/widgets/StackFading",
"app/widgets/TabFading",
"dojo/_base/lang",
"app/widgets/BusyButton",
"dojo/_base/json",
"dojo/_base/fx",
"dojo/text!app/widgets/templates/LoginForm.html",

], function(
  declare,
 ...
 , json
 , baseFx
 , templateString
 ){
// Create the "login" pane, based on a normal ContentPane
return declare('app.LoginForm', [_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin ], {

  widgetsInTemplate: true,

  templateString:  templateString,

This is a simple widget using a template and, within the template, some sub-widgets as explained here.

Now... I am trying to create an object of Loginform type in an Ajax callback. However, the loader is failing me.

The MAIN screen of the app reads like this:

define([
"dojo/_base/declare",
"dijit/_WidgetBase",
"dijit/_TemplatedMixin",
"dijit/_WidgetsInTemplateMixin",
"app/widgets/Dashboard",
"app/globals",
"dijit/layout/BorderContainer",
"dijit/layout/StackContainer",
"dijit/layout/TabContainer",
"dijit/layout/ContentPane",
"app/widgets/SearchPage",

 ], function(
 declare
 , _WidgetBase
...
 , SearchPage


){
// Create the "login" pane, based on a normal ContentPane
return declare('AppMainScreen', [_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin ], {

...

That third widget, Dashboard, has just a button:

define([
"dojo/_base/declare",
"app/globals",
"app/defaultSubmit",
"app/stores",
"dijit/form/Form",
"dijit/_WidgetBase",
"dijit/_TemplatedMixin",
"dijit/_WidgetsInTemplateMixin",
"app/widgets/AlertBar",
"app/widgets/BusyButton",
"dojo/_base/json",
"dojo/domReady!",

], function(
  declare
 , g
 , ds
 , stores
 , Form
 , _WidgetBase
 , _TemplatedMixin
 , _WidgetsInTemplateMixin
 , AlertBar
 , BusyButton
 , json
){
// Create the "login" pane, based on a normal ContentPane
return declare('app.Dashboard', [_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin ], {


widgetsInTemplate: true,

templateString: '' +
    '<div>' +
    '  <div data-dojo-type="app.AlertBar" data-dojo-attach-point="alertBar"></div>' +
    '  <form data-dojo-type="dijit.form.Form" data-dojo-attach-point="form" method="POST"> ' +
    '    <input type="submit" data-dojo-attach-point="button" data-dojo-type="app.BusyButton" label="Create!" />' +
    '  </form>' +
    '</div>',

The onSubmit for that form is then linked to a function in "app/defaultSubmit", which reads:

define([
'app/globals',
'dijit/registry',
'dojo/query',
'dojo/aspect',
'dojo/_base/lang',  
'dojo/_base/json',
'dijit/Tooltip',
'dijit/Dialog',
'app/widgets/LoginForm',
],function(
g
, registry
, query
, aspect
, lang
, json
, Tooltip
, Dialog
, LoginForm
){

The problem is that LoginForm doesn't work, because LoginForm's constructor doesn't work: it says it cannot instantiate one of the dependent classes.

NOW, if I add this to the app's mainScreen's postCreate:

var loginForm = new LoginForm({});

Then, within the callback, LoginForm() magically works (!).

I know that I didn't post all of the code, but is there any magic I need to know about, in terms of how dependencies are handled in AMD? How can I make sure that a module is resolved within a callback...?

Bye,

Merc.


Solution

  • These remarks won't really fit in the comments section.

    I can't see the defect in your code, but here are some things to look at.

    Notes on the AMD loader

    Don't make assumptions about load order.

    require(["foo", "bar", function (foo, bar) {
      // use foo and bar
    });
    

    Either foo or bar might be loaded and invoked first as loading is asynchronous. The only thing that is guaranteed is that they will both be loaded when the callback function is invoked. The thing "foo" resolves to is whatever its define function returns; if this is undefined ensure function is returning a value.

    Avoid circular dependencies.

    //bar.js
    define(["foo"], function (foo) {
      return {};
    });
    
    //foo.js
    define(["bar"], function (bar) {
      return {};
    });
    

    The behaviour is defined (see documentation on modules) but can be difficult to manage. Check that you don't introduce these through transitive dependencies (A -> B -> C -> A.)

    Load transitive template dependencies before trying to parse templates.

    define(["dojo/_base/declare", "dijit/_WidgetBase", "dojo/parser",
            "dijit/form/Button" //need to load this even though it is
                                //not referenced in the function
           ],
            function(declare, _WidgetBase, parser) {
    
        // pretend this is some custom template format (e.g. DTL)
        var template = "<div data-dojo-type='dijit.form.Button'></div>";
    
        return declare("foo.Foo", [_WidgetBase, _TemplatedMixin], {
            postCreate: function() {
                this.domNode.innerHTML = template;
                parser.parse(this.domNode);
            }
        });
    
    });
    

    Use the _WidgetsInTemplateMixin with standard template widgets where appropriate.

    Also, be sure to read the 1.7 release notes for gotchas and caveats.

    Note: this code is untested.


    As an aside, a trailing comma on an array will cause an error in IE7.