This example shows how to give TabView
buttons for adding and removing tabs.
Note: be sure to add the yui3-skin-sam
classname to the
page's <body>
element or to a parent element of the widget in order to apply
the default CSS skin. See Understanding Skinning.
<body class="yui3-skin-sam"> <!-- You need this skin class -->
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) { } });
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.
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) { } });
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); } });
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); } });
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);
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(); } });
Note: be sure to add the yui3-skin-sam
classname to the
page's <body>
element or to a parent element of the widget in order to apply
the default CSS skin. See Understanding Skinning.
<body class="yui3-skin-sam"> <!-- You need this skin class -->
<div id="demo"> <ul> <li><a href="#foo">foo</a></li> <li><a href="#bar">bar</a></li> <li><a href="#baz">baz</a></li> </ul> <div> <div id="foo"> <p>foo content</p> </div> <div id="bar"> <p>bar content</p> </div> <div id="baz"> <p>baz content</p> </div> </div> </div> <div id="demo2"> </div> <div id="demo3"> </div> <script type="text/javascript"> YUI().use('tabview', 'escape', 'plugin', function(Y) { var Addable = function(config) { Addable.superclass.constructor.apply(this, arguments); }; Addable.NAME = 'addableTabs'; Addable.NS = 'addable'; 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); }, getTabInput: function() { var tabview = this.get('host'); return { label: Y.Escape.html(window.prompt('label:', 'new tab')), content: '<p>' + Y.Escape.html(window.prompt('content:', 'new content')) + '</p>', index: Number(window.prompt('index:', tabview.size())) } }, afterRender: function(e) { var tabview = this.get('host'); tabview.get('contentBox').one('> ul').append(this.ADD_TEMPLATE); }, onAddClick: function(e) { e.stopPropagation(); var tabview = this.get('host'), input = this.getTabInput(); tabview.add(input, input.index); } }); 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 tabview = new Y.TabView({ srcNode: '#demo', plugins: [Addable] }); var tabview2 = 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: [Removeable] }); var tabview3 = new Y.TabView({ children: [{ label: 'foo', content: '<p>foo content</p>' }, { label: 'bar', content: '<p>bar content</p>' }, { label: 'baz', content: '<p>bar content</p>' }], plugins: [Addable, Removeable] }); tabview.render(); tabview2.render('#demo2'); tabview3.render('#demo3'); }); </script>