How to make closable tabs with Nitrogen and jQuery UI 1.9

A minor annoyance with using jQuery UI lib is that some of the controls are missing obvious features. I don’t want to call the lib “half-baked” – jQuery UI is of good quality but sometimes you can’t help but to be irritated by it.

One of the obvious misses is the absence of ability to add “Close” button to a tab in Tabs control. All the tabs in all the browsers always have an icon in top right corner to close the tab but jQuery UI designers for some mysterious reasons decided to leave it out.

Fortunately, it is possible to extend jQuery controls and add your own functionality. In this post I will give the example of how to add Close button to Tabs Element in Nitrogen_Element lib and have both Closable and Unclosable tabs:

Screen Shot 2013-04-13 at 19.56.38

Andrew Watts wrote a great post a while back showing how to make closable tabs for jQuery 1.8 but this method doesn’t work anymore for jQuery 1.9.

First thing you need to be aware of is that jQuery UI 1.9 has been completely re-designed in order to pave the way for the next major release jQuery 1.10. This means that prototype based extension method which worked for jQuery UI 1.8 is no longer working.

Instead, you need to use the widget factory method ($.widget()). Please refer to official Widget documentation for more details but in short, this is the method used for creation of brand new widget as well as for extension of the existing ones. Here is the useful Gist which compares the extension differences between jQuery 1.8 and 1.9.

To extend Tabs all you need to do is to call widget factory and pass the name of your widget and the base class to inherit from.
Defining a widget with the same name as the one you inherit from allows to extend the widget in place. The third parameter is the prototype object which implements the override methods of the base class implementation or adds new methods:

$.widget( "ui.tabs&amp", $.ui.tabs, { ... } );  

I re-used some of the Andrew’s code and re-wrote it to be compatible with jQuery 1.9 and added some of my own features. Here is the full ui.tabs.closable.js module and I will explain what it does and how to use it.


(function() {
    $.widget( "ui.tabs", $.ui.tabs, {
	options: {
	    spinner: "<em>Loading…</em>",
	    closable: false
	},
	_removeTab: function( index ) {
	    index = this._getIndex( index );
	    tab = this.tabs.eq( index ).remove();
	    // permanently remove the tab
	    this.tabs.splice( index, 1 );
	    // remove a panel
	    panel = this._getPanelForTab( tab ).remove();
	    // select a tab
	    if( tab.hasClass( "ui-tabs-active" ) && this.tabs.length > 2 ) {
	    	this._activate( index + ( index + 1 < this.tabs.length ? 1 : -1 ));
	    };
	    this._refresh();
	    return this;
	},

	_processTabs: function() {
	    this._super( "_processTabs" );
	    var self = this;
	    var lis = this.tablist.children( ":has(a[href])" );
	    // remove annoying link outline at tabs title
	    lis.children("a").css('outline', 'none');

	    if (this.options.closable === true) {
	    	var unclosable_lis = lis.filter(function() {
		    // return tabs which don't have '.ui-closable-tab' yet and also not marked with '.ui-unclosable-tab'
                    return ($('.ui-closable-tab', this).length === 0 && $('.ui-unclosable-tab', this).length === 0);
	    	});

		// append the close button and associated events
		unclosable_lis.each(function() {
                    $(this)
			.append('<a href="#"><span class="ui-icon ui-icon-circle-close ui-closable-tab"></span></a>')
			.css('outline', 'none')
			.find('a:last .ui-closable-tab')
                        .hover(
                            function() {
				$(this).addClass('ui-icon-circle-triangle-e');
                                $(this).css('cursor', 'pointer');
                            },
                            function() {
				$(this).removeClass('ui-icon-circle-triangle-e');
				$(this).css('cursor', 'default');
                            }
                        )
                        .click(function(e) {
                            // don't follow the link
			    e.preventDefault();
			    // get fresh state of the tabs list
			    var lis = self.tablist.children( ":has(a[href])" );
			    // get index of the tabs
                            var index = lis.index($(e.delegateTarget).parent().parent());
                            if (index > -1) {
                                // remove this tab
                                self._removeTab(index);
                            }
                        })
			.end();
		});
	    }
	}
    });
})(jQuery);


First you need to add a reference to ui.tabs.closable.js on your page, depending on where ui.tabs.closable.js lives in your directory structure:

<script src='/plugins/ui.tabs.closable/js/ui.tabs.closable.js' type='text/javascript' charset='utf-8'></script>

Then you need to add a html markup for Tabs to your page and init tabs control. Nitrogen_Elements Tabs control will create a html markup and instantiate jQuery Tabs for you if you add the following record to you Nitrogen page:

#tabs{
   id = tabs,
   options = [{selected, 0}, {closable, true}],
   tabs = [
	#tab{title = "Tab 1", url = "/content/tabs2.htm", closable=false},
	#tab{title = "Tab 2", body = ["Tab two body..."]}
    ]
}

Notice that you need to add option {closable, true} to tell the extension method to add Close link to your tabs. If you want some of your tabs *not to have* close button you need to add option “closable=false” for the Nitrogen record for a given tab. Under the covers Nitrogen Tabs control will add “.ui-unclosable-tab” class to the tab anchor and this will tell tabs extension method to skill the tab.

Extension module overrides _processTabs() method, first we call _processTabs() from base class, then select a list of tabs anchors – this is the list of tabs titles and remove outlines for anchors which some browsers render by default.

this._super( "_processTabs" );
var self = this;
var lis = this.tablist.children( ":has(a[href])" );
// remove annoying link outline at tabs title
lis.children("a").css('outline', 'none');

then, if Option closable=true was passed to the control, we get a list of tabs which don’t yet have class ‘.ui-closable-tab’ and also haven’t been explicitly excluded from having Close button with class ‘.ui-unclosable-tab’.

if (this.options.closable === true) {
     var unclosable_lis = lis.filter(function() {
	// return tabs which don't have '.ui-closable-tab' yet and also not marked with '.ui-unclosable-tab'
        return ($('.ui-closable-tab', this).length === 0 && $('.ui-unclosable-tab', this).length === 0);
});
...
}

After that for each element of this list we append a new anchor and set an iron for a span element and also mark this span with ‘ui-closable-tab’ class:

.append('<a href="#"><span class="ui-icon ui-icon-circle-close ui-closable-tab"></span></a>')

We also remove outline for this anchor and add action for Hover event : we want the icon to change when we pass a mouse over the Close icon.

.css('outline', 'none')
.find('a:last .ui-closable-tab')
.hover(
  function() {
	$(this).addClass('ui-icon-circle-triangle-e');
        $(this).css('cursor', 'pointer');
       },
  function() {
	$(this).removeClass('ui-icon-circle-triangle-e');
	$(this).css('cursor', 'default');
     }
)

We also attach to Click event on the span to detect the index of the tab whose Close button was clicked and remove the tab:

.click(function(e) {
   // don't follow the link
  e.preventDefault();
  // get fresh state of the tabs list
  var lis = self.tablist.children( ":has(a[href])" );
  // get index of the tabs
  var index = lis.index($(e.delegateTarget).parent().parent());
  if (index > -1) {
      // remove this tab
      self._removeTab(index);
  }
})

When we click Close icon – this will call private _removeTab(index) and in this method we remove the title link and tab panel and also activate the closest remaining tab:

	_removeTab: function( index ) {
	    index = this._getIndex( index );
	    tab = this.tabs.eq( index ).remove();
	    // permanently remove the tab
	    this.tabs.splice( index, 1 );
	    // remove a panel
	    panel = this._getPanelForTab( tab ).remove();
	    // select a tab
	    if( tab.hasClass( "ui-tabs-active" ) && this.tabs.length > 2 ) {
	    	this._activate( index + ( index + 1 < this.tabs.length ? 1 : -1 ));
	    };
	    this._refresh();
	    return this;
	}

This is all the code you need to implement closable Tabs control if you are using jQuery 1.9 or higher.

The full Nitrogen example is here.

But if you just need to use closable Tabs in your pure javascript code here is the separate project just for ui.tabs.closable.js module.

Advertisements
This entry was posted in custom Nitrogen elements, Erlang, Nitrogen, Nitrogen_Elements, Uncategorized and tagged , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s