#

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>