0

Admittedly I am still a novice at Dojo, and I have always been weak at Javascript, so excuse and sloppy code or language here.

I am using Dojo 1.7.1 with Spring Roo 1.2.1RELEASE. I am loading Dojo through CDM from Google.

I some time ago I created a custom image thumbnail viewer to use on my site, which I include by adding a module to my djConfig in Spring Roo's load-scripts.tagx, which gets ran on each page load. The thumbnail Widget does not follow the AMD pattern, because at the time I couldn't get it to work right.

Here is the djConfig:

  <script type="text/javascript">
    var djConfig = {
            parseOnLoad: false, 
            isDebug: false, 
            locale: '${fn:toLowerCase(userLocale)}',
            modulePaths:    {
                "message": "${message_dojo_url}",
                "img.ArtThumbnailWidget": "${artThumbnailWidget_dojo_url}",
                "img.ArtTableWidget": "${artTableWidget_dojo_url}",
            },
        };
  </script>

Here is the JS for the thumbailer:

// img.ArtThumbnailWidget
dojo.provide("img.ArtThumbnailWidget");

dojo.require("dojo._base.declare");
dojo.require("dojo.parser");
dojo.require("dojo.ready");
dojo.require("dijit._WidgetBase");
dojo.require("dijit._TemplatedMixin");
dojo.require("dojox.image.LightboxNano");

// Create the widget
require([
         "dojo/_base/declare",
         "dojo/parser",
         "dojo/ready",
         "dijit/_WidgetBase",
         "dijit/_TemplatedMixin",
         "dojo/dom",
         "dojo/dom-construct",
         "dojo/on",
         "dojo/text!img/ArtThumbnailWidget/templates/ArtThumbnailWidget.html",
         "dojox/image/LightboxNano",
         "dojo/domReady!"
    ], function(declare, parser, ready, _WidgetBase, _TemplatedMixin, dom, domConstruct, on, template) {

    dojo.declare("img.ArtThumbnailWidget",[dijit._WidgetBase, dijit._TemplatedMixin], {
        /* Our properties will go here */

        // Art JSON object, default is null
        art: null,

        // Viewer ID (the username of the person looking at this image), which will default to null
        viewerId: null,

        // maxThumbnailSize is how large of an image to return for the thumbnail.  The back-end will resize the thumbnail accordingly
        maxThumbnailSize: 100,

        // maxImageSize is how large of an image to return for the LightboxNano.  The back-end will resize the image accordingly
        maxImageSize: 500,

        // Our template - important!
        templateString: template,

        // A class to be applied to the root node in our template
        baseClass: "artThumbnailWidget",

        // Specifies there are widgets in the template itself that need to be rendered as well
        widgetsInTemplate: true,

        // Competition-related vars
        competitionUrlBase: null,
        competitionButtonIconUrl: null,

        /* This is called once the DOM structure is ready, but before anything is shown */
        postCreate: function() {
            // Get a DOM node reference for the root of our widget
            var domNode = this.domNode;

            // Run any parent postCreate processes - can be done at any point
            this.inherited(arguments);

            if(this.art!=null && this.viewerId!=null && this.art.owner.name == this.viewerId) {     // If the view is the owner, add the toolbar
                // TODO: We need to clean this up, make it "prettier", and make the URLs more generic
                var toolbarNode = domConstruct.create("div", {}, this.containerNode);

                if(this.competitionUrlBase!=null) {
                    var url = this.competitionUrlBase;
                    if(url.indexOf('?')<0) {    // URL does not have a '?'
                        url = url+"?";
                    } else {    // URL has a '?', so we need to tack on and additional '&'
                        url = url+"&";
                    }
                    url = url+"username="+this.art.owner.name+"&artPieceId="+this.art.id;

                    var compButtonNode = domConstruct.create("a",
                            {
                                href: url,
                            },toolbarNode);
                    var compButtonImg = domConstruct.create("img",
                            {
                                src: this.competitionButtonIconUrl,
                                width: this.maxThumbnailSize/4,
                                height: this.maxThumbnailSize/4,
                            },compButtonNode);
                }
            }
        },

        /* This private method is used to re-set the node values when something changes  */
        _resetNodeValues: function() {
            if(this.art !=null) {
                // Using our thumbnailNode attach point, set its src value
                this.thumbnailNode.src = this.art.url+"?maxSize="+this.maxThumbnailSize;
                this.thumbnailNode.alt = this.art.title;
                this.thumbnailNode.width = this.maxThumbnailSize;
                this.thumbnailNode.height = this.maxThumbnailSize;

                // Now setup the link for the LightboxNano
                var lightboxNano = new dojox.image.LightboxNano({
                    href: this.art.url+"?maxSize="+this.maxImageSize,
                },this.thumbnailNode);
            }
        },

        /* This is called anytime the "art" attribute is set.  Consider is a "setter" method */
        _setArtAttr: function(av) {
            if (av != null) {
                // Save it on our widget instance - note that
                // we're using _set, to support anyone using
                // our widget's Watch functionality, to watch values change
                this._set("art", av);

                this._resetNodeValues();
            } else {
                // We could have a default here...would be an error, since we
                // shouldn't be calling this without an art object
            }
        },

        _setMaxThumbnailSizeAttr: function(ms) {
            // Save it on our widget instance - note that
            // we're using _set, to support anyone using
            // our widget's Watch functionality, to watch values change
            this._set("maxThumbnailSize", ms);

            this._resetNodeValues();
        },

        _setMaxImageSizeAttr: function(ms) {
            // Save it on our widget instance - note that
            // we're using _set, to support anyone using
            // our widget's Watch functionality, to watch values change
            this._set("maxImageSize",ms);

            this._resetNodeValues();
        }
    });     // End of the widget

});

Now I am trying to add another custom component, a table of the above thumbnails. The new code needs to reference this old Widget, but I can't seem to get it to work.

The new Table widget (so far):

// in "img/ArtTableWidget"
define([
        "dojo/_base/declare", "dojo/parser", 
        "dijit/_WidgetBase", "dijit/_TemplatedMixin", 
        "dojo/dom", "dojo/dom-construct","img/ArtThumbnailWidget",
        "dojo/text!./ArtTableWidget/templates/ArtTableWidget.html"], 
    function(declare,parser,_WidgetBase,_TemplatedMixin, dom, domConstruct, ArtThumbnailWidget, template) {
        return declare("img.ArtTableWidget",[dijit._WidgetBase,dijit._TemplatedMixin], {
            // Default values for the ArtTable

            // The base URL to use for downloading the photos
            artUrlBase: null,

            // The base URL used for submitting competitions and the button URL
            newCompetitionUrlBase: null,
            newCompetitionButtonIconUrl: null,

            // Indicates what params on the URL are used to control page 
            // and size.  These will be appended to the URL as needed.
            pageNumberParameterName: "page",
            pageSizeNumberParameterName: "size",

            // Holds the page and size
            page: 1,
            size: 15,
            totalPages: 0,

            columns: 3,

            // Holds the current list of "art"
            artList: [],

            // The userid currently viewing
            viewerId: null,

            // Our HTML template
            templateString: template,

            baseClass: "artTableWidget",

            // Specifies there are widgets in the template itself that need to be rendered as well
            widgetsInTemplate: true,

            // Functions //

            postCreate: function() {
                this._load(this.page);
            },

            // Loads the given page
            _load: function(pageToLoad) {
                if(pageToLoad==null) {
                    pageToLoad=1;
                }

                // Generate the URL
                genUrl = this.artUrlBase.indexOf("?")>=0 ? this.artUrlBase+"&page="+pageToLoad+"&size="+this.size : this.artUrlBase+"?page="+pageToLoad+"&size="+this.size;

                // Make the call to the backend
                dojo.xhrGet({
                    url: genUrl,
                    handleAs: "json",
                    tableWidget: this,
                    load: function(data,ioArgs) {
                        this.tableWidget.page = data.page;
                        this.tableWidget.totalPages = data.totalPages;
                        this.tableWidget.artList = data.data;

                        this.tableWidget._updateTable();
                    }
                });
            },

            _updateTable: function() {
                // Fix the buttons at the bottom

                // Clear the artTable
                domConstruct.empty(this.artTable);

                // Loop through the art and build the rows
                tableRow = tableRow = domConstruct.create("tr",{},this.artTable);
                dojo.forEach(this.artList,function(art,index) {
                    if(index % columns == 0) {
                        tableRow = domConstruct.create("tr",{},this.artTable);
                    }
                    tableColumn = domConstruct.create("td",{style: { marginLeft: "auto", marginRight: "auto" }},tableRow);
                    var tnNode = new ArtThumbnailWidget({
                        art: art,
                        viewerId: this.viewerId,
                        competitionUrlBase: this.newCompetitionUrlBase,
                        competitionButtonIconUrl: this.newCompetitionButtonIconUrl,
                    });
                    tnNode.placeAt(tableColumn);
                });
            }
        });
});

However, when I run the new component in Chrome, I get a generic Error in dojo.js.uncompressed.js line 1716. The message of the error is "multipleDefine", and the attached Object looks to be my ArtTableWidget. I noticed in digging though this object that the "deps" member, which appears to be an Array of all the dependencies defined in the define() at the top, includes the img/ArtThumbnailWidget in it, but the "pack" member is undefined. I am guessing that it's just not loading my module or something.

The error (sorry if Copy/Paste doesn't look right) is:

dojo.js.uncompressed.js:1716
Error
  arguments: undefined
  get stack: function getter() { [native code] }
  info: Object
  cacheId: 0
  cjs: Object
  def: function (declare,parser,_WidgetBase,_TemplatedMixin, dom, domConstruct, ArtThumbnailWidget, template) {
  deps: Array[8]
    0: Object
    1: Object
    2: Object
    3: Object
    4: Object
    5: Object
    6: Object
      cacheId: 0
      def: 0
      executed: 4
      injected: 2
      isAmd: false
      isXd: null
      mid: "img/ArtThumbnailWidget"
      pack: undefined
      pid: ""
      result: Object
      url: "/ArtSite/resources/img/ArtThumbnailWidget.js"
      __proto__: Object
    7: Object
    length: 8
    __proto__: Array[0]
    executed: 0
    injected: 2
    isAmd: false
    isXd: null
    mid: "img/ArtTableWidget"
    node: HTMLScriptElement
    pack: undefined
    pid: ""
    require: function (a1, a2, a3){
    result: Object
    url: "/ArtSite/resources/img/ArtTableWidget.js"
    __proto__: Object
  message: "multipleDefine"
  set stack: function setter() { [native code] }
  src: "dojoLoader"
  type: undefined
  __proto__: ErrorPrototype
  dojo.js.uncompressed.js:1719src: dojoLoader
  dojo.js.uncompressed.js:1719info: 
  Object
  dojo.js.uncompressed.js:1721.

I need some help getting back on the right track here.

EDIT 1 I updated all of the modules using the information in BuffaloBuffalo's reply, except instead of a "path" in the dojoConfig I used the following:

    <script type="text/javascript">
    var djConfig = {
            parseOnLoad: false, 
            isDebug: false, 
            locale: '${fn:toLowerCase(userLocale)}',
            packages: [
                 { name: "message", location: "${message_dojo_module_base_url}" },
                 { name: "img", location: "${img_dojo_module_base_url}" }
            ]
        };
  </script>

It seems to find the .js files, but not the templates loaded there-in using dojo/text. I tried doing "./path/Template.html" and "/module/path/Template.html", but the first seems to try to resolve the URL through the CDN (Google APIs site linked above), the latter seems to want a fully qualified path. I shutter to put a full path in as it seems like a dirty way to do it. I also tried adding a path to the dojoConfig as such:

        paths: [
             { "message" : "${message_dojo_module_base_url}" }
        ]

but that did not seem to help at all, causing some really nasty errors in Chrome's JS console.

Doesn't dojo/text use the modules, if I am reading correctly here?

CodeChimp
  • 8,016
  • 5
  • 41
  • 79

2 Answers2

1

It's hard to tell what the exact issue is, but a couple of things that jump out at me:

The djConfig object should be named dojoConfig if you are using 1.7 (dojoConfig still works, but might as well update it).

The modulePaths property should be updated to be named path. If img.ArtThumbnailWidget and img.ArtTableWidget reside in a common directory you can just use something like:

var dojoConfig = {
            parseOnLoad: false, 
            isDebug: false, 
            locale: '${fn:toLowerCase(userLocale)}',
            paths:    {
                "message": "${message_dojo_url}",
                "img": "${art_module_url}"
            }
        };

The second thing is the mixed legacy/amd loader styles in img.ArtThumbnailWidget. You are 99% of the way there with the AMD style. All you need to do is

  1. Remove dojo.provide and dojo.requires
  2. update require([],function(){..}); to be define([],function(){..});
  3. Update references in the declare to use the local variables rather than the globals:

    //ArtThumbnailWidget
    define('img/ArtThumbnailWidget', [
        "dojo/_base/declare",
        "dojo/parser",
        "dojo/ready",
        "dijit/_WidgetBase",
        "dijit/_TemplatedMixin",
        "dojo/dom",
        "dojo/dom-construct",
        "dojo/on",
        "dojo/text!img/ArtThumbnailWidget/templates/ArtThumbnailWidget.html",
        "dojox/image/LightboxNano",
        "dojo/domReady!"
        ], function (declare, parser, ready, _WidgetBase, _TemplatedMixin, dom, domConstruct, on, template) {
    
      return declare("img.ArtThumbnailWidget", [_WidgetBase, _TemplatedMixin], {
        /* Our properties will go here */
    
        // Art JSON object, default is null
        art: null,
    
        // Viewer ID (the username of the person looking at this image), which will default to null
        viewerId: null,
    
        // maxThumbnailSize is how large of an image to return for the thumbnail.  The back-end will resize the thumbnail accordingly
        maxThumbnailSize: 100,
    
        // maxImageSize is how large of an image to return for the LightboxNano.  The back-end will resize the image accordingly
        maxImageSize: 500,
    
        // Our template - important!
        templateString: template,
    
        // A class to be applied to the root node in our template
        baseClass: "artThumbnailWidget",
    
        // Specifies there are widgets in the template itself that need to be rendered as well
        widgetsInTemplate: true,
    
        // Competition-related vars
        competitionUrlBase: null,
        competitionButtonIconUrl: null,
    
        /* This is called once the DOM structure is ready, but before anything is shown */
        postCreate: function () {
          // Get a DOM node reference for the root of our widget
          var domNode = this.domNode;
    
          // Run any parent postCreate processes - can be done at any point
          this.inherited(arguments);
    
          if (this.art != null && this.viewerId != null && this.art.owner.name == this.viewerId) { // If the view is the owner, add the toolbar
            // TODO: We need to clean this up, make it "prettier", and make the URLs more generic
            var toolbarNode = domConstruct.create("div", {}, this.containerNode);
    
            if (this.competitionUrlBase != null) {
              var url = this.competitionUrlBase;
              if (url.indexOf('?') < 0) { // URL does not have a '?'
                url = url + "?";
              } else { // URL has a '?', so we need to tack on and additional '&'
                url = url + "&";
              }
              url = url + "username=" + this.art.owner.name + "&artPieceId=" + this.art.id;
    
              var compButtonNode = domConstruct.create("a", {
                href: url,
              }, toolbarNode);
              var compButtonImg = domConstruct.create("img", {
                src: this.competitionButtonIconUrl,
                width: this.maxThumbnailSize / 4,
                height: this.maxThumbnailSize / 4,
              }, compButtonNode);
            }
          }
        },
    
        /* This private method is used to re-set the node values when something changes  */
        _resetNodeValues: function () {
          if (this.art != null) {
            // Using our thumbnailNode attach point, set its src value
            this.thumbnailNode.src = this.art.url + "?maxSize=" + this.maxThumbnailSize;
            this.thumbnailNode.alt = this.art.title;
            this.thumbnailNode.width = this.maxThumbnailSize;
            this.thumbnailNode.height = this.maxThumbnailSize;
    
            // Now setup the link for the LightboxNano
            var lightboxNano = new LightboxNano({
              href: this.art.url + "?maxSize=" + this.maxImageSize,
            }, this.thumbnailNode);
          }
        },
    
        /* This is called anytime the "art" attribute is set.  Consider is a "setter" method */
        _setArtAttr: function (av) {
          if (av != null) {
            // Save it on our widget instance - note that
            // we're using _set, to support anyone using
            // our widget's Watch functionality, to watch values change
            this._set("art", av);
    
            this._resetNodeValues();
          } else {
            // We could have a default here...would be an error, since we
            // shouldn't be calling this without an art object
          }
        },
    
        _setMaxThumbnailSizeAttr: function (ms) {
          // Save it on our widget instance - note that
          // we're using _set, to support anyone using
          // our widget's Watch functionality, to watch values change
          this._set("maxThumbnailSize", ms);
    
          this._resetNodeValues();
        },
    
        _setMaxImageSizeAttr: function (ms) {
          // Save it on our widget instance - note that
          // we're using _set, to support anyone using
          // our widget's Watch functionality, to watch values change
          this._set("maxImageSize", ms);
    
          this._resetNodeValues();
        }
      }); // End of the widget
    });
    

I suspect that the combination of legacy style with amd style in ArtThumbnailWidget is what is confusing ArtTableWidget.

BuffaloBuffalo
  • 7,703
  • 4
  • 28
  • 29
  • djConfig is the old name. dojoConfig is the new name. See http://dojotoolkit.org/documentation/tutorials/1.7/dojo_config/. – Royston Shufflebotham May 09 '12 at 16:02
  • I looked at that exact page when typing up the answer. Must have gotten switched up when writing it up since the code snippet is correct. Updated my answer to be consistent. – BuffaloBuffalo May 09 '12 at 16:22
  • Thanks BuffaloBuffalo. I took your advice, altered the custom components I had, and have now at least moved on to another problem. I am getting a similar error as above, but now in dojo/parser from the CDN. Also, it can't seem to find my local template files when I use the dojo/text! plugin...its trying to resolve "./" to the CDN url. I tried tacking on the module name in front instead, but it seems to not use the modules at all. I am still trying some things, though, and will report back. – CodeChimp May 10 '12 at 15:17
  • Having never used a CDN with custom local widgets, there may be an issue with the dojo/text!./some/path/to/template. Take a look at http://dojotoolkit.org/documentation/tutorials/1.7/cdn/ particularly the `Caveats` section. It appears using the dojo/text plugin with a cdn and unbuilt custom widgets doesn't work well together. – BuffaloBuffalo May 10 '12 at 15:40
0

So, yes, there's loads of things that could be wrong here. Can you get a smaller example to work?

  1. It's definitely worth checking the 'Network' tab of your browser developer tools to see what it's trying to load. That usually helps with module resolution. Running in sync (which I think you are), rather than async mode also helps with diagnostics there.
  2. Should you be registering img/ArtThumbnailWidget in your modulePaths instead of img.ArtThumbnailWidget?
  3. Longer term, you might want to register packages rather than module-by-module...
  4. You should be able to refer to dojo/text!./templates/ArtThumbnailWidget.html instead of dojo/text!img/ArtThumbnailWidget/templates/ArtThumbnailWidget.html in your widget, but that'll only become relevant once you've got the widget loading. (The require for your module is processed relative to your module.)