Passed
Branch develop (893f38)
by Hans Erik
07:18
created

build/media/libraries/redcore/media/redcore/lib/bootstrap-timepicker/js/bootstrap-timepicker.js   F

Complexity

Total Complexity 276
Complexity/F 5.41

Size

Lines of Code 1159
Function Count 51

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 276
c 0
b 0
f 0
dl 0
loc 1159
rs 2.4
cc 0
nc 0
mnd 6
bc 220
fnc 51
bpm 4.3137
cpm 5.4117
noi 18

40 Functions

Rating   Name   Duplication   Size   Complexity  
A Timepicker.updateElement 0 3 1
A Timepicker.remove 0 7 2
B Timepicker.getTime 0 7 6
A Timepicker.decrementMinute 0 16 3
A Timepicker.highlightSecond 0 16 2
D Timepicker.updateWidget 0 31 9
A Timepicker.highlightMinute 0 16 2
A Timepicker.changeToNearestStep 0 10 3
C Timepicker.place 0 54 12
C Timepicker.showWidget 0 48 9
C Timepicker._init 0 51 8
A Timepicker.highlightHour 0 16 2
C Timepicker.highlightNextUnit 0 26 8
B Timepicker.highlightMeridian 0 26 3
A Timepicker.updateFromWidgetInputs 0 13 4
A Timepicker.decrementSecond 0 10 2
A Timepicker.toggleMeridian 0 3 2
A $(ꞌ[data-provide="timepicker"]ꞌ).focus.timepicker.data-api click.timepicker.data-api 0 9 2
A Timepicker.incrementMinute 0 16 3
A Timepicker.updateFromElementVal 0 3 1
C Timepicker.elementKeydown 0 76 24
F Timepicker.setTime 0 130 35
A Timepicker.blurElement 0 4 1
B Timepicker.incrementHour 0 16 5
A Timepicker.widgetClick 0 16 3
A Timepicker.incrementSecond 0 10 2
C Timepicker.mousewheel 0 56 12
B Timepicker.highlightUnit 0 16 10
A $.fn.timepicker 0 17 1
A Timepicker.getCursorPosition 0 16 3
C Timepicker.highlightPrevUnit 0 26 8
C Timepicker.widgetKeyup 0 5 10
B Timepicker.hideWidget 0 28 4
F Timepicker.getTemplate 0 84 11
A Timepicker.clear 0 8 1
A Timepicker.update 0 17 2
B Timepicker.decrementHour 0 23 6
C Timepicker.setDefaultTime 0 57 12
B bootstrap-timepicker.js ➔ Timepicker 0 33 1
C Timepicker.widgetKeydown 0 57 22

How to fix   Complexity   

Complexity

Complex classes like build/media/libraries/redcore/media/redcore/lib/bootstrap-timepicker/js/bootstrap-timepicker.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*!
2
 * Timepicker Component for Twitter Bootstrap
3
 *
4
 * Copyright 2013 Joris de Wit
5
 *
6
 * Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
(function($, window, document) {
12
  'use strict';
13
14
  // TIMEPICKER PUBLIC CLASS DEFINITION
15
  var Timepicker = function(element, options) {
16
    this.widget = '';
17
    this.$element = $(element);
18
    this.defaultTime = options.defaultTime;
19
    this.disableFocus = options.disableFocus;
20
    this.disableMousewheel = options.disableMousewheel;
21
    this.isOpen = options.isOpen;
22
    this.minuteStep = options.minuteStep;
23
    this.modalBackdrop = options.modalBackdrop;
24
    this.orientation = options.orientation;
25
    this.secondStep = options.secondStep;
26
    this.snapToStep = options.snapToStep;
27
    this.showInputs = options.showInputs;
28
    this.showMeridian = options.showMeridian;
29
    this.showSeconds = options.showSeconds;
30
    this.template = options.template;
31
    this.appendWidgetTo = options.appendWidgetTo;
32
    this.showWidgetOnAddonClick = options.showWidgetOnAddonClick;
33
    this.maxHours = options.maxHours;
34
		this.explicitMode = options.explicitMode; // If true 123 = 1:23, 12345 = 1:23:45, else invalid.
35
36
    this.handleDocumentClick = function (e) {
37
      var self = e.data.scope;
38
      // This condition was inspired by bootstrap-datepicker.
39
      // The element the timepicker is invoked on is the input but it has a sibling for addon/button.
40
      if (!(self.$element.parent().find(e.target).length ||
41
          self.$widget.is(e.target) ||
42
          self.$widget.find(e.target).length)) {
43
        self.hideWidget();
44
      }
45
    };
46
    this._init();
47
  };
48
49
  Timepicker.prototype = {
50
51
    constructor: Timepicker,
52
    _init: function() {
53
      var self = this;
54
55
      if (this.showWidgetOnAddonClick && (this.$element.parent().hasClass('input-group') && this.$element.parent().hasClass('bootstrap-timepicker'))) {
56
        this.$element.parent('.input-group.bootstrap-timepicker').find('.input-group-addon').on({
57
          'click.timepicker': $.proxy(this.showWidget, this)
58
        });
59
        this.$element.on({
60
          'focus.timepicker': $.proxy(this.highlightUnit, this),
61
          'click.timepicker': $.proxy(this.highlightUnit, this),
62
          'keydown.timepicker': $.proxy(this.elementKeydown, this),
63
          'blur.timepicker': $.proxy(this.blurElement, this),
64
          'mousewheel.timepicker DOMMouseScroll.timepicker': $.proxy(this.mousewheel, this)
65
        });
66
      } else {
67
        if (this.template) {
68
          this.$element.on({
69
            'focus.timepicker': $.proxy(this.showWidget, this),
70
            'click.timepicker': $.proxy(this.showWidget, this),
71
            'blur.timepicker': $.proxy(this.blurElement, this),
72
            'mousewheel.timepicker DOMMouseScroll.timepicker': $.proxy(this.mousewheel, this)
73
          });
74
        } else {
75
          this.$element.on({
76
            'focus.timepicker': $.proxy(this.highlightUnit, this),
77
            'click.timepicker': $.proxy(this.highlightUnit, this),
78
            'keydown.timepicker': $.proxy(this.elementKeydown, this),
79
            'blur.timepicker': $.proxy(this.blurElement, this),
80
            'mousewheel.timepicker DOMMouseScroll.timepicker': $.proxy(this.mousewheel, this)
81
          });
82
        }
83
      }
84
85
      if (this.template !== false) {
86
        this.$widget = $(this.getTemplate()).on('click', $.proxy(this.widgetClick, this));
87
      } else {
88
        this.$widget = false;
89
      }
90
91
      if (this.showInputs && this.$widget !== false) {
92
        this.$widget.find('input').each(function() {
93
          $(this).on({
94
            'click.timepicker': function() { $(this).select(); },
95
            'keydown.timepicker': $.proxy(self.widgetKeydown, self),
96
            'keyup.timepicker': $.proxy(self.widgetKeyup, self)
97
          });
98
        });
99
      }
100
101
      this.setDefaultTime(this.defaultTime);
102
    },
103
104
    blurElement: function() {
105
      this.highlightedUnit = null;
106
      this.updateFromElementVal();
107
    },
108
109
    clear: function() {
110
      this.hour = '';
111
      this.minute = '';
112
      this.second = '';
113
      this.meridian = '';
114
115
      this.$element.val('');
116
    },
117
118
    decrementHour: function() {
119
      if (this.showMeridian) {
120
        if (this.hour === 1) {
121
          this.hour = 12;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
122
        } else if (this.hour === 12) {
123
          this.hour--;
124
125
          return this.toggleMeridian();
126
        } else if (this.hour === 0) {
127
          this.hour = 11;
128
129
          return this.toggleMeridian();
130
        } else {
131
          this.hour--;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
132
        }
133
      } else {
134
        if (this.hour <= 0) {
135
          this.hour = this.maxHours - 1;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
136
        } else {
137
          this.hour--;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
138
        }
139
      }
140
    },
141
142
    decrementMinute: function(step) {
143
      var newVal;
144
145
      if (step) {
146
        newVal = this.minute - step;
147
      } else {
148
        newVal = this.minute - this.minuteStep;
149
      }
150
151
      if (newVal < 0) {
152
        this.decrementHour();
153
        this.minute = newVal + 60;
154
      } else {
155
        this.minute = newVal;
156
      }
157
    },
158
159
    decrementSecond: function() {
160
      var newVal = this.second - this.secondStep;
161
162
      if (newVal < 0) {
163
        this.decrementMinute(true);
164
        this.second = newVal + 60;
165
      } else {
166
        this.second = newVal;
167
      }
168
    },
169
170
    elementKeydown: function(e) {
171
      switch (e.which) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
172
      case 9: //tab
173
        if (e.shiftKey) {
174
          if (this.highlightedUnit === 'hour') {
175
            break;
176
          }
177
          this.highlightPrevUnit();
178
        } else if ((this.showMeridian && this.highlightedUnit === 'meridian') || (this.showSeconds && this.highlightedUnit === 'second') || (!this.showMeridian && !this.showSeconds && this.highlightedUnit ==='minute')) {
179
          break;
180
        } else {
181
          this.highlightNextUnit();
182
        }
183
        e.preventDefault();
184
        this.updateFromElementVal();
185
        break;
186
      case 27: // escape
187
        this.updateFromElementVal();
188
        break;
189
      case 37: // left arrow
190
        e.preventDefault();
191
        this.highlightPrevUnit();
192
        this.updateFromElementVal();
193
        break;
194
      case 38: // up arrow
195
        e.preventDefault();
196
        switch (this.highlightedUnit) {
197
        case 'hour':
198
          this.incrementHour();
199
          this.highlightHour();
200
          break;
201
        case 'minute':
202
          this.incrementMinute();
203
          this.highlightMinute();
204
          break;
205
        case 'second':
206
          this.incrementSecond();
207
          this.highlightSecond();
208
          break;
209
        case 'meridian':
210
          this.toggleMeridian();
211
          this.highlightMeridian();
212
          break;
213
        }
214
        this.update();
215
        break;
216
      case 39: // right arrow
217
        e.preventDefault();
218
        this.highlightNextUnit();
219
        this.updateFromElementVal();
220
        break;
221
      case 40: // down arrow
222
        e.preventDefault();
223
        switch (this.highlightedUnit) {
224
        case 'hour':
225
          this.decrementHour();
226
          this.highlightHour();
227
          break;
228
        case 'minute':
229
          this.decrementMinute();
230
          this.highlightMinute();
231
          break;
232
        case 'second':
233
          this.decrementSecond();
234
          this.highlightSecond();
235
          break;
236
        case 'meridian':
237
          this.toggleMeridian();
238
          this.highlightMeridian();
239
          break;
240
        }
241
242
        this.update();
243
        break;
244
      }
245
    },
246
247
    getCursorPosition: function() {
248
      var input = this.$element.get(0);
249
250
      if ('selectionStart' in input) {// Standard-compliant browsers
251
252
        return input.selectionStart;
253
      } else if (document.selection) {// IE fix
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if document.selection is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
254
        input.focus();
255
        var sel = document.selection.createRange(),
256
          selLen = document.selection.createRange().text.length;
257
258
        sel.moveStart('character', - input.value.length);
259
260
        return sel.text.length - selLen;
261
      }
262
    },
263
264
    getTemplate: function() {
265
      var template,
266
        hourTemplate,
267
        minuteTemplate,
268
        secondTemplate,
269
        meridianTemplate,
270
        templateContent;
271
272
      if (this.showInputs) {
273
        hourTemplate = '<input type="text" class="bootstrap-timepicker-hour" maxlength="2"/>';
274
        minuteTemplate = '<input type="text" class="bootstrap-timepicker-minute" maxlength="2"/>';
275
        secondTemplate = '<input type="text" class="bootstrap-timepicker-second" maxlength="2"/>';
276
        meridianTemplate = '<input type="text" class="bootstrap-timepicker-meridian" maxlength="2"/>';
277
      } else {
278
        hourTemplate = '<span class="bootstrap-timepicker-hour"></span>';
279
        minuteTemplate = '<span class="bootstrap-timepicker-minute"></span>';
280
        secondTemplate = '<span class="bootstrap-timepicker-second"></span>';
281
        meridianTemplate = '<span class="bootstrap-timepicker-meridian"></span>';
282
      }
283
284
      templateContent = '<table>'+
285
         '<tr>'+
286
           '<td><a href="#" data-action="incrementHour"><span class="glyphicon glyphicon-chevron-up"></span></a></td>'+
287
           '<td class="separator">&nbsp;</td>'+
288
           '<td><a href="#" data-action="incrementMinute"><span class="glyphicon glyphicon-chevron-up"></span></a></td>'+
289
           (this.showSeconds ?
290
             '<td class="separator">&nbsp;</td>'+
291
             '<td><a href="#" data-action="incrementSecond"><span class="glyphicon glyphicon-chevron-up"></span></a></td>'
292
           : '') +
293
           (this.showMeridian ?
294
             '<td class="separator">&nbsp;</td>'+
295
             '<td class="meridian-column"><a href="#" data-action="toggleMeridian"><span class="glyphicon glyphicon-chevron-up"></span></a></td>'
296
           : '') +
297
         '</tr>'+
298
         '<tr>'+
299
           '<td>'+ hourTemplate +'</td> '+
300
           '<td class="separator">:</td>'+
301
           '<td>'+ minuteTemplate +'</td> '+
302
           (this.showSeconds ?
303
            '<td class="separator">:</td>'+
304
            '<td>'+ secondTemplate +'</td>'
305
           : '') +
306
           (this.showMeridian ?
307
            '<td class="separator">&nbsp;</td>'+
308
            '<td>'+ meridianTemplate +'</td>'
309
           : '') +
310
         '</tr>'+
311
         '<tr>'+
312
           '<td><a href="#" data-action="decrementHour"><span class="glyphicon glyphicon-chevron-down"></span></a></td>'+
313
           '<td class="separator"></td>'+
314
           '<td><a href="#" data-action="decrementMinute"><span class="glyphicon glyphicon-chevron-down"></span></a></td>'+
315
           (this.showSeconds ?
316
            '<td class="separator">&nbsp;</td>'+
317
            '<td><a href="#" data-action="decrementSecond"><span class="glyphicon glyphicon-chevron-down"></span></a></td>'
318
           : '') +
319
           (this.showMeridian ?
320
            '<td class="separator">&nbsp;</td>'+
321
            '<td><a href="#" data-action="toggleMeridian"><span class="glyphicon glyphicon-chevron-down"></span></a></td>'
322
           : '') +
323
         '</tr>'+
324
       '</table>';
325
326
      switch(this.template) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
327
      case 'modal':
328
        template = '<div class="bootstrap-timepicker-widget modal hide fade in" data-backdrop="'+ (this.modalBackdrop ? 'true' : 'false') +'">'+
329
          '<div class="modal-header">'+
330
            '<a href="#" class="close" data-dismiss="modal">×</a>'+
331
            '<h3>Pick a Time</h3>'+
332
          '</div>'+
333
          '<div class="modal-content">'+
334
            templateContent +
335
          '</div>'+
336
          '<div class="modal-footer">'+
337
            '<a href="#" class="btn btn-primary" data-dismiss="modal">OK</a>'+
338
          '</div>'+
339
        '</div>';
340
        break;
341
      case 'dropdown':
342
        template = '<div class="bootstrap-timepicker-widget dropdown-menu">'+ templateContent +'</div>';
343
        break;
344
      }
345
346
      return template;
0 ignored issues
show
Bug introduced by
The variable template seems to not be initialized for all possible execution paths.
Loading history...
347
    },
348
349
    getTime: function() {
350
      if (this.hour === '') {
351
        return '';
352
      }
353
354
      return this.hour + ':' + (this.minute.toString().length === 1 ? '0' + this.minute : this.minute) + (this.showSeconds ? ':' + (this.second.toString().length === 1 ? '0' + this.second : this.second) : '') + (this.showMeridian ? ' ' + this.meridian : '');
355
    },
356
357
    hideWidget: function() {
358
      if (this.isOpen === false) {
359
        return;
360
      }
361
362
      this.$element.trigger({
363
        'type': 'hide.timepicker',
364
        'time': {
365
          'value': this.getTime(),
366
          'hours': this.hour,
367
          'minutes': this.minute,
368
          'seconds': this.second,
369
          'meridian': this.meridian
370
        }
371
      });
372
373
      if (this.template === 'modal' && this.$widget.modal) {
374
        this.$widget.modal('hide');
375
      } else {
376
        this.$widget.removeClass('open');
377
      }
378
379
      $(document).off('mousedown.timepicker, touchend.timepicker', this.handleDocumentClick);
380
381
      this.isOpen = false;
382
      // show/hide approach taken by datepicker
383
      this.$widget.detach();
384
    },
385
386
    highlightUnit: function() {
387
      this.position = this.getCursorPosition();
388
      if (this.position >= 0 && this.position <= 2) {
389
        this.highlightHour();
390
      } else if (this.position >= 3 && this.position <= 5) {
391
        this.highlightMinute();
392
      } else if (this.position >= 6 && this.position <= 8) {
393
        if (this.showSeconds) {
394
          this.highlightSecond();
395
        } else {
396
          this.highlightMeridian();
397
        }
398
      } else if (this.position >= 9 && this.position <= 11) {
399
        this.highlightMeridian();
400
      }
401
    },
402
403
    highlightNextUnit: function() {
404
      switch (this.highlightedUnit) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
405
      case 'hour':
406
        this.highlightMinute();
407
        break;
408
      case 'minute':
409
        if (this.showSeconds) {
410
          this.highlightSecond();
411
        } else if (this.showMeridian){
412
          this.highlightMeridian();
413
        } else {
414
          this.highlightHour();
415
        }
416
        break;
417
      case 'second':
418
        if (this.showMeridian) {
419
          this.highlightMeridian();
420
        } else {
421
          this.highlightHour();
422
        }
423
        break;
424
      case 'meridian':
425
        this.highlightHour();
426
        break;
427
      }
428
    },
429
430
    highlightPrevUnit: function() {
431
      switch (this.highlightedUnit) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
432
      case 'hour':
433
        if(this.showMeridian){
434
          this.highlightMeridian();
435
        } else if (this.showSeconds) {
436
          this.highlightSecond();
437
        } else {
438
          this.highlightMinute();
439
        }
440
        break;
441
      case 'minute':
442
        this.highlightHour();
443
        break;
444
      case 'second':
445
        this.highlightMinute();
446
        break;
447
      case 'meridian':
448
        if (this.showSeconds) {
449
          this.highlightSecond();
450
        } else {
451
          this.highlightMinute();
452
        }
453
        break;
454
      }
455
    },
456
457
    highlightHour: function() {
458
      var $element = this.$element.get(0),
459
          self = this;
460
461
      this.highlightedUnit = 'hour';
462
463
      if ($element.setSelectionRange) {
464
        setTimeout(function() {
465
          if (self.hour < 10) {
466
            $element.setSelectionRange(0,1);
467
          } else {
468
            $element.setSelectionRange(0,2);
469
          }
470
        }, 0);
471
      }
472
    },
473
474
    highlightMinute: function() {
475
      var $element = this.$element.get(0),
476
          self = this;
477
478
      this.highlightedUnit = 'minute';
479
480
      if ($element.setSelectionRange) {
481
        setTimeout(function() {
482
          if (self.hour < 10) {
483
            $element.setSelectionRange(2,4);
484
          } else {
485
            $element.setSelectionRange(3,5);
486
          }
487
        }, 0);
488
      }
489
    },
490
491
    highlightSecond: function() {
492
      var $element = this.$element.get(0),
493
          self = this;
494
495
      this.highlightedUnit = 'second';
496
497
      if ($element.setSelectionRange) {
498
        setTimeout(function() {
499
          if (self.hour < 10) {
500
            $element.setSelectionRange(5,7);
501
          } else {
502
            $element.setSelectionRange(6,8);
503
          }
504
        }, 0);
505
      }
506
    },
507
508
    highlightMeridian: function() {
509
      var $element = this.$element.get(0),
510
          self = this;
511
512
      this.highlightedUnit = 'meridian';
513
514
      if ($element.setSelectionRange) {
515
        if (this.showSeconds) {
516
          setTimeout(function() {
517
            if (self.hour < 10) {
518
              $element.setSelectionRange(8,10);
519
            } else {
520
              $element.setSelectionRange(9,11);
521
            }
522
          }, 0);
523
        } else {
524
          setTimeout(function() {
525
            if (self.hour < 10) {
526
              $element.setSelectionRange(5,7);
527
            } else {
528
              $element.setSelectionRange(6,8);
529
            }
530
          }, 0);
531
        }
532
      }
533
    },
534
535
    incrementHour: function() {
536
      if (this.showMeridian) {
537
        if (this.hour === 11) {
538
          this.hour++;
539
          return this.toggleMeridian();
540
        } else if (this.hour === 12) {
541
          this.hour = 0;
542
        }
543
      }
544
      if (this.hour === this.maxHours - 1) {
545
        this.hour = 0;
546
547
        return;
548
      }
549
      this.hour++;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
550
    },
551
552
    incrementMinute: function(step) {
553
      var newVal;
554
555
      if (step) {
556
        newVal = this.minute + step;
557
      } else {
558
        newVal = this.minute + this.minuteStep - (this.minute % this.minuteStep);
559
      }
560
561
      if (newVal > 59) {
562
        this.incrementHour();
563
        this.minute = newVal - 60;
564
      } else {
565
        this.minute = newVal;
566
      }
567
    },
568
569
    incrementSecond: function() {
570
      var newVal = this.second + this.secondStep - (this.second % this.secondStep);
571
572
      if (newVal > 59) {
573
        this.incrementMinute(true);
574
        this.second = newVal - 60;
575
      } else {
576
        this.second = newVal;
577
      }
578
    },
579
580
    mousewheel: function(e) {
581
      if (this.disableMousewheel) {
582
        return;
583
      }
584
585
      e.preventDefault();
586
      e.stopPropagation();
587
588
      var delta = e.originalEvent.wheelDelta || -e.originalEvent.detail,
589
          scrollTo = null;
590
591
      if (e.type === 'mousewheel') {
592
        scrollTo = (e.originalEvent.wheelDelta * -1);
593
      }
594
      else if (e.type === 'DOMMouseScroll') {
595
        scrollTo = 40 * e.originalEvent.detail;
596
      }
597
598
      if (scrollTo) {
599
        e.preventDefault();
600
        $(this).scrollTop(scrollTo + $(this).scrollTop());
601
      }
602
603
      switch (this.highlightedUnit) {
604
      case 'minute':
605
        if (delta > 0) {
606
          this.incrementMinute();
607
        } else {
608
          this.decrementMinute();
609
        }
610
        this.highlightMinute();
611
        break;
612
      case 'second':
613
        if (delta > 0) {
614
          this.incrementSecond();
615
        } else {
616
          this.decrementSecond();
617
        }
618
        this.highlightSecond();
619
        break;
620
      case 'meridian':
621
        this.toggleMeridian();
622
        this.highlightMeridian();
623
        break;
624
      default:
625
        if (delta > 0) {
626
          this.incrementHour();
627
        } else {
628
          this.decrementHour();
629
        }
630
        this.highlightHour();
631
        break;
632
      }
633
634
      return false;
635
    },
636
637
    /**
638
     * Given a segment value like 43, will round and snap the segment
639
     * to the nearest "step", like 45 if step is 15. Segment will
640
     * "overflow" to 0 if it's larger than 59 or would otherwise
641
     * round up to 60.
642
     */
643
    changeToNearestStep: function (segment, step) {
644
      if (segment % step === 0) {
645
        return segment;
646
      }
647
      if (Math.round((segment % step) / step)) {
648
        return (segment + (step - segment % step)) % 60;
649
      } else {
650
        return segment - segment % step;
651
      }
652
    },
653
654
    // This method was adapted from bootstrap-datepicker.
655
    place : function() {
656
      if (this.isInline) {
657
        return;
658
      }
659
      var widgetWidth = this.$widget.outerWidth(), widgetHeight = this.$widget.outerHeight(), visualPadding = 10, windowWidth =
660
        $(window).width(), windowHeight = $(window).height(), scrollTop = $(window).scrollTop();
661
662
      var zIndex = parseInt(this.$element.parents().filter(function() { return $(this).css('z-index') !== 'auto'; }).first().css('z-index'), 10) + 10;
663
      var offset = this.component ? this.component.parent().offset() : this.$element.offset();
664
      var height = this.component ? this.component.outerHeight(true) : this.$element.outerHeight(false);
665
      var width = this.component ? this.component.outerWidth(true) : this.$element.outerWidth(false);
666
      var left = offset.left, top = offset.top;
667
668
      this.$widget.removeClass('timepicker-orient-top timepicker-orient-bottom timepicker-orient-right timepicker-orient-left');
669
670
      if (this.orientation.x !== 'auto') {
671
        this.picker.addClass('datepicker-orient-' + this.orientation.x);
672
        if (this.orientation.x === 'right') {
673
          left -= widgetWidth - width;
674
        }
675
      } else{
676
        // auto x orientation is best-placement: if it crosses a window edge, fudge it sideways
677
        // Default to left
678
        this.$widget.addClass('timepicker-orient-left');
679
        if (offset.left < 0) {
680
          left -= offset.left - visualPadding;
681
        } else if (offset.left + widgetWidth > windowWidth) {
682
          left = windowWidth - widgetWidth - visualPadding;
683
        }
684
      }
685
      // auto y orientation is best-situation: top or bottom, no fudging, decision based on which shows more of the widget
686
      var yorient = this.orientation.y, topOverflow, bottomOverflow;
687
      if (yorient === 'auto') {
688
        topOverflow = -scrollTop + offset.top - widgetHeight;
689
        bottomOverflow = scrollTop + windowHeight - (offset.top + height + widgetHeight);
690
        if (Math.max(topOverflow, bottomOverflow) === bottomOverflow) {
691
          yorient = 'top';
692
        } else {
693
          yorient = 'bottom';
694
        }
695
      }
696
      this.$widget.addClass('timepicker-orient-' + yorient);
697
      if (yorient === 'top'){
698
        top += height;
699
      } else{
700
        top -= widgetHeight + parseInt(this.$widget.css('padding-top'), 10);
701
      }
702
703
      this.$widget.css({
704
        top : top,
705
        left : left,
706
        zIndex : zIndex
707
      });
708
    },
709
710
    remove: function() {
711
      $('document').off('.timepicker');
712
      if (this.$widget) {
713
        this.$widget.remove();
714
      }
715
      delete this.$element.data().timepicker;
716
    },
717
718
    setDefaultTime: function(defaultTime) {
719
      if (!this.$element.val()) {
720
        if (defaultTime === 'current') {
721
          var dTime = new Date(),
722
            hours = dTime.getHours(),
723
            minutes = dTime.getMinutes(),
724
            seconds = dTime.getSeconds(),
725
            meridian = 'AM';
726
727
          if (seconds !== 0) {
728
            seconds = Math.ceil(dTime.getSeconds() / this.secondStep) * this.secondStep;
729
            if (seconds === 60) {
730
              minutes += 1;
731
              seconds = 0;
732
            }
733
          }
734
735
          if (minutes !== 0) {
736
            minutes = Math.ceil(dTime.getMinutes() / this.minuteStep) * this.minuteStep;
737
            if (minutes === 60) {
738
              hours += 1;
739
              minutes = 0;
740
            }
741
          }
742
743
          if (this.showMeridian) {
744
            if (hours === 0) {
745
              hours = 12;
746
            } else if (hours >= 12) {
747
              if (hours > 12) {
748
                hours = hours - 12;
749
              }
750
              meridian = 'PM';
751
            } else {
752
              meridian = 'AM';
753
            }
754
          }
755
756
          this.hour = hours;
757
          this.minute = minutes;
758
          this.second = seconds;
759
          this.meridian = meridian;
760
761
          this.update();
762
763
        } else if (defaultTime === false) {
764
          this.hour = 0;
765
          this.minute = 0;
766
          this.second = 0;
767
          this.meridian = 'AM';
768
        } else {
769
          this.setTime(defaultTime);
770
        }
771
      } else {
772
        this.updateFromElementVal();
773
      }
774
    },
775
776
    setTime: function(time, ignoreWidget) {
777
      if (!time) {
778
        this.clear();
779
        return;
780
      }
781
782
      var timeMode,
783
          timeArray,
784
          hour,
785
          minute,
786
          second,
787
          meridian;
788
789
      if (typeof time === 'object' && time.getMonth){
790
        // this is a date object
791
        hour    = time.getHours();
792
        minute  = time.getMinutes();
793
        second  = time.getSeconds();
794
795
        if (this.showMeridian){
796
          meridian = 'AM';
797
          if (hour > 12){
798
            meridian = 'PM';
799
            hour = hour % 12;
800
          }
801
802
          if (hour === 12){
803
            meridian = 'PM';
804
          }
805
        }
806
      } else {
807
        timeMode = ((/a/i).test(time) ? 1 : 0) + ((/p/i).test(time) ? 2 : 0); // 0 = none, 1 = AM, 2 = PM, 3 = BOTH.
808
        if (timeMode > 2) { // If both are present, fail.
809
          this.clear();
810
          return;
811
        }
812
813
        timeArray = time.replace(/[^0-9\:]/g, '').split(':');
814
815
        hour = timeArray[0] ? timeArray[0].toString() : timeArray.toString();
816
817
        if(this.explicitMode && hour.length > 2 && (hour.length % 2) !== 0 ) {
818
          this.clear();
819
          return;
820
        }
821
822
        minute = timeArray[1] ? timeArray[1].toString() : '';
823
        second = timeArray[2] ? timeArray[2].toString() : '';
824
825
        // adaptive time parsing
826
        if (hour.length > 4) {
827
          second = hour.slice(-2);
828
          hour = hour.slice(0, -2);
829
        }
830
831
        if (hour.length > 2) {
832
          minute = hour.slice(-2);
833
          hour = hour.slice(0, -2);
834
        }
835
836
        if (minute.length > 2) {
837
          second = minute.slice(-2);
838
          minute = minute.slice(0, -2);
839
        }
840
841
        hour = parseInt(hour, 10);
842
        minute = parseInt(minute, 10);
843
        second = parseInt(second, 10);
844
845
        if (isNaN(hour)) {
846
          hour = 0;
847
        }
848
        if (isNaN(minute)) {
849
          minute = 0;
850
        }
851
        if (isNaN(second)) {
852
          second = 0;
853
        }
854
855
        // Adjust the time based upon unit boundary.
856
        // NOTE: Negatives will never occur due to time.replace() above.
857
        if (second > 59) {
858
          second = 59;
859
        }
860
861
        if (minute > 59) {
862
          minute = 59;
863
        }
864
865
        if (hour >= this.maxHours) {
866
          // No day/date handling.
867
          hour = this.maxHours - 1;
868
        }
869
870
        if (this.showMeridian) {
871
          if (hour > 12) {
872
            // Force PM.
873
            timeMode = 2;
874
            hour -= 12;
875
          }
876
          if (!timeMode) {
877
            timeMode = 1;
878
          }
879
          if (hour === 0) {
880
            hour = 12; // AM or PM, reset to 12.  0 AM = 12 AM.  0 PM = 12 PM, etc.
881
          }
882
          meridian = timeMode === 1 ? 'AM' : 'PM';
883
        } else if (hour < 12 && timeMode === 2) {
884
          hour += 12;
885
        } else {
886
          if (hour >= this.maxHours) {
887
            hour = this.maxHours - 1;
888
          } else if (hour < 0) {
889
            hour = 0;
890
          }
891
        }
892
      }
893
894
      this.hour = hour;
895
      if (this.snapToStep) {
896
        this.minute = this.changeToNearestStep(minute, this.minuteStep);
897
        this.second = this.changeToNearestStep(second, this.secondStep);
898
      } else {
899
        this.minute = minute;
900
        this.second = second;
901
      }
902
      this.meridian = meridian;
0 ignored issues
show
Bug introduced by
The variable meridian seems to not be initialized for all possible execution paths.
Loading history...
903
904
      this.update(ignoreWidget);
905
    },
906
907
    showWidget: function() {
908
      if (this.isOpen) {
909
        return;
910
      }
911
912
      if (this.$element.is(':disabled')) {
913
        return;
914
      }
915
916
      // show/hide approach taken by datepicker
917
      this.$widget.appendTo(this.appendWidgetTo);
918
      $(document).on('mousedown.timepicker, touchend.timepicker', {scope: this}, this.handleDocumentClick);
919
920
      this.$element.trigger({
921
        'type': 'show.timepicker',
922
        'time': {
923
          'value': this.getTime(),
924
          'hours': this.hour,
925
          'minutes': this.minute,
926
          'seconds': this.second,
927
          'meridian': this.meridian
928
        }
929
      });
930
931
      this.place();
932
      if (this.disableFocus) {
933
        this.$element.blur();
934
      }
935
936
      // widget shouldn't be empty on open
937
      if (this.hour === '') {
938
        if (this.defaultTime) {
939
          this.setDefaultTime(this.defaultTime);
940
        } else {
941
          this.setTime('0:0:0');
942
        }
943
      }
944
945
      if (this.template === 'modal' && this.$widget.modal) {
946
        this.$widget.modal('show').on('hidden', $.proxy(this.hideWidget, this));
947
      } else {
948
        if (this.isOpen === false) {
949
          this.$widget.addClass('open');
950
        }
951
      }
952
953
      this.isOpen = true;
954
    },
955
956
    toggleMeridian: function() {
957
      this.meridian = this.meridian === 'AM' ? 'PM' : 'AM';
958
    },
959
960
    update: function(ignoreWidget) {
961
      this.updateElement();
962
      if (!ignoreWidget) {
963
        this.updateWidget();
964
      }
965
966
      this.$element.trigger({
967
        'type': 'changeTime.timepicker',
968
        'time': {
969
          'value': this.getTime(),
970
          'hours': this.hour,
971
          'minutes': this.minute,
972
          'seconds': this.second,
973
          'meridian': this.meridian
974
        }
975
      });
976
    },
977
978
    updateElement: function() {
979
      this.$element.val(this.getTime()).change();
980
    },
981
982
    updateFromElementVal: function() {
983
      this.setTime(this.$element.val());
984
    },
985
986
    updateWidget: function() {
987
      if (this.$widget === false) {
988
        return;
989
      }
990
991
      var hour = this.hour,
992
          minute = this.minute.toString().length === 1 ? '0' + this.minute : this.minute,
993
          second = this.second.toString().length === 1 ? '0' + this.second : this.second;
994
995
      if (this.showInputs) {
996
        this.$widget.find('input.bootstrap-timepicker-hour').val(hour);
997
        this.$widget.find('input.bootstrap-timepicker-minute').val(minute);
998
999
        if (this.showSeconds) {
1000
          this.$widget.find('input.bootstrap-timepicker-second').val(second);
1001
        }
1002
        if (this.showMeridian) {
1003
          this.$widget.find('input.bootstrap-timepicker-meridian').val(this.meridian);
1004
        }
1005
      } else {
1006
        this.$widget.find('span.bootstrap-timepicker-hour').text(hour);
1007
        this.$widget.find('span.bootstrap-timepicker-minute').text(minute);
1008
1009
        if (this.showSeconds) {
1010
          this.$widget.find('span.bootstrap-timepicker-second').text(second);
1011
        }
1012
        if (this.showMeridian) {
1013
          this.$widget.find('span.bootstrap-timepicker-meridian').text(this.meridian);
1014
        }
1015
      }
1016
    },
1017
1018
    updateFromWidgetInputs: function() {
1019
      if (this.$widget === false) {
1020
        return;
1021
      }
1022
1023
      var t = this.$widget.find('input.bootstrap-timepicker-hour').val() + ':' +
1024
              this.$widget.find('input.bootstrap-timepicker-minute').val() +
1025
              (this.showSeconds ? ':' + this.$widget.find('input.bootstrap-timepicker-second').val() : '') +
1026
              (this.showMeridian ? this.$widget.find('input.bootstrap-timepicker-meridian').val() : '')
1027
      ;
1028
1029
      this.setTime(t, true);
1030
    },
1031
1032
    widgetClick: function(e) {
1033
      e.stopPropagation();
1034
      e.preventDefault();
1035
1036
      var $input = $(e.target),
1037
          action = $input.closest('a').data('action');
1038
1039
      if (action) {
1040
        this[action]();
1041
      }
1042
      this.update();
1043
1044
      if ($input.is('input')) {
1045
        $input.get(0).setSelectionRange(0,2);
1046
      }
1047
    },
1048
1049
    widgetKeydown: function(e) {
1050
      var $input = $(e.target),
1051
          name = $input.attr('class').replace('bootstrap-timepicker-', '');
1052
1053
      switch (e.which) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
1054
      case 9: //tab
1055
        if (e.shiftKey) {
1056
          if (name === 'hour') {
1057
            return this.hideWidget();
1058
          }
1059
        } else if ((this.showMeridian && name === 'meridian') || (this.showSeconds && name === 'second') || (!this.showMeridian && !this.showSeconds && name === 'minute')) {
1060
          return this.hideWidget();
1061
        }
1062
        break;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1063
      case 27: // escape
1064
        this.hideWidget();
1065
        break;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1066
      case 38: // up arrow
1067
        e.preventDefault();
1068
        switch (name) {
1069
        case 'hour':
1070
          this.incrementHour();
1071
          break;
1072
        case 'minute':
1073
          this.incrementMinute();
1074
          break;
1075
        case 'second':
1076
          this.incrementSecond();
1077
          break;
1078
        case 'meridian':
1079
          this.toggleMeridian();
1080
          break;
1081
        }
1082
        this.setTime(this.getTime());
1083
        $input.get(0).setSelectionRange(0,2);
1084
        break;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1085
      case 40: // down arrow
1086
        e.preventDefault();
1087
        switch (name) {
1088
        case 'hour':
1089
          this.decrementHour();
1090
          break;
1091
        case 'minute':
1092
          this.decrementMinute();
1093
          break;
1094
        case 'second':
1095
          this.decrementSecond();
1096
          break;
1097
        case 'meridian':
1098
          this.toggleMeridian();
1099
          break;
1100
        }
1101
        this.setTime(this.getTime());
1102
        $input.get(0).setSelectionRange(0,2);
1103
        break;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1104
      }
0 ignored issues
show
Comprehensibility introduced by
There is no default case in this switch, so nothing gets returned when all cases fail. You might want to consider adding a default or return undefined explicitly.
Loading history...
1105
    },
1106
1107
    widgetKeyup: function(e) {
1108
      if ((e.which === 65) || (e.which === 77) || (e.which === 80) || (e.which === 46) || (e.which === 8) || (e.which >= 48 && e.which <= 57) || (e.which >= 96 && e.which <= 105)) {
1109
        this.updateFromWidgetInputs();
1110
      }
1111
    }
1112
  };
1113
1114
  //TIMEPICKER PLUGIN DEFINITION
1115
  $.fn.timepicker = function(option) {
1116
    var args = Array.apply(null, arguments);
1117
    args.shift();
1118
    return this.each(function() {
1119
      var $this = $(this),
1120
        data = $this.data('timepicker'),
1121
        options = typeof option === 'object' && option;
1122
1123
      if (!data) {
1124
        $this.data('timepicker', (data = new Timepicker(this, $.extend({}, $.fn.timepicker.defaults, options, $(this).data()))));
1125
      }
1126
1127
      if (typeof option === 'string') {
1128
        data[option].apply(data, args);
1129
      }
1130
    });
1131
  };
1132
1133
  $.fn.timepicker.defaults = {
1134
    defaultTime: 'current',
1135
    disableFocus: false,
1136
    disableMousewheel: false,
1137
    isOpen: false,
1138
    minuteStep: 15,
1139
    modalBackdrop: false,
1140
    orientation: { x: 'auto', y: 'auto'},
1141
    secondStep: 15,
1142
    snapToStep: false,
1143
    showSeconds: false,
1144
    showInputs: true,
1145
    showMeridian: true,
1146
    template: 'dropdown',
1147
    appendWidgetTo: 'body',
1148
    showWidgetOnAddonClick: true,
1149
    maxHours: 24,
1150
    explicitMode: false
1151
  };
1152
1153
  $.fn.timepicker.Constructor = Timepicker;
1154
1155
  $(document).on(
1156
    'focus.timepicker.data-api click.timepicker.data-api',
1157
    '[data-provide="timepicker"]',
1158
    function(e){
1159
      var $this = $(this);
1160
      if ($this.data('timepicker')) {
1161
        return;
1162
      }
1163
      e.preventDefault();
1164
      // component click requires us to explicitly show it
1165
      $this.timepicker();
1166
    }
1167
  );
1168
1169
})(jQuery, window, document);
1170