From fd8d5d2168586d39dac7e145c2c7b283f8ae3d93 Mon Sep 17 00:00:00 2001 From: jzaefferer Date: Mon, 22 Mar 2010 15:24:23 +0100 Subject: [PATCH 1/7] Placeholder for _bind prototype --- ui/jquery.ui.widget.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui/jquery.ui.widget.js b/ui/jquery.ui.widget.js index 49067449dcd..a888020f4d2 100644 --- a/ui/jquery.ui.widget.js +++ b/ui/jquery.ui.widget.js @@ -205,6 +205,12 @@ $.Widget.prototype = { disable: function() { return this._setOption( "disabled", true ); }, + + _bind: function() { + // TODO figure out which element to bind to: this.element, if none specified + // TODO append widget namespace to all event names + // TODO set the scope of the callback to the instance (this here) + }, _trigger: function( type, event, data ) { var callback = this.options[ type ]; From faf963c9c5ab08f561e0e21417f9d93a039aa938 Mon Sep 17 00:00:00 2001 From: jzaefferer Date: Mon, 22 Mar 2010 15:50:09 +0100 Subject: [PATCH 2/7] Extended placeholder for _bind with usage code and some TODOs --- ui/jquery.ui.widget.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/ui/jquery.ui.widget.js b/ui/jquery.ui.widget.js index a888020f4d2..837dcd8c17e 100644 --- a/ui/jquery.ui.widget.js +++ b/ui/jquery.ui.widget.js @@ -210,6 +210,26 @@ $.Widget.prototype = { // TODO figure out which element to bind to: this.element, if none specified // TODO append widget namespace to all event names // TODO set the scope of the callback to the instance (this here) + // TODO extend destroy to unbind all events to elements other then this.element + + // usage: bind to this.element + this._bind({ + mouseover: function(event) { + this.show(event.currentTarget) + }, + mouseout: function(event) { + this.hide(event.currentTarget); + } + }); + // usage: bind to a specified element + this._bind(this.element.find(".headers"), { + mouseover: function(event) { + this.show(event.currentTarget) + }, + mouseout: function(event) { + this.hide(event.currentTarget); + } + }); }, _trigger: function( type, event, data ) { From 559373631a1f3d965a7bc976338039ec721b81a4 Mon Sep 17 00:00:00 2001 From: jzaefferer Date: Fri, 26 Mar 2010 12:25:24 -0400 Subject: [PATCH 3/7] Unit tests and implementation for $.Widget.prototype._bind, handling instance-scope for handlers, options.disabled and unbinding on destroy --- tests/unit/widget/widget.js | 72 +++++++++++++++++++++++++++++++++++++ ui/jquery.ui.widget.js | 48 ++++++++++++------------- 2 files changed, 96 insertions(+), 24 deletions(-) diff --git a/tests/unit/widget/widget.js b/tests/unit/widget/widget.js index 3c870f1fe18..40afd4b2bfb 100644 --- a/tests/unit/widget/widget.js +++ b/tests/unit/widget/widget.js @@ -146,12 +146,18 @@ test('merge multiple option arguments', function() { }); }); +function teardownWidget(name) { + delete $.fn[name]; + delete $.ui[name]; +} + test(".widget() - base", function() { $.widget("ui.testWidget", { _create: function() {} }); var div = $("
").testWidget() same(div[0], div.testWidget("widget")[0]); + teardownWidget("testWidget"); }); test(".widget() - overriden", function() { @@ -163,6 +169,72 @@ test(".widget() - overriden", function() { } }); same(wrapper[0], $("
").testWidget().testWidget("widget")[0]); + teardownWidget("testWidget"); +}); + +test("_bind to element (default)", function() { + expect(12); + var self; + $.widget("ui.testWidget", { + _create: function() { + self = this; + this._bind({ + keyup: this.keyup, + keydown: this.keydown + }); + }, + keyup: function(event) { + equals( self, this ); + equals( self.element[0], event.currentTarget ); + equals( "keyup", event.type ); + }, + keydown: function(event) { + equals( self, this ); + equals( self.element[0], event.currentTarget ); + equals( "keydown", event.type ); + } + }); + var widget = $("
").testWidget().trigger("keyup").trigger("keydown"); + widget.testWidget("disable").trigger("keyup").trigger("keydown"); + widget.testWidget("enable").trigger("keyup").trigger("keydown"); + widget.testWidget("destroy").trigger("keyup").trigger("keydown"); + teardownWidget("testWidget"); }); +test("_bind to descendent", function() { + expect(12); + var self; + $.widget("ui.testWidget", { + _create: function() { + self = this; + this._bind(this.element.find("strong"), { + keyup: this.keyup, + keydown: this.keydown + }); + }, + keyup: function(event) { + equals( self, this ); + equals( self.element.find("strong")[0], event.currentTarget ); + equals( "keyup", event.type ); + }, + keydown: function(event) { + equals( self, this ); + equals( self.element.find("strong")[0], event.currentTarget ); + equals( "keydown", event.type ); + } + }); + // trigger events on both widget and descendent to ensure that only descendent receives them + var widget = $("

hello world

").testWidget().trigger("keyup").trigger("keydown"); + var descendent = widget.find("strong").trigger("keyup").trigger("keydown"); + widget.testWidget("disable").trigger("keyup").trigger("keydown"); + descendent.trigger("keyup").trigger("keydown"); + widget.testWidget("enable").trigger("keyup").trigger("keydown"); + descendent.trigger("keyup").trigger("keydown"); + widget.testWidget("destroy").trigger("keyup").trigger("keydown"); + descendent.trigger("keyup").trigger("keydown"); + teardownWidget("testWidget"); +}); + +// test destroy and disabled + })(jQuery); diff --git a/ui/jquery.ui.widget.js b/ui/jquery.ui.widget.js index 837dcd8c17e..7b29bc153e8 100644 --- a/ui/jquery.ui.widget.js +++ b/ui/jquery.ui.widget.js @@ -139,6 +139,9 @@ $.Widget.prototype = { this.element.bind( "remove." + this.widgetName, function() { self.destroy(); }); + + // used by _bind + this.bindings = $(); this._create(); this._init(); @@ -156,6 +159,8 @@ $.Widget.prototype = { .removeClass( this.widgetBaseClass + "-disabled " + this.namespace + "-state-disabled" ); + this.bindings + .unbind( "." + this.widgetName ); }, widget: function() { @@ -206,30 +211,25 @@ $.Widget.prototype = { return this._setOption( "disabled", true ); }, - _bind: function() { - // TODO figure out which element to bind to: this.element, if none specified - // TODO append widget namespace to all event names - // TODO set the scope of the callback to the instance (this here) - // TODO extend destroy to unbind all events to elements other then this.element - - // usage: bind to this.element - this._bind({ - mouseover: function(event) { - this.show(event.currentTarget) - }, - mouseout: function(event) { - this.hide(event.currentTarget); - } - }); - // usage: bind to a specified element - this._bind(this.element.find(".headers"), { - mouseover: function(event) { - this.show(event.currentTarget) - }, - mouseout: function(event) { - this.hide(event.currentTarget); - } - }); + _bind: function( element, handlers ) { + // no element argument, shuffle and use this.element + if ( handlers == undefined ) { + handlers = element; + element = this.element; + // if binding to something else then this.element, store for unbinding on destroy + } else { + this.bindings = this.bindings.add( element ); + } + var instance = this; + $.each( handlers, function(event, handler) { + element.bind( event + "." + instance.widgetName, function() { + if ( instance.options.disabled ) { + return; + } + return handler.apply( instance, arguments ); + }); + + }); }, _trigger: function( type, event, data ) { From 0a63be6367dc812700c69d272dc30da388a319b1 Mon Sep 17 00:00:00 2001 From: lthibodeaux Date: Sat, 7 Aug 2010 08:17:06 -0700 Subject: [PATCH 4/7] Slider: Corrected range option setter. Refactored _create() code to make portions accessible to both it and setter method. Fixes #5602 - slider: Slider Does Not Exhibit Proper Behavior When Switching Range --- ui/jquery.ui.slider.js | 370 +++++++++++++++++++++++------------------ 1 file changed, 209 insertions(+), 161 deletions(-) diff --git a/ui/jquery.ui.slider.js b/ui/jquery.ui.slider.js index d90a7940f8f..f4f40155e15 100644 --- a/ui/jquery.ui.slider.js +++ b/ui/jquery.ui.slider.js @@ -56,167 +56,9 @@ $.widget( "ui.slider", $.ui.mouse, { this.element.addClass( "ui-slider-disabled ui-disabled" ); } - this.range = $([]); - - if ( o.range ) { - if ( o.range === true ) { - this.range = $( "
" ); - if ( !o.values ) { - o.values = [ this._valueMin(), this._valueMin() ]; - } - if ( o.values.length && o.values.length !== 2 ) { - o.values = [ o.values[0], o.values[0] ]; - } - } else { - this.range = $( "
" ); - } - - this.range - .appendTo( this.element ) - .addClass( "ui-slider-range" ); - - if ( o.range === "min" || o.range === "max" ) { - this.range.addClass( "ui-slider-range-" + o.range ); - } - - // note: this isn't the most fittingly semantic framework class for this element, - // but worked best visually with a variety of themes - this.range.addClass( "ui-widget-header" ); - } - - if ( $( ".ui-slider-handle", this.element ).length === 0 ) { - $( "" ) - .appendTo( this.element ) - .addClass( "ui-slider-handle" ); - } - - if ( o.values && o.values.length ) { - while ( $(".ui-slider-handle", this.element).length < o.values.length ) { - $( "" ) - .appendTo( this.element ) - .addClass( "ui-slider-handle" ); - } - } - - this.handles = $( ".ui-slider-handle", this.element ) - .addClass( "ui-state-default" + - " ui-corner-all" ); + this._refreshRange(); - this.handle = this.handles.eq( 0 ); - - this.handles.add( this.range ).filter( "a" ) - .click(function( event ) { - event.preventDefault(); - }) - .hover(function() { - if ( !o.disabled ) { - $( this ).addClass( "ui-state-hover" ); - } - }, function() { - $( this ).removeClass( "ui-state-hover" ); - }) - .focus(function() { - if ( !o.disabled ) { - $( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" ); - $( this ).addClass( "ui-state-focus" ); - } else { - $( this ).blur(); - } - }) - .blur(function() { - $( this ).removeClass( "ui-state-focus" ); - }); - - this.handles.each(function( i ) { - $( this ).data( "index.ui-slider-handle", i ); - }); - - this.handles - .keydown(function( event ) { - var ret = true, - index = $( this ).data( "index.ui-slider-handle" ), - allowed, - curVal, - newVal, - step; - - if ( self.options.disabled ) { - return; - } - - switch ( event.keyCode ) { - case $.ui.keyCode.HOME: - case $.ui.keyCode.END: - case $.ui.keyCode.PAGE_UP: - case $.ui.keyCode.PAGE_DOWN: - case $.ui.keyCode.UP: - case $.ui.keyCode.RIGHT: - case $.ui.keyCode.DOWN: - case $.ui.keyCode.LEFT: - ret = false; - if ( !self._keySliding ) { - self._keySliding = true; - $( this ).addClass( "ui-state-active" ); - allowed = self._start( event, index ); - if ( allowed === false ) { - return; - } - } - break; - } - - step = self.options.step; - if ( self.options.values && self.options.values.length ) { - curVal = newVal = self.values( index ); - } else { - curVal = newVal = self.value(); - } - - switch ( event.keyCode ) { - case $.ui.keyCode.HOME: - newVal = self._valueMin(); - break; - case $.ui.keyCode.END: - newVal = self._valueMax(); - break; - case $.ui.keyCode.PAGE_UP: - newVal = self._trimAlignValue( curVal + ( (self._valueMax() - self._valueMin()) / numPages ) ); - break; - case $.ui.keyCode.PAGE_DOWN: - newVal = self._trimAlignValue( curVal - ( (self._valueMax() - self._valueMin()) / numPages ) ); - break; - case $.ui.keyCode.UP: - case $.ui.keyCode.RIGHT: - if ( curVal === self._valueMax() ) { - return; - } - newVal = self._trimAlignValue( curVal + step ); - break; - case $.ui.keyCode.DOWN: - case $.ui.keyCode.LEFT: - if ( curVal === self._valueMin() ) { - return; - } - newVal = self._trimAlignValue( curVal - step ); - break; - } - - self._slide( event, index, newVal ); - - return ret; - - }) - .keyup(function( event ) { - var index = $( this ).data( "index.ui-slider-handle" ); - - if ( self._keySliding ) { - self._keySliding = false; - self._stop( event, index ); - self._change( event, index ); - $( this ).removeClass( "ui-state-active" ); - } - - }); + this._refreshHandles(); this._refreshValue(); @@ -509,6 +351,9 @@ $.widget( "ui.slider", $.ui.mouse, { valsLength = this.options.values.length; } + // Register look-back range state for post-processing + var lastRange = this.options.range; + $.Widget.prototype._setOption.apply( this, arguments ); switch ( key ) { @@ -530,6 +375,24 @@ $.widget( "ui.slider", $.ui.mouse, { .addClass( "ui-slider-" + this.orientation ); this._refreshValue(); break; + case "range": + // Handle necessary options object changes for migrating from multiple sliders to single + // Reverse case already handled in _refreshRange() + if ( lastRange === true && this.options.range !== true ) { + if ( this.options.range === "max" ) { + this.options.value = this.options.values[1]; + } else { + this.options.value = this.options.values[0]; + } + this.options.values = null; + } + this._animateOff = true; + // Redraw the fill and handle elements and set their values according to new options + this._refreshRange(); + this._refreshHandles(); + this._refreshValue(); + this._animateOn = true; + break; case "value": this._animateOff = true; this._refreshValue(); @@ -580,7 +443,7 @@ $.widget( "ui.slider", $.ui.mouse, { return vals; } }, - + // returns the step-aligned value that val is closest to, between (inclusive) min and max _trimAlignValue: function( val ) { if ( val < this._valueMin() ) { @@ -609,7 +472,192 @@ $.widget( "ui.slider", $.ui.mouse, { _valueMax: function() { return this.options.max; }, + + _refreshRange: function() { + // Remove any pre-existing range fill div; member object reinitialized below + if ( this.range ) { + this.range.remove(); + } + + var o = this.options; + + this.range = $([]); + + if ( o.range ) { + if ( o.range === true ) { + this.range = $( "
" ); + if ( !o.values && o.value == undefined ) { + o.values = [ this._valueMin(), this._valueMin() ]; + } else if ( !o.values && o.value != undefined ) { + o.values = [ o.value, o.value ]; + o.value = null; + } + if ( o.values.length && o.values.length !== 2 ) { + o.values = [ o.values[0], o.values[0] ]; + } + } else { + this.range = $( "
" ); + } + + this.range + .appendTo( this.element ) + .addClass( "ui-slider-range" ); + + if ( o.range === "min" || o.range === "max" ) { + this.range.addClass( "ui-slider-range-" + o.range ); + } + + // note: this isn't the most fittingly semantic framework class for this element, + // but worked best visually with a variety of themes + this.range.addClass( "ui-widget-header" ); + } + }, + + _refreshHandles: function() { + // Remove any pre-existing jQuery data for handles, then remove handle objects themselves + if ( this.handles ) { + this.handles.each(function (i) { + $( this ).removeData( "index.ui-slider-handle", i ); + }); + $( ".ui-slider-handle", this.element ).remove(); + } + + var o = this.options; + + $( "" ) + .appendTo( this.element ) + .addClass( "ui-slider-handle" ); + + if ( o.values && o.values.length ) { + while ( $(".ui-slider-handle", this.element).length < o.values.length ) { + $( "" ) + .appendTo( this.element ) + .addClass( "ui-slider-handle" ); + } + } + + this.handles = $( ".ui-slider-handle", this.element ) + .addClass( "ui-state-default" + + " ui-corner-all" ); + + this.handle = this.handles.eq( 0 ); + + this.handles.add( this.range ).filter( "a" ) + .click(function( event ) { + event.preventDefault(); + }) + .hover(function() { + if ( !o.disabled ) { + $( this ).addClass( "ui-state-hover" ); + } + }, function() { + $( this ).removeClass( "ui-state-hover" ); + }) + .focus(function() { + if ( !o.disabled ) { + $( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" ); + $( this ).addClass( "ui-state-focus" ); + } else { + $( this ).blur(); + } + }) + .blur(function() { + $( this ).removeClass( "ui-state-focus" ); + }); + + this.handles.each(function( i ) { + $( this ).data( "index.ui-slider-handle", i ); + }); + + this.handles + .keydown(function( event ) { + var ret = true, + index = $( this ).data( "index.ui-slider-handle" ), + allowed, + curVal, + newVal, + step; + + if ( self.options.disabled ) { + return; + } + + switch ( event.keyCode ) { + case $.ui.keyCode.HOME: + case $.ui.keyCode.END: + case $.ui.keyCode.PAGE_UP: + case $.ui.keyCode.PAGE_DOWN: + case $.ui.keyCode.UP: + case $.ui.keyCode.RIGHT: + case $.ui.keyCode.DOWN: + case $.ui.keyCode.LEFT: + ret = false; + if ( !self._keySliding ) { + self._keySliding = true; + $( this ).addClass( "ui-state-active" ); + allowed = self._start( event, index ); + if ( allowed === false ) { + return; + } + } + break; + } + + step = self.options.step; + if ( self.options.values && self.options.values.length ) { + curVal = newVal = self.values( index ); + } else { + curVal = newVal = self.value(); + } + + switch ( event.keyCode ) { + case $.ui.keyCode.HOME: + newVal = self._valueMin(); + break; + case $.ui.keyCode.END: + newVal = self._valueMax(); + break; + case $.ui.keyCode.PAGE_UP: + newVal = curVal + ( (self._valueMax() - self._valueMin()) / numPages ); + break; + case $.ui.keyCode.PAGE_DOWN: + newVal = curVal - ( (self._valueMax() - self._valueMin()) / numPages ); + break; + case $.ui.keyCode.UP: + case $.ui.keyCode.RIGHT: + if ( curVal === self._valueMax() ) { + return; + } + newVal = curVal + step; + break; + case $.ui.keyCode.DOWN: + case $.ui.keyCode.LEFT: + if ( curVal === self._valueMin() ) { + return; + } + newVal = curVal - step; + break; + } + + self._slide( event, index, newVal ); + + return ret; + + }) + .keyup(function( event ) { + var index = $( this ).data( "index.ui-slider-handle" ); + + if ( self._keySliding ) { + self._keySliding = false; + self._stop( event, index ); + self._change( event, index ); + $( this ).removeClass( "ui-state-active" ); + } + }); + + }, + _refreshValue: function() { var oRange = this.options.range, o = this.options, From b74d3053b13924f38d0a6f741691bb977973934e Mon Sep 17 00:00:00 2001 From: lthibodeaux Date: Sat, 7 Aug 2010 11:38:09 -0500 Subject: [PATCH 5/7] html pages: added visual test for slider ticket #5602 --- tests/visual/slider/slider_ticket_5602.html | 35 +++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/visual/slider/slider_ticket_5602.html diff --git a/tests/visual/slider/slider_ticket_5602.html b/tests/visual/slider/slider_ticket_5602.html new file mode 100644 index 00000000000..75a56ff531e --- /dev/null +++ b/tests/visual/slider/slider_ticket_5602.html @@ -0,0 +1,35 @@ + + + + + Slider Visual Test : Slider ticket #5602 + + + + + + + + + + + +

#5602 - Slider Does Not Exhibit Proper Behavior When Switching Range

+ +
+ +

Set range option to:

+ + + + + \ No newline at end of file From 248b513f841144fcb28f70df7f15c90a5a2da8eb Mon Sep 17 00:00:00 2001 From: lthibodeaux Date: Sat, 7 Aug 2010 11:43:25 -0500 Subject: [PATCH 6/7] html pages: added newline to end of test for slider ticket #5602 --- tests/visual/slider/slider_ticket_5602.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/visual/slider/slider_ticket_5602.html b/tests/visual/slider/slider_ticket_5602.html index 75a56ff531e..56749fb5278 100644 --- a/tests/visual/slider/slider_ticket_5602.html +++ b/tests/visual/slider/slider_ticket_5602.html @@ -32,4 +32,4 @@

#5602 - \ No newline at end of file + From ebc15c76860576971b6867cc1c9fd0c1bccdf9c2 Mon Sep 17 00:00:00 2001 From: lthibodeaux Date: Sat, 7 Aug 2010 10:08:17 -0700 Subject: [PATCH 7/7] Slider: Corrected range option setter. Refactored _create() code to make portions accessible to both it and setter method. Fixes #5602 - slider: Slider Does Not Exhibit Proper Behavior When Switching Range --- ui/jquery.ui.slider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/jquery.ui.slider.js b/ui/jquery.ui.slider.js index f4f40155e15..12d3404b44a 100644 --- a/ui/jquery.ui.slider.js +++ b/ui/jquery.ui.slider.js @@ -391,7 +391,7 @@ $.widget( "ui.slider", $.ui.mouse, { this._refreshRange(); this._refreshHandles(); this._refreshValue(); - this._animateOn = true; + this._animateOff = false; break; case "value": this._animateOff = true;