1/** 2* Clockface - v1.0.1 3* Clockface timepicker for Twitter Bootstrap 4* 5* Confusion with noon and midnight: 6* http://en.wikipedia.org/wiki/12-hour_clock 7* Here considered '00:00 am' as midnight and '12:00 pm' as noon. 8* 9* Author: Vitaliy Potapov 10* Project page: http://github.com/vitalets/clockface 11* Copyright (c) 2012 Vitaliy Potapov. Released under MIT License. 12**/ 13(function ($) { 14 15 var Clockface = function (element, options) { 16 this.$element = $(element); 17 this.options = $.extend({}, $.fn.clockface.defaults, options, this.$element.data()); 18 this.init(); 19 }; 20 21 Clockface.prototype = { 22 constructor: Clockface, 23 init: function () { 24 //apply template 25 this.$clockface = $($.fn.clockface.template); 26 this.$clockface.find('.l1 .cell, .left.cell').html('<div class="outer"></div><div class="inner"></div>'); 27 this.$clockface.find('.l5 .cell, .right.cell').html('<div class="inner"></div><div class="outer"></div>'); 28 this.$clockface.hide(); 29 30 this.$outer = this.$clockface.find('.outer'); 31 this.$inner = this.$clockface.find('.inner'); 32 this.$ampm = this.$clockface.find('.ampm'); 33 34 //internal vars 35 this.ampm = null; 36 this.hour = null; 37 this.minute = null; 38 39 //click am/pm 40 this.$ampm.click($.proxy(this.clickAmPm, this)); 41 42 //click cell 43 this.$clockface.on('click', '.cell', $.proxy(this.click, this)); 44 45 this.parseFormat(); 46 this.prepareRegexp(); 47 48 //set ampm text 49 this.ampmtext = this.is24 ? {am: '12-23', pm: '0-11'} : {am: 'AM', pm: 'PM'}; 50 51 this.isInline = this.$element.is('div'); 52 if(this.isInline) { 53 this.$clockface.addClass('clockface-inline').appendTo(this.$element); 54 } else { 55 this.$clockface.addClass('dropdown-menu').appendTo('body'); 56 if(this.options.trigger === 'focus') { 57 this.$element.on('focus.clockface', $.proxy(function(e) { this.show(); }, this)); 58 } 59 60 // Click outside hide it. Register single handler for all clockface widgets 61 $(document).off('click.clockface').on('click.clockface', $.proxy(function (e) { 62 var $target = $(e.target); 63 //click inside some clockface --> do nothing 64 if ($target.closest('.clockface').length) { 65 return; 66 } 67 //iterate all open clockface and close all except current 68 $('.clockface-open').each(function(){ 69 if(this === e.target) { 70 return; 71 } 72 $(this).clockface('hide'); 73 }); 74 }, this)); 75 } 76 77 //fill minutes once 78 this.fill('minute'); 79 }, 80 81 /* 82 Displays widget with specified value 83 */ 84 show: function(value) { 85 if(this.$clockface.is(':visible')) { 86 return; 87 } 88 if(!this.isInline) { 89 if(value === undefined) { 90 value = this.$element.val(); 91 } 92 this.$element.addClass('clockface-open'); 93 this.$element.on('keydown.clockface', $.proxy(this.keydown, this)); 94 this.place(); 95 $(window).on('resize.clockface', $.proxy(this.place, this)); 96 } 97 this.$clockface.show(); 98 this.setTime(value); 99 100 //trigger shown event 101 this.$element.triggerHandler('shown.clockface', this.getTime(true)); 102 }, 103 /* 104 hides widget 105 */ 106 hide: function() { 107 this.$clockface.hide(); 108 if(!this.isInline) { 109 this.$element.removeClass('clockface-open'); 110 this.$element.off('keydown.clockface'); 111 $(window).off('resize.clockface'); 112 } 113 114 //trigger hidden event 115 this.$element.triggerHandler('hidden.clockface', this.getTime(true)); 116 }, 117 118 /* 119 toggles show/hide 120 */ 121 toggle: function(value) { 122 if(this.$clockface.is(':visible')) { 123 this.hide(); 124 } else { 125 this.show(value); 126 } 127 }, 128 129 /* 130 Set time of clockface. Am/pm will be set automatically. 131 Value can be Date object or string 132 */ 133 setTime: function(value) { 134 var res, hour, minute, ampm = 'am'; 135 136 //no new value 137 if(value === undefined) { 138 //if ampm null, it;s first showw, need to render hours ('am' by default) 139 if(this.ampm === null) { 140 this.setAmPm('am'); 141 } 142 return; 143 } 144 145 //take value from Date object 146 if(value instanceof Date) { 147 hour = value.getHours(); 148 minute = value.getMinutes(); 149 } 150 151 //parse value from string 152 if(typeof value === 'string' && value.length) { 153 res = this.parseTime(value); 154 155 //'24' always '0' 156 if(res.hour === 24) { 157 res.hour = 0; 158 } 159 160 hour = res.hour; 161 minute = res.minute; 162 ampm = res.ampm; 163 } 164 165 //try to set ampm automatically 166 if(hour > 11 && hour < 24) { 167 ampm = 'pm'; 168 //for 12h format substract 12 from value 169 if(!this.is24 && hour > 12) { 170 hour -= 12; 171 } 172 } else if(hour >= 0 && hour < 11) { 173 //always set am for 24h and for '0' in 12h 174 if(this.is24 || hour === 0) { 175 ampm = 'am'; 176 } 177 //otherwise ampm should be defined in value itself and retrieved when parsing 178 } 179 180 this.setAmPm(ampm); 181 this.setHour(hour); 182 this.setMinute(minute); 183 }, 184 185 /* 186 Set ampm and re-fill hours 187 */ 188 setAmPm: function(value) { 189 if(value === this.ampm) { 190 return; 191 } else { 192 this.ampm = value === 'am' ? 'am' : 'pm'; 193 } 194 195 //set link's text 196 this.$ampm.text(this.ampmtext[this.ampm]); 197 198 //re-fill and highlight hour 199 this.fill('hour'); 200 this.highlight('hour'); 201 }, 202 /* 203 Sets hour value and highlight if possible 204 */ 205 setHour: function(value) { 206 value = parseInt(value, 10); 207 value = isNaN(value) ? null : value; 208 if(value < 0 || value > 23) { 209 value = null; 210 } 211 212 if(value === this.hour) { 213 return; 214 } else { 215 this.hour = value; 216 } 217 218 this.highlight('hour'); 219 }, 220 221 /* 222 Sets minute value and highlight 223 */ 224 setMinute: function(value) { 225 value = parseInt(value, 10); 226 value = isNaN(value) ? null : value; 227 if(value < 0 || value > 59) { 228 value = null; 229 } 230 231 if(value === this.minute) { 232 return; 233 } else { 234 this.minute = value; 235 } 236 237 this.highlight('minute'); 238 }, 239 240 /* 241 Highlights hour/minute 242 */ 243 highlight: function(what) { 244 var index, 245 values = this.getValues(what), 246 value = what === 'minute' ? this.minute : this.hour, 247 $cells = what === 'minute' ? this.$outer : this.$inner; 248 249 $cells.removeClass('active'); 250 251 //find index of value and highlight if possible 252 index = $.inArray(value, values); 253 if(index >= 0) { 254 $cells.eq(index).addClass('active'); 255 } 256 }, 257 258 /* 259 Fill values around 260 */ 261 fill: function(what) { 262 var values = this.getValues(what), 263 $cells = what === 'minute' ? this.$outer : this.$inner, 264 leadZero = what === 'minute'; 265 266 $cells.each(function(i){ 267 var v = values[i]; 268 if(leadZero && v < 10) { 269 v = '0' + v; 270 } 271 $(this).text(v); 272 }); 273 }, 274 275 /* 276 returns values of hours or minutes, depend on ampm and 24/12 format (0-11, 12-23, 00-55, etc) 277 param what: 'hour'/'minute' 278 */ 279 getValues: function(what) { 280 var values = [11, 0, 1, 10, 2, 9, 3, 8, 4, 7, 6, 5], 281 result = values.slice(); 282 283 //minutes 284 if(what === 'minute') { 285 $.each(values, function(i, v) { result[i] = v*5; }); 286 } else { 287 //hours 288 if(!this.is24) { 289 result[1] = 12; //need this to show '12' instead of '00' for 12h am/pm 290 } 291 if(this.is24 && this.ampm === 'pm') { 292 $.each(values, function(i, v) { result[i] = v+12; }); 293 } 294 } 295 return result; 296 }, 297 298 /* 299 Click cell handler. 300 Stores hour/minute and highlights. 301 On second click deselect value 302 */ 303 click: function(e) { 304 var $target = $(e.target), 305 value = $target.hasClass('active') ? null : $target.text(); 306 if($target.hasClass('inner')) { 307 this.setHour(value); 308 } else { 309 this.setMinute(value); 310 } 311 312 //update value in input 313 if(!this.isInline) { 314 this.$element.val(this.getTime()); 315 } 316 317 //trigger pick event 318 this.$element.triggerHandler('pick.clockface', this.getTime(true)); 319 }, 320 321 /* 322 Click handler on ampm link 323 */ 324 clickAmPm: function(e) { 325 e.preventDefault(); 326 //toggle am/pm 327 this.setAmPm(this.ampm === 'am' ? 'pm' : 'am'); 328 329 //update value in input 330 if(!this.isInline && !this.is24) { 331 this.$element.val(this.getTime()); 332 } 333 334 //trigger pick event 335 this.$element.triggerHandler('pick.clockface', this.getTime(true)); 336 }, 337 338 339 /* 340 Place widget below input 341 */ 342 place: function(){ 343 var zIndex = parseInt(this.$element.parents().filter(function() { 344 return $(this).css('z-index') != 'auto'; 345 }).first().css('z-index'), 10)+10, 346 offset = this.$element.offset(); 347 this.$clockface.css({ 348 top: offset.top + this.$element.outerHeight(), 349 left: offset.left, 350 zIndex: zIndex 351 }); 352 }, 353 354 /* 355 keydown handler (for not inline mode) 356 */ 357 keydown: function(e) { 358 //tab, escape, enter --> hide 359 if(/^(9|27|13)$/.test(e.which)) { 360 this.hide(); 361 return; 362 } 363 364 clearTimeout(this.timer); 365 this.timer = setTimeout($.proxy(function(){ 366 this.setTime(this.$element.val()); 367 }, this), 500); 368 }, 369 370 /* 371 Parse format from options and set this.is24 372 */ 373 parseFormat: function() { 374 var format = this.options.format, 375 hFormat = 'HH', 376 mFormat = 'mm'; 377 378 //hour format 379 $.each(['HH', 'hh', 'H', 'h'], function(i, f){ 380 if(format.indexOf(f) !== -1) { 381 hFormat = f; 382 return false;
383 } 384 }); 385 386 //minute format 387 $.each(['mm', 'm'], function(i, f){ 388 if(format.indexOf(f) !== -1) { 389 mFormat = f; 390 return false; 391 } 392 }); 393 394 //is 24 hour format 395 this.is24 = hFormat.indexOf('H') !== -1; 396 397 this.hFormat = hFormat; 398 this.mFormat = mFormat; 399 }, 400 401 402 403 /* 404 Parse value passed as string or Date object 405 */ 406 parseTime: function(value) { 407 var hour = null, 408 minute = null, 409 ampm = 'am', 410 parts = [], digits; 411 412 value = $.trim(value); 413 414 //try parse time from string assuming separator exist 415 if(this.regexpSep) { 416 parts = value.match(this.regexpSep); 417 } 418 419 if(parts && parts.length) { 420 hour = parts[1] ? parseInt(parts[1], 10) : null; 421 minute = parts[2] ? parseInt(parts[2], 10): null; 422 ampm = (!parts[3] || parts[3].toLowerCase() === 'a') ? 'am' : 'pm'; 423 } else { 424 //if parse with separator failed, search for 1,4-digit block and process it 425 //use reversed string to start from end (usefull with full dates) 426 //see http://stackoverflow.com/questions/141348/what-is-the-best-way-to-parse-a-time-into-a-date-object-from-user-input-in-javas 427 value = value.split('').reverse().join('').replace(/\s/g, ''); 428 parts = value.match(this.regexpNoSep); 429 if(parts && parts.length) { 430 ampm = (!parts[1] || parts[1].toLowerCase() === 'a') ? 'am' : 'pm'; 431 //reverse back 432 digits = parts[2].split('').reverse().join(''); 433 //use smart analyzing to detect hours and minutes 434 switch(digits.length) { 435 case 1: 436 hour = parseInt(digits, 10); //e.g. '6' 437 break; 438 case 2: 439 hour = parseInt(digits, 10); //e.g. '16' 440 //if((this.is24 && hour > 24) || (!this.is24 && hour > 12)) { //e.g. 26 441 if(hour > 24) { //e.g. 26 442 hour = parseInt(digits[0], 10); 443 minute = parseInt(digits[1], 10); 444 } 445 break; 446 case 3: 447 hour = parseInt(digits[0], 10); //e.g. 105 448 minute = parseInt(digits[1]+digits[2], 10); 449 if(minute > 59) { 450 hour = parseInt(digits[0]+digits[1], 10); //e.g. 195 451 minute = parseInt(digits[2], 10); 452 if(hour > 24) { 453 hour = null; 454 minute = null; 455 } 456 } 457 break; 458 case 4: 459 hour = parseInt(digits[0]+digits[1], 10); //e.g. 2006 460 minute = parseInt(digits[2]+digits[3], 10); 461 if(hour > 24) { 462 hour = null; 463 } 464 if(minute > 59) { 465 minute = null; 466 } 467 } 468 } 469 } 470 471 return {hour: hour, minute: minute, ampm: ampm}; 472 }, 473 474 prepareRegexp: function() { 475 //take separator from format 476 var sep = this.options.format.match(/h\s*([^hm]?)\s*m/i); //HH-mm, HH:mm 477 if(sep && sep.length) { 478 sep = sep[1]; 479 } 480 481 //sep can be null for HH, and '' for HHmm 482 this.separator = sep; 483 484 //parse from string 485 //use reversed string and regexp to parse 2-digit minutes first 486 //see http://stackoverflow.com/questions/141348/what-is-the-best-way-to-parse-a-time-into-a-date-object-from-user-input-in-javas 487 //this.regexp = new RegExp('(a|p)?\\s*((\\d\\d?)' + sep + ')?(\\d\\d?)', 'i'); 488 489 //regexp, used with separator 490 this.regexpSep = (this.separator && this.separator.length) ? new RegExp('(\\d\\d?)\\s*\\' + this.separator + '\\s*(\\d?\\d?)\\s*(a|p)?', 'i') : null; 491 492 //second regexp applied if previous has no result or separator is empty (to reversed string) 493 this.regexpNoSep = new RegExp('(a|p)?\\s*(\\d{1,4})', 'i'); 494 }, 495 496 /* 497 Returns time as string in specified format 498 */ 499 getTime: function(asObject) { 500 if(asObject === true) { 501 return { 502 hour: this.hour, 503 minute: this.minute, 504 ampm: this.ampm 505 }; 506 } 507 508 var hour = this.hour !== null ? this.hour + '' : '', 509 minute = this.minute !== null ? this.minute + '' : '', 510 result = this.options.format; 511 512 if(!hour.length && !minute.length) { 513 return ''; 514 } 515 516 if(this.hFormat.length > 1 && hour.length === 1) { 517 hour = '0' + hour; 518 } 519 520 if(this.mFormat.length > 1 && minute.length === 1) { 521 minute = '0' + minute; 522 } 523 524 //delete separator if no minutes 525 if(!minute.length && this.separator) { 526 result = result.replace(this.separator, ''); 527 } 528 529 result = result.replace(this.hFormat, hour).replace(this.mFormat, minute); 530 if(!this.is24) { 531 if(result.indexOf('A') !== -1) { 532 result = result.replace('A', this.ampm.toUpperCase()); 533 } else { 534 result = result.replace('a', this.ampm); 535 } 536 } 537 538 return result; 539 }, 540 541 /* 542 Removes widget and detach events 543 */ 544 destroy: function() { 545 this.hide(); 546 this.$clockface.remove(); 547 if(!this.isInline && this.options.trigger === 'focus') { 548 this.$element.off('focus.clockface'); 549 } 550 } 551 }; 552 553 $.fn.clockface = function ( option ) { 554 var d, args = Array.apply(null, arguments); 555 args.shift(); 556 557 //getTime returns string (not jQuery onject) 558 if(option === 'getTime' && this.length && (d = this.eq(0).data('clockface'))) { 559 return d.getTime.apply(d, args); 560 } 561 562 return this.each(function () { 563 var $this = $(this), 564 data = $this.data('clockface'), 565 options = typeof option == 'object' && option; 566 if (!data) { 567 $this.data('clockface', (data = new Clockface(this, options))); 568 } 569 if (typeof option == 'string' && typeof data[option] == 'function') { 570 data[option].apply(data, args); 571 } 572 }); 573 }; 574 575 $.fn.clockface.defaults = { 576 //see http://momentjs.com/docs/#/displaying/format/ 577 format: 'H:mm', 578 trigger: 'focus' //focus|manual 579 }; 580 581 582 $.fn.clockface.template = ''+ 583 '<div class="clockface">' + 584 '<div class="l1">' + 585 '<div class="cell"></div>' + 586 '<div class="cell"></div>' + 587 '<div class="cell"></div>' + 588 '</div>' + 589 '<div class="l2">' + 590 '<div class="cell left"></div>' + 591 '<div class="cell right"></div>' + 592 '</div>'+ 593 '<div class="l3">' + 594 '<div class="cell left"></div>' + 595 '<div class="cell right"></div>' + 596 '<div class="center"><a href="#" class="ampm"></a></div>' + 597 '</div>'+ 598 '<div class="l4">' + 599 '<div class="cell left"></div>' + 600 '<div class="cell right"></div>' + 601 '</div>'+ 602 '<div class="l5">' + 603 '<div class="cell"></div>' + 604 '<div class="cell"></div>' + 605 '<div class="cell"></div>' + 606 '</div>'+ 607 '</div>'; 608 609}(window.jQuery));
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.