Why is YUI.add required when specifying YUI modules and if it is required how can non-YUI modules work?

StackOverflow https://stackoverflow.com/questions/18109852

  •  23-06-2022
  •  | 
  •  

Domanda

We use the YUI3 loader to manage loading our javascript and css files. As part of the bootstrap js code on each page, we have something like the following:

YUI({
  ...
  groups: {
     ... 
     myGroup: {
         modules: {
             "my-module": {
                 ...
                 path: "MyModule.js",
                 requires: [ "yui-base" ]
             },
         }
         ...
     }
  }
}).use("my-module", function (Y) {
    Y.MyModule.doStuff();
});

MyModule.js has something like the following:

YUI.add('my-module', function (Y) {
    Y.MyModule = function () {
        ...
        _validator: Y.Lang.isString
    };
}, '3.4.0', {
    requires: [ "yui-base" ]
});

YUI also claims here that the loader can be used with non-YUI3 "modules" given that they have their dependencies specified in the configuration. They give the following example module configuration for a yui2 group:

       yui2: {
           combine: true,
           base: 'http://yui.yahooapis.com/2.8.0r4/build/',
           comboBase: 'http://yui.yahooapis.com/combo?',
           root: '2.8.0r4/build/',
           modules:  { // one or more external modules that can be loaded along side of YUI
               yui2_yde: {
                   path: "yahoo-dom-event/yahoo-dom-event.js"
               },
               yui2_anim: {
                   path: "animation/animation.js",
                   requires: ['yui2_yde']
               }
           }
       }

This suggests that YUI is smart enough to load and run YUI2's animation.js only after yahoo-dom-event.js has loaded and run.

What I don't understand is, if this works for non-YUI modules, how come I have to wrap my own modules with YUI.add and the redundant requires list (since requires is also specified in the configuration)?

I tried dropping the add wrapper (I replaced it with (function (Y) { /* module content */ })(YUI);), but this lead to a js error on page load: Y.Lang is undefined. Thus, it seems that somehow without the wrapping add() call the script is getting executed before the base yui script where Y.Lang is defined. However, if that is the case, then won't this be a problem for non-YUI modules (which don't call YUI.add())?

È stato utile?

Soluzione

It's important to distinguish between custom modules that utilize YUI3 features (sandboxed Y.Lang, etc) and completely external code.

In the first case, the YUI.add() wrapper is always necessary, because the sandbox Y variable isn't available outside the module callback (the second argument to YUI.add()). The repetition of module configuration is unfortunately necessary in hand-written modules due to constraints within Y.Loader (where the combo-loading magic happens). Modules that employ YUI's build tools have the wrapper and metadata added automagically.

With completely external code, you only need to provide the fullpath config property, and YUI will do the right thing. Internally, YUI knows when a given <script> request finishes, and associates that success with the configured module name.

To simplify things, I'll be using YUI.applyConfig to demonstrate the config bits. Using that, you can create any number of YUI sandboxes (via YUI().use(...)) with the config mixed in, instead of repeating it all over the place.

YUI.applyConfig({
    "modules": {
        "leaflet": {
            "fullpath": "http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.js"
        },
        "my-leaflet-thing": {
            "path": "path/to/my-leaflet-thing.js",
            "requires": [
                "base-build",
                "node-base",
                "leaflet"
            ]
        }
    }
});

my-leaflet-thing.js looks something like this:

YUI.add("my-leaflet-thing", function (Y) {
    // a safe reference to the global "L" provided by leaflet.js
    var L = Y.config.global.L;

    Y.MyLeafletThing = Y.Base.create("myLeaflet", Y.Base, {
        initializer: function () {
            var id = this.get('node').get('id');
            var map = L.map(id);
            // etc
        }
    }, {
        ATTRS: {
            node: {
                getter: Y.one
            }
        }
    });

// third argument is a version number,
// but it doesn't affect anything right now
}, "1.0.0", {
    "requires": [
        "base-build",
        "node-base",
        "leaflet"
    ]
});

Given this setup, since this requires a non-asynchronous library, you can safely do this:

YUI().use("my-leaflet-thing", function (Y) {
    var instance = new Y.MyLeafletThing({
        "node": "#foo"
    });
});

Note: If an external file does dynamic loading of its own (e.g., async Google Maps API), YUI will only be aware of the initial request success, not the entire chain of files loaded. To address this, you'll need to use the querystring callback argument in the fullpath config, associated with some globally-exposed callback in the module that requires it.

In these cases, it's better to do an internal Y.use() (note the sandbox variable) to better encapsulate the required globals.

Config:

YUI.applyConfig({
    "modules": {
        "google-maps-api": {
            "fullpath": "http://maps.googleapis.com/maps/api/js" +
                            "?v=3&sensor=false&callback=initGMapsAPI"
        },
        "my-google-map-thing": {
            "path": "path/to/my-google-map-thing.js",
            "requires": [
                "base-build",
                "node-base"
            ]
        }
    }
});

my-google-map-thing.js:

YUI.add("my-google-map-thing", function (Y) {
    // publish a custom event that will be fired from the global callback
    Y.publish('gmaps:ready', {
        emitFacade: true,
        fireOnce: true
    });

    // private sentinel to determine if Y.use() has been called
    var isUsed = false;

    // expose global function that matches "callback" parameter value
    Y.config.global.initGMapsAPI = function () {
        // Y.config.global.google is now available
        Y.fire('gmaps:ready');
    };

    Y.MyGoogleMapThing = Y.Base.create("myGoogleMap", Y.Base, {
        initializer: function () {
            Y.on('gmaps:ready', this.render, this);
            if (!isUsed) {
                isUsed = true;
                Y.use("google-maps-api");
            }
        },
        render: function () {
            // safe reference to global "google"
            var google = Y.config.global.google;
            var id = this.get('node').get('id');
            var map = new google.maps.Map(id, {
                // ...
            });
            // etc
        }
    }, {
        ATTRS: {
            node: {
                getter: Y.one
            }
        }
    });

}, "1.0.0", {
    "requires": [
        "base-build",
        "node-base"
    ]
});

To sum up: YUI.add() is only necessary when writing modules that depend on YUI3 sandboxed resources. Loading external code, as long as it is all synchronous, is as simple as employing the fullpath config property. Asynchronous external loading is a bit hairier, but still possible.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top