Subversion Repositories Integrator Subversion

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 espaco 1
/*
2
 *  Bootstrap TouchSpin - v3.0.1
3
 *  A mobile and touch friendly input spinner component for Bootstrap 3.
4
 *  http://www.virtuosoft.eu/code/bootstrap-touchspin/
5
 *
6
 *  Made by István Ujj-Mészáros
7
 *  Under Apache License v2.0 License
8
 */
9
(function($) {
10
  'use strict';
11
 
12
  var _currentSpinnerId = 0;
13
 
14
  function _scopedEventName(name, id) {
15
    return name + '.touchspin_' + id;
16
  }
17
 
18
  function _scopeEventNames(names, id) {
19
    return $.map(names, function(name) {
20
      return _scopedEventName(name, id);
21
    });
22
  }
23
 
24
  $.fn.TouchSpin = function(options) {
25
 
26
    if (options === 'destroy') {
27
      this.each(function() {
28
        var originalinput = $(this),
29
            originalinput_data = originalinput.data();
30
        $(document).off(_scopeEventNames([
31
          'mouseup',
32
          'touchend',
33
          'touchcancel',
34
          'mousemove',
35
          'touchmove',
36
          'scroll',
37
          'scrollstart'], originalinput_data.spinnerid).join(' '));
38
      });
39
      return;
40
    }
41
 
42
    var defaults = {
43
      min: 0,
44
      max: 100,
45
      initval: '',
46
      step: 1,
47
      decimals: 0,
48
      stepinterval: 100,
49
      forcestepdivisibility: 'round', // none | floor | round | ceil
50
      stepintervaldelay: 500,
51
      verticalbuttons: false,
52
      verticalupclass: 'glyphicon glyphicon-chevron-up',
53
      verticaldownclass: 'glyphicon glyphicon-chevron-down',
54
      prefix: '',
55
      postfix: '',
56
      prefix_extraclass: '',
57
      postfix_extraclass: '',
58
      booster: true,
59
      boostat: 10,
60
      maxboostedstep: false,
61
      mousewheel: true,
62
      buttondown_class: 'btn btn-default',
63
      buttonup_class: 'btn btn-default',
64
          buttondown_txt: '-',
65
          buttonup_txt: '+'
66
    };
67
 
68
    var attributeMap = {
69
      min: 'min',
70
      max: 'max',
71
      initval: 'init-val',
72
      step: 'step',
73
      decimals: 'decimals',
74
      stepinterval: 'step-interval',
75
      verticalbuttons: 'vertical-buttons',
76
      verticalupclass: 'vertical-up-class',
77
      verticaldownclass: 'vertical-down-class',
78
      forcestepdivisibility: 'force-step-divisibility',
79
      stepintervaldelay: 'step-interval-delay',
80
      prefix: 'prefix',
81
      postfix: 'postfix',
82
      prefix_extraclass: 'prefix-extra-class',
83
      postfix_extraclass: 'postfix-extra-class',
84
      booster: 'booster',
85
      boostat: 'boostat',
86
      maxboostedstep: 'max-boosted-step',
87
      mousewheel: 'mouse-wheel',
88
      buttondown_class: 'button-down-class',
89
      buttonup_class: 'button-up-class',
90
          buttondown_txt: 'button-down-txt',
91
          buttonup_txt: 'button-up-txt'
92
    };
93
 
94
    return this.each(function() {
95
 
96
      var settings,
97
          originalinput = $(this),
98
          originalinput_data = originalinput.data(),
99
          container,
100
          elements,
101
          value,
102
          downSpinTimer,
103
          upSpinTimer,
104
          downDelayTimeout,
105
          upDelayTimeout,
106
          spincount = 0,
107
          spinning = false;
108
 
109
      init();
110
 
111
 
112
      function init() {
113
        if (originalinput.data('alreadyinitialized')) {
114
          return;
115
        }
116
 
117
        originalinput.data('alreadyinitialized', true);
118
        _currentSpinnerId += 1;
119
        originalinput.data('spinnerid', _currentSpinnerId);
120
 
121
 
122
        if (!originalinput.is('input')) {
123
          console.log('Must be an input.');
124
          return;
125
        }
126
 
127
        _initSettings();
128
        _setInitval();
129
        _checkValue();
130
        _buildHtml();
131
        _initElements();
132
        _hideEmptyPrefixPostfix();
133
        _bindEvents();
134
        _bindEventsInterface();
135
        elements.input.css('display', 'block');
136
      }
137
 
138
      function _setInitval() {
139
        if (settings.initval !== '' && originalinput.val() === '') {
140
          originalinput.val(settings.initval);
141
        }
142
      }
143
 
144
      function changeSettings(newsettings) {
145
        _updateSettings(newsettings);
146
        _checkValue();
147
 
148
        var value = elements.input.val();
149
 
150
        if (value !== '') {
151
          value = Number(elements.input.val());
152
          elements.input.val(value.toFixed(settings.decimals));
153
        }
154
      }
155
 
156
      function _initSettings() {
157
        settings = $.extend({}, defaults, originalinput_data, _parseAttributes(), options);
158
      }
159
 
160
      function _parseAttributes() {
161
        var data = {};
162
        $.each(attributeMap, function(key, value) {
163
          var attrName = 'bts-' + value + '';
164
          if (originalinput.is('[data-' + attrName + ']')) {
165
            data[key] = originalinput.data(attrName);
166
          }
167
        });
168
        return data;
169
      }
170
 
171
      function _updateSettings(newsettings) {
172
        settings = $.extend({}, settings, newsettings);
173
      }
174
 
175
      function _buildHtml() {
176
        var initval = originalinput.val(),
177
            parentelement = originalinput.parent();
178
 
179
        if (initval !== '') {
180
          initval = Number(initval).toFixed(settings.decimals);
181
        }
182
 
183
        originalinput.data('initvalue', initval).val(initval);
184
        originalinput.addClass('form-control');
185
 
186
        if (parentelement.hasClass('input-group')) {
187
          _advanceInputGroup(parentelement);
188
        }
189
        else {
190
          _buildInputGroup();
191
        }
192
      }
193
 
194
      function _advanceInputGroup(parentelement) {
195
        parentelement.addClass('bootstrap-touchspin');
196
 
197
        var prev = originalinput.prev(),
198
            next = originalinput.next();
199
 
200
        var downhtml,
201
            uphtml,
202
            prefixhtml = '<span class="input-group-addon bootstrap-touchspin-prefix">' + settings.prefix + '</span>',
203
            postfixhtml = '<span class="input-group-addon bootstrap-touchspin-postfix">' + settings.postfix + '</span>';
204
 
205
        if (prev.hasClass('input-group-btn')) {
206
          downhtml = '<button class="' + settings.buttondown_class + ' bootstrap-touchspin-down" type="button">' + settings.buttondown_txt + '</button>';
207
          prev.append(downhtml);
208
        }
209
        else {
210
          downhtml = '<span class="input-group-btn"><button class="' + settings.buttondown_class + ' bootstrap-touchspin-down" type="button">' + settings.buttondown_txt + '</button></span>';
211
          $(downhtml).insertBefore(originalinput);
212
        }
213
 
214
        if (next.hasClass('input-group-btn')) {
215
          uphtml = '<button class="' + settings.buttonup_class + ' bootstrap-touchspin-up" type="button">' + settings.buttonup_txt + '</button>';
216
          next.prepend(uphtml);
217
        }
218
        else {
219
          uphtml = '<span class="input-group-btn"><button class="' + settings.buttonup_class + ' bootstrap-touchspin-up" type="button">' + settings.buttonup_txt + '</button></span>';
220
          $(uphtml).insertAfter(originalinput);
221
        }
222
 
223
        $(prefixhtml).insertBefore(originalinput);
224
        $(postfixhtml).insertAfter(originalinput);
225
 
226
        container = parentelement;
227
      }
228
 
229
      function _buildInputGroup() {
230
        var html;
231
 
232
        if (settings.verticalbuttons) {
233
          html = '<div class="input-group bootstrap-touchspin"><span class="input-group-addon bootstrap-touchspin-prefix">' + settings.prefix + '</span><span class="input-group-addon bootstrap-touchspin-postfix">' + settings.postfix + '</span><span class="input-group-btn-vertical"><button class="' + settings.buttondown_class + ' bootstrap-touchspin-up" type="button"><i class="' + settings.verticalupclass + '"></i></button><button class="' + settings.buttonup_class + ' bootstrap-touchspin-down" type="button"><i class="' + settings.verticaldownclass + '"></i></button></span></div>';
234
        }
235
        else {
236
          html = '<div class="input-group bootstrap-touchspin"><span class="input-group-btn"><button class="' + settings.buttondown_class + ' bootstrap-touchspin-down" type="button">' + settings.buttondown_txt + '</button></span><span class="input-group-addon bootstrap-touchspin-prefix">' + settings.prefix + '</span><span class="input-group-addon bootstrap-touchspin-postfix">' + settings.postfix + '</span><span class="input-group-btn"><button class="' + settings.buttonup_class + ' bootstrap-touchspin-up" type="button">' + settings.buttonup_txt + '</button></span></div>';
237
        }
238
 
239
        container = $(html).insertBefore(originalinput);
240
 
241
        $('.bootstrap-touchspin-prefix', container).after(originalinput);
242
 
243
        if (originalinput.hasClass('input-sm')) {
244
          container.addClass('input-group-sm');
245
        }
246
        else if (originalinput.hasClass('input-lg')) {
247
          container.addClass('input-group-lg');
248
        }
249
      }
250
 
251
      function _initElements() {
252
        elements = {
253
          down: $('.bootstrap-touchspin-down', container),
254
          up: $('.bootstrap-touchspin-up', container),
255
          input: $('input', container),
256
          prefix: $('.bootstrap-touchspin-prefix', container).addClass(settings.prefix_extraclass),
257
          postfix: $('.bootstrap-touchspin-postfix', container).addClass(settings.postfix_extraclass)
258
        };
259
      }
260
 
261
      function _hideEmptyPrefixPostfix() {
262
        if (settings.prefix === '') {
263
          elements.prefix.hide();
264
        }
265
 
266
        if (settings.postfix === '') {
267
          elements.postfix.hide();
268
        }
269
      }
270
 
271
      function _bindEvents() {
272
        originalinput.on('keydown', function(ev) {
273
          var code = ev.keyCode || ev.which;
274
 
275
          if (code === 38) {
276
            if (spinning !== 'up') {
277
              upOnce();
278
              startUpSpin();
279
            }
280
            ev.preventDefault();
281
          }
282
          else if (code === 40) {
283
            if (spinning !== 'down') {
284
              downOnce();
285
              startDownSpin();
286
            }
287
            ev.preventDefault();
288
          }
289
        });
290
 
291
        originalinput.on('keyup', function(ev) {
292
          var code = ev.keyCode || ev.which;
293
 
294
          if (code === 38) {
295
            stopSpin();
296
          }
297
          else if (code === 40) {
298
            stopSpin();
299
          }
300
        });
301
 
302
        originalinput.on('blur', function() {
303
          _checkValue();
304
        });
305
 
306
        elements.down.on('keydown', function(ev) {
307
          var code = ev.keyCode || ev.which;
308
 
309
          if (code === 32 || code === 13) {
310
            if (spinning !== 'down') {
311
              downOnce();
312
              startDownSpin();
313
            }
314
            ev.preventDefault();
315
          }
316
        });
317
 
318
        elements.down.on('keyup', function(ev) {
319
          var code = ev.keyCode || ev.which;
320
 
321
          if (code === 32 || code === 13) {
322
            stopSpin();
323
          }
324
        });
325
 
326
        elements.up.on('keydown', function(ev) {
327
          var code = ev.keyCode || ev.which;
328
 
329
          if (code === 32 || code === 13) {
330
            if (spinning !== 'up') {
331
              upOnce();
332
              startUpSpin();
333
            }
334
            ev.preventDefault();
335
          }
336
        });
337
 
338
        elements.up.on('keyup', function(ev) {
339
          var code = ev.keyCode || ev.which;
340
 
341
          if (code === 32 || code === 13) {
342
            stopSpin();
343
          }
344
        });
345
 
346
        elements.down.on('mousedown.touchspin', function(ev) {
347
          elements.down.off('touchstart.touchspin');  // android 4 workaround
348
 
349
          if (originalinput.is(':disabled')) {
350
            return;
351
          }
352
 
353
          downOnce();
354
          startDownSpin();
355
 
356
          ev.preventDefault();
357
          ev.stopPropagation();
358
        });
359
 
360
        elements.down.on('touchstart.touchspin', function(ev) {
361
          elements.down.off('mousedown.touchspin');  // android 4 workaround
362
 
363
          if (originalinput.is(':disabled')) {
364
            return;
365
          }
366
 
367
          downOnce();
368
          startDownSpin();
369
 
370
          ev.preventDefault();
371
          ev.stopPropagation();
372
        });
373
 
374
        elements.up.on('mousedown.touchspin', function(ev) {
375
          elements.up.off('touchstart.touchspin');  // android 4 workaround
376
 
377
          if (originalinput.is(':disabled')) {
378
            return;
379
          }
380
 
381
          upOnce();
382
          startUpSpin();
383
 
384
          ev.preventDefault();
385
          ev.stopPropagation();
386
        });
387
 
388
        elements.up.on('touchstart.touchspin', function(ev) {
389
          elements.up.off('mousedown.touchspin');  // android 4 workaround
390
 
391
          if (originalinput.is(':disabled')) {
392
            return;
393
          }
394
 
395
          upOnce();
396
          startUpSpin();
397
 
398
          ev.preventDefault();
399
          ev.stopPropagation();
400
        });
401
 
402
        elements.up.on('mouseout touchleave touchend touchcancel', function(ev) {
403
          if (!spinning) {
404
            return;
405
          }
406
 
407
          ev.stopPropagation();
408
          stopSpin();
409
        });
410
 
411
        elements.down.on('mouseout touchleave touchend touchcancel', function(ev) {
412
          if (!spinning) {
413
            return;
414
          }
415
 
416
          ev.stopPropagation();
417
          stopSpin();
418
        });
419
 
420
        elements.down.on('mousemove touchmove', function(ev) {
421
          if (!spinning) {
422
            return;
423
          }
424
 
425
          ev.stopPropagation();
426
          ev.preventDefault();
427
        });
428
 
429
        elements.up.on('mousemove touchmove', function(ev) {
430
          if (!spinning) {
431
            return;
432
          }
433
 
434
          ev.stopPropagation();
435
          ev.preventDefault();
436
        });
437
 
438
        $(document).on(_scopeEventNames(['mouseup', 'touchend', 'touchcancel'], _currentSpinnerId).join(' '), function(ev) {
439
          if (!spinning) {
440
            return;
441
          }
442
 
443
          ev.preventDefault();
444
          stopSpin();
445
        });
446
 
447
        $(document).on(_scopeEventNames(['mousemove', 'touchmove', 'scroll', 'scrollstart'], _currentSpinnerId).join(' '), function(ev) {
448
          if (!spinning) {
449
            return;
450
          }
451
 
452
          ev.preventDefault();
453
          stopSpin();
454
        });
455
 
456
        originalinput.on('mousewheel DOMMouseScroll', function(ev) {
457
          if (!settings.mousewheel || !originalinput.is(':focus')) {
458
            return;
459
          }
460
 
461
          var delta = ev.originalEvent.wheelDelta || -ev.originalEvent.deltaY || -ev.originalEvent.detail;
462
 
463
          ev.stopPropagation();
464
          ev.preventDefault();
465
 
466
          if (delta < 0) {
467
            downOnce();
468
          }
469
          else {
470
            upOnce();
471
          }
472
        });
473
      }
474
 
475
      function _bindEventsInterface() {
476
        originalinput.on('touchspin.uponce', function() {
477
          stopSpin();
478
          upOnce();
479
        });
480
 
481
        originalinput.on('touchspin.downonce', function() {
482
          stopSpin();
483
          downOnce();
484
        });
485
 
486
        originalinput.on('touchspin.startupspin', function() {
487
          startUpSpin();
488
        });
489
 
490
        originalinput.on('touchspin.startdownspin', function() {
491
          startDownSpin();
492
        });
493
 
494
        originalinput.on('touchspin.stopspin', function() {
495
          stopSpin();
496
        });
497
 
498
        originalinput.on('touchspin.updatesettings', function(e, newsettings) {
499
          changeSettings(newsettings);
500
        });
501
      }
502
 
503
      function _forcestepdivisibility(value) {
504
        switch (settings.forcestepdivisibility) {
505
          case 'round':
506
            return (Math.round(value / settings.step) * settings.step).toFixed(settings.decimals);
507
          case 'floor':
508
            return (Math.floor(value / settings.step) * settings.step).toFixed(settings.decimals);
509
          case 'ceil':
510
            return (Math.ceil(value / settings.step) * settings.step).toFixed(settings.decimals);
511
          default:
512
            return value;
513
        }
514
      }
515
 
516
      function _checkValue() {
517
        var val, parsedval, returnval;
518
 
519
        val = originalinput.val();
520
 
521
        if (val === '') {
522
          return;
523
        }
524
 
525
        if (settings.decimals > 0 && val === '.') {
526
          return;
527
        }
528
 
529
        parsedval = parseFloat(val);
530
 
531
        if (isNaN(parsedval)) {
532
          parsedval = 0;
533
        }
534
 
535
        returnval = parsedval;
536
 
537
        if (parsedval.toString() !== val) {
538
          returnval = parsedval;
539
        }
540
 
541
        if (parsedval < settings.min) {
542
          returnval = settings.min;
543
        }
544
 
545
        if (parsedval > settings.max) {
546
          returnval = settings.max;
547
        }
548
 
549
        returnval = _forcestepdivisibility(returnval);
550
 
551
        if (Number(val).toString() !== returnval.toString()) {
552
          originalinput.val(returnval);
553
          originalinput.trigger('change');
554
        }
555
      }
556
 
557
      function _getBoostedStep() {
558
        if (!settings.booster) {
559
          return settings.step;
560
        }
561
        else {
562
          var boosted = Math.pow(2, Math.floor(spincount / settings.boostat)) * settings.step;
563
 
564
          if (settings.maxboostedstep) {
565
            if (boosted > settings.maxboostedstep) {
566
              boosted = settings.maxboostedstep;
567
              value = Math.round((value / boosted)) * boosted;
568
            }
569
          }
570
 
571
          return Math.max(settings.step, boosted);
572
        }
573
      }
574
 
575
      function upOnce() {
576
        _checkValue();
577
 
578
        value = parseFloat(elements.input.val());
579
        if (isNaN(value)) {
580
          value = 0;
581
        }
582
 
583
        var initvalue = value,
584
            boostedstep = _getBoostedStep();
585
 
586
        value = value + boostedstep;
587
 
588
        if (value > settings.max) {
589
          value = settings.max;
590
          originalinput.trigger('touchspin.on.max');
591
          stopSpin();
592
        }
593
 
594
        elements.input.val(Number(value).toFixed(settings.decimals));
595
 
596
        if (initvalue !== value) {
597
          originalinput.trigger('change');
598
        }
599
      }
600
 
601
      function downOnce() {
602
        _checkValue();
603
 
604
        value = parseFloat(elements.input.val());
605
        if (isNaN(value)) {
606
          value = 0;
607
        }
608
 
609
        var initvalue = value,
610
            boostedstep = _getBoostedStep();
611
 
612
        value = value - boostedstep;
613
 
614
        if (value < settings.min) {
615
          value = settings.min;
616
          originalinput.trigger('touchspin.on.min');
617
          stopSpin();
618
        }
619
 
620
        elements.input.val(value.toFixed(settings.decimals));
621
 
622
        if (initvalue !== value) {
623
          originalinput.trigger('change');
624
        }
625
      }
626
 
627
      function startDownSpin() {
628
        stopSpin();
629
 
630
        spincount = 0;
631
        spinning = 'down';
632
 
633
        originalinput.trigger('touchspin.on.startspin');
634
        originalinput.trigger('touchspin.on.startdownspin');
635
 
636
        downDelayTimeout = setTimeout(function() {
637
          downSpinTimer = setInterval(function() {
638
            spincount++;
639
            downOnce();
640
          }, settings.stepinterval);
641
        }, settings.stepintervaldelay);
642
      }
643
 
644
      function startUpSpin() {
645
        stopSpin();
646
 
647
        spincount = 0;
648
        spinning = 'up';
649
 
650
        originalinput.trigger('touchspin.on.startspin');
651
        originalinput.trigger('touchspin.on.startupspin');
652
 
653
        upDelayTimeout = setTimeout(function() {
654
          upSpinTimer = setInterval(function() {
655
            spincount++;
656
            upOnce();
657
          }, settings.stepinterval);
658
        }, settings.stepintervaldelay);
659
      }
660
 
661
      function stopSpin() {
662
        clearTimeout(downDelayTimeout);
663
        clearTimeout(upDelayTimeout);
664
        clearInterval(downSpinTimer);
665
        clearInterval(upSpinTimer);
666
 
667
        switch (spinning) {
668
          case 'up':
669
            originalinput.trigger('touchspin.on.stopupspin');
670
            originalinput.trigger('touchspin.on.stopspin');
671
            break;
672
          case 'down':
673
            originalinput.trigger('touchspin.on.stopdownspin');
674
            originalinput.trigger('touchspin.on.stopspin');
675
            break;
676
        }
677
 
678
        spincount = 0;
679
        spinning = false;
680
      }
681
 
682
    });
683
 
684
  };
685
 
686
})(jQuery);