Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add or remove jQueryUI tabs using knockout JS?

I've been trying to use knockout to add/remove jQueryUI tabs but haven't had any luck. My view model is an array of Effect objects. I'd like a tab added/removed from the tab control as objects are added/removed from the view model.

Here's a JSFiddle someone started and I updated which shows what I'm looking to do JSFiddle example

It will break when you try to add a tab. I think I need to combine the template binding w/ a new custom binding that can destroy/recreate the tab control I think. I'd greatly appreciate any help. Thanks!

like image 326
C.J. Avatar asked Sep 11 '11 21:09

C.J.


1 Answers

I thought I'd update this question with a new take on a solution that I started using. I had previously been using RP Niemeyer's fiddle http://jsfiddle.net/rniemeyer/dsKbH/ as the basis for dynamically adding/removing jQuery UI tabs bound to a KO observableArray.

Over the last few months I've bumped up against a few problems in my app related to A) The setTimeout() deferral, and B) The destroy and recreate of the tabs widget every time an update is triggered. So I came up with a different approach that avoids these issues, and IMHO, is a more elegant technique.

http://jsfiddle.net/LatencyMachine/XJPJZ/

The key idea is to introduce a very simple custom binding called "tabPanel" and a corresponding widget that you bind to your tab panel content divs. As KO creates and removes these divs based on your observableArray, the tabPanel binding makes sure to update the jQueryUI.tabs using it's "refresh" method. This works a lot smoother I think than trying to get the tabs to update (and at the right time) up in the container element's bindings.

Relevant code from fiddle

/**
KO Binding handler for a tabPanel div.  Use this on divs that can appear/disappear and/or have their id change 
depending upon an observable, usually an observableArray.
*/
ko.bindingHandlers.tabPanel = {
    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {        
        $(element).tabPanel(ko.toJS(valueAccessor()));
    }
};

/**
This widget facilitates jQuery UI tabs that appear and disappear dynamically, usually as a result of MVVM like Knockout
Whenever this widget is created, the containing jQuery UI 'tabs' widget is refreshed so that it picks up the new tab
or drops the removed one.
This also facilitates dealing with id rename 'ripple' that occurs whenever a tab is removed due to the splice of an
observable array.
*/
$.widget("bw.tabPanel", {
    options: {
        id: null
    },

    _create: function() {
        this.element.hide();
        this.tabsElement = this.element.closest(".ui-tabs");        

        if(this.options.id) {
            this.element.attr({id: this.options.id});
        }
        this.refreshTabs();
    },

    _destroy: function() {
        if(this.options.id) {
            this.element.attr({id: ""});
        }
        this.refreshTabs();
    },

    _setOption: function(key, value) {
        var previousValue = this.options[key];
        if(previousValue == value) return;

        this.options[key] = value;

        switch(key) {
            case "id":
                this.element.attr({id: this.options.id});
                this.refreshTabs();
                break;
        }
    },

    /**
    Invoke refresh on the parent tab to let it know that something has changed.
    This also preserves the active index by setting it back to what it was before the refresh, which
    may correspond to a different tab after the refresh.
    */
    refreshTabs: function() {
        var previousActiveIndex = this.tabsElement.tabs("option", "active");
        this.tabsElement.tabs("refresh");
        this.tabsElement.tabs("option", "active", previousActiveIndex);        
    }
});
like image 194
BrandonLWhite Avatar answered Sep 22 '22 14:09

BrandonLWhite