1/** 2 * wickedpicker v0.4.1 - A simple jQuery timepicker. 3 * Copyright (c) 2015-2016 Eric Gagnon - http://github.com/wickedRidge/wickedpicker 4 * License: MIT 5 */ 6 7(function ($, window, document) { 8 9 "use strict"; 10 11 if (typeof String.prototype.endsWith != 'function') { 12 /* 13 * Checks if this string end ends with another string 14 * 15 * @param {string} the string to be checked 16 * 17 * @return {bool} 18 */ 19 String.prototype.endsWith = function (string) { 20 return string.length > 0 && this.substring(this.length - string.length, this.length) === string; 21 } 22 } 23 24 /* 25 * Returns if the user agent is mobile 26 * 27 * @return {bool} 28 */ 29 var isMobile = function () { 30 return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); 31 }; 32 33 var today = new Date(); 34 35 var pluginName = "wickedpicker", 36 defaults = { 37 now: today.getHours() + ':' + today.getMinutes(), 38 twentyFour: false, 39 upArrow: 'wickedpicker__controls__control-up', 40 downArrow: 'wickedpicker__controls__control-down', 41 close: 'wickedpicker__close', 42 hoverState: 'hover-state', 43 title: 'Timepicker', 44 showSeconds: false, 45 timeSeparator: ' : ', 46 secondsInterval: 1, 47 minutesInterval: 1, 48 beforeShow: null, 49 afterShow: null, 50 show: null, 51 clearable: false, 52 closeOnClickOutside: true, 53 onClickOutside: function() {}, 54 }; 55 56 /* 57 * @param {object} The input object the timepicker is attached to. 58 * @param {object} The object containing options 59 */ 60 function Wickedpicker(element, options) { 61 this.element = $(element); 62 this.options = $.extend({}, defaults, options); 63 64 this.element.addClass('hasWickedpicker'); 65 this.element.attr('onkeypress', 'return false;'); 66 this.element.attr('aria-showingpicker', 'false'); 67 this.createPicker(); 68 this.timepicker = $('.wickedpicker'); 69 this.up = $('.' + this.options.upArrow.split(/\s+/).join('.')); 70 this.down = $('.' + this.options.downArrow.split(/\s+/).join('.')); 71 this.separator = $('.wickedpicker__controls__control--separator'); 72 this.hoursElem = $('.wickedpicker__controls__control--hours'); 73 this.minutesElem = $('.wickedpicker__controls__control--minutes'); 74 this.secondsElem = $('.wickedpicker__controls__control--seconds'); 75 this.meridiemElem = $('.wickedpicker__controls__control--meridiem'); 76 this.close = $('.' + this.options.close.split(/\s+/).join('.')); 77 78 //Create a new Date object based on the default or passing in now value 79 var time = this.timeArrayFromString(this.options.now); 80 this.options.now = new Date(today.getFullYear(), today.getMonth(), today.getDate(), time[0], time[1], time[2]); 81 this.selectedHour = this.parseHours(this.options.now.getHours()); 82 this.selectedMin = this.parseSecMin(this.options.now.getMinutes()); 83 this.selectedSec = this.parseSecMin(this.options.now.getSeconds()); 84 this.selectedMeridiem = this.parseMeridiem(this.options.now.getHours()); 85 this.setHoverState(); 86 this.attach(element); 87 this.setText(element); 88 } 89 90 $.extend(Wickedpicker.prototype, { 91 92 /* 93 * Show given input's timepicker 94 * 95 * @param {object} The input being clicked 96 */ 97 showPicker: function (element) { 98 //If there is a beforeShow function, then call it with the input calling the timepicker and the 99 // timepicker itself 100 if (typeof this.options.beforeShow === 'function') { 101 this.options.beforeShow(element, this.timepicker); 102 } 103 var timepickerPos = $(element).offset(); 104 105 $(element).attr({'aria-showingpicker': 'true', 'tabindex': -1}); 106 this.setText(element); 107 this.showHideMeridiemControl(); 108 if (this.getText(element) !== this.getTime()) { 109
110 // Check meridiem 111 var text = this.getText(element); 112 var re = /\s[ap]m$/i; 113 var meridiem = re.test(text) ? text.substr(-2, 2) : null; 114 var inputTime = text.replace(re, '').split(this.options.timeSeparator); 115 var newTime = {}; 116 newTime.hours = inputTime[0]; 117 newTime.minutes = inputTime[1]; 118 newTime.meridiem = meridiem; 119 if (this.options.showSeconds) { 120 newTime.seconds = inputTime[2]; 121 } 122 this.setTime(newTime); 123 } 124 this.timepicker.css({ 125 'z-index': this.element.css('z-index') + 1, 126 position: 'absolute', 127 left: timepickerPos.left, 128 top: timepickerPos.top + $(element)[0].offsetHeight 129 }).show(); 130 //If there is a show function, then call it with the input calling the timepicker and the 131 // timepicker itself 132 if (typeof this.options.show === 'function') { 133 this.options.show(element, this.timepicker); 134 } 135 136 this.handleTimeAdjustments(element); 137 }, 138 139 /* 140 * Hides the timepicker that is currently shown if it is not part of the timepicker 141 * 142 * @param {Object} The DOM object being clicked on the page 143 * 144 * BeinnLora: added trigger function to call on closing/hiding timepicker. 145 */ 146 hideTimepicker: function (element) { 147 this.timepicker.hide(); 148 if (typeof this.options.afterShow === 'function') { 149 this.options.afterShow(element, this.timepicker); 150 } 151 var pickerHidden = { 152 start: function () { 153 var setShowPickerFalse = $.Deferred(); 154 $('[aria-showingpicker="true"]').attr('aria-showingpicker', 'false'); 155 return setShowPickerFalse.promise(); 156 } 157 }; 158 159 function setTabIndex(index) { 160 setTimeout(function () { 161 $('[aria-showingpicker="false"]').attr('tabindex', index); 162 }, 400); 163 } 164 165 pickerHidden.start().then(setTabIndex(0)); 166 }, 167 168 /* 169 * Create a new timepicker. A single timepicker per page 170 */ 171 createPicker: function () { 172 if ($('.wickedpicker').length === 0) { 173 var picker = '<div class="wickedpicker"><p class="wickedpicker__title">' + this.options.title + '<span class="wickedpicker__close"></span></p><ul class="wickedpicker__controls"><li class="wickedpicker__controls__control"><span class="' + this.options.upArrow + '"></span><span class="wickedpicker__controls__control--hours" tabindex="-1">00</span><span class="' + this.options.downArrow + '"></span></li><li class="wickedpicker__controls__control--separator"><span class="wickedpicker__controls__control--separator-inner">:</span></li><li class="wickedpicker__controls__control"><span class="' + this.options.upArrow + '"></span><span class="wickedpicker__controls__control--minutes" tabindex="-1">00</span><span class="' + this.options.downArrow + '"></span></li>'; 174 if (this.options.showSeconds) { 175 picker += '<li class="wickedpicker__controls__control--separator"><span class="wickedpicker__controls__control--separator-inner">:</span></li><li class="wickedpicker__controls__control"><span class="' + this.options.upArrow + '"></span><span class="wickedpicker__controls__control--seconds" tabindex="-1">00</span><span class="' + this.options.downArrow + '"></span> </li>'; 176 } 177 picker += '<li class="wickedpicker__controls__control"><span class="' + this.options.upArrow + '"></span><span class="wickedpicker__controls__control--meridiem" tabindex="-1">AM</span><span class="' + this.options.downArrow + '"></span></li></ul></div>'; 178 $('body').append(picker); 179 this.attachKeyboardEvents(); 180 } 181 }, 182 183 /* 184 * Hides the meridiem control if this timepicker is a 24 hour clock 185 */ 186 showHideMeridiemControl: function () { 187 if (this.options.twentyFour === false) { 188 $(this.meridiemElem).parent().show(); 189 } 190 else { 191 $(this.meridiemElem).parent().hide(); 192 } 193 }, 194 195 /* 196 * Hides the seconds control if this timepicker has showSeconds set to true 197 */ 198 showHideSecondsControl: function () { 199 if (this.options.showSeconds) { 200 $(this.secondsElem).parent().show(); 201 } 202 else { 203 $(this.secondsElem).parent().hide(); 204 } 205 }, 206 207 /* 208 * Bind the click events to the input 209 * 210 * @param {object} The input element 211 */ 212 attach: function (element) { 213 var self = this; 214 215 if (this.options.clearable) { 216 self.makePickerInputClearable(element); 217 } 218 219 $(element).attr('tabindex', 0); 220 $(element).on('click focus', function (event) { 221 //Prevent multiple firings 222 if ($(self.timepicker).is(':hidden')) { 223 self.showPicker($(this)); 224 window.lastTimePickerControl = $(this); //Put the reference on this timepicker into global scope for unsing that in afterShow function 225 $(self.hoursElem).focus(); 226 } 227 }); 228 229 230 //Handle click events for closing Wickedpicker 231 var clickHandler = function (event) { //TODO: Fix double firing 232 //Only fire the hide event when you have to 233 if ($(self.timepicker).is(':visible')) { 234 //Clicking the X 235 if ($(event.target).is(self.close)) { 236 self.hideTimepicker(window.lastTimePickerControl); 237 } else if ($(event.target).closest(self.timepicker).length || $(event.target).closest($('.hasWickedpicker')).length) { //Clicking the Wickedpicker or one of it's input
237s 238 event.stopPropagation(); 239 } else { //Everything else 240 if (typeof self.options.onClickOutside === 'function') { 241 self.options.onClickOutside(); 242 } 243 else { 244 console.warn("Type of onClickOutside must be a function"); 245 } 246 247 if (!self.options.closeOnClickOutside) { 248 return; 249 } 250 self.hideTimepicker(window.lastTimePickerControl); 251 } 252 window.lastTimePickerControl = null; 253 } 254 }; 255 $(document).off('click', clickHandler).on('click', clickHandler); 256 }, 257 258 /** 259 * Added keyboard functionality to improve usabil 260 */ 261 attachKeyboardEvents: function () { 262 $(document).on('keydown', $.proxy(function (event) { 263 switch (event.keyCode) { 264 case 9: 265 if (event.target.className !== 'hasWickedpicker') { 266 $(this.close).trigger('click'); 267 } 268 break; 269 case 27: 270 $(this.close).trigger('click'); 271 break; 272 case 37: //Left arrow 273 if (event.target.className !== this.hoursElem[0].className) { 274 $(event.target).parent().prevAll('li').not(this.separator.selector).first().children()[1].focus(); 275 } else { 276 $(event.target).parent().siblings(':last').children()[1].focus(); 277 } 278 break; 279 case 39: //Right arrow 280 if (event.target.className !== this.meridiemElem[0].className) { 281 $(event.target).parent().nextAll('li').not(this.separator.selector).first().children()[1].focus(); 282 } else { 283 $(event.target).parent().siblings(':first').children()[1].focus(); 284 } 285 break; 286 case 38: //Up arrow 287 $(':focus').prev().trigger('click'); 288 break; 289 case 40: //Down arrow 290 $(':focus').next().trigger('click'); 291 break; 292 default: 293 break; 294 } 295 }, this)); 296 }, 297 298 /* 299 * Set the time on the timepicker 300 * 301 * @param {object} The date being set 302 */ 303 setTime: function (time) { 304 this.setHours(time.hours); 305 this.setMinutes(time.minutes); 306 this.setMeridiem(time.meridiem); 307 if (this.options.showSeconds) { 308 this.setSeconds(time.seconds); 309 } 310 }, 311 312 /* 313 * Get the time from the timepicker 314 */ 315 getTime: function () { 316 return [this.formatTime(this.getHours(), this.getMinutes(), this.getMeridiem(), this.getSeconds())]; 317 }, 318 319 /* 320 * Set the timpicker's hour(s) value 321 * 322 * @param {string} hours 323 */ 324 setHours: function (hours) { 325 var hour = new Date(); 326 hour.setHours(hours); 327 var hoursText = this.parseHours(hour.getHours()); 328 this.hoursElem.text(hoursText); 329 this.selectedHour = hoursText; 330 }, 331 332 /* 333 * Get the hour(s) value from the timepicker 334 * 335 * @return {integer} 336 */ 337 getHours: function () { 338 var hours = new Date(); 339 hours.setHours(this.hoursElem.text()); 340 return hours.getHours(); 341 }, 342 343 /* 344 * Returns the correct hour value based on the type of clock, 12 or 24 hour 345 * 346 * @param {integer} The hours value before parsing 347 * 348 * @return {string|integer} 349 */ 350 parseHours: function (hours) { 351 return (this.options.twentyFour === false) ? ((hours + 11) % 12) + 1 : (hours < 10) ? '0' + hours : hours; 352 }, 353 354 /* 355 * Sets the timpicker's minutes value 356 * 357 * @param {string} minutes 358 */ 359 setMinutes: function (minutes) { 360 var minute = new Date(); 361 minute.setMinutes(minutes); 362 var minutesText = minute.getMinutes(); 363 var min = this.parseSecMin(minutesText); 364 this.minutesElem.text(min); 365 this.selectedMin = min; 366 }, 367 368 /* 369 * Get the minutes value from the timepicker 370 * 371 * @return {integer} 372 */ 373 getMinutes: function () { 374 var minutes = new Date(); 375 minutes.setMinutes(this.minutesElem.text()); 376 return minutes.getMinutes(); 377 }, 378 379 380 /* 381 * Return a human-readable minutes/seconds value 382 * 383 * @param {string} value seconds or minutes 384 * 385 * @return {string|integer} 386 */ 387 parseSecMin: function (value) { 388 return ((value < 10) ? '0' : '') + value; 389 }, 390 391 /* 392 * Set the timepicker's meridiem value, AM or PM 393 * 394 * @param {string} The new meridiem 395 */ 396 setMeridiem: function (inputMeridiem) { 397 var newMeridiem = ''; 398 if (inputMeridiem === undefined) { 399 var meridiem = this.getMeridiem(); 400 newMeridiem = (meridiem === 'PM') ? 'AM' : 'PM'; 401 } else { 402 newMeridiem = inputMeridiem; 403 } 404 this.meridiemElem.text(newMeridiem); 405 this.selectedMeridiem = newMeridiem; 406 }, 407 408 /* 409 * Get the timepicker's meridiem value, AM or PM 410 * 411 * @return {string} 412 */ 413 getMeridiem: function () { 414 return this.meridiemElem.text(); 415 }, 416 417 /* 418 * Set the timepicker's seconds value 419 * 420 * @param {string} seconds 421 */ 422 setSeconds: function (seconds) { 423 var second = new Date(); 424 second.setSeconds(seconds); 425 var secondsText = second.getSeconds(); 426 var sec = this.parseSecMin(secondsText); 427 this.secondsElem.text(sec); 428 this.selectedSec = sec; 429 }, 430 431 /* 432 * Get the timepicker's seconds value 433 * 434 * return {string} 435 */ 436 getSeconds: function () { 437 var seconds = new Date(); 438 seconds.setSeconds(this.secondsElem.text()); 439 return seconds.getSeconds(); 440 }, 441 442 /* 443 * Get the correct meridiem based on the hours given 444 * 445 * @param {string|integer} hours 446 * 447 * @return {string} 448 */ 449 parseMeridiem: function (hours) { 450 return (hours > 11) ? 'PM' : 'AM'; 451 }, 452 453 /* 454 * Handles time incrementing and decrementing and passes 455 * the operator, '+' or '-', the input to be set after the change 456 * and the current arrow clicked, to decipher if hours, ninutes, or meridiem. 457 * 458 * @param {object} The input element 459 */ 460 handleTimeAdjustments: function (element) { 461 var timeOut = 0; 462 //Click and click and hold timepicker incrementer and decrementer 463 $(this.up).add(this.down).off('mousedown click touchstart').on('mousedown click', { 464 'Wickedpicker': this, 465 'input': element 466 }, function (event) { 467 if(event.which!=1) return false;
468 var operator = (this.className.indexOf('up') > -1) ? '+' : '-'; 469 var passedData = event.data; 470 if (event.type == 'mousedown') { 471 timeOut = setInterval($.proxy(function (args) { 472 args.Wickedpicker.changeValue(operator, args.input, this); 473 }, this, {'Wickedpicker': passedData.Wickedpicker, 'input': passedData.input}), 200); 474 } else { 475 passedData.Wickedpicker.changeValue(operator, passedData.input, this); 476 } 477 }).bind('mouseup touchend', function () { 478 clearInterval(timeOut); 479 }); 480 }, 481 482 /* 483 * Change the timepicker's time base on what is clicked 484 * 485 * @param {string} The + or - operator 486 * @param {object} The timepicker's associated input to be set post change 487 * @param {object} The DOM arrow object clicked, determines if it is hours, 488 * minutes, or meridiem base on the operator and its siblings 489 */ 490 changeValue: function (operator, input, clicked) { 491 var target = (operator === '+') ? clicked.nextSibling : clicked.previousSibling; 492 var targetClass = $(target).attr('class'); 493 if (targetClass.endsWith('hours')) { 494 this.setHours(eval(this.getHours() + operator + 1)); 495 } else if (targetClass.endsWith('minutes')) { 496 this.setMinutes(eval(this.getMinutes() + operator + this.options.minutesInterval)); 497 } else if (targetClass.endsWith('seconds')) { 498 this.setSeconds(eval(this.getSeconds() + operator + this.options.secondsInterval)); 499 } else { 500 this.setMeridiem(); 501 } 502 this.setText(input); 503 }, 504 505 506 /* 507 * Sets the give input's text to the current timepicker's time 508 * 509 * @param {object} The input element 510 */ 511 setText: function (input) { 512 $(input).val(this.formatTime(this.selectedHour, this.selectedMin, this.selectedMeridiem, this.selectedSec)).change(); 513 }, 514 515 /* 516 * Get the given input's value 517 * 518 * @param {object} The input element 519 * 520 * @return {string} 521 */ 522 getText: function (input) { 523 return $(input).val(); 524 }, 525 526 /* 527 * Returns the correct time format as a string 528 * 529 * @param {string} hour 530 * @param {string} minutes 531 * @param {string} meridiem 532 * 533 * @return {string} 534 */ 535 formatTime: function (hour, min, meridiem, seconds) { 536 var formattedTime = hour + this.options.timeSeparator + min; 537 if (this.options.showSeconds) { 538 formattedTime += this.options.timeSeparator + seconds; 539 } 540 if (this.options.twentyFour === false) { 541 formattedTime += ' ' + meridiem; 542 } 543 return formattedTime; 544 }, 545 546 /** 547 * Apply the hover class to the arrows and close icon fonts 548 */ 549 setHoverState: function () { 550 var self = this; 551 if (!isMobile()) { 552 $(this.up).add(this.down).add(this.close).hover(function () { 553 $(this).toggleClass(self.options.hoverState); 554 }); 555 } 556 }, 557 558 /** 559 * Wrapping the given input field with the clearable container 560 * , add a span that will contain the x, and bind the clear 561 * input event to the span 562 * 563 * @param input 564 */ 565 makePickerInputClearable: function(input) { 566 $(input).wrap('<div class="clearable-picker"></div>').after('<span data-clear-picker>×</span>'); 567 568 //When the x is clicked, clear its sibling input field 569 $('[data-clear-picker]').on('click', function(event) { 570 $(this).siblings('.hasWickedpicker').val(''); 571 }); 572 }, 573 574 /** 575 * Convert the options time string format 576 * to an array 577 * 578 * returns => [hours, minutes, seconds] 579 * 580 * @param stringTime
581 * @returns {*} 582 */ 583 timeArrayFromString: function (stringTime) { 584 if (stringTime.length) { 585 var time = stringTime.split(':'); 586 time[2] = (time.length < 3) ? '00' : time[2]; 587 return time; 588 } 589 return false; 590 }, 591 592 //public functions 593 /* 594 * Returns the requested input element's value 595 */ 596 _time: function () { 597 var inputValue = $(this.element).val(); 598 return (inputValue === '') ? this.formatTime(this.selectedHour, this.selectedMin, this.selectedMeridiem, this.selectedSec) : inputValue; 599 }, 600 _hide: function() { 601 this.hideTimepicker(this.element); 602 } 603 }); 604 605 //optional index if multiple inputs share the same class 606 $.fn[pluginName] = function (options, index) { 607 if (!$.isFunction(Wickedpicker.prototype['_' + options])) { 608 return this.each(function () { 609 if (!$.data(this, "plugin_" + pluginName)) { 610 $.data(this, "plugin_" + pluginName, new Wickedpicker(this, options)); 611 } 612 }); 613 } 614 else if ($(this).hasClass('hasWickedpicker')) { 615 if (index !== undefined) { 616 return $.data($(this)[index], 'plugin_' + pluginName)['_' + options](); 617 } 618 else { 619 return $.data($(this)[0], 'plugin_' + pluginName)['_' + options](); 620 } 621 } 622 }; 623 624})(jQuery, window, document);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.