YUI 3.x Home -

Site Search (YDN & YUIBlog):

# YUI Library Examples: TabView: Adding and Removing Tabs

## TabView: Adding and Removing Tabs

This example shows how to give `TabView` buttons for adding and removing tabs.

View example in new window.

- foo
- bar
- baz

foo content

bar content

baz content

### Plugin Template

In order to make these addons reusable, we can build them as plugins. This allows the option for multiple tabviews that mix and match functionality. To get started, we will first fill in a basic `Plugin` template. The `NAME` property is required to prefix events, classNames, et cetera. The `NS` is the namespace where the plugin will live on the `host`. This is where its API can be accessed (e.g. "node.addable.destroy()"). Adding the `this._host` alias provides a convenient way to get back to the TabView instance. Calling the superclass constructor kicks off the `Base` lifecycle, which will call the `initializer`.

    var Addable = function(config) {    this._host = config.host;    Addable.superclass.constructor.apply(this, arguments);}; Addable.NAME = 'addableTabs';Addable.NS = 'addable'; Y.extend(Addable, Y.Plugin.Base, {    initializer: function(config) {    }});

    var Addable = function(config) {
        this._host = config.host;
        Addable.superclass.constructor.apply(this, arguments);
    };

    Addable.NAME = 'addableTabs';
    Addable.NS = 'addable';

    Y.extend(Addable, Y.Plugin.Base, {
        initializer: function(config) {
        }
    });

var Addable = function(config) { this._host = config.host; Addable.superclass.constructor.apply(this, arguments); }; Addable.NAME = 'addableTabs'; Addable.NS = 'addable'; Y.extend(Addable, Y.Plugin.Base, { initializer: function(config) { } });

### Addable Tab Plugin

To simplify adding new tabs, we are going to add a button that users can click and that will prompt them for some details regarding the new tab. The main task we are trying to accomplish is to add some HTML to the `TabView`, listen for clicks on the button, prompt the user for input, and update the tabs accordingly.

#### HTML Template

The first thing we need is a template for the markup to be generated. Adding this to the prototype allows separate customization for each `TabView` instance. For this example, we want it to look and feel like another `Tab` without actually being one.

    Y.extend(Addable, Y.Plugin.Base, {    ADD_TEMPLATE: '<li class="yui3-tab" title="add a tab">' +                '<a class="yui3-tab-label yui3-tab-add">+</a></li>',     initializer: function(config) {    }});

    Y.extend(Addable, Y.Plugin.Base, {
        ADD_TEMPLATE: '<li class="yui3-tab" title="add a tab">' +
                    '<a class="yui3-tab-label yui3-tab-add">+</a></li>',

        initializer: function(config) {
        }
    });

Y.extend(Addable, Y.Plugin.Base, { ADD_TEMPLATE: '<li class="yui3-tab" title="add a tab">' + '<a class="yui3-tab-label yui3-tab-add">+</a></li>', initializer: function(config) { } });

#### Adding the HTML

Now that we have a markup template, we will need to add it to the `TabView` somehow. The `render` phase is the appropriate moment to do so. Listening for the `render` event will give us access to that moment. Listening for `after('render')` ensure that the rendering has actually happened. Then we just need to find the tab list and, using the template, add the new item. The `contentBox` provides a `Node` that can be used to manage the `TabView` HTML.

    Y.extend(Addable, Y.Plugin.Base, {    ADD_TEMPLATE: '<li class="yui3-tab" title="add a tab">' +                '<a class="yui3-tab-label yui3-tab-add">+</a></li>',     initializer: function(config) {        var tabview = this.get('host');        tabview.after('render', this.afterRender, this);    },     afterRender: function(e) {        var tabview = this.get('host');        tabview.get('contentBox').one('> ul').append(this.ADD_TEMPLATE);    }});

    Y.extend(Addable, Y.Plugin.Base, {
        ADD_TEMPLATE: '<li class="yui3-tab" title="add a tab">' +
                    '<a class="yui3-tab-label yui3-tab-add">+</a></li>',

        initializer: function(config) {
            var tabview = this.get('host');
            tabview.after('render', this.afterRender, this);
        },

        afterRender: function(e) {
            var tabview = this.get('host');
            tabview.get('contentBox').one('> ul').append(this.ADD_TEMPLATE);
        }
    });

Y.extend(Addable, Y.Plugin.Base, { ADD_TEMPLATE: '<li class="yui3-tab" title="add a tab">' + '<a class="yui3-tab-label yui3-tab-add">+</a></li>', initializer: function(config) { var tabview = this.get('host'); tabview.after('render', this.afterRender, this); }, afterRender: function(e) { var tabview = this.get('host'); tabview.get('contentBox').one('> ul').append(this.ADD_TEMPLATE); } });

#### Handling the Click

All that remains is to listen for clicks on the add button and prompt the user for the relevant `Tab` data. Again we can leverage the `Node` API, this time to delegate clicks on the add button. Stopping event propagation in our handler ensures that the event does not bubble any further, preventing the `TabView` from trying to handle it. To keep the example simple, a basic `prompt` is used to get the user input. This could be refined to use an `Overlay` or other custom control. The data is then handed off to `TabView`'s `add` method.

    Y.extend(Addable, Y.Plugin.Base, {    ADD_TEMPLATE: '<li class="yui3-tab" title="add a tab">' +                '<a class="yui3-tab-label yui3-tab-add">+</a></li>',     initializer: function(config) {        var tabview = this.get('host');        tabview.after('render', this.afterRender, this);         tabview.get('contentBox')            .delegate('click', this.onAddClick, '.yui3-tab-add', this);    },     afterRender: function(e) {        this.get('host').get('contentBox').one('> ul').append(this.ADD_TEMPLATE);    },     getTabInput: function() {        return {            label: window.prompt('label:', 'new tab'),            content: window.prompt('content:', '<p>new content</p>'),            index: Number(window.prompt('index:', this._host.size()))        }    },     onAddClick: function(e) {        e.stopPropagation();        var tabview = this.get('host');            input = this.getTabInput();        tabview.add(input, input.index);    }});

    Y.extend(Addable, Y.Plugin.Base, {
        ADD_TEMPLATE: '<li class="yui3-tab" title="add a tab">' +
                    '<a class="yui3-tab-label yui3-tab-add">+</a></li>',

        initializer: function(config) {
            var tabview = this.get('host');
            tabview.after('render', this.afterRender, this);

            tabview.get('contentBox')
                .delegate('click', this.onAddClick, '.yui3-tab-add', this);
        },

        afterRender: function(e) {
            this.get('host').get('contentBox').one('> ul').append(this.ADD_TEMPLATE);
        },

        getTabInput: function() {
            return {
                label: window.prompt('label:', 'new tab'),
                content: window.prompt('content:', '<p>new content</p>'),
                index: Number(window.prompt('index:', this._host.size()))
            }
        },

        onAddClick: function(e) {
            e.stopPropagation();
            var tabview = this.get('host');
                input = this.getTabInput();
            tabview.add(input, input.index);
        }
    });

Y.extend(Addable, Y.Plugin.Base, { ADD_TEMPLATE: '<li class="yui3-tab" title="add a tab">' + '<a class="yui3-tab-label yui3-tab-add">+</a></li>', initializer: function(config) { var tabview = this.get('host'); tabview.after('render', this.afterRender, this); tabview.get('contentBox') .delegate('click', this.onAddClick, '.yui3-tab-add', this); }, afterRender: function(e) { this.get('host').get('contentBox').one('> ul').append(this.ADD_TEMPLATE); }, getTabInput: function() { return { label: window.prompt('label:', 'new tab'), content: window.prompt('content:', '<p>new content</p>'), index: Number(window.prompt('index:', this._host.size())) } }, onAddClick: function(e) { e.stopPropagation(); var tabview = this.get('host'); input = this.getTabInput(); tabview.add(input, input.index); } });

#### Using the Plugin

Now we can go ahead and plug in our functionality. This can be during construction with the `plugins` attribute, or subsequently via the `plug` method.

    var tabview = new Y.TabView({    children: [{        label: 'foo',        content: '<p>foo content</p>'    }, {        label: 'bar',        content: '<p>bar content</p>'    }, {        label: 'baz',        content: '<p>baz content</p>'    }],    plugins: [Addable]}); // or// tabview.plug(Addable);

    var tabview = new Y.TabView({
        children: [{
            label: 'foo',
            content: '<p>foo content</p>'
        }, {
            label: 'bar',
            content: '<p>bar content</p>'
        }, {
            label: 'baz',
            content: '<p>baz content</p>'
        }],
        plugins: [Addable]
    });

    // or
    // tabview.plug(Addable);

var tabview = new Y.TabView({ children: [{ label: 'foo', content: '<p>foo content</p>' }, { label: 'bar', content: '<p>bar content</p>' }, { label: 'baz', content: '<p>baz content</p>' }], plugins: [Addable] }); // or // tabview.plug(Addable);

### Removeable Tabs Plugin

The process for creating a removeable plugin for `TabView` is very similar. The full source is provided below.

    var Removeable = function(config) {    Removeable.superclass.constructor.apply(this, arguments);}; Removeable.NAME = 'removeableTabs';Removeable.NS = 'removeable'; Y.extend(Removeable, Y.Plugin.Base, {    REMOVE_TEMPLATE: '<a class="yui3-tab-remove" title="remove tab">x</a>',     initializer: function(config) {        var tabview = this.get('host'),            cb = tabview.get('contentBox');         cb.addClass('yui3-tabview-removeable');        cb.delegate('click', this.onRemoveClick, '.yui3-tab-remove', this);         // Tab events bubble to TabView        tabview.after('tab:render', this.afterTabRender, this);    },     afterTabRender: function(e) {        // boundingBox is the Tab's LI        e.target.get('boundingBox').append(this.REMOVE_TEMPLATE);    },     onRemoveClick: function(e) {        e.stopPropagation();        var tab = Y.Widget.getByNode(e.target);        tab.remove();    }});

    var Removeable = function(config) {
        Removeable.superclass.constructor.apply(this, arguments);
    };

    Removeable.NAME = 'removeableTabs';
    Removeable.NS = 'removeable';

    Y.extend(Removeable, Y.Plugin.Base, {
        REMOVE_TEMPLATE: '<a class="yui3-tab-remove" title="remove tab">x</a>',

        initializer: function(config) {
            var tabview = this.get('host'),
                cb = tabview.get('contentBox');

            cb.addClass('yui3-tabview-removeable');
            cb.delegate('click', this.onRemoveClick, '.yui3-tab-remove', this);

            // Tab events bubble to TabView
            tabview.after('tab:render', this.afterTabRender, this);
        },

        afterTabRender: function(e) {
            // boundingBox is the Tab's LI
            e.target.get('boundingBox').append(this.REMOVE_TEMPLATE);
        },

        onRemoveClick: function(e) {
            e.stopPropagation();
            var tab = Y.Widget.getByNode(e.target);
            tab.remove();
        }
    });

var Removeable = function(config) { Removeable.superclass.constructor.apply(this, arguments); }; Removeable.NAME = 'removeableTabs'; Removeable.NS = 'removeable'; Y.extend(Removeable, Y.Plugin.Base, { REMOVE_TEMPLATE: '<a class="yui3-tab-remove" title="remove tab">x</a>', initializer: function(config) { var tabview = this.get('host'), cb = tabview.get('contentBox'); cb.addClass('yui3-tabview-removeable'); cb.delegate('click', this.onRemoveClick, '.yui3-tab-remove', this); // Tab events bubble to TabView tabview.after('tab:render', this.afterTabRender, this); }, afterTabRender: function(e) { // boundingBox is the Tab's LI e.target.get('boundingBox').append(this.REMOVE_TEMPLATE); }, onRemoveClick: function(e) { e.stopPropagation(); var tab = Y.Widget.getByNode(e.target); tab.remove(); } });

Copyright © 2010 Yahoo! Inc. All rights reserved.

Privacy Policy - Terms of Service - Copyright Policy - Job Openings