]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/slider/slider.js
Release 6.2.0beta4
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / slider / slider.js
1 /*
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 3.0.0
6 build: 1549
7 */
8 YUI.add('slider', function(Y) {
9
10 /**
11  * Create a sliding value range input visualized as a draggable thumb on a
12  * background element.
13  * 
14  * @module slider
15  */
16
17 var SLIDER = 'slider',
18     RAIL   = 'rail',
19     THUMB  = 'thumb',
20     VALUE  = 'value',
21     MIN    = 'min',
22     MAX    = 'max',
23     MIN_GUTTER = 'minGutter',
24     MAX_GUTTER = 'maxGutter',
25     THUMB_IMAGE = 'thumbImage',
26     RAIL_SIZE   = 'railSize',
27     CONTENT_BOX = 'contentBox',
28
29     SLIDE_START = 'slideStart',
30     SLIDE_END   = 'slideEnd',
31
32     THUMB_DRAG  = 'thumbDrag',
33     SYNC        = 'sync',
34     POSITION_THUMB = 'positionThumb',
35     RENDERED    = 'rendered',
36     DISABLED    = 'disabled',
37     DISABLED_CHANGE = 'disabledChange',
38
39     DOT      = '.',
40     PX       = 'px',
41     WIDTH    = 'width',
42     HEIGHT   = 'height',
43     COMPLETE = 'complete',
44
45     L = Y.Lang,
46     isBoolean= L.isBoolean,
47     isString = L.isString,
48     isNumber = L.isNumber,
49     
50     getCN    = Y.ClassNameManager.getClassName,
51
52     IMAGE         = 'image',
53     C_RAIL        = getCN(SLIDER,RAIL),
54     C_THUMB       = getCN(SLIDER,THUMB),
55     C_THUMB_IMAGE = getCN(SLIDER,THUMB,IMAGE),
56     C_IMAGE_ERROR = getCN(SLIDER,IMAGE,'error'),
57
58     M        = Math,
59     max      = M.max,
60     round    = M.round,
61     floor    = M.floor;
62
63 /**
64  * Create a slider to represent an integer value between a given minimum and
65  * maximum.  Sliders may be aligned vertically or horizontally, based on the
66  * <code>axis</code> configuration.
67  *
68  * @class Slider
69  * @extends Widget
70  * @param config {Object} Configuration object
71  * @constructor
72  */
73 function Slider() {
74     Slider.superclass.constructor.apply(this,arguments);
75 }
76
77 Y.mix(Slider, {
78
79     /**
80      * The identity of the widget.
81      *
82      * @property Slider.NAME
83      * @type String
84      * @static
85      */
86     NAME : SLIDER,
87
88     /**
89      * Object property names used for respective X and Y axis Sliders (e.g.
90      * &quot;left&quot; vs. &quot;top&quot; for placing the thumb according to
91      * its representative value).
92      *
93      * @property Slider._AXIS_KEYS
94      * @type Object
95      * @protected
96      * @static
97      */
98     _AXIS_KEYS : {
99         x : {
100             dim           : WIDTH,
101             offAxisDim    : HEIGHT,
102             eventPageAxis : 'pageX',
103             ddStick       : 'stickX',
104             xyIndex       : 0
105         },
106         y : {
107             dim           : HEIGHT,
108             offAxisDim    : WIDTH,
109             eventPageAxis : 'pageY',
110             ddStick       : 'stickY',
111             xyIndex       : 1
112         }
113     },
114
115     /**
116      * Static Object hash used to capture existing markup for progressive
117      * enhancement.  Keys correspond to config attribute names and values
118      * are selectors used to inspect the contentBox for an existing node
119      * structure.
120      *
121      * @property Slider.HTML_PARSER
122      * @type Object
123      * @protected
124      * @static
125      */
126     HTML_PARSER : {
127         rail       : DOT + C_RAIL,
128         thumb      : DOT + C_THUMB,
129         thumbImage : DOT + C_THUMB_IMAGE
130     },
131
132     /**
133      * Static property used to define the default attribute configuration of
134      * the Widget.
135      *
136      * @property Slider.ATTRS
137      * @type Object
138      * @protected
139      * @static
140      */
141     ATTRS : {
142
143         /**
144          * Axis upon which the Slider's thumb moves.  &quot;x&quot; for
145          * horizontal, &quot;y&quot; for vertical.
146          *
147          * @attribute axis
148          * @type String
149          * @default &quot;x&quot;
150          * @writeOnce
151          */
152         axis : {
153             value : 'x',
154             writeOnce : true,
155             validator : function (v) {
156                 return this._validateNewAxis(v);
157             },
158             setter : function (v) {
159                 return this._setAxisFn(v);
160             }
161         },
162
163         /**
164          * Value associated with the left or top most position of the thumb on
165          * the rail.
166          *
167          * @attribute min
168          * @type Number
169          * @default 0
170          */
171         min : {
172             value : 0,
173             validator : function (v) {
174                 return this._validateNewMin(v);
175             }
176         },
177
178         /**
179          * Value associated with the right or bottom most position of the thumb
180          * on the rail.
181          *
182          * @attribute max
183          * @type Number
184          * @default 100
185          */
186         max : {
187             value : 100,
188             validator : function (v) {
189                 return this._validateNewMax(v);
190             }
191         },
192
193         /**
194          * The current value of the Slider.  This value is interpretted into a
195          * position for the thumb along the Slider's rail.
196          *
197          * @attribute value
198          * @type Number
199          * @default 0
200          */
201         value : {
202             value : 0,
203             validator : function (v) {
204                 return this._validateNewValue(v);
205             }
206         },
207
208         /**
209          * The Node representing the Slider's rail, usually visualized as a
210          * bar of some sort using a background image, along which the thumb
211          * moves.  This Node contains the thumb Node.
212          *
213          * @attribute rail
214          * @type Node
215          * @default null
216          */
217         rail : {
218             value : null,
219             validator : function (v) {
220                 return this._validateNewRail(v);
221             },
222             setter : function (v) {
223                 return this._setRailFn(v);
224             }
225         },
226
227         /**
228          * <p>The Node representing the Slider's thumb, usually visualized as a
229          * pointer using a contained image Node (see thumbImage).  The current
230          * value of the Slider is calculated from the centerpoint of this
231          * Node in relation to the rail Node.  If provided, the thumbImage
232          * Node is contained within this Node.</p>
233          *
234          * <p>If no thumbImage is provided and the Node passed as the thumb is
235          * an <code>img</code> element, the assigned Node will be allocated to
236          * the thumbImage and the thumb container defaulted.</p>
237          *
238          * @attribute thumb
239          * @type Node
240          * @default null
241          */
242         thumb : {
243             value : null,
244             validator : function (v) {
245                 return this._validateNewThumb(v);
246             },
247             setter : function (v) {
248                 return this._setThumbFn(v);
249             }
250         },
251
252         /**
253          * <p>The Node representing the image element to use for the Slider's
254          * thumb.</p>
255          *
256          * <p>Alternately, an image URL can be passed and an <code>img</code>
257          * Node will be generated accordingly.</p>
258          *
259          * <p>If no thumbImage is provided and the Node passed as the thumb is
260          * an <code>img</code> element, the assigned Node will be allocated to
261          * the thumbImage and the thumb container defaulted.</p>
262          *
263          * <p>If thumbImage is provided but its URL resolves to a 404, a default
264          * style will be applied to maintain basic functionality.</p>
265          *
266          * @attribute thumbImage
267          * @type Node|String
268          * @default null
269          */
270         thumbImage : {
271             value : null,
272             validator : function (v) {
273                 return this._validateNewThumbImage(v);
274             },
275             setter : function (v) {
276                 return this._setThumbImageFn(v);
277             }
278         },
279
280         /**
281          * <p>The width or height of the rail element representing the physical
282          * space along which the thumb can move.  CSS size values (e.g. '30em')
283          * accepted but converted to pixels during render.</p>
284          *
285          * <p>Alternately, but not recommended, this attribute can be left
286          * unassigned in favor of specifying height or width.</p>
287          *
288          * @attribute railSize
289          * @type String
290          * @default '0'
291          */
292         railSize : {
293             value : '0',
294             validator : function (v) {
295                 return this._validateNewRailSize(v);
296             }
297         },
298
299         /**
300          * Boolean indicating whether clicking and dragging on the rail will
301          * trigger thumb movement.
302          *
303          * @attribute railEnabled
304          * @type Boolean
305          * @default true
306          */
307         railEnabled : {
308             value : true,
309             validator : isBoolean
310         },
311
312         /**
313          * Like CSS padding, the distance in pixels from the inner top or left
314          * edge of the rail node within which the thumb can travel.  Negative
315          * values allow the edge of the thumb to escape the rail node
316          * boundaries.
317          *
318          * @attribute minGutter
319          * @type Number
320          * @default 0
321          */
322         minGutter : {
323             value : 0,
324             validator : isNumber
325         },
326
327         /**
328          * Like CSS padding, the distance in pixels from the inner bottom or
329          * right edge of the rail node within which the thumb can travel.
330          * Negative values allow the edge of the thumb to escape the rail node
331          * boundaries.
332          *
333          * @attribute maxGutter
334          * @type Number
335          * @default 0
336          */
337         maxGutter : {
338             value : 0,
339             validator : isNumber
340         }
341     }
342 });
343
344 Y.extend(Slider, Y.Widget, {
345
346     /**
347      * Collection of object property names from the appropriate hash set in
348      * Slider._AXIS_KEYS.
349      *
350      * @property _key
351      * @type Object
352      * @protected
353      */
354     _key : null,
355
356     /**
357      * Factor used to translate positional coordinates (e.g. left or top) to
358      * the Slider's value.
359      *
360      * @property _factor
361      * @type Number
362      * @protected
363      */
364     _factor : 1,
365
366     /**
367      * Pixel dimension of the rail Node's width for X axis Sliders or height
368      * for Y axis Sliders.  Used with _factor to calculate positional
369      * coordinates for the thumb.
370      *
371      * @property _railSize
372      * @type Number
373      * @protected
374      */
375     _railSize : null,
376
377     /**
378      * Pixel dimension of the thumb Node's width for X axis Sliders or height
379      * for Y axis Sliders.  Used with _factor to calculate positional
380      * coordinates for the thumb.
381      *
382      * @property _thumbSize
383      * @type Number
384      * @protected
385      */
386     _thumbSize : null,
387
388     /**
389      * Pixel offset of the point in the thumb element from its top/left edge
390      * to where the value calculation should take place.  By default, this is
391      * calculated to half the width of the thumb, causing the value to be
392      * marked from the center of the thumb.
393      *
394      * @property _thumbOffset
395      * @type Number
396      * @protected
397      */
398     _thumbOffset : 0,
399
400     /**
401      * Object returned from temporary subscription to disabledChange event to
402      * defer setting the disabled state while Slider is loading the thumb
403      * image.
404      *
405      * @property _stall
406      * @type Object
407      * @protected
408      */
409     _stall : false,
410
411     /**
412      * Deferred value for the disabled attribute when stalled (see _stall
413      * property).
414      *
415      * @property _disabled
416      * @type Boolean
417      * @protected
418      */
419     _disabled : false,
420
421     /**
422      * Construction logic executed durint Slider instantiation. Subscribes to
423      * after events for min, max, and railSize.  Publishes custom events
424      * including slideStart and slideEnd.
425      *
426      * @method initializer
427      * @protected
428      */
429     initializer : function () {
430         this._key = Slider._AXIS_KEYS[this.get('axis')];
431
432         this.after('minChange',      this._afterMinChange);
433         this.after('maxChange',      this._afterMaxChange);
434
435         this.after('railSizeChange', this._afterRailSizeChange);
436
437         /**
438          * Signals the beginning of a thumb drag operation.  Payload includes
439          * the DD.Drag instance's drag:start event under key ddEvent.
440          *
441          * @event slideStart
442          * @param event {Event.Facade} An Event Facade object with the following attribute specific properties added:
443          *  <dl>
444          *      <dt>ddEvent</dt>
445          *          <dd><code>drag:start</code> event from the managed DD.Drag instance</dd>
446          *  </dl>
447          */
448         this.publish(SLIDE_START);
449
450         /**
451          * Signals the end of a thumb drag operation.  Payload includes
452          * the DD.Drag instance's drag:end event under key ddEvent.
453          *
454          * @event slideEnd
455          * @param event {Event.Facade} An Event Facade object with the following attribute specific properties added:
456          *  <dl>
457          *      <dt>ddEvent</dt>
458          *          <dd><code>drag:end</code> event from the managed DD.Drag instance</dd>
459          *  </dl>
460          */
461         this.publish(SLIDE_END);
462
463         /**
464          * Communicates a request to synchronize the Slider UI with the
465          * attribute state.  Links the sync request with the default sync
466          * logic in _defSyncFn.
467          *
468          * @event sync
469          * @param event {Event.Facade} Event Facade object
470          * @preventable _defSyncFn
471          */
472         this.publish(SYNC, { defaultFn: this._defSyncFn });
473
474         /**
475          * Signals a request to reposition the thumb in response to API methods.
476          * Triggers the thumb placement logic in _defPositionThumbFn.
477          *
478          * @event positionThumb
479          * @param event {Event.Facade} An Event Facade object with the following attribute specific properties added:
480          *  <dl>
481          *      <dt>changeEv</dt>
482          *          <dd><code>valueChange</code> event fired in response to the change in the value attribute</dd>
483          *  </dl>
484          * @preventable _defPositionThumbFn
485          */
486         this.publish(POSITION_THUMB, { defaultFn: this._defPositionThumbFn });
487     },
488
489     /**
490      * Create the DOM structure for the Slider.
491      *
492      * @method renderUI
493      * @protected
494      */
495     renderUI : function () {
496         this._initRail();
497         this._initThumb();
498     },
499
500     /**
501      * Creates the rail element if not provided and not discovered via
502      * HTML_PARSER.
503      *
504      * @method _initRail
505      * @protected
506      */
507     _initRail : function () {
508         var cb   = this.get(CONTENT_BOX),
509             rail = this.get(RAIL);
510
511         // Create rail if necessary. Make sure it's in the contentBox
512         if (!rail) {
513             rail = cb.appendChild(
514                 Y.Node.create('<div class="'+C_RAIL+'"></div>'));
515
516             this.set(RAIL,rail);
517         } else if (!cb.contains(rail)) {
518             cb.appendChild(rail);
519         }
520
521         rail.addClass(C_RAIL);
522         rail.addClass(this.getClassName(RAIL,this.get('axis')));
523     },
524
525     /**
526      * <p>Creates the thumb element (not image) if not provided and not
527      * discovered via HTML_PARSER.  If the thumb is an <code>img</code> element
528      * but no thumbImage configured or discovered, reassigns the thumb element
529      * to the thumbImage and defaults the thumb element as a div.</p>
530      *
531      * <p>Makes sure the thumb is a child of the rail element and calls
532      * _initThumbImage if thumbImage is provided.</p>
533      *
534      * @method _initThumb
535      * @protected
536      */
537     _initThumb : function () {
538         var rail    = this.get(RAIL),
539             thumb   = this.get(THUMB);
540
541         // Passed an img element as the thumb
542         if (thumb && !this.get(THUMB_IMAGE) &&
543             thumb.get('nodeName').toLowerCase() === 'img') {
544             this.set(THUMB_IMAGE, thumb);
545             this.set(THUMB,null);
546             thumb = null;
547         }
548
549         if (!thumb) {
550             thumb = Y.Node.create(
551                 '<div class="'+C_THUMB+'"></div>');
552
553             this.set(THUMB,thumb);
554         }
555
556         thumb.addClass(C_THUMB);
557
558         if (!rail.contains(thumb)) {
559             rail.appendChild(thumb);
560         }
561
562         if (this.get(THUMB_IMAGE)) {
563             this._initThumbImage();
564         }
565     },
566
567     /**
568      * Ensures the thumbImage is a child of the thumb element.
569      *
570      * @method _initThumbImage
571      * @protected
572      */
573     _initThumbImage : function () {
574         var thumb = this.get(THUMB),
575             img   = this.get(THUMB_IMAGE);
576
577         if (img) {
578             img.replaceClass(C_THUMB,C_THUMB_IMAGE);
579
580             if (!thumb.contains(img)) {
581                 thumb.appendChild(img);
582             }
583         }
584     },
585
586     /**
587      * Creates the Y.DD instance used to handle the thumb movement and binds
588      * Slider interaction to the configured value model.
589      *
590      * @method bindUI
591      * @protected
592      */
593     bindUI : function () {
594         /**
595          * Bridges user interaction with the thumb to the value attribute.
596          *
597          * @event thumbDrag
598          * @param event {Event.Facade} An Event Facade object with the following attribute specific properties added:
599          *  <dl>
600          *      <dt>ddEvent</dt>
601          *          <dd><code>drag:drag</code> event from the managed DD.Drag instance</dd>
602          *  </dl>
603          * @preventable _defThumbDragFn
604          */
605         this.publish(THUMB_DRAG, {defaultFn: this._defThumbDragFn});
606
607         this._bindThumbDD();
608
609         this.after('valueChange',      this._afterValueChange);
610         this.after('thumbImageChange', this._afterThumbImageChange);
611         this.after(DISABLED_CHANGE,    this._afterDisabledChange);
612     },
613
614     /**
615      * Creates the Y.DD instance used to handle the thumb interaction.
616      * 
617      * @method _bindThumbDD
618      * @protected
619      */
620     _bindThumbDD : function () {
621         var ddConf = {
622                 node : this.get(THUMB),
623                 bubble : false
624             },
625             conConf = {
626                 constrain2node : this.get(RAIL)
627             };
628
629         conConf[this._key.ddStick] = true;
630
631         this._dd = new Y.DD.Drag(ddConf).plug(Y.Plugin.DDConstrained, conConf);
632         this._dd.on('drag:start', Y.bind(this._onDDStartDrag, this));
633         this._dd.on('drag:drag',  Y.bind(this._onDDDrag,      this));
634         this._dd.on('drag:end',   Y.bind(this._onDDEndDrag,   this));
635
636         this._initRailDD();
637     },
638
639     /**
640      * Subscribes to the rail Node's mousedown event to actuate the thumb when
641      * backgroundEnabled is true.
642      *
643      * @method _initRailDD
644      * @protected
645      */
646     _initRailDD : function () {
647         this.get(RAIL).on('mousedown',Y.bind(this._handleRailMouseDown,this));
648     },
649
650     /**
651      * If the Slider is not disabled and railEnabled is true, moves the thumb
652      * to the mousedown position and hands control over to DD.
653      *
654      * @method _handleRailMouseDown
655      * @param e {Event} Mousedown event facade
656      * @protected
657      */
658     _handleRailMouseDown : function (e) {
659         if (this.get('railEnabled') && !this.get(DISABLED)) {
660             var dd      = this._dd,
661                 xyIndex = this._key.xyIndex,
662                 xy;
663
664             if (dd.get('primaryButtonOnly') && e.button > 1) {
665                 return false;
666             }
667
668             dd._dragThreshMet = true;
669
670             dd._fixIEMouseDown();
671             e.halt();
672
673             Y.DD.DDM.activeDrag = dd;
674
675             // Adjust registered starting position by half the thumb's x/y
676             xy = dd.get('dragNode').getXY();
677             xy[xyIndex] += this._thumbOffset;
678
679             dd._setStartPosition(xy);
680             dd.set('activeHandle',dd.get('dragNode'));
681
682             dd.start();
683             dd._alignNode([e.pageX,e.pageY]);
684         }
685     },
686
687     /**
688      * Synchronizes the DOM state with the attribute settings (most notably
689      * railSize and value).  If thumbImage is provided and is still loading,
690      * sync is delayed until it is complete, since the image's dimensions are
691      * taken into consideration for calculations.
692      *
693      * @method syncUI
694      */
695     syncUI : function () {
696         this.get(CONTENT_BOX).removeClass(C_IMAGE_ERROR);
697
698         var img = this.get(THUMB_IMAGE);
699
700         if (this._isImageLoading(img)) {
701             // Schedule the sync for when the image loads/errors
702             this._scheduleSync();
703         } else {
704             this._ready(img,!this._isImageLoaded(img));
705         }
706     },
707
708     /**
709      * Binds to the load and error event on the thumbImage to sync the DOM
710      * state with the attribute settings when the image resource is resolved.
711      * The Slider is disabled while it waits.
712      *
713      * @method _scheduleSync
714      * @protected
715      */
716     _scheduleSync : function () {
717         var img, handler;
718
719         if (!this._stall) {
720             // disable the control until the image is loaded
721             this._disabled = this.get(DISABLED);
722             this.set(DISABLED,true);
723             this._stall    = this.on(DISABLED_CHANGE,this._stallDisabledChange);
724
725             img     = this.get(THUMB_IMAGE);
726             handler = Y.bind(this._imageLoaded,this,img);
727             img.on('load', handler);
728             img.on('error',handler);
729         }
730     },
731
732     /**
733      * Method subscribed to the disabledChange event when thumbImage is being
734      * loaded.  Prevents manually enabling the Slider until the thumbImage
735      * resource is resolved.  Intended value is stored during load and set upon
736      * completion.
737      *
738      * @method _stallDisabledChange
739      * @param e {Event} Change event for the disabled attribute
740      * @protected
741      */
742     _stallDisabledChange : function (e) {
743         this._disabled = e.newVal;
744         e.preventDefault();
745     },
746
747     /**
748      * Event handler assigned to the thumbImage's load and error event if it
749      * was not loaded prior to instantiation.  Restores the disabled value.
750      *
751      * @method _imageLoaded
752      * @param img {Node} The thumbImage Node
753      * @param e {Event} load or error event fired by the thumbImage
754      * @protected
755      */
756     _imageLoaded : function (img,e) {
757         var error = (e.type.toLowerCase().indexOf('error') > -1);
758
759         // Need to execute inside a setTimeout because IE doesn't report
760         // img.complete === true until after the img.onload handler
761         // @TODO: readyState reports correctly in onload.  Lose this wrapper
762         // and use that in _isImageLoaded.
763         Y.later(0, this, function () {
764             if (this._stall) {
765                 this._stall.detach();
766             }
767
768
769             this._stall = false;
770
771             this._ready(img,error);
772
773             this.set(DISABLED,this._disabled);
774         });
775     },
776
777     /**
778      * Applies a class to the content box if the thumbImage failed to resolve,
779      * the fires the internal sync event triggering a sync between UI and
780      * state.
781      *
782      * @method _ready
783      * @param img {Node} the thumbImage Node
784      * @param error {Boolean} Indicates an error while loading the thumbImage
785      * @protected
786      */
787     _ready : function (img,error) {
788         var method = error ? 'addClass' : 'removeClass';
789
790         // If the thumb image url results in 404, assign a class to provide
791         // default thumb dimensions/UI
792         this.get(CONTENT_BOX)[method](C_IMAGE_ERROR);
793
794         this.fire(SYNC);
795     },
796
797     /**
798      * The default synchronization behavior, updating the Slider's DOM state to
799      * match the current attribute values.
800      *
801      * @method _defSyncFn
802      * @param e {Event} Internal sync event
803      * @protected
804      */
805     _defSyncFn : function (e) {
806         this._uiSetThumbSize();
807
808         this._setThumbOffset();
809
810         this._uiSetRailSize();
811
812         this._setRailOffsetXY();
813
814         this._setDDGutter();
815
816         this._resetDDCacheRegion();
817
818         this._setFactor();
819
820         var val = this.get(VALUE);
821
822         this.fire(POSITION_THUMB, {
823             value  : val,
824             offset : this._convertValueToOffset(val)
825         });
826
827         // Forces a reflow of the bounding box to address IE8 inline-block
828         // container not expanding correctly. bug 2527905
829         this.get('boundingBox').toggleClass('');
830     },
831
832     /**
833      * Captures the thumb's pixel height or width (depending on the Slider's
834      * axis) for use in positioning calculations.
835      *
836      * @method _uiSetThumbSize
837      * @protected
838      */
839     _uiSetThumbSize : function () {
840         var thumb = this.get(THUMB),
841             dim   = this._key.dim,
842             img   = this.get(THUMB_IMAGE),
843             size;
844
845         // offsetWidth fails in hidden containers
846         size = parseInt(thumb.getComputedStyle(dim),10);
847
848
849         if (img && this._isImageLoaded(img)) {
850
851             size = img.get(dim);
852         }
853
854         this._thumbSize = size;
855     },
856
857     /**
858      * Establishes the point in the thumb that should align to the rail
859      * position representing the calculated value.
860      *
861      * @method _setThumbOffset
862      * @protected
863      */
864     _setThumbOffset : function () {
865         this._thumbOffset = floor(this._thumbSize / 2);
866     },
867
868     /**
869      * Stores the rail Node's pixel height or width, depending on the Slider's
870      * axis, for use in calculating thumb position from the value.
871      *
872      * @method _uiSetRailSize
873      * @protected
874      */
875     _uiSetRailSize : function () {
876         var rail  = this.get(RAIL),
877             thumb = this.get(THUMB),
878             img   = this.get(THUMB_IMAGE),
879             dim   = this._key.dim,
880             size  = this.get(RAIL_SIZE),
881             setxy = false;
882
883         if (parseInt(size,10)) {
884
885             // Convert to pixels
886             rail.setStyle(dim,size);
887             size = parseInt(rail.getComputedStyle(dim),10);
888
889         } else {
890             // Default from height or width (axis respective), or dims assigned
891             // via css to the rail or thumb, whichever is largest.
892             // Dear implementers, please use railSize, not height/width to
893             // set the rail dims
894             size = this.get(dim);
895             if (parseInt(size,10)) {
896                 setxy = true;
897                 rail.setStyle(dim,size);
898                 size = parseInt(rail.getComputedStyle(dim),10);
899             }
900             size = max(
901                     size|0,
902                     parseInt(thumb.getComputedStyle(dim),10),
903                     parseInt(rail.getComputedStyle(dim),10));
904
905
906             if (img && this._isImageLoaded(img)) {
907
908                 size = max(img.get(dim),size);
909             }
910         }
911
912         rail.setStyle(dim, size + PX);
913
914         this._railSize = size;
915
916         // handle the (not recommended) fallback case of setting rail size via
917         // widget height/width params.  This is the only case that sets the
918         // off-axis rail dim in the code.
919         if (setxy) {
920             dim = this._key.offAxisDim;
921             size = this.get(dim);
922             if (size) {
923                 rail.set(dim,size);
924             }
925         }
926     },
927
928     /**
929      * Store the current XY position of the rail Node on the page.  For use in
930      * calculating thumb position from value.
931      *
932      * @method _setRailOffsetXY
933      * @protected
934      */
935     _setRailOffsetXY : function () {
936         this._offsetXY = this.get(RAIL).getXY()[this._key.xyIndex] +
937                          this.get(MIN_GUTTER);
938     },
939
940    /**
941     * Passes the gutter attribute value to the DDConstrain gutter attribute.
942     *
943     * @method _setDDGutter
944     * @protected
945     */
946     _setDDGutter : function () {
947         var gutter = this._key.xyIndex ?
948             this.get(MIN_GUTTER) + " 0 " + this.get(MAX_GUTTER) :
949             "0 " + this.get(MAX_GUTTER) + " 0 " + this.get(MIN_GUTTER);
950
951
952         this._dd.con.set('gutter', gutter);
953     },
954
955     /**
956      * Resets the cached region inside the DD constrain instance to support
957      * repositioning the Slider after instantiation.
958      *
959      * @method _resetDDCacheRegion
960      * @protected
961      */
962     _resetDDCacheRegion : function () {
963         // Workaround for ticket #2527964
964         this._dd.con._cacheRegion();
965     },
966
967     /**
968      * Calculates the multiplier used to translate the value into a thumb
969      * position.
970      *
971      * @method _setFactor
972      * @protected
973      */
974     _setFactor : function () {
975         var range = this._railSize - this._thumbSize -
976                     this.get(MIN_GUTTER) - this.get(MAX_GUTTER);
977
978         this._factor = this._railSize ?
979             (this.get(MAX) - this.get(MIN)) / range :
980             1;
981
982     },
983
984     /**
985      * Convenience method for accessing the current value of the Slider.
986      * Equivalent to <code>slider.get(&quot;value&quot;)</code>.
987      *
988      * @method getValue
989      * @return {Number} the value
990      */
991     getValue : function () {
992         return this.get(VALUE);
993     },
994
995     /**
996      * Convenience method for updating the current value of the Slider.
997      * Equivalent to <code>slider.set(&quot;value&quot;,val)</code>.
998      *
999      * @method setValue
1000      * @param val {Number} the new value
1001      */
1002     setValue : function (val) {
1003         this.set(VALUE,val);
1004     },
1005
1006     /**
1007      * Validator applied to new values for the axis attribute. Only
1008      * &quot;x&quot; and &quot;y&quot; are permitted.
1009      *
1010      * @method _validateNewAxis
1011      * @param v {String} proposed value for the axis attribute
1012      * @return Boolean
1013      * @protected
1014      */
1015     _validateNewAxis : function (v) {
1016         return isString(v) && 'xXyY'.indexOf(v.charAt(0)) > -1;
1017     },
1018
1019     /**
1020      * Validator applied to the min attribute.
1021      *
1022      * @method _validateNewMin
1023      * @param v {MIXED} proposed value for the min attribute
1024      * @return Boolean
1025      * @protected
1026      */
1027     _validateNewMin : function (v) {
1028         return isNumber(v);
1029     },
1030
1031     /**
1032      * Validator applied to the max attribute.
1033      *
1034      * @method _validateNewMax
1035      * @param v {MIXED} proposed value for the max attribute
1036      * @return Boolean
1037      * @protected
1038      */
1039     _validateNewMax : function (v) {
1040         return isNumber(v);
1041     },
1042
1043     /**
1044      * Validator applied to the value attribute.
1045      *
1046      * @method _validateNewValue
1047      * @param v {MIXED} proposed value for the value attribute
1048      * @return Boolean
1049      * @protected
1050      */
1051     _validateNewValue : function (v) {
1052         var min    = this.get(MIN),
1053             max    = this.get(MAX);
1054
1055         return isNumber(v) &&
1056                 (min < max ? (v >= min && v <= max) : (v >= max && v <= min));
1057     },
1058
1059     /**
1060      * Validator applied to the rail attribute. Rejects all values after the
1061      * Slider has been rendered.
1062      *
1063      * @method _validateNewRail
1064      * @param v {MIXED} proposed value for the rail attribute
1065      * @return Boolean
1066      * @protected
1067      */
1068     _validateNewRail : function (v) {
1069         return !this.get(RENDERED) || v;
1070     },
1071
1072     /**
1073      * Validator applied to the thumb attribute.  Rejects all values after the
1074      * Slider has been rendered.
1075      *
1076      * @method _validateNewThumb
1077      * @param v {MIXED} proposed value for the thumb attribute
1078      * @return Boolean
1079      * @protected
1080      */
1081     _validateNewThumb : function (v) {
1082         return !this.get(RENDERED) || v;
1083     },
1084
1085     /**
1086      * Validator applied to the thumbImage attribute.  Rejects all values after
1087      * the Slider has been rendered.
1088      *
1089      * @method _validateNewThumbImage
1090      * @param v {MIXED} proposed value for the thumbImage attribute
1091      * @return Boolean
1092      * @protected
1093      */
1094     _validateNewThumbImage : function (v) {
1095         return !this.get(RENDERED) || v;
1096     },
1097
1098     /**
1099      * Validator applied to the railSize attribute. Only strings of css size
1100      * values (e.g. '200px') are allowed.
1101      *
1102      * @method _validateNewRailSize
1103      * @param v {String} proposed value for the railSize attribute
1104      * @return Boolean
1105      * @protected
1106      */
1107     _validateNewRailSize : function (v) {
1108         return isString(v) &&
1109             (v === '0' || /^\d+(?:p[xtc]|%|e[mx]|in|[mc]m)$/.test(v));
1110     },
1111
1112     /**
1113      * Setter applied to the input when updating the axis attribute.
1114      *
1115      * @method _setAxisFn
1116      * @param v {String} proposed value for the axis attribute
1117      * @return {String} lowercased first character of the input string
1118      * @protected
1119      */
1120     _setAxisFn : function (v) {
1121         return v.charAt(0).toLowerCase();
1122     },
1123
1124     /**
1125      * Setter applied to the input when updating the rail attribute.  Input can
1126      * be a Node, raw HTMLElement, or a selector string to locate it.
1127      *
1128      * @method _setRailFn
1129      * @param v {Node|String|HTMLElement} The rail element Node or selector
1130      * @return {Node} The Node if found.  Otherwise null.
1131      * @protected
1132      */
1133     _setRailFn : function (v) {
1134         return Y.get(v) || null;
1135     },
1136
1137     /**
1138      * Setter applied to the input when updating the thumb attribute.  Input can
1139      * be a Node, raw HTMLElement, or a selector string to locate it.
1140      *
1141      * @method _setThumbFn
1142      * @param v {Node|String|HTMLElement} The thumb element Node or selector
1143      * @return {Node} The Node if found.  Otherwise null.
1144      * @protected
1145      */
1146     _setThumbFn : function (v) {
1147         return Y.get(v) || null;
1148     },
1149
1150     /**
1151      * Setter applied to the input when updating the thumbImage attribute.
1152      * Input can be a Node, raw HTMLElement, selector string to locate it, or
1153      * the URL for an image resource.
1154      *
1155      * String input will be treated as a selector.  If no element is found using
1156      * the selector, an <code>img</code> Node will be created with the string
1157      * used as the <code>src</code> attribute.
1158      *
1159      * @method _setThumbImageFn
1160      * @param v {Node|String|HTMLElement} The thumbImage element Node, selector,
1161      *          or image URL
1162      * @return {Node} The Node if found or created.  Otherwise null.
1163      * @protected
1164      */
1165     _setThumbImageFn : function (v) {
1166         return v ? Y.get(v) ||
1167                 Y.Node.create('<img src="'+v+'" alt="Slider thumb">') :
1168                 null;
1169     },
1170
1171
1172     /**
1173      * Caches the current page position of the rail element and fires the
1174      * slideStart event in response to the DD's drag:start.
1175      *
1176      * @method _onDDStartDrag
1177      * @param e {Event} the DD instance's drag:start custom event
1178      * @protected
1179      */
1180     _onDDStartDrag : function (e) {
1181         this._setRailOffsetXY();
1182         this.fire(SLIDE_START,{ ddEvent: e });
1183     },
1184
1185     /**
1186      * Fires the thumbDrag event to queue Slider value update.
1187      *
1188      * @method _onDDDrag
1189      * @param e {Event} the DD instance's drag:drag custom event
1190      * @protected
1191      */
1192     _onDDDrag : function (e) {
1193         this.fire(THUMB_DRAG, { ddEvent: e });
1194     },
1195
1196     /**
1197      * The default value update behavior in response to Slider thumb
1198      * interaction.  Calculates the value using stored offsets, the _factor
1199      * multiplier and the min value.
1200      *
1201      * @method _defThumbDragFn
1202      * @param e {Event} the internal thumbDrag event
1203      * @protected
1204      */
1205     _defThumbDragFn : function (e) {
1206         var before = this.get(VALUE),
1207             val    = e.ddEvent[this._key.eventPageAxis] - this._offsetXY;
1208
1209
1210         val = this._convertOffsetToValue(val);
1211
1212         if (before !== val) {
1213             this.set(VALUE, val, { ddEvent: e.ddEvent });
1214         }
1215     },
1216
1217     /**
1218      * Fires the slideEnd event.
1219      *
1220      * @method _onDDEndDrag
1221      * @param e {Event} the DD instance's drag:end custom event
1222      * @protected
1223      */
1224     _onDDEndDrag : function (e) {
1225         this.fire(SLIDE_END,{ ddEvent: e });
1226     },
1227
1228
1229
1230
1231     /**
1232      * Calls _uiPositionThumb with the value of the custom event's
1233      * &quot;offset&quot; property.
1234      *
1235      * @method _defPositionThumbFn
1236      * @param e {Event} the positionThumb custom event
1237      * @protected
1238      */
1239     _defPositionThumbFn : function (e) {
1240
1241         this._uiPositionThumb(e.offset);
1242     },
1243
1244     /**
1245      * Places the thumb at a particular X or Y location based on the configured
1246      * axis.
1247      *
1248      * @method _uiPositionThumb
1249      * @param xy {Number} the desired left or top pixel position of the thumb
1250      *           in relation to the rail Node.
1251      * @protected
1252      */
1253     _uiPositionThumb : function (xy) {
1254         var dd     = this._dd,
1255             thumb  = dd.get('dragNode'),
1256             hidden = thumb.ancestor(this._isDisplayNone);
1257
1258         if (!hidden) {
1259             dd._setStartPosition(dd.get('dragNode').getXY());
1260
1261             // stickX/stickY config on DD instance will negate off-axis move
1262             dd._alignNode([xy,xy],true);
1263         }
1264     },
1265
1266     /**
1267      * Helper function to search up the ancestor axis looking for a node with
1268      * style display: none.  This is passed as a function to node.ancestor(..)
1269      * to test if a given node is in the displayed DOM and can get accurate
1270      * positioning information.
1271      *
1272      * @method _isDisplayNone
1273      * @param el {Node} ancestor node as the function walks up the parent axis
1274      * @return {Boolean} true if the node is styled with display: none
1275      * @protected
1276      */
1277     _isDisplayNone : function (node) {
1278         return node.getComputedStyle('display') === 'none';
1279     },
1280
1281     /**
1282      * Fires the internal positionThumb event in response to a change in the
1283      * value attribute.
1284      *
1285      * @method _afterValueChange
1286      * @param e {Event} valueChange custom event
1287      * @protected
1288      */
1289     _afterValueChange : function (e) {
1290         if (!e.ddEvent) {
1291             var xy = this._convertValueToOffset(e.newVal);
1292
1293
1294             this.fire(POSITION_THUMB,{ value: e.newVal, offset: xy });
1295         }
1296     },
1297
1298     /**
1299      * Converts a value to a pixel offset for the thumb position on the rail.
1300      *
1301      * @method _convertValueToOffset
1302      * @param v {Number} value between the Slider's min and max
1303      * @protected
1304      */
1305     _convertValueToOffset : function (v) {
1306         return round((v - this.get(MIN)) / this._factor) + this._offsetXY;
1307     },
1308
1309     /**
1310      * Converts a pixel offset of the thumb on the rail to a value.
1311      *
1312      * @method _convertOffsetToValue
1313      * @param v {Number} pixel offset of the thumb on the rail
1314      * @protected
1315      */
1316     _convertOffsetToValue : function (v) {
1317         return round(this.get(MIN) + (v * this._factor));
1318     },
1319
1320     /**
1321      * Replaces the thumb Node in response to a change in the thumb attribute.
1322      * This only has effect after the Slider is rendered.
1323      *
1324      * @method _afterThumbChange
1325      * @param e {Event} thumbChange custom event
1326      * @protected
1327      */
1328     _afterThumbChange : function (e) {
1329         var thumb;
1330
1331         if (this.get(RENDERED)) {
1332             if (e.prevValue) {
1333                 e.prevValue.get('parentNode').removeChild(e.prevValue);
1334             }
1335
1336             this._initThumb();
1337             
1338             thumb = this.get(THUMB);
1339             this._dd.set('node',thumb);
1340             this._dd.set('dragNode',thumb);
1341
1342             this.syncUI();
1343         }
1344     },
1345
1346     /**
1347      * Sets or replaces the thumb's contained <code>img</code> Node with the
1348      * new Node in response to a change in the thumbImage attribute.  This only
1349      * has effect after the Slider is rendered.
1350      *
1351      * @method _afterThumbImageChange
1352      * @param e {Event} thumbImageChange custom event
1353      * @protected
1354      */
1355     _afterThumbImageChange : function (e) {
1356         if (this.get(RENDERED)) {
1357             if (e.prevValue) {
1358                 e.prevValue.get('parentNode').removeChild(e.prevValue);
1359             }
1360
1361             this._initThumbImage();
1362             
1363             this.syncUI();
1364         }
1365     },
1366
1367     /**
1368      * Updates the Slider UI in response to change in the min attribute.
1369      *
1370      * @method _afterMinChange
1371      * @param e {Event} minChange custom event
1372      * @protected
1373      */
1374     _afterMinChange : function (e) {
1375         this._refresh(e);
1376     },
1377
1378     /**
1379      * Updates the Slider UI in response to change in the max attribute.
1380      *
1381      * @method _afterMaxChange
1382      * @param e {Event} maxChange custom event
1383      * @protected
1384      */
1385     _afterMaxChange : function (e) {
1386         this._refresh(e);
1387     },
1388
1389     /**
1390      * Updates the Slider UI in response to change in the railSize attribute.
1391      *
1392      * @method _afterRailSizeChange
1393      * @param e {Event} railSizeChange custom event
1394      * @protected
1395      */
1396     _afterRailSizeChange : function (e) {
1397         this._refresh(e);
1398     },
1399
1400     /**
1401      * Locks or unlocks the DD instance in response to a change in the disabled
1402      * attribute.
1403      *
1404      * @method _afterDisabledChange
1405      * @param e {Event} disabledChange custom event
1406      * @protected
1407      */
1408     _afterDisabledChange : function (e) {
1409         if (this._dd) {
1410             this._dd.set('lock',e.newVal);
1411         }
1412     },
1413
1414     /**
1415      * Common handler to call syncUI in response to change events that occurred
1416      * after the Slider is rendered.
1417      *
1418      * @method _refresh
1419      * @param e {Event} An attribute change event
1420      * @protected
1421      */
1422     _refresh : function (e) {
1423         if (e.newVal !== e.prevVal && this.get(RENDERED)) {
1424             this.syncUI();
1425         }
1426     },
1427
1428     /**
1429      * Used to determine if there is a current or pending request for the
1430      * thumbImage resource.
1431      *
1432      * @method _isImageLoading
1433      * @param img {Node} <code>img</code> Node
1434      * @return Boolean
1435      * @protected
1436      */
1437     _isImageLoading : function (img) {
1438         return img && !img.get(COMPLETE);
1439     },
1440
1441     /**
1442      * Used to determine if the image resource loaded successfully or there was
1443      * an error.
1444      *
1445      * NOTES:
1446      * <ul>
1447      *    <li>img load error fired xbrowser for image resources not yet resolved</li>
1448      *    <li>img.complete reports false in IE for images not yet loaded as well as images that failed to load</li>
1449      *    <li>img.complete true && img.naturalWidth == 0 in FF and Safari indicate image failed to load</li>
1450      *    <li>img.complete && img.width == 0 in Opera indicates image failed to load</li>
1451      * </ul>
1452      *
1453      * @method _isImageLoaded
1454      * @param img {Node} <code>img</code> Node
1455      * @return Boolean
1456      * @protected
1457      */
1458     _isImageLoaded : function (img) {
1459         if (img) {
1460             var w = img.get('naturalWidth');
1461             return img.get(COMPLETE) && (!isNumber(w) ? img.get(WIDTH) : w);
1462         }
1463
1464         return true;
1465     }
1466
1467 });
1468
1469 Y.Slider = Slider;
1470
1471
1472 }, '3.0.0' ,{requires:['widget','dd-constrain']});