1 | /* |
---|
2 | * jQuery UI Slider |
---|
3 | * |
---|
4 | * Copyright (c) 2008 Paul Bakaus |
---|
5 | * Dual licensed under the MIT (MIT-LICENSE.txt) |
---|
6 | * and GPL (GPL-LICENSE.txt) licenses. |
---|
7 | * |
---|
8 | * http://docs.jquery.com/UI/Slider |
---|
9 | * |
---|
10 | * Depends: |
---|
11 | * ui.core.js |
---|
12 | */ |
---|
13 | (function($) { |
---|
14 | |
---|
15 | $.fn.unwrap = $.fn.unwrap || function(expr) { |
---|
16 | return this.each(function(){ |
---|
17 | $(this).parents(expr).eq(0).after(this).remove(); |
---|
18 | }); |
---|
19 | }; |
---|
20 | |
---|
21 | $.widget("ui.slider", { |
---|
22 | plugins: {}, |
---|
23 | ui: function(e) { |
---|
24 | return { |
---|
25 | options: this.options, |
---|
26 | handle: this.currentHandle, |
---|
27 | value: this.options.axis != "both" || !this.options.axis ? Math.round(this.value(null,this.options.axis == "vertical" ? "y" : "x")) : { |
---|
28 | x: Math.round(this.value(null,"x")), |
---|
29 | y: Math.round(this.value(null,"y")) |
---|
30 | }, |
---|
31 | range: this.getRange() |
---|
32 | }; |
---|
33 | }, |
---|
34 | propagate: function(n,e) { |
---|
35 | $.ui.plugin.call(this, n, [e, this.ui()]); |
---|
36 | this.element.triggerHandler(n == "slide" ? n : "slide"+n, [e, this.ui()], this.options[n]); |
---|
37 | }, |
---|
38 | destroy: function() { |
---|
39 | |
---|
40 | this.element |
---|
41 | .removeClass("ui-slider ui-slider-disabled") |
---|
42 | .removeData("slider") |
---|
43 | .unbind(".slider"); |
---|
44 | |
---|
45 | if(this.handle && this.handle.length) { |
---|
46 | this.handle |
---|
47 | .unwrap("a"); |
---|
48 | this.handle.each(function() { |
---|
49 | $(this).data("mouse").mouseDestroy(); |
---|
50 | }); |
---|
51 | } |
---|
52 | |
---|
53 | this.generated && this.generated.remove(); |
---|
54 | |
---|
55 | }, |
---|
56 | setData: function(key, value) { |
---|
57 | $.widget.prototype.setData.apply(this, arguments); |
---|
58 | if (/min|max|steps/.test(key)) { |
---|
59 | this.initBoundaries(); |
---|
60 | } |
---|
61 | |
---|
62 | if(key == "range") { |
---|
63 | value ? this.handle.length == 2 && this.createRange() : this.removeRange(); |
---|
64 | } |
---|
65 | |
---|
66 | }, |
---|
67 | |
---|
68 | init: function() { |
---|
69 | |
---|
70 | var self = this; |
---|
71 | this.element.addClass("ui-slider"); |
---|
72 | this.initBoundaries(); |
---|
73 | |
---|
74 | // Initialize mouse and key events for interaction |
---|
75 | this.handle = $(this.options.handle, this.element); |
---|
76 | if (!this.handle.length) { |
---|
77 | self.handle = self.generated = $(self.options.handles || [0]).map(function() { |
---|
78 | var handle = $("<div/>").addClass("ui-slider-handle").appendTo(self.element); |
---|
79 | if (this.id) |
---|
80 | handle.attr("id", this.id); |
---|
81 | return handle[0]; |
---|
82 | }); |
---|
83 | } |
---|
84 | |
---|
85 | |
---|
86 | var handleclass = function(el) { |
---|
87 | this.element = $(el); |
---|
88 | this.element.data("mouse", this); |
---|
89 | this.options = self.options; |
---|
90 | |
---|
91 | this.element.bind("mousedown", function() { |
---|
92 | if(self.currentHandle) this.blur(self.currentHandle); |
---|
93 | self.focus(this,1); |
---|
94 | }); |
---|
95 | |
---|
96 | this.mouseInit(); |
---|
97 | }; |
---|
98 | |
---|
99 | $.extend(handleclass.prototype, $.ui.mouse, { |
---|
100 | mouseStart: function(e) { return self.start.call(self, e, this.element[0]); }, |
---|
101 | mouseStop: function(e) { return self.stop.call(self, e, this.element[0]); }, |
---|
102 | mouseDrag: function(e) { return self.drag.call(self, e, this.element[0]); }, |
---|
103 | mouseCapture: function() { return true; }, |
---|
104 | trigger: function(e) { this.mouseDown(e); } |
---|
105 | }); |
---|
106 | |
---|
107 | |
---|
108 | $(this.handle) |
---|
109 | .each(function() { |
---|
110 | new handleclass(this); |
---|
111 | }) |
---|
112 | .wrap('<a href="javascript:void(0)" style="outline:none;border:none;"></a>') |
---|
113 | .parent() |
---|
114 | .bind('focus', function(e) { self.focus(this.firstChild); }) |
---|
115 | .bind('blur', function(e) { self.blur(this.firstChild); }) |
---|
116 | .bind('keydown', function(e) { if(!self.options.noKeyboard) self.keydown(e.keyCode, this.firstChild); }) |
---|
117 | ; |
---|
118 | |
---|
119 | // Bind the click to the slider itself |
---|
120 | this.element.bind('mousedown.slider', function(e) { |
---|
121 | self.click.apply(self, [e]); |
---|
122 | self.currentHandle.data("mouse").trigger(e); |
---|
123 | self.firstValue = self.firstValue + 1; //This is for always triggering the change event |
---|
124 | }); |
---|
125 | |
---|
126 | // Move the first handle to the startValue |
---|
127 | $.each(this.options.handles || [], function(index, handle) { |
---|
128 | self.moveTo(handle.start, index, true); |
---|
129 | }); |
---|
130 | if (!isNaN(this.options.startValue)) |
---|
131 | this.moveTo(this.options.startValue, 0, true); |
---|
132 | |
---|
133 | this.previousHandle = $(this.handle[0]); //set the previous handle to the first to allow clicking before selecting the handle |
---|
134 | if(this.handle.length == 2 && this.options.range) this.createRange(); |
---|
135 | }, |
---|
136 | initBoundaries: function() { |
---|
137 | |
---|
138 | var element = this.element[0], o = this.options; |
---|
139 | this.actualSize = { width: this.element.outerWidth() , height: this.element.outerHeight() }; |
---|
140 | |
---|
141 | $.extend(o, { |
---|
142 | axis: o.axis || (element.offsetWidth < element.offsetHeight ? 'vertical' : 'horizontal'), |
---|
143 | max: !isNaN(parseInt(o.max,10)) ? { x: parseInt(o.max, 10), y: parseInt(o.max, 10) } : ({ x: o.max && o.max.x || 100, y: o.max && o.max.y || 100 }), |
---|
144 | min: !isNaN(parseInt(o.min,10)) ? { x: parseInt(o.min, 10), y: parseInt(o.min, 10) } : ({ x: o.min && o.min.x || 0, y: o.min && o.min.y || 0 }) |
---|
145 | }); |
---|
146 | //Prepare the real maxValue |
---|
147 | o.realMax = { |
---|
148 | x: o.max.x - o.min.x, |
---|
149 | y: o.max.y - o.min.y |
---|
150 | }; |
---|
151 | //Calculate stepping based on steps |
---|
152 | o.stepping = { |
---|
153 | x: o.stepping && o.stepping.x || parseInt(o.stepping, 10) || (o.steps ? o.realMax.x/(o.steps.x || parseInt(o.steps, 10) || o.realMax.x) : 0), |
---|
154 | y: o.stepping && o.stepping.y || parseInt(o.stepping, 10) || (o.steps ? o.realMax.y/(o.steps.y || parseInt(o.steps, 10) || o.realMax.y) : 0) |
---|
155 | }; |
---|
156 | }, |
---|
157 | |
---|
158 | |
---|
159 | keydown: function(keyCode, handle) { |
---|
160 | if(/(37|38|39|40)/.test(keyCode)) { |
---|
161 | this.moveTo({ |
---|
162 | x: /(37|39)/.test(keyCode) ? (keyCode == 37 ? '-' : '+') + '=' + this.oneStep("x") : 0, |
---|
163 | y: /(38|40)/.test(keyCode) ? (keyCode == 38 ? '-' : '+') + '=' + this.oneStep("y") : 0 |
---|
164 | }, handle); |
---|
165 | } |
---|
166 | }, |
---|
167 | focus: function(handle,hard) { |
---|
168 | this.currentHandle = $(handle).addClass('ui-slider-handle-active'); |
---|
169 | if (hard) |
---|
170 | this.currentHandle.parent()[0].focus(); |
---|
171 | }, |
---|
172 | blur: function(handle) { |
---|
173 | $(handle).removeClass('ui-slider-handle-active'); |
---|
174 | if(this.currentHandle && this.currentHandle[0] == handle) { this.previousHandle = this.currentHandle; this.currentHandle = null; }; |
---|
175 | }, |
---|
176 | click: function(e) { |
---|
177 | // This method is only used if: |
---|
178 | // - The user didn't click a handle |
---|
179 | // - The Slider is not disabled |
---|
180 | // - There is a current, or previous selected handle (otherwise we wouldn't know which one to move) |
---|
181 | |
---|
182 | var pointer = [e.pageX,e.pageY]; |
---|
183 | |
---|
184 | var clickedHandle = false; |
---|
185 | this.handle.each(function() { |
---|
186 | if(this == e.target) |
---|
187 | clickedHandle = true; |
---|
188 | }); |
---|
189 | if (clickedHandle || this.options.disabled || !(this.currentHandle || this.previousHandle)) |
---|
190 | return; |
---|
191 | |
---|
192 | // If a previous handle was focussed, focus it again |
---|
193 | if (!this.currentHandle && this.previousHandle) |
---|
194 | this.focus(this.previousHandle, true); |
---|
195 | |
---|
196 | // propagate only for distance > 0, otherwise propagation is done my drag |
---|
197 | this.offset = this.element.offset(); |
---|
198 | |
---|
199 | this.moveTo({ |
---|
200 | y: this.convertValue(e.pageY - this.offset.top - this.currentHandle[0].offsetHeight/2, "y"), |
---|
201 | x: this.convertValue(e.pageX - this.offset.left - this.currentHandle[0].offsetWidth/2, "x") |
---|
202 | }, null, !this.options.distance); |
---|
203 | }, |
---|
204 | |
---|
205 | |
---|
206 | |
---|
207 | createRange: function() { |
---|
208 | if(this.rangeElement) return; |
---|
209 | this.rangeElement = $('<div></div>') |
---|
210 | .addClass('ui-slider-range') |
---|
211 | .css({ position: 'absolute' }) |
---|
212 | .appendTo(this.element); |
---|
213 | this.updateRange(); |
---|
214 | }, |
---|
215 | removeRange: function() { |
---|
216 | this.rangeElement.remove(); |
---|
217 | this.rangeElement = null; |
---|
218 | }, |
---|
219 | updateRange: function() { |
---|
220 | var prop = this.options.axis == "vertical" ? "top" : "left"; |
---|
221 | var size = this.options.axis == "vertical" ? "height" : "width"; |
---|
222 | this.rangeElement.css(prop, (parseInt($(this.handle[0]).css(prop),10) || 0) + this.handleSize(0, this.options.axis == "vertical" ? "y" : "x")/2); |
---|
223 | this.rangeElement.css(size, (parseInt($(this.handle[1]).css(prop),10) || 0) - (parseInt($(this.handle[0]).css(prop),10) || 0)); |
---|
224 | }, |
---|
225 | getRange: function() { |
---|
226 | return this.rangeElement ? this.convertValue(parseInt(this.rangeElement.css(this.options.axis == "vertical" ? "height" : "width"),10), this.options.axis == "vertical" ? "y" : "x") : null; |
---|
227 | }, |
---|
228 | |
---|
229 | handleIndex: function() { |
---|
230 | return this.handle.index(this.currentHandle[0]); |
---|
231 | }, |
---|
232 | value: function(handle, axis) { |
---|
233 | if(this.handle.length == 1) this.currentHandle = this.handle; |
---|
234 | if(!axis) axis = this.options.axis == "vertical" ? "y" : "x"; |
---|
235 | |
---|
236 | var curHandle = $(handle != undefined && handle !== null ? this.handle[handle] || handle : this.currentHandle); |
---|
237 | |
---|
238 | if(curHandle.data("mouse").sliderValue) { |
---|
239 | return parseInt(curHandle.data("mouse").sliderValue[axis],10); |
---|
240 | } else { |
---|
241 | return parseInt(((parseInt(curHandle.css(axis == "x" ? "left" : "top"),10) / (this.actualSize[axis == "x" ? "width" : "height"] - this.handleSize(handle,axis))) * this.options.realMax[axis]) + this.options.min[axis],10); |
---|
242 | } |
---|
243 | |
---|
244 | }, |
---|
245 | convertValue: function(value,axis) { |
---|
246 | return this.options.min[axis] + (value / (this.actualSize[axis == "x" ? "width" : "height"] - this.handleSize(null,axis))) * this.options.realMax[axis]; |
---|
247 | }, |
---|
248 | |
---|
249 | translateValue: function(value,axis) { |
---|
250 | return ((value - this.options.min[axis]) / this.options.realMax[axis]) * (this.actualSize[axis == "x" ? "width" : "height"] - this.handleSize(null,axis)); |
---|
251 | }, |
---|
252 | translateRange: function(value,axis) { |
---|
253 | if (this.rangeElement) { |
---|
254 | if (this.currentHandle[0] == this.handle[0] && value >= this.translateValue(this.value(1),axis)) |
---|
255 | value = this.translateValue(this.value(1,axis) - this.oneStep(axis), axis); |
---|
256 | if (this.currentHandle[0] == this.handle[1] && value <= this.translateValue(this.value(0),axis)) |
---|
257 | value = this.translateValue(this.value(0,axis) + this.oneStep(axis), axis); |
---|
258 | } |
---|
259 | if (this.options.handles) { |
---|
260 | var handle = this.options.handles[this.handleIndex()]; |
---|
261 | if (value < this.translateValue(handle.min,axis)) { |
---|
262 | value = this.translateValue(handle.min,axis); |
---|
263 | } else if (value > this.translateValue(handle.max,axis)) { |
---|
264 | value = this.translateValue(handle.max,axis); |
---|
265 | } |
---|
266 | } |
---|
267 | return value; |
---|
268 | }, |
---|
269 | translateLimits: function(value,axis) { |
---|
270 | if (value >= this.actualSize[axis == "x" ? "width" : "height"] - this.handleSize(null,axis)) |
---|
271 | value = this.actualSize[axis == "x" ? "width" : "height"] - this.handleSize(null,axis); |
---|
272 | if (value <= 0) |
---|
273 | value = 0; |
---|
274 | return value; |
---|
275 | }, |
---|
276 | handleSize: function(handle,axis) { |
---|
277 | return $(handle != undefined && handle !== null ? this.handle[handle] : this.currentHandle)[0]["offset"+(axis == "x" ? "Width" : "Height")]; |
---|
278 | }, |
---|
279 | oneStep: function(axis) { |
---|
280 | return this.options.stepping[axis] || 1; |
---|
281 | }, |
---|
282 | |
---|
283 | |
---|
284 | start: function(e, handle) { |
---|
285 | |
---|
286 | var o = this.options; |
---|
287 | if(o.disabled) return false; |
---|
288 | |
---|
289 | // Prepare the outer size |
---|
290 | this.actualSize = { width: this.element.outerWidth() , height: this.element.outerHeight() }; |
---|
291 | |
---|
292 | // This is a especially ugly fix for strange blur events happening on mousemove events |
---|
293 | if (!this.currentHandle) |
---|
294 | this.focus(this.previousHandle, true); |
---|
295 | |
---|
296 | this.offset = this.element.offset(); |
---|
297 | |
---|
298 | this.handleOffset = this.currentHandle.offset(); |
---|
299 | this.clickOffset = { top: e.pageY - this.handleOffset.top, left: e.pageX - this.handleOffset.left }; |
---|
300 | |
---|
301 | this.firstValue = this.value(); |
---|
302 | |
---|
303 | this.propagate('start', e); |
---|
304 | this.drag(e, handle); |
---|
305 | return true; |
---|
306 | |
---|
307 | }, |
---|
308 | stop: function(e) { |
---|
309 | this.propagate('stop', e); |
---|
310 | if (this.firstValue != this.value()) |
---|
311 | this.propagate('change', e); |
---|
312 | // This is a especially ugly fix for strange blur events happening on mousemove events |
---|
313 | this.focus(this.currentHandle, true); |
---|
314 | return false; |
---|
315 | }, |
---|
316 | drag: function(e, handle) { |
---|
317 | |
---|
318 | var o = this.options; |
---|
319 | var position = { top: e.pageY - this.offset.top - this.clickOffset.top, left: e.pageX - this.offset.left - this.clickOffset.left}; |
---|
320 | if(!this.currentHandle) this.focus(this.previousHandle, true); //This is a especially ugly fix for strange blur events happening on mousemove events |
---|
321 | |
---|
322 | position.left = this.translateLimits(position.left, "x"); |
---|
323 | position.top = this.translateLimits(position.top, "y"); |
---|
324 | |
---|
325 | if (o.stepping.x) { |
---|
326 | var value = this.convertValue(position.left, "x"); |
---|
327 | value = Math.round(value / o.stepping.x) * o.stepping.x; |
---|
328 | position.left = this.translateValue(value, "x"); |
---|
329 | } |
---|
330 | if (o.stepping.y) { |
---|
331 | var value = this.convertValue(position.top, "y"); |
---|
332 | value = Math.round(value / o.stepping.y) * o.stepping.y; |
---|
333 | position.top = this.translateValue(value, "y"); |
---|
334 | } |
---|
335 | |
---|
336 | position.left = this.translateRange(position.left, "x"); |
---|
337 | position.top = this.translateRange(position.top, "y"); |
---|
338 | |
---|
339 | if(o.axis != "vertical") this.currentHandle.css({ left: position.left }); |
---|
340 | if(o.axis != "horizontal") this.currentHandle.css({ top: position.top }); |
---|
341 | |
---|
342 | //Store the slider's value |
---|
343 | this.currentHandle.data("mouse").sliderValue = { |
---|
344 | x: Math.round(this.convertValue(position.left, "x")) || 0, |
---|
345 | y: Math.round(this.convertValue(position.top, "y")) || 0 |
---|
346 | }; |
---|
347 | |
---|
348 | if (this.rangeElement) |
---|
349 | this.updateRange(); |
---|
350 | this.propagate('slide', e); |
---|
351 | return false; |
---|
352 | }, |
---|
353 | |
---|
354 | moveTo: function(value, handle, noPropagation) { |
---|
355 | |
---|
356 | var o = this.options; |
---|
357 | |
---|
358 | // Prepare the outer size |
---|
359 | this.actualSize = { width: this.element.outerWidth() , height: this.element.outerHeight() }; |
---|
360 | |
---|
361 | //If no handle has been passed, no current handle is available and we have multiple handles, return false |
---|
362 | if (handle == undefined && !this.currentHandle && this.handle.length != 1) |
---|
363 | return false; |
---|
364 | |
---|
365 | //If only one handle is available, use it |
---|
366 | if (handle == undefined && !this.currentHandle) |
---|
367 | handle = 0; |
---|
368 | |
---|
369 | if (handle != undefined) |
---|
370 | this.currentHandle = this.previousHandle = $(this.handle[handle] || handle); |
---|
371 | |
---|
372 | |
---|
373 | if(value.x !== undefined && value.y !== undefined) { |
---|
374 | var x = value.x, y = value.y; |
---|
375 | } else { |
---|
376 | var x = value, y = value; |
---|
377 | } |
---|
378 | |
---|
379 | if(x !== undefined && x.constructor != Number) { |
---|
380 | var me = /^\-\=/.test(x), pe = /^\+\=/.test(x); |
---|
381 | if(me || pe) { |
---|
382 | x = this.value(null, "x") + parseInt(x.replace(me ? '=' : '+=', ''), 10); |
---|
383 | } else { |
---|
384 | x = isNaN(parseInt(x, 10)) ? undefined : parseInt(x, 10); |
---|
385 | } |
---|
386 | } |
---|
387 | |
---|
388 | if(y !== undefined && y.constructor != Number) { |
---|
389 | var me = /^\-\=/.test(y), pe = /^\+\=/.test(y); |
---|
390 | if(me || pe) { |
---|
391 | y = this.value(null, "y") + parseInt(y.replace(me ? '=' : '+=', ''), 10); |
---|
392 | } else { |
---|
393 | y = isNaN(parseInt(y, 10)) ? undefined : parseInt(y, 10); |
---|
394 | } |
---|
395 | } |
---|
396 | |
---|
397 | if(o.axis != "vertical" && x !== undefined) { |
---|
398 | if(o.stepping.x) x = Math.round(x / o.stepping.x) * o.stepping.x; |
---|
399 | x = this.translateValue(x, "x"); |
---|
400 | x = this.translateLimits(x, "x"); |
---|
401 | x = this.translateRange(x, "x"); |
---|
402 | |
---|
403 | o.animate ? this.currentHandle.stop().animate({ left: x }, (Math.abs(parseInt(this.currentHandle.css("left")) - x)) * (!isNaN(parseInt(o.animate)) ? o.animate : 5)) : this.currentHandle.css({ left: x }); |
---|
404 | } |
---|
405 | |
---|
406 | if(o.axis != "horizontal" && y !== undefined) { |
---|
407 | if(o.stepping.y) y = Math.round(y / o.stepping.y) * o.stepping.y; |
---|
408 | y = this.translateValue(y, "y"); |
---|
409 | y = this.translateLimits(y, "y"); |
---|
410 | y = this.translateRange(y, "y"); |
---|
411 | o.animate ? this.currentHandle.stop().animate({ top: y }, (Math.abs(parseInt(this.currentHandle.css("top")) - y)) * (!isNaN(parseInt(o.animate)) ? o.animate : 5)) : this.currentHandle.css({ top: y }); |
---|
412 | } |
---|
413 | |
---|
414 | if (this.rangeElement) |
---|
415 | this.updateRange(); |
---|
416 | |
---|
417 | //Store the slider's value |
---|
418 | this.currentHandle.data("mouse").sliderValue = { |
---|
419 | x: Math.round(this.convertValue(x, "x")) || 0, |
---|
420 | y: Math.round(this.convertValue(y, "y")) || 0 |
---|
421 | }; |
---|
422 | |
---|
423 | if (!noPropagation) { |
---|
424 | this.propagate('start', null); |
---|
425 | this.propagate('stop', null); |
---|
426 | this.propagate('change', null); |
---|
427 | this.propagate("slide", null); |
---|
428 | } |
---|
429 | } |
---|
430 | }); |
---|
431 | |
---|
432 | $.ui.slider.getter = "value"; |
---|
433 | |
---|
434 | $.ui.slider.defaults = { |
---|
435 | handle: ".ui-slider-handle", |
---|
436 | distance: 1, |
---|
437 | animate: false |
---|
438 | }; |
---|
439 | |
---|
440 | })(jQuery); |
---|