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.