Subversion-Projekte lars-tiefland.zeldi.de

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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