1030
|
1 /*
|
|
2 * jQuery.range - A tiny, easily styleable range selector
|
|
3 * Tom Moor, http://tommoor.com
|
|
4 * Copyright (c) 2011 Tom Moor
|
|
5 * MIT Licensed
|
|
6 * @version 1.0
|
|
7 */
|
|
8
|
|
9 (function($){
|
|
10
|
|
11 var TinyRange = function(){
|
|
12 // locals
|
|
13 var options;
|
|
14 var $input;
|
|
15 var $rail;
|
|
16 var $handle;
|
|
17 var $handle2;
|
|
18 var $selection;
|
|
19 var $dragging;
|
|
20 var $original;
|
|
21 var jump;
|
|
22 var size;
|
|
23 var defaults = {
|
|
24 orientation: 'horizontal', // todo
|
|
25 range: false,
|
|
26 values: false,
|
|
27 snap: false,
|
|
28 change: null,
|
|
29 blur: null
|
|
30 };
|
|
31
|
|
32 var jumpHandle = function(ev) {
|
|
33 ev.pageX = ev.pageX - $input.offset().left;
|
|
34
|
|
35 // get closest handle
|
|
36 var x1 = $handle.position().left;
|
|
37 var dist = ev.pageX - x1;
|
|
38
|
|
39 if($handle2){
|
|
40 var x2 = $handle2.position().left;
|
|
41 var dist2 = ev.pageX - x2;
|
|
42 }
|
|
43
|
|
44 // move towards click
|
|
45 if(!$handle2 || Math.abs(dist) < Math.abs(dist2) ){
|
|
46 if(dist > 0) moveHandle($handle, valueToPx(jump)+x1);
|
|
47 if(dist < 0) moveHandle($handle, -valueToPx(jump)+x1);
|
|
48 } else {
|
|
49 if(dist2 > 0) moveHandle($handle2, valueToPx(jump)+x2);
|
|
50 if(dist2 < 0) moveHandle($handle2, -valueToPx(jump)+x2);
|
|
51 }
|
|
52 }
|
|
53
|
|
54 var moveHandle = function($h, p, update){
|
|
55
|
|
56 var boundR = $input.width()-size;
|
|
57 var boundL = 0;
|
|
58
|
|
59 if(options.range){
|
|
60 if($h[0] === $handle[0]){
|
|
61 boundR = $handle2.position().left;
|
|
62 } else {
|
|
63 boundL = $handle.position().left;
|
|
64 }
|
|
65 }
|
|
66
|
|
67 if(p >= boundR){
|
|
68 p = boundR;
|
|
69 } else if(p <= boundL){
|
|
70 p = boundL;
|
|
71 }
|
|
72
|
1043
|
73 // leads to erratic behaviour with "step" attribute
|
|
74 // if(options.snap && p !== boundR){
|
|
75 // var snapPx = valueToPx(options.snap);
|
|
76 // p = Math.round(p/snapPx) * snapPx;
|
|
77 // }
|
1030
|
78
|
|
79 $h.css({'left': p, 'position': 'absolute'});
|
|
80 if(options.range) updateSelection();
|
|
81 if(update !== false) updateValues();
|
|
82 }
|
|
83
|
|
84 var dragStart = function(ev){
|
|
85 ev.stopPropagation();
|
|
86 ev.preventDefault();
|
|
87
|
|
88 $dragging = $(this);
|
|
89 };
|
|
90
|
|
91 var drag = function(ev){
|
|
92
|
|
93 if($dragging){
|
|
94 ev.preventDefault();
|
|
95 var pos = ev.pageX - $input.offset().left;
|
|
96
|
|
97 moveHandle($dragging, pos);
|
|
98 }
|
|
99 };
|
|
100
|
|
101 var updateSelection = function(){
|
|
102
|
|
103 var p = $handle.position().left;
|
|
104 var w = $handle2.position().left-p;
|
|
105 $selection.css({
|
|
106 'left': p,
|
|
107 'width': w,
|
|
108 'position': 'absolute'
|
|
109 });
|
|
110 };
|
|
111
|
|
112 var dragEnd = function(ev){
|
|
113 if($dragging){
|
|
114 $dragging = null;
|
|
115 if (options.blur == null) {
|
|
116 // send original blur event
|
|
117 $original.blur();
|
|
118 } else {
|
|
119 options.blur(options.values);
|
|
120 }
|
|
121 }
|
|
122 };
|
|
123
|
|
124 var updateValues = function(){
|
|
125
|
|
126 var prev;
|
|
127 if(options.range){
|
|
128
|
|
129 prev = options.values.slice(); // clone
|
1043
|
130 options.values[0] = pxToValue($handle.position().left);
|
|
131 options.values[1] = pxToValue($handle2.position().left);
|
1030
|
132
|
|
133 // set value on original element
|
|
134 $original.val(options.values[0] +','+options.values[1]);
|
|
135 } else {
|
|
136
|
|
137 prev = options.values;
|
1043
|
138 options.values = pxToValue($handle.position().left);
|
1030
|
139
|
|
140 // set value on original element
|
|
141 $original.val(options.values);
|
|
142 }
|
|
143
|
|
144 if(options.values !== prev) {
|
|
145 if (options.change == null) {
|
|
146 // trigger original change event
|
|
147 $original.change();
|
|
148 } else {
|
|
149 options.change(options.values);
|
|
150 }
|
|
151 }
|
|
152 };
|
|
153
|
|
154 var updateHandles = function(){
|
|
155
|
1040
|
156 if (options.values != null) {
|
1030
|
157 if (options.range){
|
|
158 moveHandle($handle2, valueToPx(options.values[1]), false);
|
|
159 moveHandle($handle, valueToPx(options.values[0]), false);
|
|
160 } else {
|
|
161 moveHandle($handle, valueToPx(options.values), false);
|
|
162 }
|
|
163 }
|
|
164
|
|
165 updateValues();
|
|
166 };
|
|
167
|
1043
|
168 var pxToValue = function(p){
|
1030
|
169 var w = $input.width()-size;
|
|
170 var v = (p/(w/(options.max-options.min)))+options.min;
|
|
171
|
1043
|
172 if(options.snap) {
|
|
173 v = Math.floor(v/options.snap) * options.snap;
|
|
174 return v;
|
|
175 }
|
1030
|
176 return Math.round(v);
|
|
177 };
|
|
178
|
|
179 var valueToPx = function(val){
|
1044
|
180 var w = $input.width()-size;
|
1040
|
181 var valspan = options.max - options.min;
|
|
182 var valpos = val - options.min;
|
|
183 var v = valpos * w / valspan;
|
1030
|
184
|
|
185 return v;
|
|
186 };
|
|
187
|
|
188 var bound = function(input){
|
|
189 return Math.max(Math.min(input, options.max), options.min);
|
|
190 };
|
|
191
|
|
192 var methods = {
|
|
193 init : function(o){
|
|
194
|
|
195 // element already replaced
|
|
196 if($(this).data('TinyRange')) return this;
|
|
197
|
|
198 // options
|
|
199 defaults.min = parseFloat($(this).attr('min'));
|
|
200 defaults.max = parseFloat($(this).attr('max'));
|
|
201 defaults.snap = parseFloat($(this).attr('step'));
|
|
202
|
|
203 // options passed into plugin override input attributes
|
|
204 options = $.extend(defaults, o);
|
|
205
|
|
206 if(options.values){
|
|
207 //
|
|
208 } else if(options.range){
|
|
209 options.values = [0, options.max];
|
|
210 } else {
|
|
211 options.values = parseFloat($(this).attr('value'));
|
|
212 }
|
|
213
|
|
214 // how far do handles jump on click, default to step value
|
|
215 jump = options.snap ? options.snap : options.max/10;
|
|
216
|
|
217 // create dom elements
|
|
218 $input = $('<div/>', {'class': 'range-input'}).mousedown(jumpHandle);
|
|
219 $rail = $('<div/>', {'class': 'range-rail'}).appendTo($input);
|
|
220 if(options.range) $selection = $('<div/>', {'class': 'range-selection'}).appendTo($input);
|
|
221 $handle = $('<a/>', {'class': 'range-handle'}).appendTo($input).mousedown(dragStart);
|
|
222 if(options.range) $handle2 = $handle.clone(true).appendTo($input);
|
|
223
|
|
224 // replace dom element
|
|
225 $(this).after($input);
|
|
226 $(this).hide();
|
|
227 $original = $(this);
|
|
228
|
|
229 // attach events
|
|
230 $(document).bind('mouseup', dragEnd);
|
|
231 $(document).bind('mousemove', drag);
|
|
232
|
|
233 // position handles
|
|
234 size = $handle.width();
|
|
235 updateHandles();
|
|
236
|
|
237 return this;
|
|
238 },
|
|
239 set: function(input){
|
|
240
|
|
241 if(typeof input === 'string'){
|
|
242 options.values = bound(input);
|
|
243 } else if(typeof input === 'object' && input.length === 2){
|
|
244 options.values[0] = bound(input[0]);
|
|
245 options.values[1] = bound(input[1]);
|
|
246 }
|
|
247
|
|
248 updateHandles();
|
|
249 },
|
|
250 destroy : function(){
|
|
251
|
|
252 $input.remove();
|
|
253 $(this).show().data('TinyRange', false);
|
|
254 $(document).unbind('mouseup', dragEnd);
|
|
255 $(document).unbind('mousemove', drag);
|
|
256
|
|
257 return this;
|
|
258 }
|
|
259 };
|
|
260
|
|
261 return methods;
|
|
262 };
|
|
263
|
|
264 $.fn.range = function(method) {
|
|
265
|
|
266 // so that arguments are accessible within each closure
|
|
267 var args = arguments;
|
|
268
|
|
269 return this.each(function(){
|
|
270 var state = $(this).data('TinyRange');
|
|
271
|
|
272 // Method calling logic
|
|
273 if (state && state[method] ) {
|
|
274 state[ method ].apply( this, Array.prototype.slice.call( args, 1 ));
|
|
275 } else if ( typeof method === 'object' || ! method ) {
|
|
276
|
|
277 // create new tinyrange
|
|
278 var tr = (new TinyRange(this));
|
|
279 tr.init.apply( this, args );
|
|
280
|
|
281 // save state in jquery data
|
|
282 $(this).data('TinyRange', tr);
|
|
283
|
|
284 } else {
|
|
285 $.error( 'Method ' + method + ' does not exist on jQuery.range' );
|
|
286 }
|
|
287 });
|
|
288 };
|
|
289 })(jQuery);
|