/*
Copyright (c) 2009, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
version: 3.0.0
build: 1549
*/
YUI.add('slider', function(Y) {
/**
* Create a sliding value range input visualized as a draggable thumb on a
* background element.
*
* @module slider
*/
var SLIDER = 'slider',
RAIL = 'rail',
THUMB = 'thumb',
VALUE = 'value',
MIN = 'min',
MAX = 'max',
MIN_GUTTER = 'minGutter',
MAX_GUTTER = 'maxGutter',
THUMB_IMAGE = 'thumbImage',
RAIL_SIZE = 'railSize',
CONTENT_BOX = 'contentBox',
SLIDE_START = 'slideStart',
SLIDE_END = 'slideEnd',
THUMB_DRAG = 'thumbDrag',
SYNC = 'sync',
POSITION_THUMB = 'positionThumb',
RENDERED = 'rendered',
DISABLED = 'disabled',
DISABLED_CHANGE = 'disabledChange',
DOT = '.',
PX = 'px',
WIDTH = 'width',
HEIGHT = 'height',
COMPLETE = 'complete',
L = Y.Lang,
isBoolean= L.isBoolean,
isString = L.isString,
isNumber = L.isNumber,
getCN = Y.ClassNameManager.getClassName,
IMAGE = 'image',
C_RAIL = getCN(SLIDER,RAIL),
C_THUMB = getCN(SLIDER,THUMB),
C_THUMB_IMAGE = getCN(SLIDER,THUMB,IMAGE),
C_IMAGE_ERROR = getCN(SLIDER,IMAGE,'error'),
M = Math,
max = M.max,
round = M.round,
floor = M.floor;
/**
* Create a slider to represent an integer value between a given minimum and
* maximum. Sliders may be aligned vertically or horizontally, based on the
* axis
configuration.
*
* @class Slider
* @extends Widget
* @param config {Object} Configuration object
* @constructor
*/
function Slider() {
Slider.superclass.constructor.apply(this,arguments);
}
Y.mix(Slider, {
/**
* The identity of the widget.
*
* @property Slider.NAME
* @type String
* @static
*/
NAME : SLIDER,
/**
* Object property names used for respective X and Y axis Sliders (e.g.
* "left" vs. "top" for placing the thumb according to
* its representative value).
*
* @property Slider._AXIS_KEYS
* @type Object
* @protected
* @static
*/
_AXIS_KEYS : {
x : {
dim : WIDTH,
offAxisDim : HEIGHT,
eventPageAxis : 'pageX',
ddStick : 'stickX',
xyIndex : 0
},
y : {
dim : HEIGHT,
offAxisDim : WIDTH,
eventPageAxis : 'pageY',
ddStick : 'stickY',
xyIndex : 1
}
},
/**
* Static Object hash used to capture existing markup for progressive
* enhancement. Keys correspond to config attribute names and values
* are selectors used to inspect the contentBox for an existing node
* structure.
*
* @property Slider.HTML_PARSER
* @type Object
* @protected
* @static
*/
HTML_PARSER : {
rail : DOT + C_RAIL,
thumb : DOT + C_THUMB,
thumbImage : DOT + C_THUMB_IMAGE
},
/**
* Static property used to define the default attribute configuration of
* the Widget.
*
* @property Slider.ATTRS
* @type Object
* @protected
* @static
*/
ATTRS : {
/**
* Axis upon which the Slider's thumb moves. "x" for
* horizontal, "y" for vertical.
*
* @attribute axis
* @type String
* @default "x"
* @writeOnce
*/
axis : {
value : 'x',
writeOnce : true,
validator : function (v) {
return this._validateNewAxis(v);
},
setter : function (v) {
return this._setAxisFn(v);
}
},
/**
* Value associated with the left or top most position of the thumb on
* the rail.
*
* @attribute min
* @type Number
* @default 0
*/
min : {
value : 0,
validator : function (v) {
return this._validateNewMin(v);
}
},
/**
* Value associated with the right or bottom most position of the thumb
* on the rail.
*
* @attribute max
* @type Number
* @default 100
*/
max : {
value : 100,
validator : function (v) {
return this._validateNewMax(v);
}
},
/**
* The current value of the Slider. This value is interpretted into a
* position for the thumb along the Slider's rail.
*
* @attribute value
* @type Number
* @default 0
*/
value : {
value : 0,
validator : function (v) {
return this._validateNewValue(v);
}
},
/**
* The Node representing the Slider's rail, usually visualized as a
* bar of some sort using a background image, along which the thumb
* moves. This Node contains the thumb Node.
*
* @attribute rail
* @type Node
* @default null
*/
rail : {
value : null,
validator : function (v) {
return this._validateNewRail(v);
},
setter : function (v) {
return this._setRailFn(v);
}
},
/**
*
The Node representing the Slider's thumb, usually visualized as a * pointer using a contained image Node (see thumbImage). The current * value of the Slider is calculated from the centerpoint of this * Node in relation to the rail Node. If provided, the thumbImage * Node is contained within this Node.
* *If no thumbImage is provided and the Node passed as the thumb is
* an img
element, the assigned Node will be allocated to
* the thumbImage and the thumb container defaulted.
The Node representing the image element to use for the Slider's * thumb.
* *Alternately, an image URL can be passed and an img
* Node will be generated accordingly.
If no thumbImage is provided and the Node passed as the thumb is
* an img
element, the assigned Node will be allocated to
* the thumbImage and the thumb container defaulted.
If thumbImage is provided but its URL resolves to a 404, a default * style will be applied to maintain basic functionality.
* * @attribute thumbImage * @type Node|String * @default null */ thumbImage : { value : null, validator : function (v) { return this._validateNewThumbImage(v); }, setter : function (v) { return this._setThumbImageFn(v); } }, /** *The width or height of the rail element representing the physical * space along which the thumb can move. CSS size values (e.g. '30em') * accepted but converted to pixels during render.
* *Alternately, but not recommended, this attribute can be left * unassigned in favor of specifying height or width.
* * @attribute railSize * @type String * @default '0' */ railSize : { value : '0', validator : function (v) { return this._validateNewRailSize(v); } }, /** * Boolean indicating whether clicking and dragging on the rail will * trigger thumb movement. * * @attribute railEnabled * @type Boolean * @default true */ railEnabled : { value : true, validator : isBoolean }, /** * Like CSS padding, the distance in pixels from the inner top or left * edge of the rail node within which the thumb can travel. Negative * values allow the edge of the thumb to escape the rail node * boundaries. * * @attribute minGutter * @type Number * @default 0 */ minGutter : { value : 0, validator : isNumber }, /** * Like CSS padding, the distance in pixels from the inner bottom or * right edge of the rail node within which the thumb can travel. * Negative values allow the edge of the thumb to escape the rail node * boundaries. * * @attribute maxGutter * @type Number * @default 0 */ maxGutter : { value : 0, validator : isNumber } } }); Y.extend(Slider, Y.Widget, { /** * Collection of object property names from the appropriate hash set in * Slider._AXIS_KEYS. * * @property _key * @type Object * @protected */ _key : null, /** * Factor used to translate positional coordinates (e.g. left or top) to * the Slider's value. * * @property _factor * @type Number * @protected */ _factor : 1, /** * Pixel dimension of the rail Node's width for X axis Sliders or height * for Y axis Sliders. Used with _factor to calculate positional * coordinates for the thumb. * * @property _railSize * @type Number * @protected */ _railSize : null, /** * Pixel dimension of the thumb Node's width for X axis Sliders or height * for Y axis Sliders. Used with _factor to calculate positional * coordinates for the thumb. * * @property _thumbSize * @type Number * @protected */ _thumbSize : null, /** * Pixel offset of the point in the thumb element from its top/left edge * to where the value calculation should take place. By default, this is * calculated to half the width of the thumb, causing the value to be * marked from the center of the thumb. * * @property _thumbOffset * @type Number * @protected */ _thumbOffset : 0, /** * Object returned from temporary subscription to disabledChange event to * defer setting the disabled state while Slider is loading the thumb * image. * * @property _stall * @type Object * @protected */ _stall : false, /** * Deferred value for the disabled attribute when stalled (see _stall * property). * * @property _disabled * @type Boolean * @protected */ _disabled : false, /** * Construction logic executed durint Slider instantiation. Subscribes to * after events for min, max, and railSize. Publishes custom events * including slideStart and slideEnd. * * @method initializer * @protected */ initializer : function () { this._key = Slider._AXIS_KEYS[this.get('axis')]; this.after('minChange', this._afterMinChange); this.after('maxChange', this._afterMaxChange); this.after('railSizeChange', this._afterRailSizeChange); /** * Signals the beginning of a thumb drag operation. Payload includes * the DD.Drag instance's drag:start event under key ddEvent. * * @event slideStart * @param event {Event.Facade} An Event Facade object with the following attribute specific properties added: *drag:start
event from the managed DD.Drag instancedrag:end
event from the managed DD.Drag instancevalueChange
event fired in response to the change in the value attributeCreates the thumb element (not image) if not provided and not
* discovered via HTML_PARSER. If the thumb is an img
element
* but no thumbImage configured or discovered, reassigns the thumb element
* to the thumbImage and defaults the thumb element as a div.
Makes sure the thumb is a child of the rail element and calls * _initThumbImage if thumbImage is provided.
* * @method _initThumb * @protected */ _initThumb : function () { var rail = this.get(RAIL), thumb = this.get(THUMB); // Passed an img element as the thumb if (thumb && !this.get(THUMB_IMAGE) && thumb.get('nodeName').toLowerCase() === 'img') { this.set(THUMB_IMAGE, thumb); this.set(THUMB,null); thumb = null; } if (!thumb) { thumb = Y.Node.create( ''); this.set(THUMB,thumb); } thumb.addClass(C_THUMB); if (!rail.contains(thumb)) { rail.appendChild(thumb); } if (this.get(THUMB_IMAGE)) { this._initThumbImage(); } }, /** * Ensures the thumbImage is a child of the thumb element. * * @method _initThumbImage * @protected */ _initThumbImage : function () { var thumb = this.get(THUMB), img = this.get(THUMB_IMAGE); if (img) { img.replaceClass(C_THUMB,C_THUMB_IMAGE); if (!thumb.contains(img)) { thumb.appendChild(img); } } }, /** * Creates the Y.DD instance used to handle the thumb movement and binds * Slider interaction to the configured value model. * * @method bindUI * @protected */ bindUI : function () { /** * Bridges user interaction with the thumb to the value attribute. * * @event thumbDrag * @param event {Event.Facade} An Event Facade object with the following attribute specific properties added: *drag:drag
event from the managed DD.Drag instanceslider.get("value")
.
*
* @method getValue
* @return {Number} the value
*/
getValue : function () {
return this.get(VALUE);
},
/**
* Convenience method for updating the current value of the Slider.
* Equivalent to slider.set("value",val)
.
*
* @method setValue
* @param val {Number} the new value
*/
setValue : function (val) {
this.set(VALUE,val);
},
/**
* Validator applied to new values for the axis attribute. Only
* "x" and "y" are permitted.
*
* @method _validateNewAxis
* @param v {String} proposed value for the axis attribute
* @return Boolean
* @protected
*/
_validateNewAxis : function (v) {
return isString(v) && 'xXyY'.indexOf(v.charAt(0)) > -1;
},
/**
* Validator applied to the min attribute.
*
* @method _validateNewMin
* @param v {MIXED} proposed value for the min attribute
* @return Boolean
* @protected
*/
_validateNewMin : function (v) {
return isNumber(v);
},
/**
* Validator applied to the max attribute.
*
* @method _validateNewMax
* @param v {MIXED} proposed value for the max attribute
* @return Boolean
* @protected
*/
_validateNewMax : function (v) {
return isNumber(v);
},
/**
* Validator applied to the value attribute.
*
* @method _validateNewValue
* @param v {MIXED} proposed value for the value attribute
* @return Boolean
* @protected
*/
_validateNewValue : function (v) {
var min = this.get(MIN),
max = this.get(MAX);
return isNumber(v) &&
(min < max ? (v >= min && v <= max) : (v >= max && v <= min));
},
/**
* Validator applied to the rail attribute. Rejects all values after the
* Slider has been rendered.
*
* @method _validateNewRail
* @param v {MIXED} proposed value for the rail attribute
* @return Boolean
* @protected
*/
_validateNewRail : function (v) {
return !this.get(RENDERED) || v;
},
/**
* Validator applied to the thumb attribute. Rejects all values after the
* Slider has been rendered.
*
* @method _validateNewThumb
* @param v {MIXED} proposed value for the thumb attribute
* @return Boolean
* @protected
*/
_validateNewThumb : function (v) {
return !this.get(RENDERED) || v;
},
/**
* Validator applied to the thumbImage attribute. Rejects all values after
* the Slider has been rendered.
*
* @method _validateNewThumbImage
* @param v {MIXED} proposed value for the thumbImage attribute
* @return Boolean
* @protected
*/
_validateNewThumbImage : function (v) {
return !this.get(RENDERED) || v;
},
/**
* Validator applied to the railSize attribute. Only strings of css size
* values (e.g. '200px') are allowed.
*
* @method _validateNewRailSize
* @param v {String} proposed value for the railSize attribute
* @return Boolean
* @protected
*/
_validateNewRailSize : function (v) {
return isString(v) &&
(v === '0' || /^\d+(?:p[xtc]|%|e[mx]|in|[mc]m)$/.test(v));
},
/**
* Setter applied to the input when updating the axis attribute.
*
* @method _setAxisFn
* @param v {String} proposed value for the axis attribute
* @return {String} lowercased first character of the input string
* @protected
*/
_setAxisFn : function (v) {
return v.charAt(0).toLowerCase();
},
/**
* Setter applied to the input when updating the rail attribute. Input can
* be a Node, raw HTMLElement, or a selector string to locate it.
*
* @method _setRailFn
* @param v {Node|String|HTMLElement} The rail element Node or selector
* @return {Node} The Node if found. Otherwise null.
* @protected
*/
_setRailFn : function (v) {
return Y.get(v) || null;
},
/**
* Setter applied to the input when updating the thumb attribute. Input can
* be a Node, raw HTMLElement, or a selector string to locate it.
*
* @method _setThumbFn
* @param v {Node|String|HTMLElement} The thumb element Node or selector
* @return {Node} The Node if found. Otherwise null.
* @protected
*/
_setThumbFn : function (v) {
return Y.get(v) || null;
},
/**
* Setter applied to the input when updating the thumbImage attribute.
* Input can be a Node, raw HTMLElement, selector string to locate it, or
* the URL for an image resource.
*
* String input will be treated as a selector. If no element is found using
* the selector, an img
Node will be created with the string
* used as the src
attribute.
*
* @method _setThumbImageFn
* @param v {Node|String|HTMLElement} The thumbImage element Node, selector,
* or image URL
* @return {Node} The Node if found or created. Otherwise null.
* @protected
*/
_setThumbImageFn : function (v) {
return v ? Y.get(v) ||
Y.Node.create('') :
null;
},
/**
* Caches the current page position of the rail element and fires the
* slideStart event in response to the DD's drag:start.
*
* @method _onDDStartDrag
* @param e {Event} the DD instance's drag:start custom event
* @protected
*/
_onDDStartDrag : function (e) {
this._setRailOffsetXY();
this.fire(SLIDE_START,{ ddEvent: e });
},
/**
* Fires the thumbDrag event to queue Slider value update.
*
* @method _onDDDrag
* @param e {Event} the DD instance's drag:drag custom event
* @protected
*/
_onDDDrag : function (e) {
this.fire(THUMB_DRAG, { ddEvent: e });
},
/**
* The default value update behavior in response to Slider thumb
* interaction. Calculates the value using stored offsets, the _factor
* multiplier and the min value.
*
* @method _defThumbDragFn
* @param e {Event} the internal thumbDrag event
* @protected
*/
_defThumbDragFn : function (e) {
var before = this.get(VALUE),
val = e.ddEvent[this._key.eventPageAxis] - this._offsetXY;
val = this._convertOffsetToValue(val);
if (before !== val) {
this.set(VALUE, val, { ddEvent: e.ddEvent });
}
},
/**
* Fires the slideEnd event.
*
* @method _onDDEndDrag
* @param e {Event} the DD instance's drag:end custom event
* @protected
*/
_onDDEndDrag : function (e) {
this.fire(SLIDE_END,{ ddEvent: e });
},
/**
* Calls _uiPositionThumb with the value of the custom event's
* "offset" property.
*
* @method _defPositionThumbFn
* @param e {Event} the positionThumb custom event
* @protected
*/
_defPositionThumbFn : function (e) {
this._uiPositionThumb(e.offset);
},
/**
* Places the thumb at a particular X or Y location based on the configured
* axis.
*
* @method _uiPositionThumb
* @param xy {Number} the desired left or top pixel position of the thumb
* in relation to the rail Node.
* @protected
*/
_uiPositionThumb : function (xy) {
var dd = this._dd,
thumb = dd.get('dragNode'),
hidden = thumb.ancestor(this._isDisplayNone);
if (!hidden) {
dd._setStartPosition(dd.get('dragNode').getXY());
// stickX/stickY config on DD instance will negate off-axis move
dd._alignNode([xy,xy],true);
}
},
/**
* Helper function to search up the ancestor axis looking for a node with
* style display: none. This is passed as a function to node.ancestor(..)
* to test if a given node is in the displayed DOM and can get accurate
* positioning information.
*
* @method _isDisplayNone
* @param el {Node} ancestor node as the function walks up the parent axis
* @return {Boolean} true if the node is styled with display: none
* @protected
*/
_isDisplayNone : function (node) {
return node.getComputedStyle('display') === 'none';
},
/**
* Fires the internal positionThumb event in response to a change in the
* value attribute.
*
* @method _afterValueChange
* @param e {Event} valueChange custom event
* @protected
*/
_afterValueChange : function (e) {
if (!e.ddEvent) {
var xy = this._convertValueToOffset(e.newVal);
this.fire(POSITION_THUMB,{ value: e.newVal, offset: xy });
}
},
/**
* Converts a value to a pixel offset for the thumb position on the rail.
*
* @method _convertValueToOffset
* @param v {Number} value between the Slider's min and max
* @protected
*/
_convertValueToOffset : function (v) {
return round((v - this.get(MIN)) / this._factor) + this._offsetXY;
},
/**
* Converts a pixel offset of the thumb on the rail to a value.
*
* @method _convertOffsetToValue
* @param v {Number} pixel offset of the thumb on the rail
* @protected
*/
_convertOffsetToValue : function (v) {
return round(this.get(MIN) + (v * this._factor));
},
/**
* Replaces the thumb Node in response to a change in the thumb attribute.
* This only has effect after the Slider is rendered.
*
* @method _afterThumbChange
* @param e {Event} thumbChange custom event
* @protected
*/
_afterThumbChange : function (e) {
var thumb;
if (this.get(RENDERED)) {
if (e.prevValue) {
e.prevValue.get('parentNode').removeChild(e.prevValue);
}
this._initThumb();
thumb = this.get(THUMB);
this._dd.set('node',thumb);
this._dd.set('dragNode',thumb);
this.syncUI();
}
},
/**
* Sets or replaces the thumb's contained img
Node with the
* new Node in response to a change in the thumbImage attribute. This only
* has effect after the Slider is rendered.
*
* @method _afterThumbImageChange
* @param e {Event} thumbImageChange custom event
* @protected
*/
_afterThumbImageChange : function (e) {
if (this.get(RENDERED)) {
if (e.prevValue) {
e.prevValue.get('parentNode').removeChild(e.prevValue);
}
this._initThumbImage();
this.syncUI();
}
},
/**
* Updates the Slider UI in response to change in the min attribute.
*
* @method _afterMinChange
* @param e {Event} minChange custom event
* @protected
*/
_afterMinChange : function (e) {
this._refresh(e);
},
/**
* Updates the Slider UI in response to change in the max attribute.
*
* @method _afterMaxChange
* @param e {Event} maxChange custom event
* @protected
*/
_afterMaxChange : function (e) {
this._refresh(e);
},
/**
* Updates the Slider UI in response to change in the railSize attribute.
*
* @method _afterRailSizeChange
* @param e {Event} railSizeChange custom event
* @protected
*/
_afterRailSizeChange : function (e) {
this._refresh(e);
},
/**
* Locks or unlocks the DD instance in response to a change in the disabled
* attribute.
*
* @method _afterDisabledChange
* @param e {Event} disabledChange custom event
* @protected
*/
_afterDisabledChange : function (e) {
if (this._dd) {
this._dd.set('lock',e.newVal);
}
},
/**
* Common handler to call syncUI in response to change events that occurred
* after the Slider is rendered.
*
* @method _refresh
* @param e {Event} An attribute change event
* @protected
*/
_refresh : function (e) {
if (e.newVal !== e.prevVal && this.get(RENDERED)) {
this.syncUI();
}
},
/**
* Used to determine if there is a current or pending request for the
* thumbImage resource.
*
* @method _isImageLoading
* @param img {Node} img
Node
* @return Boolean
* @protected
*/
_isImageLoading : function (img) {
return img && !img.get(COMPLETE);
},
/**
* Used to determine if the image resource loaded successfully or there was
* an error.
*
* NOTES:
* img
Node
* @return Boolean
* @protected
*/
_isImageLoaded : function (img) {
if (img) {
var w = img.get('naturalWidth');
return img.get(COMPLETE) && (!isNumber(w) ? img.get(WIDTH) : w);
}
return true;
}
});
Y.Slider = Slider;
}, '3.0.0' ,{requires:['widget','dd-constrain']});