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 theasync
anddefer
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 theYUI
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.
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.
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:
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.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, theuse()
method works in the following manner: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 globalYUI
object.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.When
use()
finishes loading modules, it decorates the YUI instance with the complete API you requested.Finally,
use()
executes the callback function, passing in the YUI instance as theY
argument. Within the callback function, theY
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.
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.
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.
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.
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>
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.
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.
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>
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 .
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.
You want to load a useful third-party module from the YUI gallery and use it alongside core YUI modules.
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>
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.
You want to use one of your favorite widgets from YUI 2, but it hasn’t been ported over to YUI 3 yet.
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>
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.
By default, the YUI
object is configured to fetch from Yahoo!
servers. You can change this by:
Downloading the latest stable YUI SDK zip file from http://yuilibrary.com.
Unzipping the zip file in some directory under your web server’s web root.
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).
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
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.path
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/
.
For alternative seed files, YUI inspects your seed file URL and resets
version
/buildbase
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:
PHP Combo Loader, the reference implementation, written by the YUI team. Old and stable, but not under active development.
Node.js Combo Loader, written and maintained by Ryan Grove.
Perl Combo Loader, written and maintained by Brian Miller.
ASP.NET Combo Loader, written and maintained by Gabe Moothart.
Python/WSGI Combo Loader, written and maintained by Chris George.
Ruby on Rails Combo Loader, written and maintained by Scott Jungling.
To install and operate a particular combo loader, refer to that combo loader’s documentation.
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.
Use YUI.add()
to register your code as a module
with the YUI
global object. At minimum,
YUI.add()
takes:
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 .
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 theYUI
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.
Instructions for using YUI Builder.
You want to create a custom YUI module and ensure that it pulls in another YUI module as a dependency.
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 therequires
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.
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 totrue
. For example,autocomplete-base
declares an optional dependency onautocomplete-sources
, which contains extra functionality for populating anAutoComplete
widget from YQL and other remote sources.loadOptional
isfalse
by default.Even if
loadOptional
isfalse
, 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 earlierYUI().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
.csswhere
base
is the value of thebase
field (discussed in Defining Groups of Custom Modules) andskin-name
is the name of the skin, which defaults to the valuesam
. 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.
Bundling CSS with a Widget as a Skin; Douglas Crockford on “Private Members in JavaScript”.
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.
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']});
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.
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.
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.pngmodule-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.
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.
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 .
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>
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.
You want to load jQuery and some jQuery plugins into the sandbox alongside YUI, just like any YUI module.
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).
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.
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.
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.)
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 local
Storage
. 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
.
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:
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.
The W3C standard for
web storage; the YUI
Cookie
API.
You want to conditionally load extra code at runtime to patch a YUI bug or hack new behavior into YUI.
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); } });
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.
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.
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:
Within
showOverlay()
, callY.use()
to load theoverlay
module.Within the
Y.use()
callback function:Create a new
Overlay
instance, initially set to be invisible.Redefine the
showOverlay()
function to do something else. The next timeshowOverlay()
is called, it will simply show the hidden overlay instance.Call the newly redefined
showOverlay()
from withinshowOverlay()
to make the overlay instance visible.
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 ofshowOverlay()
, the version that loads theoverlay
module, instantiates an overlay, and then redefines itself. Subsequent clicks invoke the “light” version ofshowOverlay()
, 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>
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.
Eric Ferraiuolo’s gallery-base-componentmgr
module, which makes it easy to lazy-load Y.Base
-derived objects and their
dependencies.
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.
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
load
Overlay()
function and a showOverlay()
function.
The
loadOverlay()
function has different behavior depending on whether the overlay has already been instantiated, theoverlay
module is currently loading, or theoverlay
module needs to start loading.loadOverlay()
takes a callback function, which turns out to beshowOverlay()
. If the overlay has already been instantiated,loadOverlay()
executes the callback and returns immediately.If the
overlay
module is currently loading, this means the overlay is not yet ready to show.loadOverlay()
queues the callback up in thecallbacks
array and returns immediately.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 theoverlay
module.loadOverlay()
callsY.use()
to load theoverlay
module on the fly.The
Y.use()
callback instantiates the overlay, setsoverlayLoading
tofalse
(indicating that it is permissible to show the overlay), and finally executes anyshowOverlay()
callbacks that have queued up while the code was loading.
The
showOverlay()
function is considerably simpler. If the overlay is already instantiated, the function shows the overlay. Otherwise,showOverlay()
callsload
Overlay()
with itself as the callback, which guarantees thatloadOverlay()
has at least one instance ofshowOverlay()
queued up and ready to fire as soon as the overlay is instantiated.The
hideOverlay()
function is simpler still. If the overlay is already instantiated, the function shows the overlay.Finally, the script attaches event handlers to the Show Button and Hide Button. The
on()
method attaches an event handler, while theonce()
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.
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.
You want to manipulate an iframe using JavaScript in the parent document, without actually having to directly load YUI into the iframe.
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.
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.
“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.
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.
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.
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.