|
1 YUI.add('slider-value-range', function (Y, NAME) { |
|
2 |
|
3 /** |
|
4 * Adds value support for Slider as a range of integers between a configured |
|
5 * minimum and maximum value. For use with <code>Y.Base.build(..)</code> to |
|
6 * add the plumbing to <code>Y.SliderBase</code>. |
|
7 * |
|
8 * @module slider |
|
9 * @submodule slider-value-range |
|
10 */ |
|
11 |
|
12 // Constants for compression or performance |
|
13 var MIN = 'min', |
|
14 MAX = 'max', |
|
15 VALUE = 'value', |
|
16 // MINORSTEP = 'minorStep', |
|
17 // MAJORSTEP = 'majorStep', |
|
18 |
|
19 round = Math.round; |
|
20 |
|
21 /** |
|
22 * One class of value algorithm that can be built onto SliderBase. By default, |
|
23 * values range between 0 and 100, but you can configure these on the |
|
24 * built Slider class by setting the <code>min</code> and <code>max</code> |
|
25 * configurations. Set the initial value (will cause the thumb to move to the |
|
26 * appropriate location on the rail) in configuration as well if appropriate. |
|
27 * |
|
28 * @class SliderValueRange |
|
29 */ |
|
30 function SliderValueRange() { |
|
31 this._initSliderValueRange(); |
|
32 } |
|
33 |
|
34 Y.SliderValueRange = Y.mix( SliderValueRange, { |
|
35 |
|
36 // Prototype properties and methods that will be added onto host class |
|
37 prototype: { |
|
38 |
|
39 /** |
|
40 * Factor used to translate value -> position -> value. |
|
41 * |
|
42 * @property _factor |
|
43 * @type {Number} |
|
44 * @protected |
|
45 */ |
|
46 _factor: 1, |
|
47 |
|
48 /** |
|
49 * Stub for construction logic. Override if extending this class and |
|
50 * you need to set something up during the initializer phase. |
|
51 * |
|
52 * @method _initSliderValueRange |
|
53 * @protected |
|
54 */ |
|
55 _initSliderValueRange: function () {}, |
|
56 |
|
57 /** |
|
58 * Override of stub method in SliderBase that is called at the end of |
|
59 * its bindUI stage of render(). Subscribes to internal events to |
|
60 * trigger UI and related state updates. |
|
61 * |
|
62 * @method _bindValueLogic |
|
63 * @protected |
|
64 */ |
|
65 _bindValueLogic: function () { |
|
66 this.after( { |
|
67 minChange : this._afterMinChange, |
|
68 maxChange : this._afterMaxChange, |
|
69 valueChange: this._afterValueChange |
|
70 } ); |
|
71 }, |
|
72 |
|
73 /** |
|
74 * Move the thumb to appropriate position if necessary. Also resets |
|
75 * the cached offsets and recalculates the conversion factor to |
|
76 * translate position to value. |
|
77 * |
|
78 * @method _syncThumbPosition |
|
79 * @protected |
|
80 */ |
|
81 _syncThumbPosition: function () { |
|
82 this._calculateFactor(); |
|
83 |
|
84 this._setPosition( this.get( VALUE ) ); |
|
85 }, |
|
86 |
|
87 /** |
|
88 * Calculates and caches |
|
89 * (range between max and min) / (rail length) |
|
90 * for fast runtime calculation of position -> value. |
|
91 * |
|
92 * @method _calculateFactor |
|
93 * @protected |
|
94 */ |
|
95 _calculateFactor: function () { |
|
96 var length = this.get( 'length' ), |
|
97 thumbSize = this.thumb.getStyle( this._key.dim ), |
|
98 min = this.get( MIN ), |
|
99 max = this.get( MAX ); |
|
100 |
|
101 // The default thumb width is based on Sam skin's thumb dimension. |
|
102 // This attempts to allow for rendering off-DOM, then attaching |
|
103 // without the need to call syncUI(). It is still recommended |
|
104 // to call syncUI() in these cases though, just to be sure. |
|
105 length = parseFloat( length ) || 150; |
|
106 thumbSize = parseFloat( thumbSize ) || 15; |
|
107 |
|
108 this._factor = ( max - min ) / ( length - thumbSize ); |
|
109 |
|
110 Y.log("Calculating factor(~" + this._factor.toFixed(3) + " = (max(" + max + ") - min(" + min + ")) / (length(" + length + ") - thumb size(" + thumbSize + "))","info","slider"); |
|
111 }, |
|
112 |
|
113 /** |
|
114 * Dispatch the new position of the thumb into the value setting |
|
115 * operations. |
|
116 * |
|
117 * @method _defThumbMoveFn |
|
118 * @param e { EventFacade } The host's thumbMove event |
|
119 * @protected |
|
120 */ |
|
121 _defThumbMoveFn: function ( e ) { |
|
122 // To prevent set('value', x) from looping back around |
|
123 if (e.source !== 'set') { |
|
124 this.set(VALUE, this._offsetToValue(e.offset)); |
|
125 } |
|
126 }, |
|
127 |
|
128 /** |
|
129 * <p>Converts a pixel position into a value. Calculates current |
|
130 * thumb offset from the leading edge of the rail multiplied by the |
|
131 * ratio of <code>(max - min) / (constraining dim)</code>.</p> |
|
132 * |
|
133 * <p>Override this if you want to use a different value mapping |
|
134 * algorithm.</p> |
|
135 * |
|
136 * @method _offsetToValue |
|
137 * @param offset { Number } X or Y pixel offset |
|
138 * @return { mixed } Value corresponding to the provided pixel offset |
|
139 * @protected |
|
140 */ |
|
141 _offsetToValue: function ( offset ) { |
|
142 |
|
143 var value = round( offset * this._factor ) + this.get( MIN ); |
|
144 |
|
145 Y.log("Offset: " + offset + " => Value: " + value, "info", "slider"); |
|
146 return round( this._nearestValue( value ) ); |
|
147 }, |
|
148 |
|
149 /** |
|
150 * Converts a value into a pixel offset for use in positioning |
|
151 * the thumb according to the reverse of the |
|
152 * <code>_offsetToValue( xy )</code> operation. |
|
153 * |
|
154 * @method _valueToOffset |
|
155 * @param val { Number } The value to map to pixel X or Y position |
|
156 * @return { Number } The pixel offset |
|
157 * @protected |
|
158 */ |
|
159 _valueToOffset: function ( value ) { |
|
160 var offset = round( ( value - this.get( MIN ) ) / this._factor ); |
|
161 |
|
162 Y.log("Value: " + value + " => Offset: " + offset, "info", "slider"); |
|
163 return offset; |
|
164 }, |
|
165 |
|
166 /** |
|
167 * Returns the current value. Override this if you want to introduce |
|
168 * output formatting. Otherwise equivalent to slider.get( "value" ); |
|
169 * |
|
170 * @method getValue |
|
171 * @return {Number} |
|
172 */ |
|
173 getValue: function () { |
|
174 return this.get( VALUE ); |
|
175 }, |
|
176 |
|
177 /** |
|
178 * Updates the current value. Override this if you want to introduce |
|
179 * input value parsing or preprocessing. Otherwise equivalent to |
|
180 * slider.set( "value", v ); |
|
181 * |
|
182 * @method setValue |
|
183 * @param val {Number} The new value |
|
184 * @return {Slider} |
|
185 * @chainable |
|
186 */ |
|
187 setValue: function ( val ) { |
|
188 return this.set( VALUE, val ); |
|
189 }, |
|
190 |
|
191 /** |
|
192 * Update position according to new min value. If the new min results |
|
193 * in the current value being out of range, the value is set to the |
|
194 * closer of min or max. |
|
195 * |
|
196 * @method _afterMinChange |
|
197 * @param e { EventFacade } The <code>min</code> attribute change event. |
|
198 * @protected |
|
199 */ |
|
200 _afterMinChange: function ( e ) { |
|
201 this._verifyValue(); |
|
202 |
|
203 this._syncThumbPosition(); |
|
204 }, |
|
205 |
|
206 /** |
|
207 * Update position according to new max value. If the new max results |
|
208 * in the current value being out of range, the value is set to the |
|
209 * closer of min or max. |
|
210 * |
|
211 * @method _afterMaxChange |
|
212 * @param e { EventFacade } The <code>max</code> attribute change event. |
|
213 * @protected |
|
214 */ |
|
215 _afterMaxChange: function ( e ) { |
|
216 this._verifyValue(); |
|
217 |
|
218 this._syncThumbPosition(); |
|
219 }, |
|
220 |
|
221 /** |
|
222 * Verifies that the current value is within the min - max range. If |
|
223 * not, value is set to either min or max, depending on which is |
|
224 * closer. |
|
225 * |
|
226 * @method _verifyValue |
|
227 * @protected |
|
228 */ |
|
229 _verifyValue: function () { |
|
230 var value = this.get( VALUE ), |
|
231 nearest = this._nearestValue( value ); |
|
232 |
|
233 if ( value !== nearest ) { |
|
234 // @TODO Can/should valueChange, minChange, etc be queued |
|
235 // events? To make dd.set( 'min', n ); execute after minChange |
|
236 // subscribers before on/after valueChange subscribers. |
|
237 this.set( VALUE, nearest ); |
|
238 } |
|
239 }, |
|
240 |
|
241 /** |
|
242 * Propagate change to the thumb position unless the change originated |
|
243 * from the thumbMove event. |
|
244 * |
|
245 * @method _afterValueChange |
|
246 * @param e { EventFacade } The <code>valueChange</code> event. |
|
247 * @protected |
|
248 */ |
|
249 _afterValueChange: function ( e ) { |
|
250 var val = e.newVal; |
|
251 Y.log("Positioning thumb after set('value',x)","info","slider"); |
|
252 this._setPosition( val, { source: 'set' } ); |
|
253 }, |
|
254 |
|
255 /** |
|
256 * Positions the thumb and its ARIA attributes in accordance with the |
|
257 * translated value. |
|
258 * |
|
259 * @method _setPosition |
|
260 * @param value {Number} Value to translate to a pixel position |
|
261 * @param [options] {Object} Details object to pass to `_uiMoveThumb` |
|
262 * @protected |
|
263 */ |
|
264 _setPosition: function ( value, options ) { |
|
265 this._uiMoveThumb( this._valueToOffset( value ), options ); |
|
266 this.thumb.set('aria-valuenow', value); |
|
267 this.thumb.set('aria-valuetext', value); |
|
268 }, |
|
269 |
|
270 /** |
|
271 * Validates new values assigned to <code>min</code> attribute. Numbers |
|
272 * are acceptable. Override this to enforce different rules. |
|
273 * |
|
274 * @method _validateNewMin |
|
275 * @param value {Any} Value assigned to <code>min</code> attribute. |
|
276 * @return {Boolean} True for numbers. False otherwise. |
|
277 * @protected |
|
278 */ |
|
279 _validateNewMin: function ( value ) { |
|
280 return Y.Lang.isNumber( value ); |
|
281 }, |
|
282 |
|
283 /** |
|
284 * Validates new values assigned to <code>max</code> attribute. Numbers |
|
285 * are acceptable. Override this to enforce different rules. |
|
286 * |
|
287 * @method _validateNewMax |
|
288 * @param value { mixed } Value assigned to <code>max</code> attribute. |
|
289 * @return { Boolean } True for numbers. False otherwise. |
|
290 * @protected |
|
291 */ |
|
292 _validateNewMax: function ( value ) { |
|
293 return Y.Lang.isNumber( value ); |
|
294 }, |
|
295 |
|
296 /** |
|
297 * Restricts new values assigned to <code>value</code> attribute to be |
|
298 * between the configured <code>min</code> and <code>max</code>. |
|
299 * Rounds to nearest integer value. |
|
300 * |
|
301 * @method _setNewValue |
|
302 * @param value { Number } Value assigned to <code>value</code> attribute |
|
303 * @return { Number } Normalized and constrained value |
|
304 * @protected |
|
305 */ |
|
306 _setNewValue: function ( value ) { |
|
307 if ( Y.Lang.isNumber( value ) ) { |
|
308 return round( this._nearestValue( value ) ); |
|
309 } |
|
310 return Y.Attribute.INVALID_VALUE; |
|
311 }, |
|
312 |
|
313 /** |
|
314 * Returns the nearest valid value to the value input. If the provided |
|
315 * value is outside the min - max range, accounting for min > max |
|
316 * scenarios, the nearest of either min or max is returned. Otherwise, |
|
317 * the provided value is returned. |
|
318 * |
|
319 * @method _nearestValue |
|
320 * @param value { mixed } Value to test against current min - max range |
|
321 * @return { Number } Current min, max, or value if within range |
|
322 * @protected |
|
323 */ |
|
324 _nearestValue: function ( value ) { |
|
325 var min = this.get( MIN ), |
|
326 max = this.get( MAX ), |
|
327 tmp; |
|
328 |
|
329 // Account for reverse value range (min > max) |
|
330 tmp = ( max > min ) ? max : min; |
|
331 min = ( max > min ) ? min : max; |
|
332 max = tmp; |
|
333 |
|
334 return ( value < min ) ? |
|
335 min : |
|
336 ( value > max ) ? |
|
337 max : |
|
338 value; |
|
339 } |
|
340 |
|
341 }, |
|
342 |
|
343 /** |
|
344 * Attributes that will be added onto host class. |
|
345 * |
|
346 * @property ATTRS |
|
347 * @type {Object} |
|
348 * @static |
|
349 * @protected |
|
350 */ |
|
351 ATTRS: { |
|
352 /** |
|
353 * The value associated with the farthest top, left position of the |
|
354 * rail. Can be greater than the configured <code>max</code> if you |
|
355 * want values to increase from right-to-left or bottom-to-top. |
|
356 * |
|
357 * @attribute min |
|
358 * @type { Number } |
|
359 * @default 0 |
|
360 */ |
|
361 min: { |
|
362 value : 0, |
|
363 validator: '_validateNewMin' |
|
364 }, |
|
365 |
|
366 /** |
|
367 * The value associated with the farthest bottom, right position of |
|
368 * the rail. Can be less than the configured <code>min</code> if |
|
369 * you want values to increase from right-to-left or bottom-to-top. |
|
370 * |
|
371 * @attribute max |
|
372 * @type { Number } |
|
373 * @default 100 |
|
374 */ |
|
375 max: { |
|
376 value : 100, |
|
377 validator: '_validateNewMax' |
|
378 }, |
|
379 |
|
380 /** |
|
381 * amount to increment/decrement the Slider value |
|
382 * when the arrow up/down/left/right keys are pressed |
|
383 * |
|
384 * @attribute minorStep |
|
385 * @type {Number} |
|
386 * @default 1 |
|
387 */ |
|
388 minorStep : { |
|
389 value: 1 |
|
390 }, |
|
391 |
|
392 /** |
|
393 * amount to increment/decrement the Slider value |
|
394 * when the page up/down keys are pressed |
|
395 * |
|
396 * @attribute majorStep |
|
397 * @type {Number} |
|
398 * @default 10 |
|
399 */ |
|
400 majorStep : { |
|
401 value: 10 |
|
402 }, |
|
403 |
|
404 /** |
|
405 * The value associated with the thumb's current position on the |
|
406 * rail. Defaults to the value inferred from the thumb's current |
|
407 * position. Specifying value in the constructor will move the |
|
408 * thumb to the position that corresponds to the supplied value. |
|
409 * |
|
410 * @attribute value |
|
411 * @type { Number } |
|
412 * @default (inferred from current thumb position) |
|
413 */ |
|
414 value: { |
|
415 value : 0, |
|
416 setter: '_setNewValue' |
|
417 } |
|
418 } |
|
419 }, true ); |
|
420 |
|
421 |
|
422 }, '@VERSION@', {"requires": ["slider-base"]}); |