Chapter 1. Loading Modules

Consider the humble <script> element. Introduced in 1995, it is still the gateway for injecting JavaScript into the browser. Unfortunately, if you want to build sophisticated applications, <script> shows its age:

  • <script> conflates the concepts of loading code and executing code. Programmers need fine-grained control over both phases.

  • <script> is synchronous, blocking the browser’s flow/paint cycle until the entire script downloads. This is why performance guides currently recommend moving <script> to the bottom of the page. The good news is that HTML now provides the async and defer attributes, so this issue might improve over time.

  • <script> has a shared global context with no formal namespacing or security built in. This is bad enough when you’re simply trying to protect your own code from your own mistakes, but becomes disastrous when your code must run alongside an unknown number of third-party scripts.

  • <script> has no information about its relationships with other <script> elements. A script might require another script as a dependency, but there is no way to express this. If <script> elements are on the page in the wrong order, the application fails.

The root of the problem is that unlike nearly every programming environment on the planet, JavaScript in the browser has no built-in concept of modules (defined in Loading Rollups and Modules). For small scripts, this is not necessarily a big deal. But small scripts have a way of growing into full-fledged applications.

To protect code from interference, many JavaScript libraries use a global object to contain all the library’s methods. For example, the hypothetical “Code Ninja” library might instantiate a global object named NINJA that supplies methods such as NINJA.throwShuriken(). Here, NINJA serves as a kind of namespace. This is a reasonable first line of defense.

YUI 3 takes things one step further. There is a global YUI object, but you work with this object “inside out.” Instead of using YUI just as a namespace, you call YUI().use() and then write all of your code inside a callback function nested inside use() itself. Within this scope is a private instance of the library named Y, which provides access to YUI methods such as Y.one() and objects such as Y.AutoComplete.

The disadvantage of YUI 3’s approach is that at first glance, it looks profoundly weird.

The advantages of YUI 3’s approach are:

  • YUI can decouple loading into registration and execution phases. YUI.add() registers code as modules with the YUI global object, to be loaded on demand. YUI().use() provides access to those modules in a safe sandbox.

  • YUI can load modules synchronously or asynchronously, since registration is now a separate phase from execution.

  • Other than a few static methods, YUI avoids using the shared global context. The Y instance that carries the API is private, impossible to overwrite from outside the sandbox.

  • YUI supports real dependency logic. When you register modules with YUI.add(), you can include metadata about other modules, CSS resources, and more. YUI().use() uses this information to build a dependency tree, fetching modules that are needed to complete the tree and skipping modules that are already present. YUI can even load modules conditionally based on browser capabilities. This frees you up to write code optimized for different environments, enabling you to support older, less capable browsers without serving unnecessary code to modern browsers.

Work on YUI’s module and loader system began in the middle of 2007, and the system was revamped for the release of YUI 3 in 2009. In the years since, JavaScript modules have quite rightfully become a hot topic. Server-side JavaScript environments now provide native support for the CommonJS module format. The Dojo toolkit has adopted AMD modules as its native format. Future versions of the ECMAScript standard are likely to bake support for modules into JavaScript’s core.

As mentioned in the Preface, there are many great JavaScript libraries available, each bringing its own philosophy and strengths. If you are looking for a single feature that captures YUI’s design goals, the module system is an excellent place to start. The module system prioritizes code safety and encapsulation. It has intelligent defaults, but it also grants you a tremendous amount of fine-grained control. It works well for small page effects, but it really shines when you’re assembling larger applications. You will see these principles expressed time and time again throughout the library.

Because the module and loader system is one of YUI’s signature features, this chapter is extensive. If you are just starting out with YUI, you can get away with reading just the first or second recipe, but be sure to return later to learn how to load modules optimally and how to package your own code into modules for later reuse.

Note

Most of the examples in this chapter make some visible change to the page in order to prove that the code works. The typical example uses Y.one("#demo") to grab the <div> with an id of demo, followed by setHTML() to change the <div>’s contents. If you haven’t seen YUI’s DOM manipulation API in action yet, please peek ahead at Recipes and .

Loading Rollups and Modules defines the canonical way to load YUI onto the page. This is the most important recipe in the entire book.

Loading SimpleYUI describes SimpleYUI, a convenient bundle of DOM manipulation, event façades, UI effects, and Ajax. Using SimpleYUI makes loading YUI more like loading other, more monolithic JavaScript libraries. This is a good alternative place to start if Loading Rollups and Modules is making your head spin.

Identifying and Loading Individual Modules explains the concept of loading individual YUI modules, rather than larger rollups. For production-quality code, you can improve performance by identifying and loading only the modules you really need.

Loading a Different Default Skin introduces the YUI configuration object, which is important for defining your own modules and for gaining fine-grained control over the YUI Loader.

Recipes and describe loading different categories of modules. Loading Gallery Modules explains how to load third-party modules from the YUI gallery, and Loading a YUI 2 Widget explains how to incorporate legacy YUI 2 widgets as YUI 3 modules.

Loading Locally Hosted Builds explains how to load the YUI core modules from your own servers rather than Yahoo! edge servers. You should strongly consider doing this if you are dealing with private user data over SSL, as loading third-party JavaScript from servers outside your control breaks the SSL security model.

Recipes , , , and take you step-by-step through the process of creating your own modules. After Loading Rollups and Modules, these four recipes are the ones that every serious YUI developer should know by heart. Understanding how to create modules is vital for being able to reuse your code effectively.

Reusing a YUI Configuration introduces the YUI_config object, which makes it easier to share complex YUI configurations between pages and sites.

Defining Your Own Rollups demonstrates how to create your own custom rollups, similar to core rollups such as node and io.

Loading jQuery as a YUI Module explains how to load jQuery and other third-party libraries into the YUI sandbox as if they were YUI modules. The YUI Loader and module system are flexible enough to wrap and asynchronously load just about anything you might want to use alongside YUI.

The next six recipes discuss more advanced loading scenarios. Loading Modules Based on Browser Capabilities covers the concept of conditional loading, where YUI fetches a module only if a browser capability test passes. The YUI core libraries use this powerful technique to patch up old browsers without penalizing modern ones. Monkeypatching YUI is a variation of Loading Modules Based on Browser Capabilities where instead of using conditional loading to patch old browsers, you use it to patch YUI itself.

Recipes and explain how to load modules in response to user actions, or even in anticipation of user actions. The ability to fetch additional modules after the initial page load provides you with great control over the perceived performance of your application.

Binding a YUI Instance to an iframe explains how to load YUI into an iframe while still maintaining control via the YUI instance in the parent document.

Finally, Implementing Static Loading discusses static loading. By default, YUI modules load asynchronously. Static loading is an advanced technique that trades flexibility and developer convenience for extra performance.

Loading Rollups and Modules

Problem

You want to load YUI on the page and run some code.

Solution

Load the YUI seed file, yui-min.js. Then call YUI().use(), passing in the name of a module or rollup you want to load, followed by an anonymous callback function that contains some code that exercises those modules.

Within the callback function, the Y object provides the tailored YUI API you just requested. Technically, you can name this object anything you like, but you should stick with the Y convention except for rare circumstances, such as Binding a YUI Instance to an iframe.

Example 1-1 loads the YUI Node API, then uses that API to get a reference to the <div> with an id of demo and set its content. For more information about how to select and modify node instances, refer to Chapter 2.

Example 1-1. Loading the YUI Node API

<!DOCTYPE html>
<title>Loading the YUI Node API</title>

<div id="demo"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI().use('node', function (Y) {
    Y.one('#demo').setHTML('Whoa.');
});
</script>

Note

In YUI, you do not need to litter your pages with dozens of <script> elements. The Loader is specifically designed to kill this antipattern. As a corollary, you should never fetch the YUI seed file more than once.

Discussion

YUI().use() supports loading both modules and rollups.

A module in YUI is a named collection of reusable code. To learn how to create your own modules, start with Creating Your Own Modules and related recipes.

A rollup is a kind of “supermodule” that represents multiple smaller modules. For example, node is a rollup that pulls in node-base, node-style, and several other modules for manipulating the DOM. Rollups exist for convenience, although sometimes it pays to be more selective and load individual modules, as described in Identifying and Loading Individual Modules.

But how does this even work? The line:

YUI().use('foo', function (Y) {...});

is pretty mystifying. To break this down step-by-step:

The first <script> element in Example 1-1 loads the YUI seed file, which defines the YUI global object. YUI is not just a namespace object; it is a module registry system. It contains just enough code to bootstrap your way to the rest of the library: some critical YUI utility functions, the Loader code that loads scripts onto the page, and Loader metadata that describes the core YUI modules and their dependencies.

The second <script> element calls YUI().use(). This call has two stages:

  1. Calling YUI() creates a new YUI instance. A YUI instance is a host object for assembling a customized YUI API. The instance starts out fairly bare bones—it does not yet provide APIs for doing things like DOM manipulation or Ajax calls.

  2. Calling use() then augments that instance with additional methods. use() takes one or more string parameters representing the names of modules and rollups to load, followed by a callback function (more on that a little later). Somewhat simplified, the use() method works in the following manner:

    1. The use() method determines which modules it actually needs to fetch. It calculates dependencies and builds a list of modules to load, excluding any modules already loaded and registered with the global YUI object.

    2. After resolving dependencies, use() constructs a “combo load” URL, and the Loader retrieves all the missing modules from Yahoo’s fast edge servers with a single HTTP request. This happens asynchronously so as not to block the UI thread of the browser.

    3. When use() finishes loading modules, it decorates the YUI instance with the complete API you requested.

    4. Finally, use() executes the callback function, passing in the YUI instance as the Y argument. Within the callback function, the Y object is a private handle to your own customized instance of the YUI library.

In other words, a YUI instance starts out small and relies on use() to carefully build up the API you requested. YUI().use() automatically handles dependencies and tailors its downloads for the browser you’re running in. This is already a huge advantage over downloading libraries as giant monolithic blocks of code.

The use() callback function is referred to as the “YUI sandbox.” It encapsulates all your code into a private scope, making it impossible for other scripts on the page to accidentally clobber one of your variables or functions. In fact, if you want to run multiple applications on the same page, you can even create multiple independent sandboxes. Once any sandbox loads a module, other sandboxes can use that module without interference and without having to fetch the code again.

Keep in mind that any code you write directly in a use() callback function is not actually a module itself, and is therefore not reusable. A use() callback should contain only the code required to wire modules into that particular page. Any code that might be reusable, you should bundle into a custom module using YUI.add(). For more information, refer to Creating Your Own Modules.

To improve performance, by default YUI loads the minified version of each module. The minified version has been run through YUI Compressor, a utility that shrinks the file size of each module by stripping out whitespace and comments, shortening variable names, and performing various other optimizations described in Minifying Your Code.

As shown in the next section, Loading SimpleYUI, it is possible to load YUI with the simpler pattern that other libraries use. SimpleYUI is great for learning purposes, but less appropriate for production code.

Note

In addition to the Y instance, YUI passes an obscure second parameter to your use() callback. This object represents the response from the Loader, and includes a Boolean success field, a string msg field that holds a success or error message, and a data array that lists all modules that successfully loaded. Unfortunately, this reporting mechanism is not 100% reliable in all browsers.

Loading SimpleYUI

Problem

You want to load YUI onto the page like people loaded JavaScript libraries in the good old days, without all this newfangled module loading and sandboxing nonsense.

Solution

Instead of pointing <script> to yui-min.js, point it to simpleyui-min.js. SimpleYUI includes all modules in YUI’s node, event, io, and transition rollups, flattened out into a single JavaScript file. These modules are more than enough to create interesting page effects and simple applications.

As shown in Example 1-2, loading SimpleYUI on the page automatically instantiates a global Y instance that provides access to the YUI API.

Example 1-2. Loading SimpleYUI

<!DOCTYPE html>
<title>Loading SimpleYUI</title>

<div id="demo"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/simpleyui/simpleyui-min.js"></script>
<script>
Y.one('#demo').setHTML('This message brought to you by SimpleYUI.');
</script>

Discussion

SimpleYUI provides the same functionality you would have received by loading these modules individually, as described in Loading Rollups and Modules. So why use SimpleYUI at all? If you are new to YUI, SimpleYUI acts like jQuery and other popular JavaScript libraries: you simply load a script onto the page and start calling methods from a global object. SimpleYUI is a great way to try out YUI, particularly for people who are still getting used to YUI’s idioms.

SimpleYUI is a starter kit that contains DOM, event, and Ajax functionality. However, SimpleYUI is in no way crippled or limited to just these modules; it also includes the Loader, so you are free to call Y.use() at any time to pull in additional modules such as autocomplete or model. For an example of calling Y.use() from within YUI().use(), refer to Example 1-22.

The disadvantages of using SimpleYUI are that it pulls in code that you might not need, and that it lacks a sandbox. You can address the latter issue by wrapping your code in an anonymous function and then immediately executing that function, as shown in Example 1-3.

Example 1-3. Loading SimpleYUI in a generic sandbox

<!DOCTYPE html>
<title>Loading SimpleYUI in a generic sandbox</title>

<div id="demo"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/simpleyui/simpleyui-min.js"></script>
<script>
var message = 'BOGUS MESSAGE';

(function () {
    var message = 'This message brought to you by sandboxed SimpleYUI.';
    Y.one('#demo').setHTML(message);
}());
</script>

JavaScript’s scoping rules ensure that variables outside the function can be referenced from within the function. However, any variables redeclared inside the function will trump any values declared outside. Or, looking at this the other way around, code outside the sandbox cannot overwrite private variables inside the sandbox.

Experienced JavaScript developers often use this kind of generic sandbox with other libraries. It is a fine defensive pattern in general, but less common in YUI simply because the standard loading pattern shown in Example 1-1 provides a sandbox already.

Note

If you search the Web, you’ll find a popular alternative pattern that works just as well, but is a little less aesthetically pleasing:

(function(){})()

JavaScript guru Douglas Crockford refers to this as the “dogballs” pattern.

Strictly speaking, you don’t need to resort to SimpleYUI to get a global Y object. YUI().use() returns a Y instance, so you can always do:

var Y = YUI().use(...);

In any case, these caveats about performance and sandboxing might not be important to you, depending on your situation. Some engineering groups use SimpleYUI as a way to segment different projects: critical pages and core pieces of infrastructure use the YUI sandbox, while prototypes and temporary marketing pages use SimpleYUI to make life easier for designers and prototypers. SimpleYUI is also a good tool for developers who are starting to transition code into the YUI “inside-out” sandbox pattern. Projects in transition can load SimpleYUI and leverage those APIs in existing legacy code, rather than having to immediately migrate large amounts of legacy JavaScript into YUI modules.

Identifying and Loading Individual Modules

Problem

You want to load the smallest possible amount of code necessary to accomplish a given task.

Solution

The YUI API documentation indicates which modules supply which individual methods and properties. As you write your code, consult the documentation and include only the specific modules you need in your YUI().use() call, in order to avoid loading code that contains unnecessary functionality.

Example 1-4 illustrates loading smaller, focused modules instead of larger rollups. As mentioned in Loading Rollups and Modules, YUI passes a second parameter to the use() callback that represents the response from the Loader. Example 1-4 converts this object into a string with Y.JSON.stringify(), using stringify()’s extended signature to pretty-print the output, and then displays the string by inserting it into a <pre> element. You could do all of this by loading the node and json rollups, but it turns out that the script only really requires the smaller modules node-base and json-stringify.

Example 1-4. Using individual modules

<!DOCTYPE html>
<title>Using individual modules</title>

<pre id="demo"></pre>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI().use('json-stringify', 'node-base', function (Y, loaderResponse) {
    var pre = Y.one('#demo');
    pre.set('text', Y.JSON.stringify(loaderResponse, null, 4));
});
</script>

Note

The example uses set('text') rather than setHTML(). Methods like setHTML() and set('innerHTML') are insecure when used for non-HTML strings or strings whose actual content or origin is unknown.

Discussion

YUI is broken into small modules that enable you to define very tight sets of dependencies. For convenience, YUI users often load rollups, which represent a group of related modules. For example, the node rollup is an alias for loading a list of modules that includes node-base, node-style, node-event-delegate, and nodelist.

Likewise, the json rollup includes json-parse and json-stringify, on the assumption that most applications that work with JSON need to convert JSON in both directions. However, if your application only needs to convert objects into strings, you can load json-stringify and avoid loading deadweight code from json-parse.

If you understand exactly which modules your implementation needs, you can save bytes by loading just those modules instead of loading rollups. However, this does require checking the YUI API documentation carefully for which methods and properties come from which modules, so that you’re not caught off-guard by “missing” features.

One option is to use rollups when prototyping and developing, then replace them with a narrower list of modules when you are getting ready to release to production. The YUI Configurator is a handy tool for determining an exact list of dependencies. If you take this approach, be sure to have a test suite in place to verify that your application still works after narrowing down your requirements. For more information about testing YUI, refer to Chapter 12.

Loading a Different Default Skin

Problem

You want the Loader to load the "night" skin for all YUI widgets—a darker CSS skin that is designed to match themes that are popular on mobile devices.

Solution

Pass in a YUI configuration object that includes a skin property with an alternative defaultSkin name. Some modules provide one or more named CSS skins. By default, when the Loader loads a module with a skin, the Loader attempts to fetch the module’s "sam" skin file. However, if you are loading modules that happen to have multiple skins, you can instruct the Loader to fetch a different skin across the board.

Example 1-5 loads and instantiates a Calendar widget with its alternative, darker "night" skin. By convention, all YUI skin styles are scoped within a class name of yui3-skin-skinname. This means that to actually apply the night skin once it has loaded on the page, you must add the class yui3-skin-night to the <body> or to a containing <div>.

Example 1-5. Changing YUI’s default skin

<!DOCTYPE html>
<title>Changing YUI's default skin</title>

<div id="demo" class="yui3-skin-night"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI({
    skin: { defaultSkin: 'night' }}).use('calendar', function (Y) {
    new Y.Calendar({ width: 300 }).render('#demo');
});
</script>

Discussion

YUI offers a great variety of configuration options that control the behavior of the Loader and certain properties of the YUI sandbox. For example, to prevent the Loader from dynamically loading any CSS, you can pass in a fetchCSS: false. This setting is useful if you plan to manually add all YUI CSS resources as static <link> elements, and you don’t want the Loader to fetch the same CSS resources twice.

One of the most important use cases is configuring metadata for custom modules. The Loader already has metadata for core YUI modules included in the seed file, but to properly load any modules you have created, you must provide the Loader with your module names, dependencies, and more. For recipes that demonstrate how to do this, refer to Recipes and .

See Also

More information about skins and loading CSS in Recipes and ; a variety of Slider skins shown side by side; the YUI Global Object User Guide; YUI config API documentation; YUI Loader API documentation.

Loading Gallery Modules

Problem

You want to load a useful third-party module from the YUI gallery and use it alongside core YUI modules.

Solution

Load the gallery module from the Yahoo! content delivery network (CDN) with YUI().use() as you would with any other YUI module. Gallery module names all start with the prefix gallery-. Once loaded, gallery modules attach to the Y just like core YUI modules.

Example 1-6 loads the To Relative Time gallery module, which adds a toRelativeTime() method. This method converts Date objects to English strings that express a relative time value, such as "3 hours ago".

To ensure that the example loads a specific snapshot of the gallery, the YUI configuration specifies a gallery build tag. For more information, refer to the Discussion.

Example 1-6. Using the To Relative Time gallery module with YUI Node

<!DOCTYPE html>
<title>Using the "To Relative Time" gallery module with YUI Node</title>

<div id="demo"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI({
    gallery: 'gallery-2010.08.25-19-45'
}).use('gallery-torelativetime', 'node', function (Y) {
    var entryTime = new Date(2011,10,1);
    Y.one('#demo').setHTML(Y.toRelativeTime(entryTime));
});
</script>

Discussion

The YUI gallery is a repository for sharing third-party modules. Modules in the gallery range from tiny standalone utilities to large families of related components.

YUI contributors can choose to serve their gallery modules from the Yahoo! CDN. Developers who want to take advantage of this feature must:

  • Sign and submit a YUI Contributor License Agreement (CLA)

  • Release their code under the open source BSD license, the same license YUI uses

  • Host their source code on GitHub, the same repository where YUI is hosted

Some gallery modules have not gone through these steps and so are not served from the Yahoo! CDN. You can use non-CDN gallery modules by downloading and installing them on your own server. For more information about hosting modules locally, refer to Loading Locally Hosted Builds.

The main difference between gallery modules and the core modules is that for the core modules, the YUI engineering team is fully responsible for fixing bugs, reviewing code, and testing changes. Gallery modules have whatever level of support the module’s owner is willing to provide.

Updates to gallery modules get picked up on the CDN when the YUI team pushes out the gallery build, which occurs roughly every week. Each gallery build has a build tag, such as gallery-2011.05.04-20-03. If you omit the gallery configuration option, YUI falls back to loading a default gallery build tag associated with the particular version of core YUI you are using. Thus, the following code works:

YUI().use('gallery-torelativetime', 'node', function (Y) {
    var entryTime = new Date(2011,10,1);
    Y.one('#demo').setHTML(Y.toRelativeTime(entryTime));
});

However, it is better to declare an explicit, tested gallery build tag. Otherwise, upgrading your YUI version later on will silently change the gallery tag, which might not be what you want.

For gallery modules served from the Yahoo! CDN, the YUI engineering team lightly examines code changes for serious security issues (such as blatant malware) and glaring bugs. Beyond that, there is no guarantee of code quality. Non-CDN gallery modules are completely unreviewed. Before using any gallery module, be sure to carefully evaluate the module’s functionality, source code, and license for yourself.

Loading a YUI 2 Widget

Problem

You want to use one of your favorite widgets from YUI 2, but it hasn’t been ported over to YUI 3 yet.

Solution

Load the widget as a YUI 3 module using its YUI 2in3 wrappers, as shown in Example 1-7.

Example 1-7. Loading a YUI 2 TreeView in YUI 3

<!DOCTYPE html>
<title>Loading a YUI 2 TreeView in YUI 3</title>

<div id="demo"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI().use('yui2-treeview', function (Y) {
    var YAHOO = Y.YUI2,
        tree = new YAHOO.widget.TreeView('demo', [
            { 
                label: 'hats',
                children: [
                    { label: 'bowler' },
                    { label: 'fedora' }
                ]
            },
            { 
                label: 'gloves'
            }
        ]);
    tree.render();
});
</script>

Discussion

With YUI 2in3, core YUI 2 widgets such as ImageCropper, ColorPicker, and ProgressBar are represented as first-class YUI 3 modules. Any YUI 2 widget you load this way attaches to the Y object as Y.YUI2. To make this look more like classic YUI 2–style code, you can rename Y.YUI2 to YAHOO, as shown in Example 1-7.

Although you may freely intermix YUI 3 code with YUI 2 wrapped modules, keep in mind that just because it loads like YUI 3 doesn’t mean it behaves like YUI 3. For example, new YUI 2 widgets take their container <div>’s id as a string, as in 'demo'. For YUI 3 widgets, you pass in the CSS selector for the <div>, as in '#demo'.

By default, the version of YUI 2 you get is version 2.8.2. However, you can retrieve any previous version by setting the yui2 field in the YUI object config:

YUI({ yui2: '2.7.0' }).use('yui2-treeview', function (Y) {
...
});

To load the absolute latest and greatest (and final!) version of YUI 2, use:

YUI({ 
    'yui2': '2.9.0', 
    '2in3': '4' 
}).use('yui2-treeview', function (Y) {
...
});

The 2in3 property configures the version of the YUI 2in3 wrapper to use, which must be at version 4 to load version 2.9.0.

Loading Locally Hosted Builds

Problem

You want to load YUI from your own servers instead of from Yahoo! servers.

Solution

By default, the YUI object is configured to fetch from Yahoo! servers. You can change this by:

  1. Downloading the latest stable YUI SDK zip file from http://yuilibrary.com.

  2. Unzipping the zip file in some directory under your web server’s web root.

  3. Creating a <script> element that points to the yui-min.js file.

For example, if you unzipped the SDK under the top level directory /js and pointed the first <script> element’s src at the local seed file (as shown in Example 1-8), this automatically configures YUI to load all YUI core modules locally. This also disables combo loading (discussed shortly).

Example 1-8. Loading a local copy of YUI

<!DOCTYPE html>
<title>Loading a local copy of YUI</title>

<div id="demo"></div>

<script src="/js/yui/build/yui/yui-min.js"></script>
<script>
YUI().use('node', function (Y) {
    Y.one('#demo').setHTML('All politics is local.');
});
</script>

To verify that YUI is loading from your own site rather than yui.yahooapis.com, use your browser’s component inspector (such as Firefox’s Web Inspector pane or Chrome’s Developer Tools pane).

Discussion

Yahoo! maintains a distributed collection of servers known as a content delivery network (CDN). A CDN is designed to serve files from systems that are physically close to the user who made the request. By default, YUI uses the Yahoo! CDN, which grants all YUI users free access to the same network that runs Yahoo’s own high-traffic sites. This saves your own bandwidth, reduces your own server load, and greatly improves performance thanks to browser caching and improved geographical distribution.

However, there are plenty of reasons to go it alone. Perhaps your organization forbids loading resources from remote servers as a matter of policy. Or perhaps your pages use SSL, in which case loading remote resources is a bad idea, as it exposes your users’ secure information to the remote site. In these cases, you can serve YUI from your own server.

Each release of YUI provides a full developer kit for download under http://yuilibrary.com/downloads/. The zip file contains the library, API documentation, and example files.

Note

If you want the latest-and-greatest version of YUI’s source, you can check it out by running:

git clone https://github.com/yui/yui3.git

For more information about how to send code to the upstream YUI project, refer to the tutorial “Contribute Code to YUI”.

Download the zip file, unzip it into your preferred location under your web server’s root, and then reference the local YUI seed file in your web page:

<script src="path/yui/yui-min.js"></script>

where path is the path under the web root in which the YUI module directories reside, such as /js/yui/build. In addition to the core YUI 3 SDK, you can also download and serve up the latest build of the YUI gallery and the YUI 2in3 project from your own server.

Loading a local YUI seed file automatically reconfigures the Loader to work with local files. Under the covers, this is like instantiating a sandbox with a configuration of:

YUI({
    base: '/js/yui/build/',
    combine: false
}).use('node', function (Y) {
    Y.one('#demo').setHTML('All politics is local.');
});

The base field defines the server name and base filepath on the server for finding YUI modules. By default, this is http://yui.yahooapis.com/version/build. For alternative seed files, YUI inspects your seed file URL and resets base appropriately. This means you rarely have to set base yourself, at least at the top level. Sometimes you might need to override base within a module group, as described in Defining Groups of Custom Modules.

The combine field selects whether YUI attempts to fetch all modules in one “combo load” HTTP request. A combo loader is a server-side script designed to accept a single HTTP request that represents a list of modules, decompose the request, and concatenate all the requested JavaScript into a single response.

Loading a seed file from yui.yahooapis.com sets the combine field to true. For seed files loaded from unknown domains, YUI changes combine to false, on the assumption that a random server does not have a combo loader installed. Setting combine to false is a safety measure that ensures that local installations of YUI “just work,” at the cost of generating lots of HTTP requests. To set up a production-quality local YUI installation, you should install your own local combo loader and set combine back to true. Implementations are available for a variety of server environments:

To install and operate a particular combo loader, refer to that combo loader’s documentation.

See Also

YUI 3 SDK downloads; Brian Miller’s article on locally served YUI3, which includes a configuration for serving up local copies of the gallery and YUI 2in3.

Creating Your Own Modules

Problem

You want to bundle and reuse your own code as a YUI module.

Solution

Use YUI.add() to register your code as a module with the YUI global object. At minimum, YUI.add() takes:

  • A name for your module. By convention, YUI module names are lowercase and use hyphens to separate words.

  • A callback function that defines your actual module code. To expose a property or function in the module’s public interface, you attach the component to the Y object.

Once YUI.add() executes, you can use your code like any other YUI module. In Example 1-9, YUI().use() immediately follows the module definition, loading the modules it needs and then executing module methods in a callback function.

Example 1-9. Creating and using a Hello World module

<!DOCTYPE html>
<title>Creating and using a Hello World module</title>

<div id="demo"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI.add('hello', function (Y) {
    Y.namespace('Hello');

    Y.Hello.sayHello = function () {
        return 'GREETINGS PROGRAMS';
    };
});

YUI().use('node-base', 'hello', function (Y) {
    Y.one('#demo').setHTML(Y.Hello.sayHello());
});
</script>

To help avoid naming collisions, you can use Y.namespace() to manufacture a Hello namespace for the sayHello() method. Y.namespace() is a handy utility, though in this simple example, the call is essentially equivalent to:

Y.Hello = {};

Note

Example 1-9 represents only the most basic building block for creating modules. This example is not enough to create truly reusable code. Real-world modules declare dependencies and other metadata, and are defined in a separate file from where they are used. For more information, refer to Recipes and .

Discussion

As mentioned in the introduction and in Loading Rollups and Modules, YUI separates module registration from module execution. YUI.add() registers modules with the YUI global object, while YUI().use() attaches modules to a Y instance so that you can execute the module’s functions. YUI.add() and YUI().use() are designed to work together; first you register some code, and then later you retrieve and execute it.

When designing your applications, always think about how to move as much code as possible out of use() and into add(). Code in an add() callback is reusable, while code in the use() callback is unreusable “glue” code designed to wire an application into a particular page.

If you compare YUI().use() and YUI.add() closely, you might notice the lack of parentheses on the YUI for YUI.add(). This is a key distinction:

  • YUI.add() is a static method that registers module code with the YUI global object.

  • YUI().use() is a factory method that creates YUI instances with the given configuration.

The YUI global object stores a common pool of available code. The Y object holds the particular subset of code that you want to actually register in a YUI.add() or use in a YUI().use(). Again, the name Y is just a strong convention. Within a sandbox, you can name the instance anything you like, but you should do this only if you are creating nested use() sandboxes, or if you need to inform other developers that this instance is “weird” in some way. For an example, refer to Binding a YUI Instance to an iframe.

The heart of YUI.add() is the callback function that defines your module code. Any functions or objects that you attach to the Y in the add() callback function become available later on in the use() callback function. Anything you do not attach to the Y remains private. For an example of a private function in a module, refer to Example 1-10.

When attaching functions and objects, consider using a namespace rather than attaching directly to the Y, as this space is reserved for a small number of core YUI methods. You can either add namespaces manually by creating empty objects, or call the Y.namespace() utility method. Y.namespace() takes one or more strings and creates corresponding namespaces on the Y object. Any namespaces that already exist do not get overwritten. Y.namespace() is convenient for creating multiple namespaces at once and for creating nested namespaces such as Y.Example.Hello. Y.namespace() also returns the last namespace specified, so you can use it inline:

Y.namespace('Hello').sayHello = function () { ...

You might be wondering about the YUI core modules—do they use YUI.add()? In fact, YUI core modules all get wrapped in a YUI.add() at build time, thanks to the YUI Builder tool. If you download and unzip the YUI SDK, you will find the raw, unwrapped source files under the /src directory, and the wrapped module files under the /build directory. In other words, there’s no magic here—the core YUI modules all register themselves with the same interface as your own modules.

See Also

Instructions for using YUI Builder.

Creating a Module with Dependencies

Problem

You want to create a custom YUI module and ensure that it pulls in another YUI module as a dependency.

Solution

Use YUI.add() to register your code as a module with the YUI global object, and pass in a configuration object that includes your module’s dependencies. After the module name and definition, YUI.add() takes two optional parameters:

  • A string version number for your module. This is the version of your module, not the version of YUI your module is compatible with.

  • A configuration object containing metadata about the module. By far the most common field in this configuration object is the requires array, which lists your module’s dependencies. For each module name in the requires array, YUI pulls in the requirement wherever it is needed, loading it remotely if necessary.

Example 1-10 is a variation on Example 1-9. Instead of returning a string value, Y.Hello.sayHello() now changes the contents of a single Y.Node. The hello module now declares a dependency on node-base to ensure that node.setHTML() is always available wherever hello runs.

To make things a little more interesting, sayHello() uses a private helper function named setNodeMessage(). Users cannot call setNodeMessage() directly because it is not attached to Y. setNodeMessage() uses Y.one() to normalize the input to a YUI node, then sets the message text.

Example 1-10. Creating a module that depends on a YUI node

<!DOCTYPE html>
<title>Creating a module that depends on a YUI node</title>

<div id="demo"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI.add('hello', function (Y) {

    function setNodeMessage(node, html) {
        node = Y.one(node);
        if (node) {
            node.setHTML(html);
        }
    }

    Y.namespace('Hello').sayHello = function (node) {
        setNodeMessage(node, 'GREETINGS PROGRAMS');
    };

}, '0.0.1', {requires: ['node-base']});

YUI().use('hello', function (Y) {
    Y.Hello.sayHello(Y.one('#demo'));
});
</script>

Unlike Example 1-9, the use() call in Example 1-10 does not need to explicitly request node-base. The new, improved hello module now pulls in this requirement automatically.

Discussion

Example 1-10 lists the module node-base in the requires array for the hello module. This guarantees that YUI().use() loads and attaches hello to the Y after attaching node-base (or any other modules you add to that array).

When providing requirements, take care to avoid circular dependencies. For example, if hello declares that the goodbye module must be loaded before hello, but the goodbye module declares that hello must be loaded before goodbye, you have a problem. The Loader does have some logic to defend against metadata with circular dependencies, but you shouldn’t count on your code running correctly.

For performance reasons, you should also provide your module’s requirements in the Loader metadata, as described in Creating Truly Reusable Modules.

As mentioned earlier, requires is the most important field. Some of the other fields for YUI.add() include:

optional

An array of module names to automatically include with your module, but only if the YUI configuration value loadOptional is set to true. For example, autocomplete-base declares an optional dependency on autocomplete-sources, which contains extra functionality for populating an AutoComplete widget from YQL and other remote sources. loadOptional is false by default.

Even if loadOptional is false, an optional dependency still causes a module to activate if the module’s code happens to already be loaded on the page. Modules can be present on the page due to an earlier YUI().use() call, or by loading module code statically, as shown in Implementing Static Loading.

skinnable

A Boolean indicating whether your module has a CSS skin. If this field is true, YUI automatically creates a <link> element in the document and attempts to load a CSS file using a URL of:

base/module-name/assets/skins/skin-name/module-name.css

where base is the value of the base field (discussed in Defining Groups of Custom Modules) and skin-name is the name of the skin, which defaults to the value sam. For more information about creating skins, refer to Bundling CSS with a Widget as a Skin.

use

Deprecated. An array of module names used to define “physical rollups,” an older deprecated type of rollup. To create modern rollups, refer to Defining Your Own Rollups.

In addition to module dependencies, Example 1-10 also illustrates a private function within a module. Since JavaScript lacks an explicit private keyword, many JavaScript developers signify private data with an underscore prefix, which warns other developers that the function or variable “should” be private. In many cases, this form of privacy is good enough.

However, the setNodeMessage() function in the example is truly private. Once YUI executes the add() callback, module users can call sayHello(), but they can never call setNodeMessage() directly, even though sayHello() maintains its internal reference to setNodeMessage(). In JavaScript, an inner function continues to have access to all the members of its outer function, even after the outer function executes. This important property of the language is called closure.

Creating Truly Reusable Modules

Problem

You want to create a custom YUI module by defining the module’s code in a separate file, then reuse the module in multiple HTML pages.

Solution

Examples 1-9 and 1-10 each define a custom module, but then proceed to use() the module in the same <script> block on the same HTML page. Truly reusable modules are defined in a file separate from where they are used.

This creates a problem. For modules not yet on the page, Loader needs metadata about a module before attempting to load that module, such as where the module resides and what its dependencies are. Fortunately, you can provide this information by configuring the YUI object, as shown in Example 1-11.

Example 1-11. Creating a reusable module

add_reusable.html: Creates a YUI instance and passes in a configuration object that defines the hello module’s full path and dependencies.

<!DOCTYPE html>
<title>Creating a reusable module</title>

<div id="demo"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI({
    modules: {
        'hello': {
            fullpath: 'hello.js',
            requires: ['node-base']
        }
    }
}).use('hello', function (Y) {
    Y.Hello.sayHello(Y.one('#demo'));
});
</script>

With this metadata, you do not need to manually add an extra <script> element to load the hello.js file. The fullpath, which can point to a local file or remote URL, is enough information for the YUI Loader to fetch the code. Declaring node-base as a dependency instructs the Loader to fetch node-base before fetching hello .

Since YUI module names often contain dashes, it is a YUI convention to always quote module names in configuration metadata, even if those quotes are not strictly necessary.

hello.js: Contains only the JavaScript for the hello module, identical to the version in Example 1-10. This file resides in the same directory as add_reusable.html.

YUI.add('hello', function (Y) {

    function setNodeMessage(node, html) {
        node = Y.one(node);
        if (node) {
            node.setHTML(html);
        }
    }

    Y.namespace('Hello').sayHello = function (node) {
        setNodeMessage(node, 'GREETINGS PROGRAMS');
    };

}, '0.0.1', {requires: ['node-base']});

Discussion

Example 1-11 is a minimal example of a single, simple module. The configuration object gets more complex as you add more modules and more dependencies, as shown shortly in Example 1-12.

So why doesn’t YUI need a giant configuration object to load the core YUI modules? The answer is that YUI cheats—this information is included in the YUI seed. The default seed file includes both the Loader code and metadata for all the core YUI modules, but you can load more minimal seeds if need be. For more information about alternate seed files, refer to “YUI and Loader changes for 3.4.0”.

You might have noticed that the metadata requires: ['node-base'] is provided twice: once in the YUI configuration that gets passed to the Loader, and again in the YUI.add() that defines the module. If the Loader has this metadata, why bother repeating this information in YUI.add()?

The answer has to do with certain advanced use cases where the Loader is not present. For example, if you build your own combo load URL, load a minimal seed that lacks the Loader code, and then call YUI().use('*') as described in Implementing Static Loading, the metadata in YUI.add() serves as a fallback for determining dependencies.

Defining Groups of Custom Modules

Problem

You want to define a group of related modules that all reside under the same path on the server.

Solution

In your YUI configuration, use the groups field to create a group of related modules that share the same base path and other characteristics.

Note

Example 1-12 is configured to run from a real web server. If you prefer to open add_group.html as a local file, change the base configuration field to be a relative filepath such as ./js/local-modules/.

Example 1-12. Defining a module group

add_group.html: Defines the local-modules module group, which contains four modules that reside under /js/local-modules, plus a CSS skin file. The main module, reptiles-core, pulls in the node rollup for DOM manipulation and two more local modules for additional giant reptile-related functionality.

<!DOCTYPE html>
<title>Defining a module group</title>

<div id="demo"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI({
    groups: {
        'local-modules': {
            base: '/js/local-modules/',
            modules: {
                'reptiles-core': {
                    path: 'reptiles-core/reptiles-core.js',
                    requires: ['node', 'reptiles-stomp', 'reptiles-fiery-breath'],
                    skinnable: true
                },
                'reptiles-stomp': {
                    path: 'reptiles-stomp/reptiles-stomp.js'
                },
                'reptiles-fiery-breath': {
                    path: 'reptiles-fiery-breath/reptiles-fiery-breath.js'
                },
                'samurai': {
                    path: 'samurai/samurai.js'
                }
            }
        }
    }
}).use('reptiles-core', function (Y) {
    Y.Reptiles.info(Y.one('#demo'));
});
</script>

/js/reptiles/giant-reptiles.js: Defines the reptiles-core module, which pulls in three other modules and provides an info() method that appends a <ul> into the DOM.

YUI.add('reptiles-core', function (Y) {
    var reptiles = Y.namespace('Reptiles');
    
    reptiles.traits = [
        'dark eyes',
        'shiny teeth'
    ];
    
    reptiles.info = function (node) {
        var out = '', i;
        for (i = 0; i < reptiles.traits.length; i += 1) {
            out += '<li>' + reptiles.traits[i] + '</li>';
        };
        out += '<li>' + reptiles.breathe() + '</li>';
        out += '<li>' + reptiles.stomp() + '</li>';
        node.append('<ul class="reptile">' + out + '</ul>');
    };
}, '0.0.1', {requires: ['node', 'reptiles-stomp', 'reptiles-fiery-breath']});

/js/reptiles/stomp.js: Defines the Y.Reptiles.stomp() method.

YUI.add('reptiles-stomp', function (Y) {
    Y.namespace('Reptiles').stomp = function () {
        return 'STOMP!!';
    };
}, '0.0.1');

/js/reptiles/fiery-breath.js: Defines the Y.Reptiles.breathe() method.

YUI.add('reptiles-fiery-breath', function (Y) {
    Y.namespace('Reptiles').breathe = function () {
        return 'WHOOOSH!';
    };
}, '0.0.1');

/js/local-modules/reptiles-core/assets/skins/sam/reptiles-core.css: Defines the CSS skin for the reptiles-core module. YUI attempts to load this file because the skinnable field for reptiles-core is set to true. For more information about how this works, refer to the Discussion.

.reptile li { color: #060; }

The samurai module definition is empty. Feel free to make up your own definition.

Discussion

For multiple custom modules, consider using this convention for your module structure:

base/
  module-foo/
    module-foo.js
    assets/
      skins/
        sam/
          module-foo.css
          sprite.png
  module-bar/
    ...

that is, a base path with one directory per module. Each module directory contains at least one JavaScript file, possibly more if you include the *-min.js or *-debug.js versions of your modules. If the module has a skin, it should also contain an assets/ directory, as shown in Bundling CSS with a Widget as a Skin. If it has localized language resources, it should contain a lang/ directory, as shown in Internationalizing a Widget.

Module groups create a configuration context where you can load modules from somewhere other than the Yahoo! CDN. You do not need to use module groups for logical groupings of your own modules (“all my widgets,” “all my utility objects,” and so on). For those kinds of logical groupings, it is more appropriate to create custom rollups, as described in Defining Your Own Rollups. Module groups are for providing the Loader with a different set of metadata for loading modules from a particular server and set of paths: your own custom modules, third-party modules on some remote server, your own local copy of the core YUI library or YUI gallery, and so on.

In many cases, a module group is a necessity. Consider loading a local CSS skin. As described in Creating a Module with Dependencies, setting skinnable to true causes YUI to attempt to fetch a skin from:

base/module-name/assets/skins/skin-name/module-name.css

base defaults to the same prefix that you loaded the YUI seed file from, typically something like http://yui.yahooapis.com/3.5.0/build. So what happens if you try to load skin CSS from your own local server without using a module group?

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI({
    modules: {
        'reptiles-core': {
            fullpath: '/js/local-modules/reptiles-core/reptiles-core.js',
            skinnable: true
        },
        ... 
    }
}).use('reptiles-core', ...);

This configuration fails because YUI attempts to load your skin from http://yui.yahooapis.com/3.5.0/build/reptiles-core/assets/skins/sam/reptiles-core.css, instead of your local server.

What if you set base to act locally? For example:

YUI({
    base: '/js/local-modules/',
    modules: {
        'reptiles-core': {
            path: 'reptiles-core/reptiles-core.js',
            skinnable: true
        },
        ... 
    }
}).use('reptiles-core', ...);

This is also undesirable because now YUI is configured to fetch all modules, including the YUI core and gallery modules, from this local path. Using a module group enables you to set the base path for all of your local modules without messing up the loader configuration for the core modules.

Reusing a YUI Configuration

Problem

You want to reuse a complex configuration across multiple pages.

Solution

Before creating any YUI instances, load a separate script file containing a YUI_config object that stores all custom module configuration and other metadata you need. If the page contains a YUI_config object, YUI automatically applies this configuration to any YUI instances on the page.

Example 1-13 is a variation of Example 1-12, but with the module metadata broken out into its own reusable file.

Note

Example 1-13 is configured to run from a real web server. If you prefer to open add_yui_config.html as a local file, change all /js filepaths to relative filepaths such as ./js/.

Example 1-13. Reusing a YUI configuration

add_yui_config.html: Loads and exercises the reptiles-core module using an implicit YUI configuration supplied by /js/yui_config.js. The key word is “implicit”—you do not need to explicitly pass YUI_config into the YUI() constructor.

<!DOCTYPE html>
<title>Reusing a YUI configuration</title>

<div id="demo"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script src="/js/yui_config.js"></script>
<script>
YUI().use('reptiles-core', function (Y) {
    Y.Reptiles.info(Y.one('#demo'));
});
</script>

/js/yui_config.js: Provides the configuration data for loading a set of custom modules.

var YUI_config = {
    groups: {
        'local-modules': {
            base: '/js/local-modules/',
            modules: {
                'reptiles-core': {
                    path: 'reptiles-core/reptiles-core.js',
                    requires: ['node', 'reptiles-stomp', 'reptiles-fiery-breath'],
                    skinnable: true
                },
                'reptiles-stomp': {
                    path: 'reptiles-stomp/reptiles-stomp.js'
                },
                'reptiles-fiery-breath': {
                    path: 'reptiles-fiery-breath/reptiles-fiery-breath.js'
                },
                'samurai': {
                    path: 'samurai/samurai.js'
                }
            }
        }
    }
};

The other JavaScript files in this example are identical to the ones in Example 1-12.

Discussion

At construction time, each YUI instance attempts to merge the common YUI_config object into the configuration object you passed into the YUI() constructor. Thus, something like:

<script src="/js/yui_config.js"></script>
<script>YUI({ lang: 'jp' }).use('reptiles-core', function (Y) {
    Y.Reptiles.info(Y.one('#demo'));
});
</script>

would safely add the lang property without clobbering the module metadata. Properties you supply to the constructor override properties in YUI_config.

If you’re careful about how you merge configuration data, you can add new module groups or even new modules within an existing module group, as shown in Example 1-14.

Example 1-14. Merging common and page-specific YUI configuration

add_yui_config_merged.html: Loads and exercises the reptiles-core module using an implicit YUI configuration supplied by /js/yui_config_incomplete.js, and merges some extra configuration information into the YUI() constructor.

<!DOCTYPE html>
<title>Merging common and page-specific YUI configuration</title>

<div id="demo"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script src="/js/yui_config_incomplete.js"></script>
<script>
YUI({
    groups: {
        'local-modules': {
            base: '/js/local-modules/',
            modules: {
                'reptiles-stomp': {
                    path: 'reptiles-stomp/reptiles-stomp.js'
                }
            }
        }
    }    
}).use('reptiles-core', function (Y) {
    Y.Reptiles.info(Y.one('#demo'));
});
</script>

/js/yui_config.js: Provides some (intentionally incomplete) configuration data for loading a set of custom modules. The configuration is broken in two places: first, the reptiles-stomp module definition is missing, and second, the base path is incorrect. However, the configuration object provided in the HTML file fixes both problems.

// WARNING: Config intentionally incomplete/broken
var YUI_config = {
    groups: {
        'local-modules': {
            base: '/js/BOGUS_PATH',
            modules: {
                'reptiles-core': {
                    path: 'reptiles-core/reptiles-core.js',
                    requires: ['node', 'reptiles-stomp', 'reptiles-fiery-breath'],
                    skinnable: true
                },
                'reptiles-fiery-breath': {
                    path: 'reptiles-fiery-breath/reptiles-fiery-breath.js'
                },
                'samurai': {
                    path: 'samurai/samurai.js'
                }
            }
        }
    }
};

Example 1-14 supplies an incomplete YUI_config object in order to demonstrate that the merging actually works. More generally, you would use YUI_config to provide a complete, working configuration for everything that is common across your site, and then supply additional page-specific information either in the YUI instance constructor, or by modifying YUI_config (which would affect all instances on the page).

Once you’re within a YUI instance, you can call Y.applyConfig() at any time to merge in additional configuration. You can even call Y.applyConfig() to load more module metadata, perhaps along with on-demand loading techniques such as those shown in Recipes and .

Defining Your Own Rollups

Problem

You would like to define a particular stack of modules under a friendly alias for convenient reuse.

Solution

Define an empty module and provide it with a use field containing an array of other module or rollup names. Then load and use it as you would any other module.

Example 1-15 represents a simple rollup that serves as an alias for node-base and json (which is itself a rollup of json-parse and json-stringify). The custom my-stack rollup behaves like any of the other popular core YUI rollups, such as node, io, json, or transition.

Example 1-15. Defining your own rollups

<!DOCTYPE html>
<title>Defining your own rollups</title>

<div id="demo"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI({
    modules: {
        'my-stack': {
            use: ['node-base', 'json']
        }
    }
}).use('my-stack', function (Y) {
    var dataStr = '{ "rollups": "are neat" }',
        data    = Y.JSON.parse(dataStr);

    Y.one('#demo').setHTML(data.rollups);
});
</script>

Discussion

As Example 1-15 demonstrates, a rollup is just an alias for a list of other rollups and modules. The example uses core YUI modules, but you can also include gallery modules, your own custom modules, or anything else.

Rollups are great for logically grouping modules that represent major components of your application stack, or for grouping modules that are closely related, but don’t strictly depend on each other. For example, json-parse and json-stringify are completely independent modules, but applications often end up using both anyway.

Another benefit of rollups is that they free you up to encapsulate your code into even smaller chunks than you otherwise might have. You can use rollups to bundle very tiny modules into larger units, making it easier for others to use your code without having to worry about the fiddly details of what to include.

Loading jQuery as a YUI Module

Problem

You want to load jQuery and some jQuery plugins into the sandbox alongside YUI, just like any YUI module.

Solution

Create a module group that defines module metadata for the main jQuery library and any other jQuery-related code that you want to load as well. Use base and path (or fullpath) to point to the remote files.

If you need to load multiple jQuery files in a particular order, use requires to specify the dependency tree, and set async: false for the overall module group. Setting async: false is necessary for loading any code that is not wrapped in a YUI.add()—it ensures that third-party code loads synchronously, in the correct file order.

After defining jQuery files as YUI modules, you can then use() them alongside any ordinary YUI modules you like. Example 1-16 pulls in the YUI calendar module along with jQuery and jQuery UI, which includes the jQuery Datepicker plugin. Unlike YUI core widgets, the jQuery Datepicker’s CSS does not get loaded automatically, so you must load it as a separate CSS module. For more information about loading arbitrary CSS as a YUI module, refer to Bundling CSS with a Widget as a CSS Module.

Note

Experienced jQuery developers might have noticed that the example simply renders the Datepicker without bothering to wrap it in a $(document).ready(). The standard YUI loading pattern with JavaScript at the bottom of the page usually makes DOM readiness a nonissue. However, if you modify elements that occur after your <script> element or load YUI in an unusual way, you might need to wait for DOM readiness. For YUI’s equivalent of jQuery’s ready(), refer to Responding to Element and Page Lifecycle Events.

Example 1-16. Loading jQuery as a YUI module

<!DOCTYPE html>
<title>Loading jQuery as a YUI module</title>
<style>
h4 { margin: 25px 0px 10px 0px; }
div.container { width: 300px; }
</style>

<body class="yui3-skin-sam">

<h4>YUI 3 Calendar Widget</h4>
<div class="container" id="ycalendar"></div>

<h4>jQuery UI Calendar Plugin</h4>
<div class="container" id="datepicker"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI({
    groups: {
        'jquery': {
            base: 'http://ajax.googleapis.com/ajax/libs/',
            async: false,
            modules: {
                'jquery': {
                    path: 'jquery/1.7/jquery.min.js'
                },
                'jquery-ui': {
                    path: 'jqueryui/1.8/jquery-ui.min.js',
                    requires: ['jquery', 'jquery-ui-css']
                },
                'jquery-ui-css': {
                    path: 'jqueryui/1.8/themes/base/jquery-ui.css',
                    type: 'css'
                }
            }
        }
    }
}).use('calendar', 'jquery-ui', function (Y) {
    new Y.Calendar().render('#ycalendar');
    $('#datepicker').datepicker();
    Y.one('body').append('<p>YUI and jQuery, living together, mass hysteria!</p>');
});
</script>
</body>

As with any module, it’s critical to define your dependencies correctly. Here, the jquery-ui module declares a dependency on jquery and jquery-ui-css, which ensures that YUI adds jQuery’s code to the page above jQuery UI’s code. If you somehow got the dependencies backward and declared that jquery depended on jquery-ui, then YUI would add jQuery below jQuery UI, which would break the Datepicker plugin.

Of course, you’re not restricted to just core jQuery and jQuery UI. As long as you declare your paths and dependencies correctly, you can load any third-party jQuery plugin (or any other library code, for that matter).

Discussion

Loading jQuery, Dojo, Scriptaculous, or any other major framework into a YUI sandbox is not exactly a recipe for great efficiency. If you’ve loaded the code necessary to do both Y.one('#demo') and $('#demo') in the same page, you’ve loaded an awful lot of duplicate code for rummaging around the DOM.

That said, the YUI Loader is an excellent standalone script and CSS loader. It can load any third-party JS or CSS file you like, in any order you like, as long as you provide the correct metadata. Some reasons you might want to do this include:

Easy code reuse

You have found some critical feature or component that is available only in some other library.

Better collaboration

You are working primarily in YUI, but you have teammates or contractors who have written non-YUI code that you need to quickly integrate, or vice versa.

Improving perceived performance

Your non-YUI pages are currently littered with blocking <script> and <link> elements at the top of the document. You’re looking for a quick way to migrate over to a more advanced loading pattern, and perhaps even take advantage of some advanced YUI Loader tricks such as those covered in Recipes and .

In fact, if you want to use the Loader to load non-YUI scripts only, and you are sure that you don’t need to load any core YUI modules, consider loading the yui-base-min.js seed rather than the yui-min.js seed:

<script src="http://yui.yahooapis.com/3.5.0/build/yui-base/yui-base-min.js"></script>

The yui-base-min.js seed includes the YUI module registry and the YUI Loader, but leaves out all the metadata for the core YUI modules. This makes it a little more efficient to load the YUI seed solely for loading and managing third-party scripts.

YUI is designed to be compatible with most major libraries, although you might run into strange conflicts here and there. The most common reason for bugs is when the other library modifies the prototype of a native JavaScript or native DOM object. YUI provides solid abstraction layers around native objects, but these abstractions can break if the other library changes object behavior at a deep level.

The other thing to watch out for is forgetting that different libraries use different abstractions. For example, you can’t pass a YUI Node instance directly into some other library for further DOM manipulation. If you are building some kind of Frankenstein’s Monster application that does some DOM manipulation with YUI and some in Dojo, keep a close eye on each point where the two libraries communicate.

See Also

jQuery; jQuery UI.Datepicker; jCarousel; the jQuery–YUI 3 Rosetta Stone; an explanation of the different seed files in YUI and Loader changes for 3.4.0.

Loading Modules Based on Browser Capabilities

Problem

You want YUI to supply additional fallback code to support users who have legacy browsers, but without penalizing users who have modern browsers. (This is called capability-based loading.)

Solution

In your YUI configuration, use the condition field to flag a module as conditional. A conditional module loads only if some other module specified by trigger is present, and then only if the test function returns true.

Example 1-17 demonstrates a simple suitcase module that can store data on the client. By default, the module tries to use localStorage, but if the browser is too old to support this feature natively, YUI loads an extra module that stores data using cookies instead.

Example 1-17. Loading modules based on browser capabilities

add_capability.html: Creates a YUI instance and passes in a configuration object that defines metadata for the suitcase module and for the suitcase-legacy conditional module.

<!DOCTYPE html>
<title>Loading modules based on browser capabilities</title>

<div id="demo"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI({
    modules: {
        'suitcase': {
            fullpath: 'suitcase.js' 
        },
        'suitcase-legacy': {
            fullpath: 'suitcase-legacy.js', 
            condition: {
                trigger: 'suitcase',
                test: function () {
                    try {
                        return window.localStorage ? false : true;
                    } catch(ex) {
                        return true;
                    }
                }
            },
            requires: ['suitcase', 'cookie'] 
        }
    }
}).use('node', 'suitcase', function (Y) {
    var type = Y.Cookie ? 'battered, legacy' : 'sleek, ultra-modern';
    Y.Suitcase.set('foo', 'bar');
    Y.one('#demo').setHTML('In your ' + type + ' suitcase: ' + Y.Suitcase.get('foo'));
});
</script>

The suitcase-legacy module has a trigger condition. If the suitcase module is passed into use(), YUI executes suitcase-legacy’s test function. If the browser does not support localStorage, the function returns true, which causes YUI to also fetch suitcase-legacy and all its dependencies. If the function does support localStorage, YUI skips fetching suitcase-legacy.

Within the use() callback, the presence of Y.Cookie is a quick way to check whether suitcase-legacy was successfully triggered.

suitcase.js: Defines a simple get/set API for storing data on the client using localStorage. Note that the suitcase module is written without any “knowledge” of the suitcase-legacy API. Capability-based loading is designed to help you avoid having to include extra conditionals or other unnecessary code in your main modules.

YUI.add('suitcase', function (Y) {
    Y.Suitcase = {
        get: function (name) {
            return localStorage.getItem(name);
        },
        set: function (name, value) {
            localStorage.setItem(name, value);
        }
    };
}, '0.0.1');

suitcase-legacy.js: Defines the legacy cookie-based get/set API. Because of dependency ordering, YUI must load suitcase-legacy after suitcase, which means that the get() and set() methods from suitcase-legacy always overwrite the get() and set() methods from suitcase. In other words, if both modules are loaded on the page, calling Y.Suitcase.get() will use cookies, not localStorage.

YUI.add('suitcase-legacy', function (Y) {
    Y.Suitcase = {
        get: function (name) {
            return Y.Cookie.get(name);
        },
        set: function (name, value) {
            Y.Cookie.set(name, value);
        }
    };
}, '0.0.1', { requires: ['suitcase', 'cookie'] });

Fortunately for users (but unfortunately for demonstration purposes), localStorage is widely available in most browsers. If you don’t have a really old browser available that can show the legacy module in action, feel free to hack the example and change the test function to just return true.

Note

The Suitcase object is a toy example. YUI already provides more professional storage APIs called Cache and CacheOffline. Like Suitcase, CacheOffline is able to use localStorage when that feature is available.

Discussion

Supporting older, less capable browsers often requires supplying extra JavaScript to correct for bugs and to emulate more advanced native features. After writing and testing code to correct older browsers, the last thing you want to do is penalize cutting-edge users by forcing them to download extra code.

YUI’s capability-based loading solves this problem by enabling you to break legacy code out into separate modules. Older browsers can load and execute the extra code they need, while newer browsers suffer only the small performance hit of evaluating a few conditionals.

The core YUI library uses capability-based loading to do things like:

  • Avoid loading support for physical keyboard events on iPhones

  • Make DOM-ready events safer on old versions of Internet Explorer, without penalizing other browsers

  • Seamlessly use the best graphics feature available for the given browser: SVG, Canvas, or VML

While capability-based loading was originally designed for patching up legacy browsers, you can also flip this idea around and serve up extra code that unlocks features in a more capable browser. For example, let’s say your application must perform an expensive calculation. Older browsers run the calculation directly and suffer an annoying UI freeze. However, if the browser supports the Web Worker API, YUI could trigger a conditional module that uses workers to run the calculation in the background. Usually you want to avoid “penalizing” newer browsers with an extra download, but if the benefits are high enough, it might be worth doing.

Most conditional modules should be abstracted behind another API. In Example 1-17, the modules are designed so that developers can call Y.Suitcase.get() and Y.Suitcase.set() without knowing whether the legacy implementation was in effect. Of course, this abstraction can be slower than the native implementation, or break down at the edges in some other way. For example, anyone who tries to store a 3 MB object in Y.Suitcase using a legacy browser will be sorely disappointed.

For obvious reasons, capability test functions should execute quickly. A typical capability test either checks for the existence of an object property, or creates a new DOM element and runs some subsequent operation on that element. Unfortunately, touching the DOM is expensive, and even more unfortunately, sometimes capability tests need to do substantial work, since just because a browser exposes a certain property or method doesn’t mean that the feature works properly. As an example, the test function in Example 1-17 needs a try/catch statement in order to work around an edge-case bug in older versions of Firefox.

Capability testing can be a surprisingly deep rabbit hole. In extreme cases where capability testing has become hopelessly complex or slow, you might consider using the Y.UA object. Y.UA performs user-agent sniffing, which many web developers regard as evil. Still, Y.UA is there, just in case you really do need to use the Dark Side of the Force. Y.UA can also be useful when capability testing isn’t helpful for answering the question, such as when you need to detect certain CSS or rendering quirks.

Monkeypatching YUI

Problem

You want to conditionally load extra code at runtime to patch a YUI bug or hack new behavior into YUI.

Solution

In your YUI configuration, define one or more patch modules, using the condition field to flag those modules as conditional. Set the trigger field to the name of the module to patch, and create a test function that simply returns true.

Example 1-18 loads a module that patches node-base, changing the behavior of setHTML(). Ordinarily, setHTML() is a safer version of setting innerHTML; before blowing away the node’s internal contents, setHTML() walks the DOM and cleanly detaches any event listeners. For whatever reason, you’ve decided this safer behavior is undesirable. The “patch” clobbers setHTML(), turning it into a simple alias for setting innerHTML.

Note

Example 1-18 is configured to run from a real web server. If you prefer to open add_monkeypatching.html as a local file, change the base configuration field to be a relative filepath such as ./js/patches/.

Example 1-18. Monkeypatching YUI

add_monkeypatching.html: Creates a YUI instance and passes in a configuration object that defines metadata for the node-patches conditional module.

<!DOCTYPE html>
<title>Monkeypatching YUI</title>

<div id="demo"></div>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI({
    groups: {
        patches: {
            base: '/js/patches/',
            modules: {
                'node-patches' : {
                    path: 'node-patches/node-patches.js',
                    condition: {
                        name: 'node-patches',
                        trigger : 'node-base',
                        test : function () { return true; }
                    }
                }
            }
        }
    }
}).use('node-base', function (Y) {
    Y.one('#demo').setHTML("Hmmm, setHTML() is unusually fast these days.");
});
</script>

/js/patches/node-patches/node-patches.js: Provides additional code that overrides Node’s setHTML() method. The patch module loads only if node-base is loaded.

YUI.add('node-patches', function (Y) {
    Y.Node.prototype.setHTML = function (content) {
        this.set('innerHTML', content);
    }
});

Discussion

Monkeypatching refers to modifying the behavior of a program at runtime without altering the upstream source. Monkeypatching can be useful for implementing quick fixes, but as the name implies, it isn’t necessarily the best approach for long-term stability.

Example 1-18 represents a somewhat contrived behavior change. More generally, you could use monkeypatching to temporarily address a serious bug in the YUI library, or to inject behavior that you need in a development or staging environment, but not in production.

Note

When patching someone else’s code, you can use Y.Do.before() and Y.Do.after() to cleanly inject behavior into a program without clobbering an existing method. For more information, refer to Responding to a Method Call with Another Method.

Loading Modules on Demand

Problem

You have a feature that your application needs only some of the time. You want to load this code only for users who need it, without affecting the initial page load.

Solution

Instead of loading the optional code up front, call Y.use() within the top-level YUI().use() sandbox to load the optional code on demand.

For example, suppose you need to display a confirmation pane when the user clicks a button. The straightforward approach is to load the overlay module with YUI().use(), create a new Overlay instance, and then bind a click event to the button that will show() the overlay. For examples of using overlays, refer to Creating an Overlay.

Although there’s nothing wrong with that approach, users still have to load the overlay module and its dependencies even if they never click the button. You can improve the performance of the initial page view by deferring loading and executing code until the moment the user needs it, as shown in Example 1-19:

  1. Create a top-level showOverlay() function.

  2. Within showOverlay(), call Y.use() to load the overlay module.

  3. Within the Y.use() callback function:

    1. Create a new Overlay instance, initially set to be invisible.

    2. Redefine the showOverlay() function to do something else. The next time showOverlay() is called, it will simply show the hidden overlay instance.

    3. Call the newly redefined showOverlay() from within showOverlay() to make the overlay instance visible.

  4. Bind “hide” and “show” callback functions as click events for the two respective buttons:

    • The “hide” callback first checks whether the overlay has been created.

    • The “show” callback calls showOverlay(). The first button click invokes the “heavy” version of showOverlay(), the version that loads the overlay module, instantiates an overlay, and then redefines itself. Subsequent clicks invoke the “light” version of showOverlay(), which flips the overlay into the visible state.

Example 1-19. Loading the overlay module on demand

<!DOCTYPE html>
<title>Loading the overlay module on demand</title>
<style>
.yui3-overlay-content {
    padding: 2px;
    border: 1px solid #000;
    border-radius: 6px;
    background-color: #afa;
}
</style>

<button id="show">Show Overlay</button>
<button id="hide">Hide Overlay</button>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI().use('node-base', function (Y) {
    var overlay;

    var showOverlay = function () {
        Y.use('overlay', function () {
            overlay = new Y.Overlay({
                bodyContent: 'Hello!',
                centered: true,
                height: 100,
                render: true,
                visible: false,
                width: 200,
                zIndex: 2
            });

            showOverlay = function () {
                overlay.show();
            };

            showOverlay();
        });
    };

    Y.one('#hide').on('click', function () {
        if (overlay) {
            overlay.hide();
        }
    });

    Y.one('#show').on('click', function () {
        showOverlay();
    });
});
</script>

Discussion

Example 1-19 illustrates two concepts. The first is the ability of functions in JavaScript to redefine themselves. A function calling itself (recursion) is common enough, but a function that redefines and then calls itself is less common. This pattern is useful if you have a function that needs to do one thing the first time it is called, and something else on subsequent calls. Use this technique sparingly, as there’s a good chance you’ll confuse people who read your code later on—including, possibly, yourself.

The second concept is the difference between the exterior YUI().use(), which creates a new YUI sandbox, and the interior Y.use(), which loads modules into the existing sandbox that’s referenced by the Y variable. Y.use() enables you to load and attach additional modules at any time, for any reason. This is sometimes called lazy loading.

Lazy-loading modules can greatly improve your application’s perceived performance. Native applications have a great advantage in that they start out with most or all of their code preloaded, while web applications have to bootstrap themselves over the network.

To compensate for this, you can divide your application into two pieces: a minimal interactivity core that provides just enough functionality to render the application, and additional components that you can lazy-load in the background as the user starts poking around. Example 1-19 attempts to be “smart” by loading extra code only if it is needed, but your application doesn’t have to be this fancy. You could wait for your interactivity core to finish loading and then start loading all secondary components in the background, in order of priority.

Loading modules in response to user actions can cause a delay at the moment when the user triggers the loading. If this becomes a problem, you can just lazy-load all modules in the background regardless of whether they are needed, or alternatively, you can try to improve performance with predictive loading, as described in Enabling Predictive Module Loading on User Interaction.

See Also

Eric Ferraiuolo’s gallery-base-componentmgr module, which makes it easy to lazy-load Y.Base-derived objects and their dependencies.

Enabling Predictive Module Loading on User Interaction

Problem

You have a feature that your application needs only some of the time, but that requires a lot of extra code to run. You want to load this code only for users who need it, without impacting the initial page load. You want to minimize any delay that occurs if a user does invoke the feature.

Solution

Use predictive loading to load the necessary code after the initial page load, but just before the user tries to invoke the feature.

In Example 1-19, the application defers loading the overlay module until the user clicks the button, which improves the initial page load time. However, this could cause an annoying delay when the user makes the first click.

Example 1-20 adds a refinement to the previous example. It calls Y.use() to load the overlay module in the background, but only if the user’s mouse hovers over the Show Overlay button or if the button acquires focus. If the user then clicks on the button and the module has not yet loaded, the click event gets queued up until the Overlay widget is ready. To do this, the example separates loading from execution by creating a loadOverlay() function and a showOverlay() function.

  1. The loadOverlay() function has different behavior depending on whether the overlay has already been instantiated, the overlay module is currently loading, or the overlay module needs to start loading.

    1. loadOverlay() takes a callback function, which turns out to be showOverlay(). If the overlay has already been instantiated, loadOverlay() executes the callback and returns immediately.

    2. If the overlay module is currently loading, this means the overlay is not yet ready to show. loadOverlay() queues the callback up in the callbacks array and returns immediately.

    3. If both of these conditions fail, this means the loadOverlay() function has been invoked for the first time. It is therefore time to start loading the overlay module. loadOverlay() calls Y.use() to load the overlay module on the fly.

    4. The Y.use() callback instantiates the overlay, sets overlayLoading to false (indicating that it is permissible to show the overlay), and finally executes any showOverlay() callbacks that have queued up while the code was loading.

  2. The showOverlay() function is considerably simpler. If the overlay is already instantiated, the function shows the overlay. Otherwise, showOverlay() calls loadOverlay() with itself as the callback, which guarantees that loadOverlay() has at least one instance of showOverlay() queued up and ready to fire as soon as the overlay is instantiated.

  3. The hideOverlay() function is simpler still. If the overlay is already instantiated, the function shows the overlay.

  4. Finally, the script attaches event handlers to the Show Button and Hide Button. The on() method attaches an event handler, while the once() method attaches an event listener that automatically detaches itself the first time it is called.

Example 1-20. Loading the overlay module predictively

<!DOCTYPE html>
<title>Loading the overlay module predictively</title>
<style>
.yui3-overlay-content {
    padding: 2px;
    border: 1px solid #000;
    border-radius: 6px;
    background-color: #afa;
}
</style>

<button id="show">Show Overlay</button>
<button id="hide">Hide Overlay</button>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI().use('node-base', function (Y) {
    var callbacks = [],
        overlay,
        overlayLoading,
        showButton = Y.one('#show'),
        hideButton = Y.one('#hide');

    var loadOverlay = function (callback) {
        if (overlay) {
            if (callback) {
                callback();
            }
            return;
        }

        if (callback) {
            callbacks.push(callback);
        }
        
        if (overlayLoading) {
            return;
        }

        overlayLoading = true;

        Y.use('overlay', function () {
            var callback;

            overlay = new Y.Overlay({
                bodyContent: 'Hello!',
                centered: true,
                visible: false,
                height: 100,
                width: 200,
                zIndex: 2
            }).render();

            overlayLoading = false;

            while (callback = callbacks.shift()) {
                if (Y.Lang.isFunction(callback)) {
                    callback();
                }
            }
        });
    };

    var showOverlay = function () {
        if (overlay) {
            overlay.show();
        } else {
            loadOverlay(showOverlay);
        }
    };

    var hideOverlay = function () {
        if (overlay) {
            overlay.hide();
            callbacks = [];
        }
    };

    showButton.once('focus', loadOverlay);
    showButton.once('mouseover', loadOverlay);
    showButton.on('click', showOverlay);
    hideButton.on('click', hideOverlay);
});
</script>

For more information about the Overlay widget, refer to Creating an Overlay.

Discussion

While on-demand loading modules can help reduce initial load times, it can cause a delay when the user triggers the main event that requires the extra code. The goal of predictive loading is to start the loading a little earlier by using some other, related browser event that signals the user’s possible intent to use the feature.

A reasonable way to predict that the user is likely to click a button is to listen for mouseover or focus events on the button or its container. You must listen for both events, since some users may use the mouse while others may use the keyboard. To get an even earlier indication of the user’s intent, you could attach the focus and mouseover listeners to the button’s container. For more information about using on() and once() to attach event handlers, refer to Chapter 4.

Thanks to these event handlers, the loadOverlay() function is called when the user is about to click the Show Overlay button. Since dynamic script loading is an asynchronous operation, loadOverlay() accepts an optional callback function as an argument, and calls that function once the overlay is ready to use.

To ensure that user clicks don’t get lost while the overlay module is loading, multiple calls to loadOverlay() just add more callbacks to the queue, and all queued callbacks will be executed in order as soon as the overlay is ready. By the time the user actually clicks, the overlay should be ready to go, but if the user does manage to click while the code is loading, the overlay still appears as expected.

Binding a YUI Instance to an iframe

Problem

You want to manipulate an iframe using JavaScript in the parent document, without actually having to directly load YUI into the iframe.

Solution

Create a child YUI instance within your main YUI instance and bind the child instance to the iframe, as shown in Example 1-21. Every YUI instance has a win and a doc configuration value. By default, these values point to the native DOM window and document that are hosting YUI, but you can change them to point to the window and document of a different frame.

To set win and doc, use document.getElementById() to get a DOM reference to the iframe, then set win to the frame’s contentWindow and doc to the frame’s contentWindow.document. Note that win and doc are core configuration values and cannot be set to be YUI Node objects, as this would presuppose that every YUI instance has the node rollup loaded and available.

Example 1-21. Binding a YUI instance to an iframe

<!DOCTYPE html>
<title>Binding a YUI instance to an iframe</title>

<iframe src="iframe.html" id="frame"></iframe>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI().use('node', function (Y) {
    var frame = document.getElementById('frame'),
        win = frame.contentWindow,
        doc = frame.contentWindow.document;

    YUI({ win: win, doc: doc }).use('node', function (innerY) {
        var innerBody = innerY.one('body');
        innerBody.addClass('foo');
        innerBody.append('<p>YUI3 was loaded!</p>');
    });
});
</script>

Nested instances are one of the few reasons to name the callback something other than Y. innerY is a fully functional YUI instance bound to the iframe’s window and document. It has all the capabilities of a conventional Y instance, but scoped to the iframe. For example, calling innerY.one('body') gets the iframe’s body, not the parent’s body.

Note

For security reasons, modern browsers prevent a parent document from manipulating a framed document with JavaScript unless the URLs of both documents have the same domain, protocol, and port. For this reason, be sure to host your iframes on the same server as the parent document.

If you try out Example 1-21 on your local filesystem using Chrome, the example fails due to Chrome’s strict security policies around local files and JavaScript. In this case, just copy the example files to a real web server.

Discussion

If win and doc are not configured properly, iframes can be tricky to work with. For instance, the following code fails:

var frame = Y.one('#foo');
var h1 = frame.one('h1'); 

The first line is just fine: it retrieves a Y.Node instance for the iframe with an id of foo. But a naive call to frame.one() or frame.all() fails because YUI is scoped to work on the parent document.

One approach would be to add <script> markup and JavaScript code directly in the iframe, but this is clunky. The better strategy is to bind the iframe’s window and document objects to a nested YUI instance. Within that instance, the YUI Node API works as expected on the iframe’s content. Driving the iframe from the parent keeps all your code in one place and avoids having to fetch all your JavaScript code a second time from within the iframe. The iframe also has access to the Y instance for easy communication with the parent document.

If the iframe needs additional modules, you can first load them into the parent instance with a Y.use(), and then in the Y.use() callback, call innerY.use() to attach the module to the inner YUI instance. Example 1-22 is identical to Example 1-21, except that it also pulls in the event rollup in order to set a click event on the body of the iframe.

Example 1-22. Loading additional modules into an iframe

<!DOCTYPE html>
<title>Loading additional modules into an iframe</title>

<iframe src="iframe.html" id="frame"></iframe>

<script src="http://yui.yahooapis.com/3.5.0/build/yui/yui-min.js"></script>
<script>
YUI().use('node', function (Y) {
    var frame = document.getElementById('frame'),
        win = frame.contentWindow,
        doc = frame.contentWindow.document;

    YUI({ win: win, doc: doc }).use('node', function (innerY) {
        var innerBody = innerY.one('body');
        innerBody.addClass('foo');
        innerBody.append('<p>YUI3 was loaded!</p>');

        Y.use('event', function () {
            innerY.use('event', function () {
                innerBody.on('click', function () {
                    innerBody.replaceClass('foo', 'bar');
                });
            });
        });
    });
});
</script>

If your application makes heavy use of iframes, consider using Y.Frame, a utility included in the YUI Rich Text Editor widget.

See Also

“Security in Depth: Local Web Pages” and Chromium Issue 47416, which describe the Chrome team’s security concerns around local files, JavaScript, and frames; Andrew Wooldridge’s “Hidden YUI Gem—Frame” (http://andrewwooldridge.com/blog/2011/04/14/hidden-yui-gem-frame/), which discusses a handy utility for working with iframes.

Implementing Static Loading

Problem

You want to improve YUI’s initial load time by first loading all the modules you need in a single HTTP request, then attaching all modules to the Y instance at once.

Solution

Use the YUI Configurator to handcraft a combo load URL for the YUI seed file and the exact list of modules you need. Then use this URL to fetch all YUI code in a single HTTP request. Once the code has downloaded, call use('*') to attach all YUI modules in the registry.

Ordinarily, the callback function passed into use() executes asynchronously after YUI calculates dependencies and fetches any missing resources. However, if you know that you have already loaded all modules you need onto the page, you can provide the special value '*' to use(), as shown in Example 1-23. This special value means that all necessary modules have already been loaded statically, and instructs YUI to simply attach every module in the registry to the Y. Even conditional modules, described in Loading Modules Based on Browser Capabilities, get attached right away—regardless of the results of their test function.

Example 1-23. Loading node-base and dependencies statically

<!DOCTYPE html>
<title>Loading node-base and dependencies statically</title>

<div id="demo"></div>

<script type="text/javascript" src="http://yui.yahooapis.com/combo?
3.5.0/build/yui-base/yui-base-min.js&3.5.0/build/oop/oop-min.js&
3.5.0/build/event-custom-base/event-custom-base-min.js&
3.5.0/build/features/features-min.js&3.5.0/build/dom-core/dom-core-min.js&
3.5.0/build/dom-base/dom-base-min.js&3.5.0/build/selector-native/selector-native-min.js&
3.5.0/build/selector/selector-min.js&3.5.0/build/node-core/node-core-min.js&
3.5.0/build/node-base/node-base-min.js&3.5.0/build/event-base/event-base-min.js"></script>
<script>
YUI({
    bootstrap: false,
}).use('*', function (Y) {
    Y.one('#demo').setHTML('Real Programmers manage their dependencies manually.');
});
</script>

For good measure, the example sets bootstrap to false, which prevents the Loader from filling in any missing dependencies.

Warning

This technique can improve performance, but not without tradeoffs. For more information, refer to this recipe’s Discussion.

Discussion

Static loading is yet another tool in your toolbox for managing application performance.

The YUI module system is designed to break large frameworks into tiny, digestible chunks that can be loaded asynchronously. This flexibility provides a huge performance advantage over monolithic libraries that force you to download the entire API whether you need it or not.

However, while dynamically constructing a custom library improves performance tremendously, it brings its own performance cost. First, calculating dependencies does not come for free. It is reasonably fast when done on the client side and very fast when done on the server side, but the cost is not zero. Second, loading YUI requires a minimum of two HTTP requests: one call to load the YUI seed file and one call to fetch the combo-loaded YUI modules.

If you are willing to throw the flexibility of the module system away, it is possible to squeeze a little extra performance from YUI. By listing all modules in the combo load URL, you can fetch everything in a single HTTP request and eliminate the need to calculate dependencies.

The disadvantage of this technique is that you are now responsible for managing your own dependencies across your entire application. If you want to upgrade to a new YUI minor version, add a YUI module to support a new feature, or remove a module that is no longer needed, you must recalculate your dependencies and update all your combo URLs yourself. If different pages might have different module requirements, you will have to maintain multiple distinct combo URLs. Static loading also makes it harder to take advantage of capability-based loading and other advanced techniques. If you are considering static loading, be sure to measure the real-world performance difference and weigh it against these increased maintenance costs.

Get YUI 3 Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.