Jump to Table of Contents

Example: Creating a Node Plugin that chains transitions

In order to run transitions sequentially, you would normally have to use the callback provided by node.transition(). This example shows how to create your own Node plugin based on promises that lets you chain CSS transitions.

Using Promises to Chain CSS Transitions

The plan

Plugins are a way to add functionality to Node without modifying its existing methods. They also are usually subclasses of Plugin.Base that contain various methods to interact with in a different way with a node. In our case we will skip the use of Plugin.Base to focus on returning promises from a plugin method.

The plan is to create a Promise subclass that represents a Node and store one of these promises in the plugin instance. Then the plugin's transition method will return a new promise based on the one already stored.

Creating a Promise Subclass

Promises represent a value. Since we want to chain transitions on a Node we need to create a Promise sublcass that represents a Node. Promises can be extended the same way as any other YUI class by using Y.extend.

// NodePromise will represent a YUI Node
function NodePromise() {
    NodePromise.superclass.constructor.apply(this, arguments);
}
Y.extend(NodePromise, Y.Promise);

The next step is to add the transition() method to this promise and have it return a promise that is fulfilled when the transition is completed.

// This method takes the same "config" parameter as Node's transition method
// but returns a NodePromise instead
NodePromise.prototype.transition = function (config) {
    // We call this.then to ensure the promise is fulfilled.
    // Since we will be creating a chain of transitions this means we will be
    // waiting for the previous transition to end
    return this.then(function (node) {
        // As noted in the user guide, returning a promise inside the then()
        // callback causes the promise returned by then() to be synced with this
        // new promise. This is a way to control when the returned promise is
        // fulfilled
        return new Y.Promise(function (fulfill, reject) {
            node.transition(config, function () {
                // The transition is done, signal the promise that all is ready
                // by fulfilling it with the same node
                fulfill(node);
            });
        });
    });
};

Creating the Plugin

Our plugin is a very simple class that contains a NodePromise. In order for it to let us write chains of transitions like node.promise.transition(config1).transition(config2) we will add a transition method to it that simply points to the NodePromise's same method.

function PromisePlugin(config) {
    // Create a private NodePromise instance that points to the plugin host
    this._promise = new NodePromise(function (fulfill) {
        // Since this is a Node plugin, config.host will be an instance of Node
        fulfill(config.host);
    });
}

// Set up the plugin's namespace
PromisePlugin.NS = 'promise';

PromisePlugin.prototype.transition = function (config) {
    // Simply point to the private promise's transition method
    return this._promise.transition(config);
};

Using the Plugin

Now that we have the plugin ready, we can easily chain transitions from the plugin instance:

var square = Y.one('#square');
square.plug(PromisePlugin);

// run a sequence of transitions
square.promise
    .transition({width: '300px'})
    .transition({height: '300px'})
    .transition({left: '200px'});

Full Code Listing

HTML

<button id="without-plugin">Without Plugin</button>
<button id="with-plugin">With Plugin</button>
<div id="square"></div>

CSS

<style scoped>
    #square {
        width: 100px;
        height: 100px;
        background: gray;
        position: relative;
        margin: 20px;
    }
</style>

JavaScript

<script>
YUI().use('promise', 'transition', 'node-pluginhost', function (Y) {

// NodePromise will represent a YUI Node
function NodePromise() {
    NodePromise.superclass.constructor.apply(this, arguments);
}
Y.extend(NodePromise, Y.Promise);

// This method takes the same "config" parameter as Node's transition method
// but returns a NodePromise instead
NodePromise.prototype.transition = function (config) {
    // We call this.then to ensure the promise is fulfilled.
    // Since we will be creating a chain of transitions this means we will be
    // waiting for the previous transition to end
    return this.then(function (node) {
        // As noted in the user guide, returning a promise inside the then()
        // callback causes the promise returned by then() to be synced with this
        // new promise. This is a way to control when the returned promise is
        // fulfilled
        return new Y.Promise(function (fulfill, reject) {
            node.transition(config, function () {
                // The transition is done, signal the promise that all is ready
                // by fulfilling it with the same node
                fulfill(node);
            });
        });
    });
};

function PromisePlugin(config) {
    // Create a private NodePromise instance that points to the plugin host
    this._promise = new NodePromise(function (fulfill) {
        // Since this is a Node plugin, config.host will be an instance of Node
        fulfill(config.host);
    });
}

// Set up the plugin's namespace
PromisePlugin.NS = 'promise';

PromisePlugin.prototype.transition = function (config) {
    // Simply point to the private promise's transition method
    return this._promise.transition(config);
};


var square = Y.one('#square');
square.plug(PromisePlugin);

function resetStyles() {
    square.setStyles({
        width: '100px',
        height: '100px',
        left: '0'
    });
}

Y.one('#without-plugin').on('click', function () {
    resetStyles();
    square
        .transition({width: '300px'})
        .transition({height: '300px'})
        .transition({left: '200px'});
});
Y.one('#with-plugin').on('click', function () {
    resetStyles();
    square.promise
        .transition({width: '300px'})
        .transition({height: '300px'})
        .transition({left: '200px'});
});

});
</script>