Jump to Table of Contents

App Framework

The YUI App Framework is a rollup of the App, Model, Model List, Router, and View components, combined to form a simple MVC-style framework for writing single-page JavaScript applications.

You can use these components separately or together to create anything from simple non-interactive views to rich applications with URL-based routing, data binding, and full client-server synchronization.

If you've used DocumentCloud's excellent Backbone.js framework, many of the classes and APIs provided by App Framework components will look familiar to you. There are important differences, though, and the App Framework takes full advantage of YUI's powerful component and event infrastructure under the hood.

Getting Started

To include the source files for App Framework and its dependencies, first load the YUI seed file if you haven't already loaded it.

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

Next, create a new YUI instance for your application and populate it with the modules you need by specifying them as arguments to the YUI().use() method. YUI will automatically load any dependencies required by the modules you specify.

<script>
// Create a new YUI instance and populate it with the required modules.
YUI().use('app', function (Y) {
    // App Framework is available and ready for use. Add implementation
    // code here.
});
</script>

For more information on creating YUI instances and on the use() method, see the documentation for the YUI Global Object.

Components of the App Framework

The app module is a rollup module consisting of the following individual components. These components may also be used individually if you don't need all of them at the same time.

Component Module Description
App app-base

Provides a top-level application component which manages navigation and views. This gives you a foundation and structure on which to build your application; it combines robust URL navigation with powerful routing and flexible view management.

Model model

A lightweight Y.Attribute-based data model with APIs for getting, setting, validating, and syncing attribute values to a persistence layer or server, as well as events for notifying subscribers of model changes.

Model List model-list

An array-like ordered list of Y.Model instances with APIs for adding, removing, sorting, filtering, and performing other actions on models in the list. Acts as a bubble target for events fired by the models in the list.

Router router

Provides URL-based same-page routing using HTML5 history (pushState) or the location hash, depending on what the user's browser supports.

View view

Represents a piece of an application's user interface and is responsible for rendering content and handling DOM events. Views are commonly associated with Models or Model Lists and are re-rendered when they change, although this isn't required.

App Component

App is a high-level component that builds upon other components in the App Framework. The App component is composed of Router and View, and also the Pjax utility. This combination creates a solid foundation and structure on which to build your application. It connects together robust URL navigation with powerful routing and flexible view management.

The goal of the App component is to provide you a place to organize and connect together the parts of your application. App implements infrastructural pieces which are common to all apps — such as managing views and the navigation between pages — allowing you to focus on the specifics of your app.

App will enable you to seamlessly enhance the user experience and performance of traditional client/server apps. It enables you to create richer interactions without compromising standard browser behavior, URLs, or search engine crawl-ability. The Routing Coordination with Server section of this guide contain details on accomplishing this.

You can also use the App component to build client-only apps for when there is no server, or the server is not capable of routing and handling requests. There are drawbacks to client-only apps which you need to be aware of and fully understand their implications. Be sure to read the Progressively-enhanced vs. Client-only Apps section which contains details on these drawbacks and best practices.

The remaining sections of this guide provide details on what you'll need to know to start working with the App component. Refer to the previous section for information about the other components of the App Framework.

Using App

Instantiating App

Creating an App instance couldn't be any simpler:

var app = new Y.App();

Two instantiable classes are provided by the app-base module: Y.App, and Y.App.Base. The difference between these is that Y.App.Base provides the basic app functionality and will remain pure; whereas Y.App (which extends Y.App.Base) will have all of the app-component extensions automatically mixed-in when they are included in the YUI instance.

In the following example, we are including both the app-base and app-transitions modules. When the app-transitions module is added to the YUI instance it will automatically mix itself into the Y.App class, adding transitions to all Y.App instances, but Y.App.Base will remain unaffected by the inclusion of this module.

YUI().use('app-base', 'app-transitions', function (Y) {
    // This will create two YUI Apps, `basicApp` will not have transitions,
    // but `fancyApp` will have transitions support included and turn itself on.
    var basicApp = new Y.App.Base(),
        fancyApp = new Y.App({transitions: true});
});

While App instances are usable without any configuration, any non-trivial app will need to be configured. You might also want to extend Y.App by mixing-in additional functionality, or even create a custom App class to implement the specific features of your application. The Extending Y.App section explains how to do this.

Config Properties

When constructing a new Y.App instance you can provide a config object with initial values for attributes along with values for Y.View's properties and Y.Base's "special" properties (used only during initialization). In addition to these, the following non-attribute properties have specific initialization logic applied and can be passed to the Y.App constructor:

views

Hash of view-name to metadata used to declaratively describe an application's views and their relationship with the app and its other views. The views specified here will override any defaults provided by the views object on the prototype. Every Y.App instance gets its own copy of a views object so this object on the prototype will not be polluted.

See the App Properties and Declaring Views sections for more details.

Here's an example that defines some views at instantiation time:

var app = new Y.App({
    views: {
        home : {preserve: true},
        users: {preserve: true},
        user : {parent: 'users'}
    }
});

App Properties

The following properties are meaningful to App classes and subclasses. In addition to these, View's properties are also applicable Y.View is part of App's composition.

Property Default Value Description
views {}

Hash of view-name to metadata used to declaratively describe an application's views and their relationship with the app and its other views. See Declaring Views for more details.

The view metadata is composed of Objects keyed to a view-name that can have any or all of the following properties:

type

Function or a string representing the view constructor to use to create view instances. If a string is used, the constructor function is assumed to be on the Y object; e.g. "SomeView" -> Y.SomeView.

preserve

Boolean for whether the view instance should be retained. By default, the view instance will be destroyed when it is no longer the activeView. If true the view instance will simply be removed() from the DOM when it is no longer active. This is useful when the view is frequently used and may be expensive to re-create. See Preserved Views for best practices.

parent

String to another named view in this hash that represents the parent view within the application's view hierarchy; e.g. a "photo" view could have "album" as its parent view. This parent/child relationship is a useful cue for things like transitions.

instance

Used internally to manage the current instance of this named view. This can be used if your view instance is created up-front, or if you would rather manage the View lifecycle, but you probably should just let this be handled for you.

transitions {}

Default transitions to use when the activeView changes. See the Switching the Active View and Y.App.Transitions sections for more details.

The following are types of changes for which transitions can be defined that correspond to the relationship between the new and old activeView:

navigate

The default transition to use when changing the activeView of the application. This will be "fade" by default.

toChild

The transition to use when the new activeView is configured as a child of the previously active view via its parent property as defined in this app's views. This will be "slideLeft" by default.

toParent

The transition to use when the new activeView is configured as the parent of the previously active view as defined in this app's views. This will be "slideRight" by default.

The App class uses both properties and attributes. Properties are best when their stored data might be useful to multiple App instances, whereas attributes are best when the data being stored only pertains to a single instance.

App Attributes

App is composed of View, Router, and Pjax, all of which provide attributes that will be of interest to you — beyond these, App adds the following attributes:

Attribute Default Value Description
activeView null

The application's active/visible view. This attribute is read-only, to set the activeView use the showView() method.

See Switching the Active View for more details.

serverRouting undefined

Whether or not this application's server is capable of properly routing all requests and rendering the initial state in the HTML responses.

This can have three different values, each having particular implications on how the app will handle routing and navigation:

undefined

The best form of URLs will be chosen based on the capabilities of the browser. Given no information about the server environment, a balanced approach to routing and navigation is chosen. See URLs Based on Browser Capabilities for more details.

true

The server is fully capable of properly handling requests to all full-path URLs the app can produce.

This is the best option for progressive-enhancement because it will cause all URLs to always have full-paths, which means the server will be able to accurately handle all URLs this app produces. See Full-path URLs Only and Progressively-enhanced vs. Client-only Apps for more details.

false

The server is not capable of properly handling requests to all full-path URLs the app can produce, therefore all routing will be handled by this App instance.

Be aware that this will cause all URLs to always be hash-based, even in browsers that are capable of using HTML5 history. Please make sure you fully understand the implications of client-only apps. See Hash-based URLs Only and Progressively-enhanced vs. Client-only Apps for more details.

See Routing Coordination with Server for more details.

transitions false

Whether or not this application should use view transitions, and if so then which ones or true for the defaults which are specified by the transitions prototype property.

Note: Transitions are an opt-in feature and will only be used in browsers which support native CSS3 transitions.

See the Switching the Active View and Y.App.Transitions sections for more details.

viewContainer <div> Node

The node into which this app's views will be rendered when they become the activeView.

The view container node serves as the container to hold the app's activeView. Each time the activeView is set via showView(), the previous view will be removed from this node, and the new active view's container node will be appended.

The default view container is a <div> Node, but you can override this in a subclass, or by passing in a custom viewContainer config value at instantiation time. The viewContainer may be provided as a selector string, DOM element, or a Y.Node instance (having the viewContainer and the container be the same node is also supported).

The app's render() method will stamp the view container with the CSS class "yui3-app-views" and append it to the app's container node if it isn't already, and any activeView will be appended to this node if it isn't already.

See Rendering an App for more details.

A few of App's inherited attributes are given new default values:

Attribute Inherited From New Default Value Reason
container Y.View <body> Node

Apps are considered to be full-page by default.

html5 Y.Router auto

This value is dependent on the value of serverRouting and will default accordingly.

linkSelector Y.PjaxBase "a"

By default this selector should match all links on the page because full-page apps are the default.

Routing Coordination with Server

A primary feature of App is its powerful routing which enables you to enhance the user experience and performance of traditional client/server apps. Using Y.App you can create more meaningful and richer interactions for a user navigating through your application. App's routing features center on progressive enhancement and do not compromise standard browser features (back/forward button support), URLs, or search engine craw-ability.

There are two attributes which are used to configure an app's URLs and how its routing should coordinate with the server:

root

An app's root is the absolute path from which all routes should be evaluated, i.e. the path at which the app is mounted, usually "/".

See Setting the Root Path in Router's user guide for more information.

serverRouting

Models a higher level concept over the capabilities of, and interactions between the browser and server with respect to URLs and inter-page navigation. This attribute determines how your Y.App instance will interact with the server: set this to true if the app can be progressively enhanced, or false if it's a client side-only app.

Note: The value of an app's html5 attribute depends on the value of its serverRouting attribute. When serverRouting is explicit set to false (not just falsy), the default value for html5 will be set to false for all browsers. When serverRouting is true or undefined the returned value will be dependent on the browser's capability of using HTML5 history.

Looking for general information about routing? See the Routing section in Router's user guide.

URLs Based on Browser Capabilities

This is the default behavior of how an app's routing will coordinate with the server. When serverRouting is left undefined, the app is given no information about the server's capabilities. In this case a balanced approach to URLs, routing, and navigation will be used:

  • Full-path URLs will only be used if the browser is capable of using HTML5 history, otherwise hash-based URLs will be used.

  • The Y.App instance will handle all routing since it is unknown whether the server is capable of properly routing requests.

  • The server is required to handle full-path URLs which will be generated by browsers which use the HTML5 pushState() history API. A server can handle these requests by either rendering the initial state in the HTML, or redirecting to a hash-based URL and letting the app dispatch to its route handlers.

This behavior has been chosen as the default because it provides the best path forward for a developer to upgrade a client side-only app to a progressively enhanced app (but you should always prefer to build an app using progressive enhancement from the start, that's the whole point).

The following is a simple Node.js Express server and very basic Y.App instance which demonstrate how the client and server can coordinate routing and the fulfillment of a request when using URLs that conform to the capabilities of the browser, but the server is "dumb":

server.js - The Node.js Express server
var express = require('express'),
    server  = express();

// Handles requests to the root path ("/") by simply sending the "shell" page
// which creates the `Y.App` instance.
server.get('/', function (req, res) {
    res.sendfile('index.html');
});

// Handles all other requests by redirecting the browser back to the root path
// and tacking on URL's path as the fragment; e.g. "/foo/" => "/#/foo/".
server.get('*', function (req, res) {
    res.redirect('/#' + req.url);
});

server.listen(process.env.PORT || 3000);
index.html - The "shell" page for the Y.App instance
<!DOCTYPE html>

<html>
<head>
    <meta charset="utf-8" />
    <title>Example App</title>
</head>
<body>

    <h1></h1>

    <script src="http://yui.yahooapis.com/3.11.0/build/yui/yui-min.js"></script>
    <script>
    YUI().use('app-base', function (Y) {

        var app = new Y.App({
            // Configure the app.
        });

        // Handles requests for the root by updating the page heading.
        app.route('/', function () {
            Y.one('h1').set('text', 'Example App - Home');
        });

        // Handles all other requests by updating the page heading and
        // displaying current path in it.
        app.route('*', function (req) {
            Y.one('h1').set('text', 'Example App - ' + req.path);
        });

        // Make sure to dispatch the current hash-based URL which was set by
        // the server to our route handlers.
        app.render().dispatch();

    });
    </script>

</body>
</html>

In the above example, the server handles full URLs by sending a redirect back to the root ("/") with a hash-based path and serves a "shell" HTML page to delegate control to a Y.App instance. Once the app is loaded, it will dispatch the hash-based path to its route handlers, finishing the fulfillment of the request on the client.

You could also implement the server-side redirection logic using an Apache Mod Rewrite .htaccess file:

RewriteEngine On
RewriteRule ^(.+)$ /#/$1 [NE,L]

Full-path URLs Only

Full-path URLs are standard URLs which have matching routes based on the URL's path segments. The follow are example full-path URLs you might expect to see in a photos application:

http://example.com/
http://example.com/photos/
http://example.com/photos/1
http://example.com/photos/2
http://example.com/albums/
http://example.com/albums/vacation

Setting an app's serverRouting attribute to true signals that the server is capable of properly handling requests to all full-path URLs the app can produce. This causes the app to always use full-path URLs, regardless of whether the browser can use HTML5 history or not. In this case the capabilities of the client and server are coordinated in the most optimal way, giving the app the following qualities:

  • Full-path URLs are always used, older browsers which can not use HTML5 history will have to do full page loads.

  • The Y.App instance will only handle routing in browser which support HTML5 history, the other browser's "requests" will be handled completely by the server.

  • The server is required to handle full-path URLs which will be generated by the app. Ideally the server should render the application's initial state in the response HTML for the given URL.

This is the best option for progressive enhancement! All users of your app will have the same, full-path URLs, independent of the browser or device they are using. Configuring your app with serverRouting set to true means that the client and server components of your application are working together to provide the best overall experience for your users.

See Progressively-enhanced vs. Client-only Apps for more information about best practices.

Hash-based URLs Only

Hash-based URLs have matching routes based on the URL's fragment identifier ("#"). The follow are example hash-based URLs you might expect to see in a photos application:

http://example.com/#/
http://example.com/#/photos/
http://example.com/#/photos/1
http://example.com/#/photos/2
http://example.com/#/albums/
http://example.com/#/albums/vacation

Notice how the above URLs actually all contain the same path segment ("/"), but use the fragment identifier ("#") to hold a pseudo-path value.

Setting an app's serverRouting attribute to false signals that the server is not capable of properly handling requests to full-path URLs. This causes the app to always use hash-based URLs, regardless of the browser's capabilities. In this case the app will assume that there is no server to rely on, giving the app the following qualities:

  • Be aware that all URLs will be hash-based, even in browsers which are capable of using HTML5 history.

  • The Y.App instance will handle all routing since there is no server capable of properly routing handling requests.

  • The server is only expected to serve out a static HTML "shell" page with the required JavaScript to instantiate the Y.App instance, which will then render the initial state of the application.

This is the best option if there is no guarantee that your app will be served by a capable server. This allows an app to run when hosted on the filesystem or static file server. You should be aware that using hash-based URLs means there will be information loss. The server will not receive the full URL because browsers do not send the fragment-part to the server, that is everything after and including the "#".

Please read Progressively-enhanced vs. Client-only Apps for more information and best practices.

Rendering an App

App inherits both its container attribute and render() method from View. Unlike View's empty render() implementation, App provides a default implementation which appends the activeView (if there is one) to the viewContainer node which itself is appended to the container node.

The basic usage of your app's render() method is to call it at least once, usually after you instantiate your app, this ensures the proper DOM structure is setup to handle rendering the app's views.

var app = new Y.App();
app.render();

This results in the HTML of the page looking like this:

<body class="yui3-app">
    ...
    <div class="yui3-app-views"></div>
</body>

By default, an app's container node will be the <body> element and its viewContainer node will be a new <div>.

Note: The "yui3-app" and "yui3-app-views" CSS classes are added to the container and viewContainer respectively — this happens when the app is rendered.

Specifying Container Nodes

When constructing a new App instance you can specify values for the app's container and viewContainer attributes, and they can even reference the same node.

var app = new Y.App({
    container    : '#wrapper',
    viewContainer: '#wrapper'
});

app.render();

Assuming that a <div id="wrapper"> node already exists on the page, this uses a CSS selector string to reference the node, assigns it to both containers, and results in the following HTML:

<div id="wrapper" class="yui3-app yui3-app-views">
    ...
</div>

If you specify a container that is not already within the markup of the page, you'll need to manually append it to an element that is:

var app = new Y.App({
    container: Y.Node.create('<div id="fancy-app" />')
});

app.render().get('container').appendTo('body');

This results in the HTML of the page looking like this:

<body>
    ...
    <div id="fancy-app" class="yui3-app">
        <div class="yui3-app-views"></div>
    </div>
</body>

Refer to App's API Docs for more details about container and viewContainer attributes.

Overriding render()

You may override the render() method to customize how the app renders itself, particularly if you are creating an App subclass.

Note: You should expect that the viewContainer's contents will be modified by the app for the purpose of rendering the activeView when it changes; and ideally your render() method should also return this at the end to allow chaining, but that's up to you.

The following provides a templates for how you could subclass Y.App and implement a custom render() method while still preserving its default behavior:

Y.CustomApp = Y.Base.create('customApp', Y.App, [], {
    render: function () {
        // This calls the superclass' (Y.App) implementation of the `render()`
        // method to preserve the default behavior.
        Y.CustomApp.superclass.render.apply(this, arguments);

        // Provide your custom rendering logic here.

        // Returns this `Y.CustomApp` instance to allow for chaining.
        return this;
    }
});

Refer to App's API Docs for more details about the render() method. See Subclassing for more details on extending Y.App.

View Management

A primary feature of App is its flexible view management, which enables you to declare the primary views of your application and easily switch which one is active. This is very handy for defining your app's top-level "page" views, then switching between them as a user navigates through the application.

Declaring Views

The views property of an App class allows you to specify a mapping of view-names to view-metadata that should be registered to your app. This way you can specify information about your app's views — how they should be treated by the app, and how they relate to other views — up-front, in a declarative way that is self-documenting.

You can setup this views mapping on both App subclasses and instances. Every App instance will receive its own copy of a views object, any views metadata defined at the class-level will be used as defaults and merged with any views specified during instantiation time.

The following example shows the creation of an App subclass, CustomApp, which has a few default views defined, and an instance of CustomApp which defines another view and overrides some of the defaults.

// Create a Y.CustomApp class that extends Y.App.
Y.CustomApp = Y.Base.create('customApp', Y.App, [], {
    // Default registered views inherited by all CustomApp instances.
    views: {
        home : {preserve: true},
        users: {preserve: true},
        user : {parent: 'users'}
    }
});

// Create a CustomApp instance that inherits the defaults and adds to them.
var app = new Y.CustomApp({
    // Additional view metadata to be merged with the defaults.
    views: {
        home : {preserve: false},
        user : {preserve: false},
        about: {preserve: true}
    }
});

Using the getViewInfo() method, we can see how the views metadata from our CustomView class and instance were merged together.

// Overwrote "home"'s default `preserve` value.
Y.log(app.getViewInfo('home').preserve);  // => false

// Added `preserve` to "user" view,
// and this did not overwrite the default `parent` value.
Y.log(app.getViewInfo('user').parent);    // => "home"
Y.log(app.getViewInfo('user').preserve);  // => false

// The specified "about" view was registered.
Y.log(app.getViewInfo('about').preserve); // => true

See the App Properties section above for more details on what metadata can be stored for each view in views mapping.

Switching the Active View

When decomposing an application into discrete user-interfaces, it is natural to think of these as different "pages"—with each one serving a particular role and being the main content on the screen. With the App component, changing the main content/user-interface is done by updating an app's activeView attribute via the showView() method.

Working in concert with an app's registered views, the showView() method will take a specified view and make it the app's activeView. This view will be "attached" to the app by rendering it inside the app's viewContainer and any custom events fired by the view will bubble to the app. Any previously active view will be "detached" from the app, removed from the DOM, and either preserved for later use or properly destroyed.

The following example is the most basic "Hello World" app:

// Creates a new App and View instance.
var app  = new Y.App(),
    view = new Y.View();

// Overrides the view's `render()` method to render text into its `container`.
view.render = function () {
    this.get('container').set('text', 'Hello World!');
    return this;
};

// Renders the `app` and `view`, then sets the view as the app's `activeView`.
// We have to explicity tell the app to render the view because by default, if
// the view instance already exists it won't be rendered automatically.
app.render().showView(view, null, {render: true});

// Verify that `view` is now the `activeView`, and that the view's `container`
// is now rendered within the app's `viewContainer`.
Y.log(app.get('activeView') === view);                           // => true
Y.log(app.get('viewContainer').contains(view.get('container'))); // => true

This results in the HTML of the page looking like this:

<body class="yui3-app">
    ...
    <div class="yui3-app-views">
        <div>Hello World!</div>
    </div>
</body>

This example app can easily become dynamic and have the ability to say hello to someone by name. By creating a reusable HelloView class the app can dynamically switch between outputting "Hello World!" and "Hello [name]!" where the "name" is a path segment in the URL.

// Creates a HelloView which can say hello to someone named, or to the World
// if a name is not specified.
Y.HelloView = Y.Base.create('helloView', Y.View, [], {
    render: function () {
        var name = this.get('name');
        this.get('container').set('text', 'Hello ' + (name || 'World') + '!');
        return this;
    }
});

// Creates a new App instance and registers the HelloView.
var app = new Y.App({
    views: {
        hello: {type: 'HelloView'}
    }
});

// Adds a route handler for "/" to show the HelloView.
app.route('/', function (req) {
    // Sets the `activeView` to a new instance of a `Y.HelloView` by just
    // passing "hello", the name of the registered view.
    this.showView('hello');
});

// Adds a route handler for "/:name" to show the HelloView with a `name`.
app.route('/:name', function (req) {
    // The name which we want to say hello to is specified on `req.params`.
    var name = req.params.name;

    // Sets the `activeView` to a new instance of a `Y.HelloView`, but here
    // we are also passing a config object which the new view instance will
    // be constructed with, and it contains the name which we'll say hello to.
    this.showView('hello', {name: name});
});

// Renders the app, then saves a new history entry for "/eric" which will
// dispatch the "/:name" route handler.
app.render().save('/eric');

This results in the URL being updated to either "/eric" or "/#/eric" depending on whether the browser is capable of HTML5 history, and the HTML of the page looking like this:

<body class="yui3-app">
    ...
    <div class="yui3-app-views">
        <div>Hello eric!</div>
    </div>
</body>
Understanding showView() Options

App's showView() method has the following signature:

showView ( view  [config]  [options]  [callback] )

As seen in the previous usage examples, view can either be a view instance or the string-name of a view defined in the app's views object. The optional config object is used when creating a new view instance. The config object can also be used to update an existing or preserved view's attributes when options.update is true.

The following is the list of options which affect how the app will switch its activeView:

callback

Optional callback function to call after new activeView is ready to use, the function will be passed a reference to the view.

Specifying a callback function is useful if you need access to the view instance which is created as a result of calling showView(). Also, if an app is using transitions, the callback function will be called once the transitions have completed.

prepend

Whether the view should be prepended instead of appended to the app's viewContainer node.

This option is used internally to aid transitions by prepending the new activeView if it has been defined as the parent of the currently active view. This puts the view as the first child of the app's viewContainer node which helps the sliding transitions.

render

Whether the view should be rendered. Note: If no value is specified, a view instance will only be rendered if it's newly created by this method.

Sometimes you may need greater control over when a view is rendered. Under certain circumstances you may wish to re-render a preserved view, you can use this option to accomplish this.

transition

Optional transition override. A transition can be specified which will override the default, or false for no transition.

When an app is using transitions, this option allows you to override the default transition that will occur per the app's transitions configuration — enabling finer-grained control over how views transition.

update

Whether an existing view should have its attributes updated by passing the config object to its setAttrs() method. Note: This option does not have an effect if the view instance is created as a result of calling this method.

This option would usually be used to update a preserved view with new data and can be used in conjunction with the render option to force a view to update and re-render.

Refer to App's API docs for more details about the showView() method.

Server Rendered Views

There are certain situations where an app needs to show a view which is not rendered in the browser, instead the content is rendered server side. The App component provides a few features for working with pre-rendered views:

showContent()

Method which provides an easy way to view-ify HTML content which should be shown as an app's active/visible view.

loadContent()

Route middleware which load content from a server. This makes an Ajax request for the requested URL, parses the returned content and puts it on the route's response object.

Y.App.Content.route

A stack of middleware which forms a pjax-style content route. This provides a standard convention which uses loadContent() and showContent() to load and show server rendered content as views for an app.

Progressively Enhanced Views

When a person first accesses an app, the initial rendering of the UI might be done by the server; in this situation, when the app "boots up" in the browser, the showContent() method can be used to view-ify this pre-rendered content and make it the app's activeView. Using the server to render the initial state of the app in the HTML it sends to the browser is a general best practice. See Progressively-enhanced vs. Client-only Apps for more details.

The following is a basic example of using showContent() during the initialization of an app:

Assume the page contains the following HTML:

...
<body>
    <div id="wrapper">

        <div class="view">
            <h1>Some Page</h1>
            <p>With some content.</p>
        </div>

    </div>
</body>
...

The Y.App instance can be setup to use this initial content as its activeView:

var app = new Y.App();

// Renders the app and shows the content which matches the specified selector.
app.render().showContent('#wrapper > .view');

Y.log(Y.one('#wrapper > .view') === app.get('activeView').get('container'));
// => true

When calling the showContent() method here, a new Y.View instance will be created and the <div class="view"> node will be its container; and we can see this by checking the app's activeView.

The showContent() method becomes more powerful when it's called with options. The following is a list of options in addition to any showView() options:

view

The name of a view defined in this app's views, or an object with the following properties:

name

The name of a view defined in this app's views.

config

Optional configuration to use when creating the new view instance. This config object can also be used to update an existing or preserved view's attributes when options.update is true. Note: If a container is specified, it will be overridden by the content specified in the first argument of showContent().

Let's look at a more interesting example which specifies a view name registered with the app:

Assume the page contains the following HTML:

...
<body>
    <div id="wrapper">

        <div class="view" data-view="foo">
            <h1>Foo Page</h1>
            <p>With some content.</p>
            <p>
                <button>Close</button>
            </p>
        </div>

    </div>
</body>
...

This time, the Y.App instance will be setup to use a Y.FooView with the initial content as its activeView:

Y.FooView = Y.Base.create('foo', Y.View, [], {
    events: {
        'button': {click: 'handleButtonClick'}
    },

    handleButtonClick: function (e) {
        alert('You click the button, and alerts are annoying.');
    }
});

var app, content;

app = new Y.App({
    // The example assumes there's support for rendering on the server. That's
    // the whole point with progressive enhancement.
    serverRouting: true,

    views: {
        foo: {type: Y.FooView}
    }
});

content = Y.one('#wrapper > .view');

// Renders the app and shows the `content` node _and_ specifies which view
// should be created.
app.render().showContent(content, {view: content.getData('view')});

Y.log(app.get('activeView') instanceof Y.FooView); // => true

Refer to App's API Docs for more details about the showContent() method.

Loading Static Content

Another common use of server rendered views is static content pages. While the interesting parts of an app are using models, views, and templates to render content dynamically in the browser, the app might also contain static content pages; e.g. "About Us". Instead of giving the client side of the app the smarts to render this type of static content, Y.App.Content.route—which is a stack of middleware that forms a pjax-style content route—can be used. This route uses a convention of loading HTML from the server via an XHR, and making that content the app's activeView by calling into the showContent() method.

The following example uses both the Y.App.Content.route for the "/about/" page, and the loadContent() middleware for the "/about/people/", allowing for more control over which view is used:

Y.PeopleView = Y.Base.create('people', Y.View, [], {
    events: {
        '.person': {click: 'showPersonOverlay'}
    },

    showPersonOverlay: function (e) {
        // Show an overlay with the all info about the person...
    }
});

var app = new Y.App({
    views: {
        people: {type: Y.PeopleView}
    }
});

// Uses the convenient content route to load the "/about/" pjax-style.
app.route('/about/', Y.App.Content.route);

// Uses the `loadContent()` middleware to load the HTML from the server, but
// specifies a custom route callback to control which view is created.
app.route('/about/people/', 'loadContent', function (req, res, next) {
    Y.config.doc.title = res.content.title;
    this.showContent(res.content.node, {view: 'people'});
});

// Render the app and dispatch to the route handlers.
app.render().dispatch();

Refer to App's API Docs for more details about the loadContent() middleware, and the Y.App.Content.route route.

A key feature of Y.App is its robust URL navigation management. You can simply use standard HTML links within your app and when the user clicks a link, Y.App will handle it if there’s a matching route for that URL. With the navigation-related config options you have full control over your app's navigation behavior and experience, plus your app will automatically use the best available navigation method based on these settings, the browser’s capabilities, and the actions of the user.

This enables an app to fulfill a "page request" by using data it already has stored in models or loading new data, then composing and showing a view which represents the app's current state for this URL — all without requiring a full page load.

Programmatic navigation

Beyond handling navigation via link clicks, Y.App also exposes a programmatic way to navigate the user though your app via the navigate() method.

App's navigate() method implements a higher level concept of "browsing" over the save() and replace() methods. It will manage a user's navigation history like a browser, and is the recommended method to use when programmatically navigating the user to URLs within your app.

The navigate() method will do the right thing (what the browser would do) when navigating the user to the same URL they are currently on — it will replace the history entry — or to an in-page fragment identifier — which only when configured will navigate. The following demonstrates some of these behaviors:

var app = new Y.App();

app.route('*', function (req, res, next) {
    // Handle all URLs.
});

// Save a new history entry for "/", or replaces the current entry if we're
// already on "/".
app.navigate('/'); // => true

// Does not navigate even though there's a matching route handler.
app.navigate('#top'); // => false

// Enable navigation on hash-only changes to the URL.
app.set('navigateOnHash', true);

// Does navigate because `navigateOnHash` was enabled.
app.navigate('#top'); // => true

Refer to App's API Docs for more details about the navigate() method.

Configuring Navigation Behavior

The navigation features of Y.App are built on the base pjax functionality. This is what enables users to navigate to the different sections or "pages" of an app while avoiding full page loads.

The following are configuration attributes which define an app’s navigation behavior:

Attribute Defined in Default Value Description
linkSelector Y.PjaxBase "a"

CSS selector string used to filter link click events so that only the links which match it will have the enhanced-navigation behavior of pjax applied.

When a link is clicked and that link matches this selector, navigating to the link's href URL using the enhanced, pjax, behavior will be attempted; and the browser's default way to navigate to new pages will be the fallback.

By default this selector will match all links on the page.

navigateOnHash Y.PjaxBase false

Whether navigating to a hash-fragment identifier on the current page should be enhanced and cause the navigate event to fire.

By default Pjax allows the browser to perform its default action when a user is navigating within a page by clicking in-page links (e.g. <a href="#top">Top of page</a>) and does not attempt to interfere or enhance in-page navigation.

scrollToTop Y.PjaxBase true

Whether the page should be scrolled to the top after navigating to a URL.

When the user clicks the browser's back button, the previous scroll position will be maintained.

serverRouting Y.App.Base undefined

Whether or not this application's server is capable of properly routing all requests and rendering the initial state in the HTML responses.

See App Attributes and Routing Coordination with Server for more details.

When the user is navigating to a URL for which the app has a route handler, the navigate event will fire. The default action of this event updates the browser’s address bar to reflect the new URL, causing the app to dispatch to the matching route handlers.

Listening to your app's navigate event is a useful way to indicate to the user that something is loading while they wait for the app to fully handle the new URL, possibly loading data from a remote server.

var app = new Y.App({
    views: {
        users: {}
    },

    users: new Y.ModelList()
});

app.route('/users/', function () {
    var users = app.get('users');

    // Load users data from a remote server.
    users.load(function () {
        app.showView('users', {users: users});

        // Removes the "loading" class from the app's `container` node.
        app.get('container').removeClass('loading');
    });
});

// Listen for the app's `navigate` event.
app.on('navigate', function (e) {
    // Adds the "loading" class to the app's `container` node.
    app.get('container').addClass('loading');
});

// Navigate the user to "/users/".
app.navigate('/users/');

Refer to App's API Docs for more details about the navigate event.

Extending Y.App

The Y.App class is intended to be mutable insofar as being the host for all App-component features, whereas Y.App.Base is intended to remain pure and just host the basic set of features. This allows for two different ways to extend the functionality of Y.App: mixing-in features, and subclassing.

Mixing-In Features

Additional class extensions can be automatically mixed-into Y.App, doing so will dynamically extend the functionality of the App class making these new features available to all of its instances and subclasses. The Y.App.Content and Y.App.Transitions class extensions, provided by the app-content and app-transitions modules respectively, use this pattern to add features Y.App.

The following shows a simplified view of how the transitions feature is added to Y.App:

// Creates the namespace for the transitions class extension and assigns a
// simple constructor function to it.
Y.App.Transitions = function () {};

// Defines the `transitions` attribute with a default value of `false`. This
// makes the view transitions an opt-in feature.
Y.App.Transitions.ATTRS = {
    transitions: {value: false}
};

// This defines the prototype of the transitions class extension, the actual
// implementation has been left out for the sake of simplicity.
Y.App.Transitions.prototype = {

    // The implementation of the transition features would be here.

};

// Here the transitions class extension is being applied automatically by
// mixing itself into the `Y.App` class.
Y.Base.mix(Y.App, [Y.App.Transitions]);

When this module is included in the YUI instance, the transitions support for App can be used by simply toggling it on:

YUI().use('app-base', 'app-transitions', function (Y) {
    var app = new Y.App({transitions: true});
});

When writing your own class extensions to add features to Y.App, feel free to add them to App's namespace (e.g., Y.App.SomeNewFeature), and be sure to follow these two rules:

  1. The additional functionality should be disabled by default. The API for the class extensions should provide some way for the user to opt-in to using the features it adds.

    The Y.App.Transitions class extension does this be requiring a "truthy" value for the transitions attribute. By default the value will be false, making this an opt-in feature.

  2. Be courteous to the other App component class extensions, since they all share the same prototype be careful not to unintentionally overwrite other properties or methods.

Subclassing

Creating class extensions for mixing-in features to Y.App is a great way to extend its functionality in a reusable way while not changing how you Instantiate an App; but might find it more useful to extend the Y.App class to create a subclass customized to your specific needs.

Use the Y.Base.create() method to extend Y.App and add or override prototype and static members and attributes. You may also optionally specify one or more class extensions to mix into your new class.

// Create a Y.CustomApp class that extends Y.App.
Y.CustomApp = Y.Base.create('customApp', Y.App, [], {
    // Add or override prototype properties and methods here.
}, {
    // Add static properties and methods here.

    ATTRS: {
        // Add or override default attributes here.
    }
});

One benefit of extending Y.App is that you can easily add default views, routes, and route handlers to your custom App class, and they'll be shared by all instances of that class unless overridden at the instance level:

// Create a Y.CustomApp class that extends Y.App.
Y.CustomApp = Y.Base.create('customApp', Y.App, [], {
    // Default registered views inherited by all CustomApp instances.
    views: {
        home : {preserve: true},
        users: {preserve: true},
        user : {parent: 'users'}
    },

    // Default route handlers inherited by all CustomApp instances.

    handleHome: function (req) {
        // Handle the "/" route here.
    },

    handleUsers: function (req) {
        // Handle the "/users/" route here.
    },

    handleUser: function (req) {
        // Handle the "/users/:name/" route here.
    }
}, {
    ATTRS: {
        // Share these routes with all CustomApp instances.
        routes: {
            value: [
                {path: '/',             callbacks: 'handleHome'},
                {path: '/users/',       callbacks: 'handleUsers'},
                {path: '/users/:name/', callbacks: 'handleUser'}
            ]
        }
    }
});

// Create a CustomApp instance that inherits the defaults and adds to them.
var app = new Y.CustomApp({
    // Register an additional view. The `home`, `users`, and `user` views will
    // also be inherited.
    views: {
        about: {preserve: true}
    }
});

// Add a route and route handler.
app.route('/about/', function (req) {
    // Handle the "/about/" route here.
});

Now all instances of Y.CustomApp will inherit all the custom defaults and can add to or override them. The app instance created here will handle the "/", "/users/", and "/users/:name/" routes in addition to its own "/about/" route.

Before you subclass Y.App, you should refer to App's API docs to become familiar with its public and protected properties and methods.

App Extensions

Y.App.Content

The Content extension provides pjax-style content fetching and handling. This makes it easy to fetch server rendered content for URLs using Ajax. The HTML content returned from the server will be view-ified and set as the app's main content, making it seamless to use a mixture of server and client rendered views.

Y.App.Transitions

The Transitions extension provides view transitions for apps in browsers which support native CSS3 transitions. View transitions visually enhance the change from one "page" to the next that is both pleasant to the user and helps to communicate a hierarchy between sections of an application.

Enabling Transitions

Enabling transitions for an app just requires opting-in. The following will create a new Y.App instance with the default transitions enabled:

var app = new Y.App({transitions: true});

With transitions enabled for an app, anytime the app's activeView changes, there will be a visual transition from the old to the new active view. How an app's views are configured effects which transition will be used, e.g. when changing between views which have a hierarchical relationship a sliding transition will be used.

Types of activeView Changes

The following are the types of activeView changes for which transitions can be defined that correspond to the relationship between the new and old activeView:

navigate

The default transition to use when changing the activeView of the application. This will be "fade" by default.

toChild

The transition to use when the new activeView is configured as a child of the previously active view via its parent property as defined in this app's views. This will be "slideLeft" by default.

toParent

The transition to use when the new activeView is configured as the parent of the previously active view as defined in this app's views. This will be "slideRight" by default.

Overriding Default Transitions

The default transitions can be overridden in the following ways:

The following example will override which transitions should be used by default, and specifically set the transition option on certain calls to showView().

var app = new Y.App({
    // The app's three views and their relationships between one another.
    views: {
        home   : {},
        about  : {},
        contact: {parent: 'about'},
        team   : {parent: 'about'}
    },

    // Overrides the default transitions types all to "fade" which will
    // cross-fade between `activeView` changes.
    transitions: {
        navigate: 'fade',
        toChild : 'fade',
        toParent: 'fade'
    },

    // The app's `team` model list for use by the "team" view.
    team: new Y.ModelList().reset([
        {name: 'Eric Ferraiuolo'},
        {name: 'Ryan Grove'}
    ])
});

app.route('/', function () {
    // Will transition via "fade".
    app.showView('home');
});

app.route('/about/', function () {
    // Will transition via "fade", even though we maybe coming from a child
    // view (i.e. "contact" or "team").
    app.showView('about');
});

app.route('/about/contact/', function () {
    // Will transition via "fade", even though we maybe coming from the parent
    // "about" view.
    app.showView('contact');
});

app.route('/about/team/', function () {
    // Shows the "team" view, passing it the app's `team` model list, and
    // overrides the `transition` options so no visual transitions will occur.
    app.showView('team', {team: this.get('team')}, {transition: false});
});

app.render().dispatch();

Transition-Aiding CSS

Some structural CSS is required to setup an app's container and viewContainer nodes so the app's views transition properly. While an app's views are transitioning, the CSS class: yui3-app-transitioning will be added to the app's container node.

Note: While transitioning, the app's viewContainer node will have its overflow-x set to hidden. This causes its margins to not collapse with its child nodes. To compensate for this, it is best to not style your views with margins.

Best Practices

Progressively-enhanced vs. Client-only Apps

It's important to understand the difference between the preferred approach of developing an app using a progressive enhancement strategy vs. creating a client side-only app, and when it might be appropriate to do so. As discussed in the Routing Coordination with Server, Y.App provides the tools and is flexible enough to implement an app using either strategy, but there are drawbacks to client-only applications.

The following sections build on Router's Best Practices, all of which apply, by looking at things from a higher level, application perspective:

Full-path vs. Hash-based URLs

Before making a decision to use hash-based URLs in your application, you should be aware of their drawbacks.

A client-only app will use hash-based URLs instead of the standard, full-path URLs. When the server receives a request for a hash-based URL everything after and including the fragment identifier ("#") is lost. This means your application will have to rely on the JavaScript to finish fulfilling the request for the user.

The following shows how this information loss manifests itself:

Browser URL: http://example.com/#/foo/bar/
Request URL: http://example.com/

The request URL that the server will see is missing the entire path, this means every URL in your application looks to the server as if it's the same page! To prevent link rot in your application, you'll have to support the hash-based variation of your URLs indefinitely.

Ideally you should prefer full-path URLs (e.g., http://example.com/foo/bar/) for your application and be able to handle full page loads for browsers which are not capable of using HTML5 history. This requires a server capable of routing requests and rendering the initial state of the application in the HTML responses. This is the main idea behind a progressive enhancement development strategy — providing a better experience for more capable browsers/devices.

Performance and SEO

Client-only apps suffer in more places than just their URLs. There are horrible side-effects of using hash-based URLs: degradation in performance of rendering the app's initial state, and lack of SEO.

It is faster to render the initial state of an app on the server before delegating control to the client-side JavaScript. But in order to achieve this, the server must be capable of routing requests and rendering this initial state. This means your application needs to be using full-path URLs.

To render the initial state in a client-only app, the server has to send over the HTML "shell" page, which then makes more HTTP requests to get the JavaScript. Once the JavaScript has been downloaded and parsed, it can execute and construct the Y.App instance. The app now needs to fetch data from the server (yet another HTTP request), and then it can display the initial state. All while the user is staring at a blank page — which is a horrible user experience, especially on a mobile device with a slower connection.

Optimizing your app to be indexed by search engines also relates to this. SEO requires that your app uses full-path URLs so the server can route requests and render the initial state. The basic functions of your app which you want to be indexable should work without JavaScript.

When Client-only Apps are Appropriate

Given all the drawbacks of client-only apps, there's one major advantage: they don't rely on the server. If your app needs to run off the filesystem, or be deployed to a "dumb" static file server, then this might be your only option.

Developing a client-only app using Y.App still gives you nice (but hash-based) URLs that are bookmarkable, browser back/forward button support, and view management.

Please make sure you understand the issues with hash-based URLs and their side-effects. Consider developing your application using a progressive enhancement strategy, it will be worth the extra effort in the long run.

Flow Control

When developing an app, it's important to have a clear understanding about how the various components are working together. The use of consistent patterns for information sharing, and execution of operations aid in maintaining a separation of concerns which becomes ever more crucial as the size of the app and team increases.

Restricting the flow of information to a single direction creates a "one-way street" which enforces a strict separation of concerns and divides control over execution throughout an application. This strategy can be implemented by leveraging YUI's powerful custom event infrastructure. All of the components of App Framework are Y.Base-based, and therefore are primed to be both publishers and subscribers of an app's custom events.

The following example app demonstrates how to wire up the components of an application using custom events to achieve the one-way flow of information and divide control by maintaining a strict separation of concerns:

// Create the `UserView`.
Y.UserView = Y.Base.create('userView', Y.View, [], {
    template: '<p>{name}</p>' +
              '<label for="name-input">Name:</label>' +
              '<input id="name-input" type="text" value="{name}" />' +
              '<button>Save</button>',

    // Listens for DOM events from the user and translates them into application
    // events; e.g., "click" -> "save".
    events: {
        'button': {click: 'save'}
    },

    initializer: function () {
        // Listens for the user model to change, and re-renders the view.
        this.get('user').after('change', this.render, this);
    },

    render: function () {
        var user    = this.get('user'),
            content = Y.Lang.sub(this.template, {name: user.get('name')});

        this.get('container').setHTML(content);
        return this;
    },

    // This view doesn't actually do the saving, instead, it is translating the
    // user's intent (they click the Save button) to an application-level event.
    // This way control is given to the app to respond to this action.
    save: function () {
        var user = this.get('user'),
            name = this.get('container').one('input').get('value');

        // Fires the save event and passes along the needed information. When
        // this is the app's `activeView` this event will bubble to the app.
        this.fire('save', {
            user: user,
            name: name
        });
    }
});

// Create the app and define the app's `views`, and assign a new `Model`
// instance as an ad-hoc attribute to the app.
var app = new Y.App({
    views: {
        user: {
            type    : 'UserView',
            preserve: true
        }
    },

    user: new Y.Model()
});

// The app listens for the `UserView` to fire its `save` event. The only will
// only be notified if the view is the app's current `activeView`.
app.on('userView:save', function (e) {
    // The app has the control to execute update the model and execute the save
    // operation. Updating the model will cause the view to re-render.
    e.user.set('name', e.name).save();
});

// Renders the app and set's the `user` view and the `activeView`, passing it
// the user model.
app.render().showView('user', {user: app.get('user')});

The main thing to note about the above example is that the UserView is not updating its user model directly, instead it is translating the user's intent via DOM events into application-level events. This gives control back to the app so it can decide how best to proceed. The result is a flow of information and control that moves in one direction and in a continuous loop.

Preserved Views

An app's views can be defined so that a single instance of a particular view is preserved and can be reused. When a view is defined with its preserve property set to true, the same view instance will be used each time the app's showView() method is called to set it as the app's activeView.

Preserved views should be used in situations where the contents of the view might not change and that view will be displayed often. They are especially useful if its contents have a complex DOM structure that would be costly to create each time the view was to be shown.

Imagine a simple app that displays user information in two views: a users view that lists all the users, and a user view that displays the details of a single user. If the set of all users in this application won't change often, then it would be a good idea to make the users view a preserved view. That way it only has to be constructed once, and the same view instance can be shown many times. Whereas the contents of the user view are always going to change depending on which user is being displayed, making it not suitable to preserve.

The following example demonstrates how this hypothetical users app would define its two views:

// Create the `UsersView`.
Y.UsersView = Y.Base.create('usersView', Y.View, [], {
    // Implementation would be here...
});

// Create the `UserView`.
Y.UserView = Y.Base.create('userView', Y.View, [], {
    // Implementation would be here...
});

// Create the app and define its two views.
var app = new Y.App({
    views: {
        // Only one `users` view instance will be created and it will be
        // preserved for reuse.
        users: {
            preserve: true,
            type    : 'UsersView'
        },

        // A new `user` view instance will be created every time.
        user: {
            type  : 'UserView',
            parent: 'users'
        }
    }
});

Be cautious about the number of preserved views you have in your application. Their DOM structures are maintained even when they are not in the visible portion of the page's DOM, this means the more preserved views, the more memory your application will consume. On mobile devices with very limited memory and no swap space, consuming too much memory in your app can cause the browser to crash.

Known Limitations

  • When multiple App instances are on the page, only one can manage the URL and navigation. All of the app instances can have route handlers, but only one should have its linkSelector attribute set to a non-falsy value. See Configuring Navigation Behavior for more details.

  • Transitions are only supported in WebKit and Firefox browsers which also support CSS3 transitions. It is intended to only support transitions in browser which have native CSS3 transitions, and YUI's Transition component only supports WebKit and Firefox.

App relies heavily on Router which has some known limitations in Internet Explorer 9 and lower, and Android 2.x. See Router's Known Limitations for more details.