Mercurial > hg > NetworkVis
comparison query_builder/bootstrap/bootstrap-slider/js/bootstrap-slider.js @ 25:f82512502b31
Initial commit for query builder, still need to tailor for ISMI purposes
author | alistair |
---|---|
date | Mon, 23 Nov 2015 02:03:51 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
24:eb0be386736f | 25:f82512502b31 |
---|---|
1 /*! ========================================================= | |
2 * bootstrap-slider.js | |
3 * | |
4 * Maintainers: | |
5 * Kyle Kemp | |
6 * - Twitter: @seiyria | |
7 * - Github: seiyria | |
8 * Rohit Kalkur | |
9 * - Twitter: @Rovolutionary | |
10 * - Github: rovolution | |
11 * | |
12 * ========================================================= | |
13 * | |
14 * Licensed under the Apache License, Version 2.0 (the "License"); | |
15 * you may not use this file except in compliance with the License. | |
16 * You may obtain a copy of the License at | |
17 * | |
18 * http://www.apache.org/licenses/LICENSE-2.0 | |
19 * | |
20 * Unless required by applicable law or agreed to in writing, software | |
21 * distributed under the License is distributed on an "AS IS" BASIS, | |
22 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
23 * See the License for the specific language governing permissions and | |
24 * limitations under the License. | |
25 * ========================================================= */ | |
26 | |
27 | |
28 /** | |
29 * Bridget makes jQuery widgets | |
30 * v1.0.1 | |
31 * MIT license | |
32 */ | |
33 | |
34 (function(root, factory) { | |
35 if(typeof define === "function" && define.amd) { | |
36 define(["jquery"], factory); | |
37 } | |
38 else if(typeof module === "object" && module.exports) { | |
39 var jQuery; | |
40 try { | |
41 jQuery = require("jquery"); | |
42 } | |
43 catch (err) { | |
44 jQuery = null; | |
45 } | |
46 module.exports = factory(jQuery); | |
47 } | |
48 else { | |
49 root.Slider = factory(root.jQuery); | |
50 } | |
51 }(this, function($) { | |
52 // Reference to Slider constructor | |
53 var Slider; | |
54 | |
55 | |
56 (function( $ ) { | |
57 | |
58 'use strict'; | |
59 | |
60 // -------------------------- utils -------------------------- // | |
61 | |
62 var slice = Array.prototype.slice; | |
63 | |
64 function noop() {} | |
65 | |
66 // -------------------------- definition -------------------------- // | |
67 | |
68 function defineBridget( $ ) { | |
69 | |
70 // bail if no jQuery | |
71 if ( !$ ) { | |
72 return; | |
73 } | |
74 | |
75 // -------------------------- addOptionMethod -------------------------- // | |
76 | |
77 /** | |
78 * adds option method -> $().plugin('option', {...}) | |
79 * @param {Function} PluginClass - constructor class | |
80 */ | |
81 function addOptionMethod( PluginClass ) { | |
82 // don't overwrite original option method | |
83 if ( PluginClass.prototype.option ) { | |
84 return; | |
85 } | |
86 | |
87 // option setter | |
88 PluginClass.prototype.option = function( opts ) { | |
89 // bail out if not an object | |
90 if ( !$.isPlainObject( opts ) ){ | |
91 return; | |
92 } | |
93 this.options = $.extend( true, this.options, opts ); | |
94 }; | |
95 } | |
96 | |
97 | |
98 // -------------------------- plugin bridge -------------------------- // | |
99 | |
100 // helper function for logging errors | |
101 // $.error breaks jQuery chaining | |
102 var logError = typeof console === 'undefined' ? noop : | |
103 function( message ) { | |
104 console.error( message ); | |
105 }; | |
106 | |
107 /** | |
108 * jQuery plugin bridge, access methods like $elem.plugin('method') | |
109 * @param {String} namespace - plugin name | |
110 * @param {Function} PluginClass - constructor class | |
111 */ | |
112 function bridge( namespace, PluginClass ) { | |
113 // add to jQuery fn namespace | |
114 $.fn[ namespace ] = function( options ) { | |
115 if ( typeof options === 'string' ) { | |
116 // call plugin method when first argument is a string | |
117 // get arguments for method | |
118 var args = slice.call( arguments, 1 ); | |
119 | |
120 for ( var i=0, len = this.length; i < len; i++ ) { | |
121 var elem = this[i]; | |
122 var instance = $.data( elem, namespace ); | |
123 if ( !instance ) { | |
124 logError( "cannot call methods on " + namespace + " prior to initialization; " + | |
125 "attempted to call '" + options + "'" ); | |
126 continue; | |
127 } | |
128 if ( !$.isFunction( instance[options] ) || options.charAt(0) === '_' ) { | |
129 logError( "no such method '" + options + "' for " + namespace + " instance" ); | |
130 continue; | |
131 } | |
132 | |
133 // trigger method with arguments | |
134 var returnValue = instance[ options ].apply( instance, args); | |
135 | |
136 // break look and return first value if provided | |
137 if ( returnValue !== undefined && returnValue !== instance) { | |
138 return returnValue; | |
139 } | |
140 } | |
141 // return this if no return value | |
142 return this; | |
143 } else { | |
144 var objects = this.map( function() { | |
145 var instance = $.data( this, namespace ); | |
146 if ( instance ) { | |
147 // apply options & init | |
148 instance.option( options ); | |
149 instance._init(); | |
150 } else { | |
151 // initialize new instance | |
152 instance = new PluginClass( this, options ); | |
153 $.data( this, namespace, instance ); | |
154 } | |
155 return $(this); | |
156 }); | |
157 | |
158 if(!objects || objects.length > 1) { | |
159 return objects; | |
160 } else { | |
161 return objects[0]; | |
162 } | |
163 } | |
164 }; | |
165 | |
166 } | |
167 | |
168 // -------------------------- bridget -------------------------- // | |
169 | |
170 /** | |
171 * converts a Prototypical class into a proper jQuery plugin | |
172 * the class must have a ._init method | |
173 * @param {String} namespace - plugin name, used in $().pluginName | |
174 * @param {Function} PluginClass - constructor class | |
175 */ | |
176 $.bridget = function( namespace, PluginClass ) { | |
177 addOptionMethod( PluginClass ); | |
178 bridge( namespace, PluginClass ); | |
179 }; | |
180 | |
181 return $.bridget; | |
182 | |
183 } | |
184 | |
185 // get jquery from browser global | |
186 defineBridget( $ ); | |
187 | |
188 })( $ ); | |
189 | |
190 | |
191 /************************************************* | |
192 | |
193 BOOTSTRAP-SLIDER SOURCE CODE | |
194 | |
195 **************************************************/ | |
196 | |
197 (function($) { | |
198 | |
199 var ErrorMsgs = { | |
200 formatInvalidInputErrorMsg : function(input) { | |
201 return "Invalid input value '" + input + "' passed in"; | |
202 }, | |
203 callingContextNotSliderInstance : "Calling context element does not have instance of Slider bound to it. Check your code to make sure the JQuery object returned from the call to the slider() initializer is calling the method" | |
204 }; | |
205 | |
206 var SliderScale = { | |
207 linear: { | |
208 toValue: function(percentage) { | |
209 var rawValue = percentage/100 * (this.options.max - this.options.min); | |
210 if (this.options.ticks_positions.length > 0) { | |
211 var minv, maxv, minp, maxp = 0; | |
212 for (var i = 0; i < this.options.ticks_positions.length; i++) { | |
213 if (percentage <= this.options.ticks_positions[i]) { | |
214 minv = (i > 0) ? this.options.ticks[i-1] : 0; | |
215 minp = (i > 0) ? this.options.ticks_positions[i-1] : 0; | |
216 maxv = this.options.ticks[i]; | |
217 maxp = this.options.ticks_positions[i]; | |
218 | |
219 break; | |
220 } | |
221 } | |
222 if (i > 0) { | |
223 var partialPercentage = (percentage - minp) / (maxp - minp); | |
224 rawValue = minv + partialPercentage * (maxv - minv); | |
225 } | |
226 } | |
227 | |
228 var value = this.options.min + Math.round(rawValue / this.options.step) * this.options.step; | |
229 if (value < this.options.min) { | |
230 return this.options.min; | |
231 } else if (value > this.options.max) { | |
232 return this.options.max; | |
233 } else { | |
234 return value; | |
235 } | |
236 }, | |
237 toPercentage: function(value) { | |
238 if (this.options.max === this.options.min) { | |
239 return 0; | |
240 } | |
241 | |
242 if (this.options.ticks_positions.length > 0) { | |
243 var minv, maxv, minp, maxp = 0; | |
244 for (var i = 0; i < this.options.ticks.length; i++) { | |
245 if (value <= this.options.ticks[i]) { | |
246 minv = (i > 0) ? this.options.ticks[i-1] : 0; | |
247 minp = (i > 0) ? this.options.ticks_positions[i-1] : 0; | |
248 maxv = this.options.ticks[i]; | |
249 maxp = this.options.ticks_positions[i]; | |
250 | |
251 break; | |
252 } | |
253 } | |
254 if (i > 0) { | |
255 var partialPercentage = (value - minv) / (maxv - minv); | |
256 return minp + partialPercentage * (maxp - minp); | |
257 } | |
258 } | |
259 | |
260 return 100 * (value - this.options.min) / (this.options.max - this.options.min); | |
261 } | |
262 }, | |
263 | |
264 logarithmic: { | |
265 /* Based on http://stackoverflow.com/questions/846221/logarithmic-slider */ | |
266 toValue: function(percentage) { | |
267 var min = (this.options.min === 0) ? 0 : Math.log(this.options.min); | |
268 var max = Math.log(this.options.max); | |
269 var value = Math.exp(min + (max - min) * percentage / 100); | |
270 value = this.options.min + Math.round((value - this.options.min) / this.options.step) * this.options.step; | |
271 /* Rounding to the nearest step could exceed the min or | |
272 * max, so clip to those values. */ | |
273 if (value < this.options.min) { | |
274 return this.options.min; | |
275 } else if (value > this.options.max) { | |
276 return this.options.max; | |
277 } else { | |
278 return value; | |
279 } | |
280 }, | |
281 toPercentage: function(value) { | |
282 if (this.options.max === this.options.min) { | |
283 return 0; | |
284 } else { | |
285 var max = Math.log(this.options.max); | |
286 var min = this.options.min === 0 ? 0 : Math.log(this.options.min); | |
287 var v = value === 0 ? 0 : Math.log(value); | |
288 return 100 * (v - min) / (max - min); | |
289 } | |
290 } | |
291 } | |
292 }; | |
293 | |
294 | |
295 /************************************************* | |
296 | |
297 CONSTRUCTOR | |
298 | |
299 **************************************************/ | |
300 Slider = function(element, options) { | |
301 createNewSlider.call(this, element, options); | |
302 return this; | |
303 }; | |
304 | |
305 function createNewSlider(element, options) { | |
306 | |
307 /* | |
308 The internal state object is used to store data about the current 'state' of slider. | |
309 | |
310 This includes values such as the `value`, `enabled`, etc... | |
311 */ | |
312 this._state = { | |
313 value: null, | |
314 enabled: null, | |
315 offset: null, | |
316 size: null, | |
317 percentage: null, | |
318 inDrag: false, | |
319 over: false | |
320 }; | |
321 | |
322 | |
323 if(typeof element === "string") { | |
324 this.element = document.querySelector(element); | |
325 } else if(element instanceof HTMLElement) { | |
326 this.element = element; | |
327 } | |
328 | |
329 /************************************************* | |
330 | |
331 Process Options | |
332 | |
333 **************************************************/ | |
334 options = options ? options : {}; | |
335 var optionTypes = Object.keys(this.defaultOptions); | |
336 | |
337 for(var i = 0; i < optionTypes.length; i++) { | |
338 var optName = optionTypes[i]; | |
339 | |
340 // First check if an option was passed in via the constructor | |
341 var val = options[optName]; | |
342 // If no data attrib, then check data atrributes | |
343 val = (typeof val !== 'undefined') ? val : getDataAttrib(this.element, optName); | |
344 // Finally, if nothing was specified, use the defaults | |
345 val = (val !== null) ? val : this.defaultOptions[optName]; | |
346 | |
347 // Set all options on the instance of the Slider | |
348 if(!this.options) { | |
349 this.options = {}; | |
350 } | |
351 this.options[optName] = val; | |
352 } | |
353 | |
354 /* | |
355 Validate `tooltip_position` against 'orientation` | |
356 - if `tooltip_position` is incompatible with orientation, swith it to a default compatible with specified `orientation` | |
357 -- default for "vertical" -> "right" | |
358 -- default for "horizontal" -> "left" | |
359 */ | |
360 if(this.options.orientation === "vertical" && (this.options.tooltip_position === "top" || this.options.tooltip_position === "bottom")) { | |
361 | |
362 this.options.tooltip_position = "right"; | |
363 | |
364 } | |
365 else if(this.options.orientation === "horizontal" && (this.options.tooltip_position === "left" || this.options.tooltip_position === "right")) { | |
366 | |
367 this.options.tooltip_position = "top"; | |
368 | |
369 } | |
370 | |
371 function getDataAttrib(element, optName) { | |
372 var dataName = "data-slider-" + optName.replace(/_/g, '-'); | |
373 var dataValString = element.getAttribute(dataName); | |
374 | |
375 try { | |
376 return JSON.parse(dataValString); | |
377 } | |
378 catch(err) { | |
379 return dataValString; | |
380 } | |
381 } | |
382 | |
383 /************************************************* | |
384 | |
385 Create Markup | |
386 | |
387 **************************************************/ | |
388 | |
389 var origWidth = this.element.style.width; | |
390 var updateSlider = false; | |
391 var parent = this.element.parentNode; | |
392 var sliderTrackSelection; | |
393 var sliderTrackLow, sliderTrackHigh; | |
394 var sliderMinHandle; | |
395 var sliderMaxHandle; | |
396 | |
397 if (this.sliderElem) { | |
398 updateSlider = true; | |
399 } else { | |
400 /* Create elements needed for slider */ | |
401 this.sliderElem = document.createElement("div"); | |
402 this.sliderElem.className = "slider"; | |
403 | |
404 /* Create slider track elements */ | |
405 var sliderTrack = document.createElement("div"); | |
406 sliderTrack.className = "slider-track"; | |
407 | |
408 sliderTrackLow = document.createElement("div"); | |
409 sliderTrackLow.className = "slider-track-low"; | |
410 | |
411 sliderTrackSelection = document.createElement("div"); | |
412 sliderTrackSelection.className = "slider-selection"; | |
413 | |
414 sliderTrackHigh = document.createElement("div"); | |
415 sliderTrackHigh.className = "slider-track-high"; | |
416 | |
417 sliderMinHandle = document.createElement("div"); | |
418 sliderMinHandle.className = "slider-handle min-slider-handle"; | |
419 | |
420 sliderMaxHandle = document.createElement("div"); | |
421 sliderMaxHandle.className = "slider-handle max-slider-handle"; | |
422 | |
423 sliderTrack.appendChild(sliderTrackLow); | |
424 sliderTrack.appendChild(sliderTrackSelection); | |
425 sliderTrack.appendChild(sliderTrackHigh); | |
426 | |
427 /* Create ticks */ | |
428 this.ticks = []; | |
429 if (Array.isArray(this.options.ticks) && this.options.ticks.length > 0) { | |
430 for (i = 0; i < this.options.ticks.length; i++) { | |
431 var tick = document.createElement('div'); | |
432 tick.className = 'slider-tick'; | |
433 | |
434 this.ticks.push(tick); | |
435 sliderTrack.appendChild(tick); | |
436 } | |
437 | |
438 sliderTrackSelection.className += " tick-slider-selection"; | |
439 } | |
440 | |
441 sliderTrack.appendChild(sliderMinHandle); | |
442 sliderTrack.appendChild(sliderMaxHandle); | |
443 | |
444 this.tickLabels = []; | |
445 if (Array.isArray(this.options.ticks_labels) && this.options.ticks_labels.length > 0) { | |
446 this.tickLabelContainer = document.createElement('div'); | |
447 this.tickLabelContainer.className = 'slider-tick-label-container'; | |
448 | |
449 for (i = 0; i < this.options.ticks_labels.length; i++) { | |
450 var label = document.createElement('div'); | |
451 var noTickPositionsSpecified = this.options.ticks_positions.length === 0; | |
452 var tickLabelsIndex = (this.options.reversed && noTickPositionsSpecified) ? (this.options.ticks_labels.length - (i + 1)) : i; | |
453 label.className = 'slider-tick-label'; | |
454 label.innerHTML = this.options.ticks_labels[tickLabelsIndex]; | |
455 | |
456 this.tickLabels.push(label); | |
457 this.tickLabelContainer.appendChild(label); | |
458 } | |
459 } | |
460 | |
461 | |
462 var createAndAppendTooltipSubElements = function(tooltipElem) { | |
463 var arrow = document.createElement("div"); | |
464 arrow.className = "tooltip-arrow"; | |
465 | |
466 var inner = document.createElement("div"); | |
467 inner.className = "tooltip-inner"; | |
468 | |
469 tooltipElem.appendChild(arrow); | |
470 tooltipElem.appendChild(inner); | |
471 | |
472 }; | |
473 | |
474 /* Create tooltip elements */ | |
475 var sliderTooltip = document.createElement("div"); | |
476 sliderTooltip.className = "tooltip tooltip-main"; | |
477 createAndAppendTooltipSubElements(sliderTooltip); | |
478 | |
479 var sliderTooltipMin = document.createElement("div"); | |
480 sliderTooltipMin.className = "tooltip tooltip-min"; | |
481 createAndAppendTooltipSubElements(sliderTooltipMin); | |
482 | |
483 var sliderTooltipMax = document.createElement("div"); | |
484 sliderTooltipMax.className = "tooltip tooltip-max"; | |
485 createAndAppendTooltipSubElements(sliderTooltipMax); | |
486 | |
487 | |
488 /* Append components to sliderElem */ | |
489 this.sliderElem.appendChild(sliderTrack); | |
490 this.sliderElem.appendChild(sliderTooltip); | |
491 this.sliderElem.appendChild(sliderTooltipMin); | |
492 this.sliderElem.appendChild(sliderTooltipMax); | |
493 | |
494 if (this.tickLabelContainer) { | |
495 this.sliderElem.appendChild(this.tickLabelContainer); | |
496 } | |
497 | |
498 /* Append slider element to parent container, right before the original <input> element */ | |
499 parent.insertBefore(this.sliderElem, this.element); | |
500 | |
501 /* Hide original <input> element */ | |
502 this.element.style.display = "none"; | |
503 } | |
504 /* If JQuery exists, cache JQ references */ | |
505 if($) { | |
506 this.$element = $(this.element); | |
507 this.$sliderElem = $(this.sliderElem); | |
508 } | |
509 | |
510 /************************************************* | |
511 | |
512 Setup | |
513 | |
514 **************************************************/ | |
515 this.eventToCallbackMap = {}; | |
516 this.sliderElem.id = this.options.id; | |
517 | |
518 this.touchCapable = 'ontouchstart' in window || (window.DocumentTouch && document instanceof window.DocumentTouch); | |
519 | |
520 this.tooltip = this.sliderElem.querySelector('.tooltip-main'); | |
521 this.tooltipInner = this.tooltip.querySelector('.tooltip-inner'); | |
522 | |
523 this.tooltip_min = this.sliderElem.querySelector('.tooltip-min'); | |
524 this.tooltipInner_min = this.tooltip_min.querySelector('.tooltip-inner'); | |
525 | |
526 this.tooltip_max = this.sliderElem.querySelector('.tooltip-max'); | |
527 this.tooltipInner_max= this.tooltip_max.querySelector('.tooltip-inner'); | |
528 | |
529 if (SliderScale[this.options.scale]) { | |
530 this.options.scale = SliderScale[this.options.scale]; | |
531 } | |
532 | |
533 if (updateSlider === true) { | |
534 // Reset classes | |
535 this._removeClass(this.sliderElem, 'slider-horizontal'); | |
536 this._removeClass(this.sliderElem, 'slider-vertical'); | |
537 this._removeClass(this.tooltip, 'hide'); | |
538 this._removeClass(this.tooltip_min, 'hide'); | |
539 this._removeClass(this.tooltip_max, 'hide'); | |
540 | |
541 // Undo existing inline styles for track | |
542 ["left", "top", "width", "height"].forEach(function(prop) { | |
543 this._removeProperty(this.trackLow, prop); | |
544 this._removeProperty(this.trackSelection, prop); | |
545 this._removeProperty(this.trackHigh, prop); | |
546 }, this); | |
547 | |
548 // Undo inline styles on handles | |
549 [this.handle1, this.handle2].forEach(function(handle) { | |
550 this._removeProperty(handle, 'left'); | |
551 this._removeProperty(handle, 'top'); | |
552 }, this); | |
553 | |
554 // Undo inline styles and classes on tooltips | |
555 [this.tooltip, this.tooltip_min, this.tooltip_max].forEach(function(tooltip) { | |
556 this._removeProperty(tooltip, 'left'); | |
557 this._removeProperty(tooltip, 'top'); | |
558 this._removeProperty(tooltip, 'margin-left'); | |
559 this._removeProperty(tooltip, 'margin-top'); | |
560 | |
561 this._removeClass(tooltip, 'right'); | |
562 this._removeClass(tooltip, 'top'); | |
563 }, this); | |
564 } | |
565 | |
566 if(this.options.orientation === 'vertical') { | |
567 this._addClass(this.sliderElem,'slider-vertical'); | |
568 this.stylePos = 'top'; | |
569 this.mousePos = 'pageY'; | |
570 this.sizePos = 'offsetHeight'; | |
571 } else { | |
572 this._addClass(this.sliderElem, 'slider-horizontal'); | |
573 this.sliderElem.style.width = origWidth; | |
574 this.options.orientation = 'horizontal'; | |
575 this.stylePos = 'left'; | |
576 this.mousePos = 'pageX'; | |
577 this.sizePos = 'offsetWidth'; | |
578 | |
579 } | |
580 this._setTooltipPosition(); | |
581 /* In case ticks are specified, overwrite the min and max bounds */ | |
582 if (Array.isArray(this.options.ticks) && this.options.ticks.length > 0) { | |
583 this.options.max = Math.max.apply(Math, this.options.ticks); | |
584 this.options.min = Math.min.apply(Math, this.options.ticks); | |
585 } | |
586 | |
587 if (Array.isArray(this.options.value)) { | |
588 this.options.range = true; | |
589 this._state.value = this.options.value; | |
590 } | |
591 else if (this.options.range) { | |
592 // User wants a range, but value is not an array | |
593 this._state.value = [this.options.value, this.options.max]; | |
594 } | |
595 else { | |
596 this._state.value = this.options.value; | |
597 } | |
598 | |
599 this.trackLow = sliderTrackLow || this.trackLow; | |
600 this.trackSelection = sliderTrackSelection || this.trackSelection; | |
601 this.trackHigh = sliderTrackHigh || this.trackHigh; | |
602 | |
603 if (this.options.selection === 'none') { | |
604 this._addClass(this.trackLow, 'hide'); | |
605 this._addClass(this.trackSelection, 'hide'); | |
606 this._addClass(this.trackHigh, 'hide'); | |
607 } | |
608 | |
609 this.handle1 = sliderMinHandle || this.handle1; | |
610 this.handle2 = sliderMaxHandle || this.handle2; | |
611 | |
612 if (updateSlider === true) { | |
613 // Reset classes | |
614 this._removeClass(this.handle1, 'round triangle'); | |
615 this._removeClass(this.handle2, 'round triangle hide'); | |
616 | |
617 for (i = 0; i < this.ticks.length; i++) { | |
618 this._removeClass(this.ticks[i], 'round triangle hide'); | |
619 } | |
620 } | |
621 | |
622 var availableHandleModifiers = ['round', 'triangle', 'custom']; | |
623 var isValidHandleType = availableHandleModifiers.indexOf(this.options.handle) !== -1; | |
624 if (isValidHandleType) { | |
625 this._addClass(this.handle1, this.options.handle); | |
626 this._addClass(this.handle2, this.options.handle); | |
627 | |
628 for (i = 0; i < this.ticks.length; i++) { | |
629 this._addClass(this.ticks[i], this.options.handle); | |
630 } | |
631 } | |
632 | |
633 this._state.offset = this._offset(this.sliderElem); | |
634 this._state.size = this.sliderElem[this.sizePos]; | |
635 this.setValue(this._state.value); | |
636 | |
637 /****************************************** | |
638 | |
639 Bind Event Listeners | |
640 | |
641 ******************************************/ | |
642 | |
643 // Bind keyboard handlers | |
644 this.handle1Keydown = this._keydown.bind(this, 0); | |
645 this.handle1.addEventListener("keydown", this.handle1Keydown, false); | |
646 | |
647 this.handle2Keydown = this._keydown.bind(this, 1); | |
648 this.handle2.addEventListener("keydown", this.handle2Keydown, false); | |
649 | |
650 this.mousedown = this._mousedown.bind(this); | |
651 if (this.touchCapable) { | |
652 // Bind touch handlers | |
653 this.sliderElem.addEventListener("touchstart", this.mousedown, false); | |
654 } | |
655 this.sliderElem.addEventListener("mousedown", this.mousedown, false); | |
656 | |
657 | |
658 // Bind tooltip-related handlers | |
659 if(this.options.tooltip === 'hide') { | |
660 this._addClass(this.tooltip, 'hide'); | |
661 this._addClass(this.tooltip_min, 'hide'); | |
662 this._addClass(this.tooltip_max, 'hide'); | |
663 } | |
664 else if(this.options.tooltip === 'always') { | |
665 this._showTooltip(); | |
666 this._alwaysShowTooltip = true; | |
667 } | |
668 else { | |
669 this.showTooltip = this._showTooltip.bind(this); | |
670 this.hideTooltip = this._hideTooltip.bind(this); | |
671 | |
672 this.sliderElem.addEventListener("mouseenter", this.showTooltip, false); | |
673 this.sliderElem.addEventListener("mouseleave", this.hideTooltip, false); | |
674 | |
675 this.handle1.addEventListener("focus", this.showTooltip, false); | |
676 this.handle1.addEventListener("blur", this.hideTooltip, false); | |
677 | |
678 this.handle2.addEventListener("focus", this.showTooltip, false); | |
679 this.handle2.addEventListener("blur", this.hideTooltip, false); | |
680 } | |
681 | |
682 if(this.options.enabled) { | |
683 this.enable(); | |
684 } else { | |
685 this.disable(); | |
686 } | |
687 } | |
688 | |
689 | |
690 | |
691 /************************************************* | |
692 | |
693 INSTANCE PROPERTIES/METHODS | |
694 | |
695 - Any methods bound to the prototype are considered | |
696 part of the plugin's `public` interface | |
697 | |
698 **************************************************/ | |
699 Slider.prototype = { | |
700 _init: function() {}, // NOTE: Must exist to support bridget | |
701 | |
702 constructor: Slider, | |
703 | |
704 defaultOptions: { | |
705 id: "", | |
706 min: 0, | |
707 max: 10, | |
708 step: 1, | |
709 precision: 0, | |
710 orientation: 'horizontal', | |
711 value: 5, | |
712 range: false, | |
713 selection: 'before', | |
714 tooltip: 'show', | |
715 tooltip_split: false, | |
716 handle: 'round', | |
717 reversed: false, | |
718 enabled: true, | |
719 formatter: function(val) { | |
720 if (Array.isArray(val)) { | |
721 return val[0] + " : " + val[1]; | |
722 } else { | |
723 return val; | |
724 } | |
725 }, | |
726 natural_arrow_keys: false, | |
727 ticks: [], | |
728 ticks_positions: [], | |
729 ticks_labels: [], | |
730 ticks_snap_bounds: 0, | |
731 scale: 'linear', | |
732 focus: false, | |
733 tooltip_position: null | |
734 }, | |
735 | |
736 getElement: function() { | |
737 return this.sliderElem; | |
738 }, | |
739 | |
740 getValue: function() { | |
741 if (this.options.range) { | |
742 return this._state.value; | |
743 } | |
744 else { | |
745 return this._state.value[0]; | |
746 } | |
747 }, | |
748 | |
749 setValue: function(val, triggerSlideEvent, triggerChangeEvent) { | |
750 if (!val) { | |
751 val = 0; | |
752 } | |
753 var oldValue = this.getValue(); | |
754 this._state.value = this._validateInputValue(val); | |
755 var applyPrecision = this._applyPrecision.bind(this); | |
756 | |
757 if (this.options.range) { | |
758 this._state.value[0] = applyPrecision(this._state.value[0]); | |
759 this._state.value[1] = applyPrecision(this._state.value[1]); | |
760 | |
761 this._state.value[0] = Math.max(this.options.min, Math.min(this.options.max, this._state.value[0])); | |
762 this._state.value[1] = Math.max(this.options.min, Math.min(this.options.max, this._state.value[1])); | |
763 } | |
764 else { | |
765 this._state.value = applyPrecision(this._state.value); | |
766 this._state.value = [ Math.max(this.options.min, Math.min(this.options.max, this._state.value))]; | |
767 this._addClass(this.handle2, 'hide'); | |
768 if (this.options.selection === 'after') { | |
769 this._state.value[1] = this.options.max; | |
770 } else { | |
771 this._state.value[1] = this.options.min; | |
772 } | |
773 } | |
774 | |
775 if (this.options.max > this.options.min) { | |
776 this._state.percentage = [ | |
777 this._toPercentage(this._state.value[0]), | |
778 this._toPercentage(this._state.value[1]), | |
779 this.options.step * 100 / (this.options.max - this.options.min) | |
780 ]; | |
781 } else { | |
782 this._state.percentage = [0, 0, 100]; | |
783 } | |
784 | |
785 this._layout(); | |
786 var newValue = this.options.range ? this._state.value : this._state.value[0]; | |
787 | |
788 if(triggerSlideEvent === true) { | |
789 this._trigger('slide', newValue); | |
790 } | |
791 if( (oldValue !== newValue) && (triggerChangeEvent === true) ) { | |
792 this._trigger('change', { | |
793 oldValue: oldValue, | |
794 newValue: newValue | |
795 }); | |
796 } | |
797 this._setDataVal(newValue); | |
798 | |
799 return this; | |
800 }, | |
801 | |
802 destroy: function(){ | |
803 // Remove event handlers on slider elements | |
804 this._removeSliderEventHandlers(); | |
805 | |
806 // Remove the slider from the DOM | |
807 this.sliderElem.parentNode.removeChild(this.sliderElem); | |
808 /* Show original <input> element */ | |
809 this.element.style.display = ""; | |
810 | |
811 // Clear out custom event bindings | |
812 this._cleanUpEventCallbacksMap(); | |
813 | |
814 // Remove data values | |
815 this.element.removeAttribute("data"); | |
816 | |
817 // Remove JQuery handlers/data | |
818 if($) { | |
819 this._unbindJQueryEventHandlers(); | |
820 this.$element.removeData('slider'); | |
821 } | |
822 }, | |
823 | |
824 disable: function() { | |
825 this._state.enabled = false; | |
826 this.handle1.removeAttribute("tabindex"); | |
827 this.handle2.removeAttribute("tabindex"); | |
828 this._addClass(this.sliderElem, 'slider-disabled'); | |
829 this._trigger('slideDisabled'); | |
830 | |
831 return this; | |
832 }, | |
833 | |
834 enable: function() { | |
835 this._state.enabled = true; | |
836 this.handle1.setAttribute("tabindex", 0); | |
837 this.handle2.setAttribute("tabindex", 0); | |
838 this._removeClass(this.sliderElem, 'slider-disabled'); | |
839 this._trigger('slideEnabled'); | |
840 | |
841 return this; | |
842 }, | |
843 | |
844 toggle: function() { | |
845 if(this._state.enabled) { | |
846 this.disable(); | |
847 } else { | |
848 this.enable(); | |
849 } | |
850 return this; | |
851 }, | |
852 | |
853 isEnabled: function() { | |
854 return this._state.enabled; | |
855 }, | |
856 | |
857 on: function(evt, callback) { | |
858 this._bindNonQueryEventHandler(evt, callback); | |
859 return this; | |
860 }, | |
861 | |
862 off: function(evt, callback) { | |
863 if($) { | |
864 this.$element.off(evt, callback); | |
865 this.$sliderElem.off(evt, callback); | |
866 } else { | |
867 this._unbindNonQueryEventHandler(evt, callback); | |
868 } | |
869 }, | |
870 | |
871 getAttribute: function(attribute) { | |
872 if(attribute) { | |
873 return this.options[attribute]; | |
874 } else { | |
875 return this.options; | |
876 } | |
877 }, | |
878 | |
879 setAttribute: function(attribute, value) { | |
880 this.options[attribute] = value; | |
881 return this; | |
882 }, | |
883 | |
884 refresh: function() { | |
885 this._removeSliderEventHandlers(); | |
886 createNewSlider.call(this, this.element, this.options); | |
887 if($) { | |
888 // Bind new instance of slider to the element | |
889 $.data(this.element, 'slider', this); | |
890 } | |
891 return this; | |
892 }, | |
893 | |
894 relayout: function() { | |
895 this._layout(); | |
896 return this; | |
897 }, | |
898 | |
899 /******************************+ | |
900 | |
901 HELPERS | |
902 | |
903 - Any method that is not part of the public interface. | |
904 - Place it underneath this comment block and write its signature like so: | |
905 | |
906 _fnName : function() {...} | |
907 | |
908 ********************************/ | |
909 _removeSliderEventHandlers: function() { | |
910 // Remove keydown event listeners | |
911 this.handle1.removeEventListener("keydown", this.handle1Keydown, false); | |
912 this.handle2.removeEventListener("keydown", this.handle2Keydown, false); | |
913 | |
914 if (this.showTooltip) { | |
915 this.handle1.removeEventListener("focus", this.showTooltip, false); | |
916 this.handle2.removeEventListener("focus", this.showTooltip, false); | |
917 } | |
918 if (this.hideTooltip) { | |
919 this.handle1.removeEventListener("blur", this.hideTooltip, false); | |
920 this.handle2.removeEventListener("blur", this.hideTooltip, false); | |
921 } | |
922 | |
923 // Remove event listeners from sliderElem | |
924 if (this.showTooltip) { | |
925 this.sliderElem.removeEventListener("mouseenter", this.showTooltip, false); | |
926 } | |
927 if (this.hideTooltip) { | |
928 this.sliderElem.removeEventListener("mouseleave", this.hideTooltip, false); | |
929 } | |
930 this.sliderElem.removeEventListener("touchstart", this.mousedown, false); | |
931 this.sliderElem.removeEventListener("mousedown", this.mousedown, false); | |
932 }, | |
933 _bindNonQueryEventHandler: function(evt, callback) { | |
934 if(this.eventToCallbackMap[evt] === undefined) { | |
935 this.eventToCallbackMap[evt] = []; | |
936 } | |
937 this.eventToCallbackMap[evt].push(callback); | |
938 }, | |
939 _unbindNonQueryEventHandler: function(evt, callback) { | |
940 var callbacks = this.eventToCallbackMap[evt]; | |
941 if(callbacks !== undefined) { | |
942 for (var i = 0; i < callbacks.length; i++) { | |
943 if (callbacks[i] === callback) { | |
944 callbacks.splice(i, 1); | |
945 break; | |
946 } | |
947 } | |
948 } | |
949 }, | |
950 _cleanUpEventCallbacksMap: function() { | |
951 var eventNames = Object.keys(this.eventToCallbackMap); | |
952 for(var i = 0; i < eventNames.length; i++) { | |
953 var eventName = eventNames[i]; | |
954 this.eventToCallbackMap[eventName] = null; | |
955 } | |
956 }, | |
957 _showTooltip: function() { | |
958 if (this.options.tooltip_split === false ){ | |
959 this._addClass(this.tooltip, 'in'); | |
960 this.tooltip_min.style.display = 'none'; | |
961 this.tooltip_max.style.display = 'none'; | |
962 } else { | |
963 this._addClass(this.tooltip_min, 'in'); | |
964 this._addClass(this.tooltip_max, 'in'); | |
965 this.tooltip.style.display = 'none'; | |
966 } | |
967 this._state.over = true; | |
968 }, | |
969 _hideTooltip: function() { | |
970 if (this._state.inDrag === false && this.alwaysShowTooltip !== true) { | |
971 this._removeClass(this.tooltip, 'in'); | |
972 this._removeClass(this.tooltip_min, 'in'); | |
973 this._removeClass(this.tooltip_max, 'in'); | |
974 } | |
975 this._state.over = false; | |
976 }, | |
977 _layout: function() { | |
978 var positionPercentages; | |
979 | |
980 if(this.options.reversed) { | |
981 positionPercentages = [ 100 - this._state.percentage[0], this.options.range ? 100 - this._state.percentage[1] : this._state.percentage[1]]; | |
982 } | |
983 else { | |
984 positionPercentages = [ this._state.percentage[0], this._state.percentage[1] ]; | |
985 } | |
986 | |
987 this.handle1.style[this.stylePos] = positionPercentages[0]+'%'; | |
988 this.handle2.style[this.stylePos] = positionPercentages[1]+'%'; | |
989 | |
990 /* Position ticks and labels */ | |
991 if (Array.isArray(this.options.ticks) && this.options.ticks.length > 0) { | |
992 | |
993 var styleSize = this.options.orientation === 'vertical' ? 'height' : 'width'; | |
994 var styleMargin = this.options.orientation === 'vertical' ? 'marginTop' : 'marginLeft'; | |
995 var labelSize = this._state.size / (this.options.ticks.length - 1); | |
996 | |
997 if (this.tickLabelContainer) { | |
998 var extraMargin = 0; | |
999 if (this.options.ticks_positions.length === 0) { | |
1000 if (this.options.orientation !== 'vertical') { | |
1001 this.tickLabelContainer.style[styleMargin] = -labelSize/2 + 'px'; | |
1002 } | |
1003 | |
1004 extraMargin = this.tickLabelContainer.offsetHeight; | |
1005 } else { | |
1006 /* Chidren are position absolute, calculate height by finding the max offsetHeight of a child */ | |
1007 for (i = 0 ; i < this.tickLabelContainer.childNodes.length; i++) { | |
1008 if (this.tickLabelContainer.childNodes[i].offsetHeight > extraMargin) { | |
1009 extraMargin = this.tickLabelContainer.childNodes[i].offsetHeight; | |
1010 } | |
1011 } | |
1012 } | |
1013 if (this.options.orientation === 'horizontal') { | |
1014 this.sliderElem.style.marginBottom = extraMargin + 'px'; | |
1015 } | |
1016 } | |
1017 for (var i = 0; i < this.options.ticks.length; i++) { | |
1018 | |
1019 var percentage = this.options.ticks_positions[i] || this._toPercentage(this.options.ticks[i]); | |
1020 | |
1021 if (this.options.reversed) { | |
1022 percentage = 100 - percentage; | |
1023 } | |
1024 | |
1025 this.ticks[i].style[this.stylePos] = percentage + '%'; | |
1026 | |
1027 /* Set class labels to denote whether ticks are in the selection */ | |
1028 this._removeClass(this.ticks[i], 'in-selection'); | |
1029 if (!this.options.range) { | |
1030 if (this.options.selection === 'after' && percentage >= positionPercentages[0]){ | |
1031 this._addClass(this.ticks[i], 'in-selection'); | |
1032 } else if (this.options.selection === 'before' && percentage <= positionPercentages[0]) { | |
1033 this._addClass(this.ticks[i], 'in-selection'); | |
1034 } | |
1035 } else if (percentage >= positionPercentages[0] && percentage <= positionPercentages[1]) { | |
1036 this._addClass(this.ticks[i], 'in-selection'); | |
1037 } | |
1038 | |
1039 if (this.tickLabels[i]) { | |
1040 this.tickLabels[i].style[styleSize] = labelSize + 'px'; | |
1041 | |
1042 if (this.options.orientation !== 'vertical' && this.options.ticks_positions[i] !== undefined) { | |
1043 this.tickLabels[i].style.position = 'absolute'; | |
1044 this.tickLabels[i].style[this.stylePos] = percentage + '%'; | |
1045 this.tickLabels[i].style[styleMargin] = -labelSize/2 + 'px'; | |
1046 } else if (this.options.orientation === 'vertical') { | |
1047 this.tickLabels[i].style['marginLeft'] = this.sliderElem.offsetWidth + 'px'; | |
1048 this.tickLabelContainer.style['marginTop'] = this.sliderElem.offsetWidth / 2 * -1 + 'px'; | |
1049 } | |
1050 } | |
1051 } | |
1052 } | |
1053 | |
1054 var formattedTooltipVal; | |
1055 | |
1056 if (this.options.range) { | |
1057 formattedTooltipVal = this.options.formatter(this._state.value); | |
1058 this._setText(this.tooltipInner, formattedTooltipVal); | |
1059 this.tooltip.style[this.stylePos] = (positionPercentages[1] + positionPercentages[0])/2 + '%'; | |
1060 | |
1061 if (this.options.orientation === 'vertical') { | |
1062 this._css(this.tooltip, 'margin-top', -this.tooltip.offsetHeight / 2 + 'px'); | |
1063 } else { | |
1064 this._css(this.tooltip, 'margin-left', -this.tooltip.offsetWidth / 2 + 'px'); | |
1065 } | |
1066 | |
1067 if (this.options.orientation === 'vertical') { | |
1068 this._css(this.tooltip, 'margin-top', -this.tooltip.offsetHeight / 2 + 'px'); | |
1069 } else { | |
1070 this._css(this.tooltip, 'margin-left', -this.tooltip.offsetWidth / 2 + 'px'); | |
1071 } | |
1072 | |
1073 var innerTooltipMinText = this.options.formatter(this._state.value[0]); | |
1074 this._setText(this.tooltipInner_min, innerTooltipMinText); | |
1075 | |
1076 var innerTooltipMaxText = this.options.formatter(this._state.value[1]); | |
1077 this._setText(this.tooltipInner_max, innerTooltipMaxText); | |
1078 | |
1079 this.tooltip_min.style[this.stylePos] = positionPercentages[0] + '%'; | |
1080 | |
1081 if (this.options.orientation === 'vertical') { | |
1082 this._css(this.tooltip_min, 'margin-top', -this.tooltip_min.offsetHeight / 2 + 'px'); | |
1083 } else { | |
1084 this._css(this.tooltip_min, 'margin-left', -this.tooltip_min.offsetWidth / 2 + 'px'); | |
1085 } | |
1086 | |
1087 this.tooltip_max.style[this.stylePos] = positionPercentages[1] + '%'; | |
1088 | |
1089 if (this.options.orientation === 'vertical') { | |
1090 this._css(this.tooltip_max, 'margin-top', -this.tooltip_max.offsetHeight / 2 + 'px'); | |
1091 } else { | |
1092 this._css(this.tooltip_max, 'margin-left', -this.tooltip_max.offsetWidth / 2 + 'px'); | |
1093 } | |
1094 } else { | |
1095 formattedTooltipVal = this.options.formatter(this._state.value[0]); | |
1096 this._setText(this.tooltipInner, formattedTooltipVal); | |
1097 | |
1098 this.tooltip.style[this.stylePos] = positionPercentages[0] + '%'; | |
1099 if (this.options.orientation === 'vertical') { | |
1100 this._css(this.tooltip, 'margin-top', -this.tooltip.offsetHeight / 2 + 'px'); | |
1101 } else { | |
1102 this._css(this.tooltip, 'margin-left', -this.tooltip.offsetWidth / 2 + 'px'); | |
1103 } | |
1104 } | |
1105 | |
1106 if (this.options.orientation === 'vertical') { | |
1107 this.trackLow.style.top = '0'; | |
1108 this.trackLow.style.height = Math.min(positionPercentages[0], positionPercentages[1]) +'%'; | |
1109 | |
1110 this.trackSelection.style.top = Math.min(positionPercentages[0], positionPercentages[1]) +'%'; | |
1111 this.trackSelection.style.height = Math.abs(positionPercentages[0] - positionPercentages[1]) +'%'; | |
1112 | |
1113 this.trackHigh.style.bottom = '0'; | |
1114 this.trackHigh.style.height = (100 - Math.min(positionPercentages[0], positionPercentages[1]) - Math.abs(positionPercentages[0] - positionPercentages[1])) +'%'; | |
1115 } | |
1116 else { | |
1117 this.trackLow.style.left = '0'; | |
1118 this.trackLow.style.width = Math.min(positionPercentages[0], positionPercentages[1]) +'%'; | |
1119 | |
1120 this.trackSelection.style.left = Math.min(positionPercentages[0], positionPercentages[1]) +'%'; | |
1121 this.trackSelection.style.width = Math.abs(positionPercentages[0] - positionPercentages[1]) +'%'; | |
1122 | |
1123 this.trackHigh.style.right = '0'; | |
1124 this.trackHigh.style.width = (100 - Math.min(positionPercentages[0], positionPercentages[1]) - Math.abs(positionPercentages[0] - positionPercentages[1])) +'%'; | |
1125 | |
1126 var offset_min = this.tooltip_min.getBoundingClientRect(); | |
1127 var offset_max = this.tooltip_max.getBoundingClientRect(); | |
1128 | |
1129 if (offset_min.right > offset_max.left) { | |
1130 this._removeClass(this.tooltip_max, 'top'); | |
1131 this._addClass(this.tooltip_max, 'bottom'); | |
1132 this.tooltip_max.style.top = 18 + 'px'; | |
1133 } else { | |
1134 this._removeClass(this.tooltip_max, 'bottom'); | |
1135 this._addClass(this.tooltip_max, 'top'); | |
1136 this.tooltip_max.style.top = this.tooltip_min.style.top; | |
1137 } | |
1138 } | |
1139 }, | |
1140 _removeProperty: function(element, prop) { | |
1141 if (element.style.removeProperty) { | |
1142 element.style.removeProperty(prop); | |
1143 } else { | |
1144 element.style.removeAttribute(prop); | |
1145 } | |
1146 }, | |
1147 _mousedown: function(ev) { | |
1148 if(!this._state.enabled) { | |
1149 return false; | |
1150 } | |
1151 | |
1152 this._state.offset = this._offset(this.sliderElem); | |
1153 this._state.size = this.sliderElem[this.sizePos]; | |
1154 | |
1155 var percentage = this._getPercentage(ev); | |
1156 | |
1157 if (this.options.range) { | |
1158 var diff1 = Math.abs(this._state.percentage[0] - percentage); | |
1159 var diff2 = Math.abs(this._state.percentage[1] - percentage); | |
1160 this._state.dragged = (diff1 < diff2) ? 0 : 1; | |
1161 } else { | |
1162 this._state.dragged = 0; | |
1163 } | |
1164 | |
1165 this._state.percentage[this._state.dragged] = percentage; | |
1166 this._layout(); | |
1167 | |
1168 if (this.touchCapable) { | |
1169 document.removeEventListener("touchmove", this.mousemove, false); | |
1170 document.removeEventListener("touchend", this.mouseup, false); | |
1171 } | |
1172 | |
1173 if(this.mousemove){ | |
1174 document.removeEventListener("mousemove", this.mousemove, false); | |
1175 } | |
1176 if(this.mouseup){ | |
1177 document.removeEventListener("mouseup", this.mouseup, false); | |
1178 } | |
1179 | |
1180 this.mousemove = this._mousemove.bind(this); | |
1181 this.mouseup = this._mouseup.bind(this); | |
1182 | |
1183 if (this.touchCapable) { | |
1184 // Touch: Bind touch events: | |
1185 document.addEventListener("touchmove", this.mousemove, false); | |
1186 document.addEventListener("touchend", this.mouseup, false); | |
1187 } | |
1188 // Bind mouse events: | |
1189 document.addEventListener("mousemove", this.mousemove, false); | |
1190 document.addEventListener("mouseup", this.mouseup, false); | |
1191 | |
1192 this._state.inDrag = true; | |
1193 var newValue = this._calculateValue(); | |
1194 | |
1195 this._trigger('slideStart', newValue); | |
1196 | |
1197 this._setDataVal(newValue); | |
1198 this.setValue(newValue, false, true); | |
1199 | |
1200 this._pauseEvent(ev); | |
1201 | |
1202 if (this.options.focus) { | |
1203 this._triggerFocusOnHandle(this._state.dragged); | |
1204 } | |
1205 | |
1206 return true; | |
1207 }, | |
1208 _triggerFocusOnHandle: function(handleIdx) { | |
1209 if(handleIdx === 0) { | |
1210 this.handle1.focus(); | |
1211 } | |
1212 if(handleIdx === 1) { | |
1213 this.handle2.focus(); | |
1214 } | |
1215 }, | |
1216 _keydown: function(handleIdx, ev) { | |
1217 if(!this._state.enabled) { | |
1218 return false; | |
1219 } | |
1220 | |
1221 var dir; | |
1222 switch (ev.keyCode) { | |
1223 case 37: // left | |
1224 case 40: // down | |
1225 dir = -1; | |
1226 break; | |
1227 case 39: // right | |
1228 case 38: // up | |
1229 dir = 1; | |
1230 break; | |
1231 } | |
1232 if (!dir) { | |
1233 return; | |
1234 } | |
1235 | |
1236 // use natural arrow keys instead of from min to max | |
1237 if (this.options.natural_arrow_keys) { | |
1238 var ifVerticalAndNotReversed = (this.options.orientation === 'vertical' && !this.options.reversed); | |
1239 var ifHorizontalAndReversed = (this.options.orientation === 'horizontal' && this.options.reversed); | |
1240 | |
1241 if (ifVerticalAndNotReversed || ifHorizontalAndReversed) { | |
1242 dir = -dir; | |
1243 } | |
1244 } | |
1245 | |
1246 var val = this._state.value[handleIdx] + dir * this.options.step; | |
1247 if (this.options.range) { | |
1248 val = [ (!handleIdx) ? val : this._state.value[0], | |
1249 ( handleIdx) ? val : this._state.value[1]]; | |
1250 } | |
1251 | |
1252 this._trigger('slideStart', val); | |
1253 this._setDataVal(val); | |
1254 this.setValue(val, true, true); | |
1255 | |
1256 this._setDataVal(val); | |
1257 this._trigger('slideStop', val); | |
1258 this._layout(); | |
1259 | |
1260 this._pauseEvent(ev); | |
1261 | |
1262 return false; | |
1263 }, | |
1264 _pauseEvent: function(ev) { | |
1265 if(ev.stopPropagation) { | |
1266 ev.stopPropagation(); | |
1267 } | |
1268 if(ev.preventDefault) { | |
1269 ev.preventDefault(); | |
1270 } | |
1271 ev.cancelBubble=true; | |
1272 ev.returnValue=false; | |
1273 }, | |
1274 _mousemove: function(ev) { | |
1275 if(!this._state.enabled) { | |
1276 return false; | |
1277 } | |
1278 | |
1279 var percentage = this._getPercentage(ev); | |
1280 this._adjustPercentageForRangeSliders(percentage); | |
1281 this._state.percentage[this._state.dragged] = percentage; | |
1282 this._layout(); | |
1283 | |
1284 var val = this._calculateValue(true); | |
1285 this.setValue(val, true, true); | |
1286 | |
1287 return false; | |
1288 }, | |
1289 _adjustPercentageForRangeSliders: function(percentage) { | |
1290 if (this.options.range) { | |
1291 var precision = this._getNumDigitsAfterDecimalPlace(percentage); | |
1292 precision = precision ? precision - 1 : 0; | |
1293 var percentageWithAdjustedPrecision = this._applyToFixedAndParseFloat(percentage, precision); | |
1294 if (this._state.dragged === 0 && this._applyToFixedAndParseFloat(this._state.percentage[1], precision) < percentageWithAdjustedPrecision) { | |
1295 this._state.percentage[0] = this._state.percentage[1]; | |
1296 this._state.dragged = 1; | |
1297 } else if (this._state.dragged === 1 && this._applyToFixedAndParseFloat(this._state.percentage[0], precision) > percentageWithAdjustedPrecision) { | |
1298 this._state.percentage[1] = this._state.percentage[0]; | |
1299 this._state.dragged = 0; | |
1300 } | |
1301 } | |
1302 }, | |
1303 _mouseup: function() { | |
1304 if(!this._state.enabled) { | |
1305 return false; | |
1306 } | |
1307 if (this.touchCapable) { | |
1308 // Touch: Unbind touch event handlers: | |
1309 document.removeEventListener("touchmove", this.mousemove, false); | |
1310 document.removeEventListener("touchend", this.mouseup, false); | |
1311 } | |
1312 // Unbind mouse event handlers: | |
1313 document.removeEventListener("mousemove", this.mousemove, false); | |
1314 document.removeEventListener("mouseup", this.mouseup, false); | |
1315 | |
1316 this._state.inDrag = false; | |
1317 if (this._state.over === false) { | |
1318 this._hideTooltip(); | |
1319 } | |
1320 var val = this._calculateValue(true); | |
1321 | |
1322 this._layout(); | |
1323 this._setDataVal(val); | |
1324 this._trigger('slideStop', val); | |
1325 | |
1326 return false; | |
1327 }, | |
1328 _calculateValue: function(snapToClosestTick) { | |
1329 var val; | |
1330 if (this.options.range) { | |
1331 val = [this.options.min,this.options.max]; | |
1332 if (this._state.percentage[0] !== 0){ | |
1333 val[0] = this._toValue(this._state.percentage[0]); | |
1334 val[0] = this._applyPrecision(val[0]); | |
1335 } | |
1336 if (this._state.percentage[1] !== 100){ | |
1337 val[1] = this._toValue(this._state.percentage[1]); | |
1338 val[1] = this._applyPrecision(val[1]); | |
1339 } | |
1340 } else { | |
1341 val = this._toValue(this._state.percentage[0]); | |
1342 val = parseFloat(val); | |
1343 val = this._applyPrecision(val); | |
1344 } | |
1345 | |
1346 if (snapToClosestTick) { | |
1347 var min = [val, Infinity]; | |
1348 for (var i = 0; i < this.options.ticks.length; i++) { | |
1349 var diff = Math.abs(this.options.ticks[i] - val); | |
1350 if (diff <= min[1]) { | |
1351 min = [this.options.ticks[i], diff]; | |
1352 } | |
1353 } | |
1354 if (min[1] <= this.options.ticks_snap_bounds) { | |
1355 return min[0]; | |
1356 } | |
1357 } | |
1358 | |
1359 return val; | |
1360 }, | |
1361 _applyPrecision: function(val) { | |
1362 var precision = this.options.precision || this._getNumDigitsAfterDecimalPlace(this.options.step); | |
1363 return this._applyToFixedAndParseFloat(val, precision); | |
1364 }, | |
1365 _getNumDigitsAfterDecimalPlace: function(num) { | |
1366 var match = (''+num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); | |
1367 if (!match) { return 0; } | |
1368 return Math.max(0, (match[1] ? match[1].length : 0) - (match[2] ? +match[2] : 0)); | |
1369 }, | |
1370 _applyToFixedAndParseFloat: function(num, toFixedInput) { | |
1371 var truncatedNum = num.toFixed(toFixedInput); | |
1372 return parseFloat(truncatedNum); | |
1373 }, | |
1374 /* | |
1375 Credits to Mike Samuel for the following method! | |
1376 Source: http://stackoverflow.com/questions/10454518/javascript-how-to-retrieve-the-number-of-decimals-of-a-string-number | |
1377 */ | |
1378 _getPercentage: function(ev) { | |
1379 if (this.touchCapable && (ev.type === 'touchstart' || ev.type === 'touchmove')) { | |
1380 ev = ev.touches[0]; | |
1381 } | |
1382 | |
1383 var eventPosition = ev[this.mousePos]; | |
1384 var sliderOffset = this._state.offset[this.stylePos]; | |
1385 var distanceToSlide = eventPosition - sliderOffset; | |
1386 // Calculate what percent of the length the slider handle has slid | |
1387 var percentage = (distanceToSlide / this._state.size) * 100; | |
1388 percentage = Math.round(percentage / this._state.percentage[2]) * this._state.percentage[2]; | |
1389 if (this.options.reversed) { | |
1390 percentage = 100 - percentage; | |
1391 } | |
1392 | |
1393 // Make sure the percent is within the bounds of the slider. | |
1394 // 0% corresponds to the 'min' value of the slide | |
1395 // 100% corresponds to the 'max' value of the slide | |
1396 return Math.max(0, Math.min(100, percentage)); | |
1397 }, | |
1398 _validateInputValue: function(val) { | |
1399 if (typeof val === 'number') { | |
1400 return val; | |
1401 } else if (Array.isArray(val)) { | |
1402 this._validateArray(val); | |
1403 return val; | |
1404 } else { | |
1405 throw new Error( ErrorMsgs.formatInvalidInputErrorMsg(val) ); | |
1406 } | |
1407 }, | |
1408 _validateArray: function(val) { | |
1409 for(var i = 0; i < val.length; i++) { | |
1410 var input = val[i]; | |
1411 if (typeof input !== 'number') { throw new Error( ErrorMsgs.formatInvalidInputErrorMsg(input) ); } | |
1412 } | |
1413 }, | |
1414 _setDataVal: function(val) { | |
1415 this.element.setAttribute('data-value', val); | |
1416 this.element.setAttribute('value', val); | |
1417 this.element.value = val; | |
1418 }, | |
1419 _trigger: function(evt, val) { | |
1420 val = (val || val === 0) ? val : undefined; | |
1421 | |
1422 var callbackFnArray = this.eventToCallbackMap[evt]; | |
1423 if(callbackFnArray && callbackFnArray.length) { | |
1424 for(var i = 0; i < callbackFnArray.length; i++) { | |
1425 var callbackFn = callbackFnArray[i]; | |
1426 callbackFn(val); | |
1427 } | |
1428 } | |
1429 | |
1430 /* If JQuery exists, trigger JQuery events */ | |
1431 if($) { | |
1432 this._triggerJQueryEvent(evt, val); | |
1433 } | |
1434 }, | |
1435 _triggerJQueryEvent: function(evt, val) { | |
1436 var eventData = { | |
1437 type: evt, | |
1438 value: val | |
1439 }; | |
1440 this.$element.trigger(eventData); | |
1441 this.$sliderElem.trigger(eventData); | |
1442 }, | |
1443 _unbindJQueryEventHandlers: function() { | |
1444 this.$element.off(); | |
1445 this.$sliderElem.off(); | |
1446 }, | |
1447 _setText: function(element, text) { | |
1448 if(typeof element.innerText !== "undefined") { | |
1449 element.innerText = text; | |
1450 } else if(typeof element.textContent !== "undefined") { | |
1451 element.textContent = text; | |
1452 } | |
1453 }, | |
1454 _removeClass: function(element, classString) { | |
1455 var classes = classString.split(" "); | |
1456 var newClasses = element.className; | |
1457 | |
1458 for(var i = 0; i < classes.length; i++) { | |
1459 var classTag = classes[i]; | |
1460 var regex = new RegExp("(?:\\s|^)" + classTag + "(?:\\s|$)"); | |
1461 newClasses = newClasses.replace(regex, " "); | |
1462 } | |
1463 | |
1464 element.className = newClasses.trim(); | |
1465 }, | |
1466 _addClass: function(element, classString) { | |
1467 var classes = classString.split(" "); | |
1468 var newClasses = element.className; | |
1469 | |
1470 for(var i = 0; i < classes.length; i++) { | |
1471 var classTag = classes[i]; | |
1472 var regex = new RegExp("(?:\\s|^)" + classTag + "(?:\\s|$)"); | |
1473 var ifClassExists = regex.test(newClasses); | |
1474 | |
1475 if(!ifClassExists) { | |
1476 newClasses += " " + classTag; | |
1477 } | |
1478 } | |
1479 | |
1480 element.className = newClasses.trim(); | |
1481 }, | |
1482 _offsetLeft: function(obj){ | |
1483 return obj.getBoundingClientRect().left; | |
1484 }, | |
1485 _offsetTop: function(obj){ | |
1486 var offsetTop = obj.offsetTop; | |
1487 while((obj = obj.offsetParent) && !isNaN(obj.offsetTop)){ | |
1488 offsetTop += obj.offsetTop; | |
1489 } | |
1490 return offsetTop; | |
1491 }, | |
1492 _offset: function (obj) { | |
1493 return { | |
1494 left: this._offsetLeft(obj), | |
1495 top: this._offsetTop(obj) | |
1496 }; | |
1497 }, | |
1498 _css: function(elementRef, styleName, value) { | |
1499 if ($) { | |
1500 $.style(elementRef, styleName, value); | |
1501 } else { | |
1502 var style = styleName.replace(/^-ms-/, "ms-").replace(/-([\da-z])/gi, function (all, letter) { | |
1503 return letter.toUpperCase(); | |
1504 }); | |
1505 elementRef.style[style] = value; | |
1506 } | |
1507 }, | |
1508 _toValue: function(percentage) { | |
1509 return this.options.scale.toValue.apply(this, [percentage]); | |
1510 }, | |
1511 _toPercentage: function(value) { | |
1512 return this.options.scale.toPercentage.apply(this, [value]); | |
1513 }, | |
1514 _setTooltipPosition: function(){ | |
1515 var tooltips = [this.tooltip, this.tooltip_min, this.tooltip_max]; | |
1516 if (this.options.orientation === 'vertical'){ | |
1517 var tooltipPos = this.options.tooltip_position || 'right'; | |
1518 var oppositeSide = (tooltipPos === 'left') ? 'right' : 'left'; | |
1519 tooltips.forEach(function(tooltip){ | |
1520 this._addClass(tooltip, tooltipPos); | |
1521 tooltip.style[oppositeSide] = '100%'; | |
1522 }.bind(this)); | |
1523 } else if(this.options.tooltip_position === 'bottom') { | |
1524 tooltips.forEach(function(tooltip){ | |
1525 this._addClass(tooltip, 'bottom'); | |
1526 tooltip.style.top = 22 + 'px'; | |
1527 }.bind(this)); | |
1528 } else { | |
1529 tooltips.forEach(function(tooltip){ | |
1530 this._addClass(tooltip, 'top'); | |
1531 tooltip.style.top = -this.tooltip.outerHeight - 14 + 'px'; | |
1532 }.bind(this)); | |
1533 } | |
1534 } | |
1535 }; | |
1536 | |
1537 /********************************* | |
1538 | |
1539 Attach to global namespace | |
1540 | |
1541 *********************************/ | |
1542 if($) { | |
1543 var namespace = $.fn.slider ? 'bootstrapSlider' : 'slider'; | |
1544 $.bridget(namespace, Slider); | |
1545 } | |
1546 | |
1547 })( $ ); | |
1548 | |
1549 return Slider; | |
1550 })); |