Administrator
2023-04-21 195945efc5db921a4c9eb8cf9421c172273293f5
提交 | 用户 | 时间
58d006 1 /*
A 2  * Fuel UX Spinbox
3  * https://github.com/ExactTarget/fuelux
4  *
5  * Copyright (c) 2014 ExactTarget
6  * Licensed under the BSD New license.
7  */
8
9 // -- BEGIN UMD WRAPPER PREFACE --
10
11 // For more information on UMD visit:
12 // https://github.com/umdjs/umd/blob/master/jqueryPlugin.js
13
14 (function (factory) {
15     if (typeof define === 'function' && define.amd) {
16         // if AMD loader is available, register as an anonymous module.
17         define(['jquery'], factory);
18     } else if (typeof exports === 'object') {
19         // Node/CommonJS
20         module.exports = factory(require('jquery'));
21     } else {
22         // OR use browser globals if AMD is not present
23         factory(jQuery);
24     }
25 }(function ($) {
26     // -- END UMD WRAPPER PREFACE --
27
28     // -- BEGIN MODULE CODE HERE --
29
30     var old = $.fn.spinbox;
31
32     // SPINBOX CONSTRUCTOR AND PROTOTYPE
33
34     var Spinbox = function Spinbox(element, options) {
35         this.$element = $(element);
36         this.$element.find('.btn').on('click', function (e) {
37             //keep spinbox from submitting if they forgot to say type="button" on their spinner buttons
38             e.preventDefault();
39         });
40         this.options = $.extend({}, $.fn.spinbox.defaults, options);
41         this.options.step = this.$element.data('step') || this.options.step;
42
43         if (this.options.value < this.options.min) {
44             this.options.value = this.options.min;
45         } else if (this.options.max < this.options.value) {
46             this.options.value = this.options.max;
47         }
48
49         this.$input = this.$element.find('.spinbox-input');
50         this.$input.on('focusout.fu.spinbox', this.$input, $.proxy(this.change, this));
51         this.$element.on('keydown.fu.spinbox', this.$input, $.proxy(this.keydown, this));
52         this.$element.on('keyup.fu.spinbox', this.$input, $.proxy(this.keyup, this));
53
54         this.bindMousewheelListeners();
55         this.mousewheelTimeout = {};
56
57         if (this.options.hold) {
58             this.$element.on('mousedown.fu.spinbox', '.spinbox-up', $.proxy(function () {
59                 this.startSpin(true);
60             }, this));
61             this.$element.on('mouseup.fu.spinbox', '.spinbox-up, .spinbox-down', $.proxy(this.stopSpin, this));
62             this.$element.on('mouseout.fu.spinbox', '.spinbox-up, .spinbox-down', $.proxy(this.stopSpin, this));
63             this.$element.on('mousedown.fu.spinbox', '.spinbox-down', $.proxy(function () {
64                 this.startSpin(false);
65             }, this));
66         } else {
67             this.$element.on('click.fu.spinbox', '.spinbox-up', $.proxy(function () {
68                 this.step(true);
69             }, this));
70             this.$element.on('click.fu.spinbox', '.spinbox-down', $.proxy(function () {
71                 this.step(false);
72             }, this));
73         }
74
75         this.switches = {
76             count: 1,
77             enabled: true
78         };
79
80         if (this.options.speed === 'medium') {
81             this.switches.speed = 300;
82         } else if (this.options.speed === 'fast') {
83             this.switches.speed = 100;
84         } else {
85             this.switches.speed = 500;
86         }
87
88         this.options.defaultUnit = _isUnitLegal(this.options.defaultUnit, this.options.units) ? this.options.defaultUnit : '';
89         this.unit = this.options.defaultUnit;
90
91         this.lastValue = this.options.value;
92
93         this.render();
94
95         if (this.options.disabled) {
96             this.disable();
97         }
98     };
99
100     // Truly private methods
101     var _limitToStep = function _limitToStep(number, step) {
102         return Math.round(number / step) * step;
103     };
104
105     var _isUnitLegal = function _isUnitLegal(unit, validUnits) {
106         var legalUnit = false;
107         var suspectUnit = unit.toLowerCase();
108
109         $.each(validUnits, function (i, validUnit) {
110             validUnit = validUnit.toLowerCase();
111             if (suspectUnit === validUnit) {
112                 legalUnit = true;
113                 return false;//break out of the loop
114             }
115         });
116
117         return legalUnit;
118     };
119
120     var _applyLimits = function _applyLimits(value) {
121         // if unreadable
122         if (isNaN(parseFloat(value))) {
123             return value;
124         }
125
126         // if not within range return the limit
127         if (value > this.options.max) {
128             if (this.options.cycle) {
129                 value = this.options.min;
130             } else {
131                 value = this.options.max;
132             }
133         } else if (value < this.options.min) {
134             if (this.options.cycle) {
135                 value = this.options.max;
136             } else {
137                 value = this.options.min;
138             }
139         }
140
141         if (this.options.limitToStep && this.options.step) {
142             value = _limitToStep(value, this.options.step);
143
144             //force round direction so that it stays within bounds
145             if(value > this.options.max){
146                 value = value - this.options.step;
147             } else if(value < this.options.min) {
148                 value = value + this.options.step;
149             }
150         }
151
152         return value;
153     };
154
155     Spinbox.prototype = {
156         constructor: Spinbox,
157
158         destroy: function destroy() {
159             this.$element.remove();
160             // any external bindings
161             // [none]
162             // set input value attrbute
163             this.$element.find('input').each(function () {
164                 $(this).attr('value', $(this).val());
165             });
166             // empty elements to return to original markup
167             // [none]
168             // returns string of markup
169             return this.$element[0].outerHTML;
170         },
171
172         render: function render() {
173             this.setValue(this.getDisplayValue());
174         },
175
176         change: function change() {
177             this.setValue(this.getDisplayValue());
178
179             this.triggerChangedEvent();
180         },
181
182         stopSpin: function stopSpin() {
183             if (this.switches.timeout !== undefined) {
184                 clearTimeout(this.switches.timeout);
185                 this.switches.count = 1;
186                 this.triggerChangedEvent();
187             }
188         },
189
190         triggerChangedEvent: function triggerChangedEvent() {
191             var currentValue = this.getValue();
192             if (currentValue === this.lastValue) return;
193             this.lastValue = currentValue;
194
195             // Primary changed event
196             this.$element.trigger('changed.fu.spinbox', currentValue);
197         },
198
199         startSpin: function startSpin(type) {
200             if (!this.options.disabled) {
201                 var divisor = this.switches.count;
202
203                 if (divisor === 1) {
204                     this.step(type);
205                     divisor = 1;
206                 } else if (divisor < 3) {
207                     divisor = 1.5;
208                 } else if (divisor < 8) {
209                     divisor = 2.5;
210                 } else {
211                     divisor = 4;
212                 }
213
214                 this.switches.timeout = setTimeout($.proxy(function () {
215                     this.iterate(type);
216                 }, this), this.switches.speed / divisor);
217                 this.switches.count++;
218             }
219         },
220
221         iterate: function iterate(type) {
222             this.step(type);
223             this.startSpin(type);
224         },
225
226         step: function step(isIncrease) {
227             //refresh value from display before trying to increment in case they have just been typing before clicking the nubbins
228             this.setValue(this.getDisplayValue());
229             var newVal;
230
231             if (isIncrease) {
232                 newVal = this.options.value + this.options.step;
233             } else {
234                 newVal = this.options.value - this.options.step;
235             }
236
237             newVal = newVal.toFixed(5);
238
239             this.setValue(newVal + this.unit);
240         },
241
242         getDisplayValue: function getDisplayValue() {
243             var inputValue = this.parseInput(this.$input.val());
244             var value = (!!inputValue) ? inputValue : this.options.value;
245             return value;
246         },
247
248         setDisplayValue: function setDisplayValue(value) {
249             this.$input.val(value);
250         },
251
252         getValue: function getValue() {
253             var val = this.options.value;
254             if (this.options.decimalMark !== '.'){
255                 val = (val + '').split('.').join(this.options.decimalMark);
256             }
257             return val + this.unit;
258         },
259
260         setValue: function setValue(val) {
261             //remove any i18n on the number
262             if (this.options.decimalMark !== '.') {
263                 val = this.parseInput(val);
264             }
265
266             //are we dealing with united numbers?
267             if(typeof val !== "number"){
268                 var potentialUnit = val.replace(/[0-9.-]/g, '');
269                 //make sure unit is valid, or else drop it in favor of current unit, or default unit (potentially nothing)
270                 this.unit = _isUnitLegal(potentialUnit, this.options.units) ? potentialUnit : this.options.defaultUnit;
271             }
272
273             var intVal = this.getIntValue(val);
274
275             //make sure we are dealing with a number
276             if (isNaN(intVal) && !isFinite(intVal)) {
277                 return this.setValue(this.options.value);
278             }
279
280             //conform
281             intVal = _applyLimits.call(this, intVal);
282
283             //cache the pure int value
284             this.options.value = intVal;
285
286             //prepare number for display
287             val = intVal + this.unit;
288
289             if (this.options.decimalMark !== '.'){
290                 val = (val + '').split('.').join(this.options.decimalMark);
291             }
292
293             //display number
294             this.setDisplayValue(val);
295
296             return this;
297         },
298
299         value: function value(val) {
300             if (val || val === 0) {
301                 return this.setValue(val);
302             } else {
303                 return this.getValue();
304             }
305         },
306
307         parseInput: function parseInput(value) {
308             value = (value + '').split(this.options.decimalMark).join('.');
309
310             return value;
311         },
312
313         getIntValue: function getIntValue(value) {
314             //if they didn't pass in a number, try and get the number
315             value = (typeof value === "undefined") ? this.getValue() : value;
316             // if there still isn't a number, abort
317             if(typeof value === "undefined"){return;}
318
319             if (typeof value === 'string'){
320                 value = this.parseInput(value);
321             }
322
323             value = parseFloat(value, 10);
324
325             return value;
326         },
327
328         disable: function disable() {
329             this.options.disabled = true;
330             this.$element.addClass('disabled');
331             this.$input.attr('disabled', '');
332             this.$element.find('button').addClass('disabled');
333         },
334
335         enable: function enable() {
336             this.options.disabled = false;
337             this.$element.removeClass('disabled');
338             this.$input.removeAttr('disabled');
339             this.$element.find('button').removeClass('disabled');
340         },
341
342         keydown: function keydown(event) {
343             var keyCode = event.keyCode;
344             if (keyCode === 38) {
345                 this.step(true);
346             } else if (keyCode === 40) {
347                 this.step(false);
348             } else if (keyCode === 13) {
349                 this.change();
350             }
351         },
352
353         keyup: function keyup(event) {
354             var keyCode = event.keyCode;
355
356             if (keyCode === 38 || keyCode === 40) {
357                 this.triggerChangedEvent();
358             }
359         },
360
361         bindMousewheelListeners: function bindMousewheelListeners() {
362             var inputEl = this.$input.get(0);
363             if (inputEl.addEventListener) {
364                 //IE 9, Chrome, Safari, Opera
365                 inputEl.addEventListener('mousewheel', $.proxy(this.mousewheelHandler, this), false);
366                 // Firefox
367                 inputEl.addEventListener('DOMMouseScroll', $.proxy(this.mousewheelHandler, this), false);
368             } else {
369                 // IE <9
370                 inputEl.attachEvent('onmousewheel', $.proxy(this.mousewheelHandler, this));
371             }
372         },
373
374         mousewheelHandler: function mousewheelHandler(event) {
375             if (!this.options.disabled) {
376                 var e = window.event || event;// old IE support
377                 var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
378                 var self = this;
379
380                 clearTimeout(this.mousewheelTimeout);
381                 this.mousewheelTimeout = setTimeout(function () {
382                     self.triggerChangedEvent();
383                 }, 300);
384
385                 if (delta > 0) {//ACE
386                     this.step(true);
387                 } else {
388                     this.step(false);
389                 }
390
391                 if (e.preventDefault) {
392                     e.preventDefault();
393                 } else {
394                     e.returnValue = false;
395                 }
396
397                 return false;
398             }
399         }
400     };
401
402
403     // SPINBOX PLUGIN DEFINITION
404
405     $.fn.spinbox = function spinbox(option) {
406         var args = Array.prototype.slice.call(arguments, 1);
407         var methodReturn;
408
409         var $set = this.each(function () {
410             var $this = $(this);
411             var data = $this.data('fu.spinbox');
412             var options = typeof option === 'object' && option;
413
414             if (!data) {
415                 $this.data('fu.spinbox', (data = new Spinbox(this, options)));
416             }
417
418             if (typeof option === 'string') {
419                 methodReturn = data[option].apply(data, args);
420             }
421         });
422
423         return (methodReturn === undefined) ? $set : methodReturn;
424     };
425
426     // value needs to be 0 for this.render();
427     $.fn.spinbox.defaults = {
428         value: 0,
429         min: 0,
430         max: 999,
431         step: 1,
432         hold: true,
433         speed: 'medium',
434         disabled: false,
435         cycle: false,
436         units: [],
437         decimalMark: '.',
438         defaultUnit: '',
439         limitToStep: false
440     };
441
442     $.fn.spinbox.Constructor = Spinbox;
443
444     $.fn.spinbox.noConflict = function noConflict() {
445         $.fn.spinbox = old;
446         return this;
447     };
448
449
450     // DATA-API
451
452     $(document).on('mousedown.fu.spinbox.data-api', '[data-initialize=spinbox]', function (e) {
453         var $control = $(e.target).closest('.spinbox');
454         if (!$control.data('fu.spinbox')) {
455             $control.spinbox($control.data());
456         }
457     });
458
459     // Must be domReady for AMD compatibility
460     $(function () {
461         $('[data-initialize=spinbox]').each(function () {
462             var $this = $(this);
463             if (!$this.data('fu.spinbox')) {
464                 $this.spinbox($this.data());
465             }
466         });
467     });
468
469     // -- BEGIN UMD WRAPPER AFTERWORD --
470 }));
471 // -- END UMD WRAPPER AFTERWORD --