jQuery UI Tabs Plugin Broke

2019-07-17 19:17发布

问题:

We are using the jquery ui tabs arrow plugin from this fiddle: http://jsfiddle.net/dECtZ/282/, but like many plugins, it breaks with the latest version of jQuery. We were able to get the csscur to work, but still get the following error in the jquery core (line 353):

Uncaught TypeError: Cannot assign to read only property 'length' of function (e,t){if(!this._createWidget)return new o(e,t);arguments.length&&this._createWidget(e,t)}

Code is as follows:

(function($, undefined) {
    if (!$.xui) {
        $.xui = {};
    }
    var tabs = $.extend({}, $.ui.tabs.prototype),
        _super = {
            _create: tabs._create,
            _destroy: tabs._destroy,
            _update: tabs._update
        };
    $.xui.tabs = $.extend(tabs, {
        options: $.extend({}, tabs.options, {
            scrollable: false,
            changeOnScroll: false,
            closable: false,
            resizable: false,
            resizeHandles: "e,s,se"
        }),
        _create: function() {
            var self = this,
                o = self.options;
            _super._create.apply(self);
            if (o.scrollable) {
                self.element.addClass("ui-tabs-scrollable");
                var scrollContainer = $('<div class="ui-tabs-scroll-container"></div>').prependTo(this.element);
                self.header = $('<div class="ui-tabs-nav-scrollable ui-widget-header ui-corner-all"></div>').prependTo(scrollContainer);
                var nav = self.element.find(".ui-tabs-nav:first").removeClass("ui-widget-header ui-corner-all").appendTo(this.header);
                var arrowsNav = $('<ol class="ui-helper-reset ui-helper-clearfix ui-tabs-nav-arrows"></ol>').prependTo(self.element);
                var navPrev = $('<li class="ui-tabs-arrow-previous ui-state-default ui-corner-bl ui-corner-tl" title="Previous"><a href="#"><span class="ui-icon ui-icon-carat-1-w">Previous tab</span></a></li>').prependTo(arrowsNav).hide(),
                    navNext = $('<li class="ui-tabs-arrow-next ui-state-default ui-corner-tr ui-corner-br" title="Next"><a href="#"><span class="ui-icon ui-icon-carat-1-e">Next tab</span></a></li>').appendTo(arrowsNav).hide();


                var scrollTo = function(to, delay) {
                    var navWidth = 0,
                        arrowWidth = navPrev.outerWidth(),
                        marginLeft = -(parseInt(nav.css("marginLeft"), 10)),
                        hwidth = self.header.width(),
                        newMargin = 0;

                    nav.find("li").each(function() {
                        navWidth += $(this).outerWidth(true);
                    });

                    if (to instanceof $.Event) {

                    } else {
                        newMargin = marginLeft+to;
                        if (newMargin > (navWidth-hwidth)) {
                            newMargin = (navWidth-hwidth);
                        } else if (newMargin < 0) {
                            newMargin = 0;
                        }
                        nav.stop(true).animate({
                            marginLeft: -(newMargin)
                        }, delay, function(){
                            $(window).trigger("resize.tabs");
                        });
                    }
                }


                var holdTimer = false;
                navPrev.add(navNext).bind({
                    "click": function(e) {
                        var isNext = this === navNext[0];
                        e.preventDefault();
                        if (o.changeOnScroll) {
                            self.select(self.options.selected + (isNext ? 1 : -1));
                        } else {
                            if (!holdTimer)
                                scrollTo(isNext ? 150 : -150, 250);
                        }
                    },
                    "mousedown": function(e){
                        if (!o.changeOnScroll) {
                            var isNext = this === navNext[0],
                                duration = 10, pos = 15, timer;
                            if (holdTimer)
                                clearTimeout(holdTimer);
                            holdTimer = setTimeout(timer = function(){
                                scrollTo(isNext ? pos : -(pos), duration);
                                holdTimer = setTimeout(arguments.callee, duration);
                            }, 150);
                        }
                    },
                    "mouseup mouseout": function(e){
                        if (!o.changeOnScroll) {
                            clearTimeout(holdTimer);
                            holdTimer = false;
                            nav.stop();
                        }
                    }
                });

                self.header.bind('mousewheel', function(e, d, dX, dY) {
                    e.preventDefault();
                    if (d === -1) {
                        navNext.click();
                    } else if (d === 1) {
                        navPrev.click();
                    }
                });

                $(window).bind("resize.tabs", function(e) {
                    var navWidth = 0;
                    var arrowWidth = navPrev.outerWidth();
                    nav.find("li").each(function() {
                        navWidth += $(this).outerWidth(true);
                    });

                    var marginLeft = -(parseInt(nav.css("marginLeft"), 10)),
                        hwidth = self.header.width();

                    if (navWidth > (hwidth+marginLeft)) {
                        self.header.addClass("ui-tabs-arrow-r");
                        navNext.show("fade");
                        if (marginLeft > 0) {
                            self.header.addClass("ui-tabs-arrow-l");
                            navPrev.show("fade");
                        } else {
                            self.header.removeClass("ui-tabs-arrow-l");
                            navPrev.hide("fade");
                        }
                    } else {
                        self.header.removeClass("ui-tabs-arrows ui-tabs-arrow-l");
                        navNext.hide("fade");
                        if (marginLeft > 0) {
                            self.header.addClass("ui-tabs-arrow-l");
                            navPrev.show("fade");
                        } else {
                            self.header.removeClass("ui-tabs-arrow-l");
                            navPrev.hide("fade");
                        }
                    }
                }).trigger("resize.tabs");

                arrowsNav.find("li").bind({
                    "mouseenter focus": function(e) {
                        $(this).addClass("ui-state-hover");
                    },
                    "mouseleave blur": function(e) {
                        $(this).removeClass("ui-state-hover");
                    }
                });

                this.anchors.bind("click.tabs", function(){
                    var li = $(this).parent(),
                        arrowWidth = navPrev.outerWidth(),
                        width = li.outerWidth(true),
                        hwidth = self.header.width(),
                        pos = li.position().left,
                        marginLeft = -(parseInt(nav.stop(true,true).css("marginLeft"),10)),
                        newMargin = -1;

                    if (li.index() === 0) {
                        newMargin = 0;
                    } else if ((pos+width) >= (hwidth+marginLeft)) {
                        newMargin = pos-hwidth+width;
                        if ((li.index()+1) < nav.find("li").length) {
                            newMargin += arrowWidth;
                        }
                    } else if (pos < marginLeft) {
                        newMargin = pos-arrowWidth;
                    }

                    if (newMargin > -1) {
                        nav.animate({
                            marginLeft: -(newMargin)
                        }, 250, function(){
                            $(window).trigger("resize.tabs");
                        });
                    }
                });
            }
            return self;
        },
        _update: function(){
            console.log(arguments);
            _super._update.apply(this);
        }
    });
    $.widget("xui.tabs", $.xui.tabs);
})(jQuery);
$(function() {
    $("#tabs").tabs({
        scrollable: true,
        changeOnScroll: false,
        closable: true
    });
    $("#switcher").themeswitcher();
});

回答1:

I've found a work around in both jquery-ui 1.10.3 and jquery-ui-1.9.2. The version of JQuery itself didn't seem to make a difference.

So, in jquery-ui-1.10.3.custom.js at line 427

$.each( existingConstructor._childConstructors, function( i, child ) {

fails because _childConstructors is undefined with this extension.

I've worked around it by

adding a check to see if existingConstructor._childConstructors is undefined or not before the .each

original

    if ( existingConstructor ) {
            $.each( existingConstructor._childConstructors, function( i, child ) {
                    var childPrototype = child.prototype;

                    // redefine the child widget using the same prototype that was
                    // originally used, but inherit from the new version of the base
                    $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
            });
            // remove the list of existing child constructors from the old constructor
            // so the old child constructors can be garbage collected
            delete existingConstructor._childConstructors;
    } else {
            base._childConstructors.push( constructor );
    }

so that the code in that reads

    if ( existingConstructor ) {
        if (!(typeof existingConstructor._childConstructors === 'undefined')) {
            $.each( existingConstructor._childConstructors, function( i, child ) {
                    var childPrototype = child.prototype;

                    // redefine the child widget using the same prototype that was
                    // originally used, but inherit from the new version of the base
                    $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
            });
            // remove the list of existing child constructors from the old constructor
            // so the old child constructors can be garbage collected
            delete existingConstructor._childConstructors;
        }
    } else {
            base._childConstructors.push( constructor );
    }

Not the best workaround, but it does work with jquery-ui-1.9.2 and jquery-ui-1.10.3 and for all the recent jquery.js I've tried.

In jquery-ui-1.9.2.custom.js the the line number about 460