1/* 2 * Author: @senthil2rajan 3 * plugin: timepicker 4 * website: senthilraj.github.io/Timepicki 5 */ 6(function($) { 7 8 $.fn.timepicki = function(options) { 9 10 var defaults = { 11 format_output: function(tim, mini, meri) { 12 if (settings.show_meridian) { 13 // limit hours between 1 and 12 - inculsive. 14 tim = Math.min(Math.max(parseInt(tim), 1), 12); 15 if (tim < 10) 16 tim = "0" + tim; 17 18 19 mini = Math.min(Math.max(parseInt(mini), 0), 59); 20 if (mini < 10) 21 mini = "0" + mini; 22 23 return tim + ":" + mini + " " + meri; 24 } else { 25 26 // limit hours between 0 and 23 - inculsive. 27 tim = Math.min(Math.max(parseInt(tim), 0), 23); 28 29 if (tim < 10) 30 tim = "0" + tim; 31 32 33 mini = Math.min(Math.max(parseInt(mini), 0), 59); 34 if (mini < 10) 35 mini = "0" + mini; 36 37 return tim + ":" + mini; 38 } 39 }, 40 increase_direction: 'up', 41 custom_classes: '', 42 min_hour_value: 1, 43 max_hour_value: 12, 44 show_meridian: true, 45 step_size_hours: '1', 46 step_size_minutes: '1', 47 overflow_minutes: false, 48 disable_keyboard_mobile: false, 49 max_time: null, 50 reset: false, 51 on_change: null, 52 input_writable: false 53 }; 54 55 var settings = $.extend({}, defaults, options); 56 57 return this.each(function() { 58 59 var ele = $(this); 60 var ele_hei = ele.outerHeight(); 61 ele_hei += 10; 62 $(ele).wrap("<div class='time_pick'>"); 63 var ele_par = $(this).parents(".time_pick"); 64 65 // developer can specify which arrow makes the numbers go up or down 66 var top_arrow_button = (settings.increase_direction === 'down') ? 67 "<div class='prev action-prev'></div>" : 68 "<div class='prev action-next'></div>"; 69 var bottom_arrow_button = (settings.increase_direction === 'down') ? 70 "<div class='next action-next'></div>" : 71 "<div class='next action-prev'></div>"; 72 73 var new_ele = $( 74 "<div class='timepicker_wrap " + settings.custom_classes + "'>" + 75 "<div class='arrow_top'></div>" + 76 "<div class='time'>" + 77 top_arrow_button + 78 "<div class='ti_tx'><input type='text' class='timepicki-input' title='××× ×××¨× ×©×¢×ת' aria-label='××× ×××¨× ×©×¢×ת'" + (settings.disable_keyboard_mobile ? "readonly" : "") + "><span class='timepickiHoursTextContainer' style='font:16px OpenSans, sans-serif, arial;'>שע×ת</span></div>" + 79 bottom_arrow_button + 80 "</div>" + 81 "<div class='mins'>" + 82 top_arrow_button + 83 "<div class='mi_tx'><input type='text' class='timepicki-input' title='××× ×××¨× ××§×ת' aria-label='××× ×××¨× ××§×ת'" + (settings.disable_keyboard_mobile ? "readonly" : "") + "><span class='timepickiMinutesTextContainer' style='font:16px OpenSans, sans-serif, arial;'>××§×ת</span></div>" + 84 bottom_arrow_button + 85 "</div>"); 86 if(settings.show_meridian){ 87 new_ele.append( 88 "<div class='meridian'>" + 89 top_arrow_button + 90 "<div class='mer_tx'><input type='text' class='timepicki-input' readonly></div>" + 91 bottom_arrow_button + 92 "</div>"); 93 } 94 if(settings.reset){ 95 new_ele.append( 96 "<div><a href='#' class='reset_time'>Reset</a></div>"); 97 } 98 ele_par.append(new_ele); 99 var ele_next = $(this).next(".timepicker_wrap"); 100 var ele_next_all_child = ele_next.find("div"); 101 var inputs = ele_par.find('input'); 102 103 $('.reset_time').on("click", function(event) { 104 ele.val(""); 105 close_timepicki(); 106 }); 107 108 $(".timepicki-input").keydown(function (keyevent) { 109 // our goal here is very simple. 110 // no matter what the user presses 111 // we must ensure that the values in our 112 // timepicki inputs are valid, and that pressing 113 // enter does not submit the form if the 114 // input field on which timepicki is applied is a part of a form. 115 116 117 // With that in mind. We proceed like this: 118 // 1) If enter is pressed: 119 // i) Prevent default operations - form submission. 120 // ii) close_timepicki(). 121 // iii) return. 122 // 123 // 2) For any other key presses: 124 // i) realize that we cannot check what the user has typed 125 // just yet, because this function is a handler 126 // that runs before any text is rendered in the input 127 // box. 128 // ii) So, register a function validate() that will execute right 129 // after the keypress character is rendered. All validation 130 // is done inside validate(). 131 //----------------------------------------------------------------------------------- 132 // NOTE:.change() event does not work here, as it is called when input looses focus| 133 //----------------------------------------------------------------------------------- 134 135 // (1) 136 // prevent potential form submission, if enter is pressed. 137 if (keyevent.keyCode == 13) { 138 139 keyevent.preventDefault(); 140 141 set_value(); 142 close_timepicki(); 143 // nothing to do here. 144 return; 145 } 146 147 148 149 // the grand father div specifies the type of 150 // input that we are dealing with. if the grandFatherDiv 151 // has a class "time", then its a time input, if it has a class 152 // "mins", then its a minutes input, and if it has a class "meri
152dian" 153 // then its a meridian input. 154 var grandfatherDiv = $(this).parent().parent(); 155 156 // aliasing for readability 157 var input = $(this); 158 159 // pick the value from the field, 160 // because before change the field always has a 161 // valid value. 162 var lastValue = input.val(); 163 164 // (2) 165 // validate() function validates the 166 // user input. 167 function validate() { 168 169 var isValidNumber = /^\d+$/.test(input.val()); 170 var isEmpty = input.val() === ""; 171 172 173 if (grandfatherDiv.hasClass("time")) { /// HOUR 174 175 // if its a valid number. 176 // clip it and assign it. 177 if (isValidNumber) { 178 179 // clip number. 180 var hours = (settings.show_meridian) ? 181 Math.min(Math.max(parseInt(input.val()), 1), 12) : // for 12 hour date picker. 182 Math.min(Math.max(parseInt(input.val()), 0), 23); // for 24 hours date picker. 183 184 isValidNumber = validateMaxTime(input, hours, null); 185 186 // assign number. 187 if (isValidNumber) 188 input.val(hours); 189 else 190 input.val(lastValue); 191 192 } else if(!isEmpty) { 193 // else if the number is invalid and not empty 194 // assign the lastValue 195 input.val(lastValue); 196 197 } 198 199 200 201 } else if (grandfatherDiv.hasClass("mins")) { /// MINUTE 202 203 204 // if its a valid number. 205 // clip it and assign it. 206 if (isValidNumber) { 207 208 // clip number. 209 var minutes = Math.min(Math.max(parseInt(input.val()), 0), 59); 210 211 isValidNumber = validateMaxTime(input, null, minutes); 212 213 // assign number. 214 if (isValidNumber) 215 input.val(minutes); 216 else 217 input.val(lastValue); 218 219 } else if (!isEmpty) { 220 // else if the number is invalid and not empty 221 // assign the lastValue 222 input.val(lastValue); 223 224 } 225 226 227 } else if (grandfatherDiv.hasClass("meridian")) { /// MERIDIAN 228 // key presses should not affect 229 // meridian - except up and down 230 // which are handled else where 231 // and will still work. 232 keyevent.preventDefault(); 233 } else { 234 // alert("This should not happen."); 235 } 236 237 } 238 239 // wrapValidate() ensures that validate() 240 // is not called more than once. 'done' 241 // is a flag used to ensure this. 242 done = false; 243 function wrapValidate() { 244 if (!done) { 245 246 247 validate(); 248 249 done = true; 250 } 251 } 252 // enqueue wrapValidate function before any thing 253 // else takes place. For this we use setTimeout() 254 // with 0 255 setTimeout(wrapValidate, 0); 256 257 258 259 260 261 262 }); 263 264 function validateMaxTime(sender, hours, mins) { 265 isValid = true; 266 267 if (settings.max_time == null) 268 return isValid; 269 270 var context = sender.closest(".timepicker_wrap"); 271 272 if (hours == null) 273 hours = Number($(".time .timepicki-input", context).val()); 274 if (mins == null) 275 mins = Number($(".mins .timepicki-input", context).val()); 276 277 if (hours > settings.max_time[0] || 278 (hours == settings.max_time[0] && mins > settings.max_time[1])) { 279 isValid = false; 280 } 281 282 return isValid; 283 } 284 285 // open or close time picker when clicking 286 $(document).on("click", function(event) { 287 if (!$(event.target).is(ele_next) && ele_next.css("display")=="block" && !$(event.target).is($('.reset_time'))) { 288 if (!$(event.target).is(ele)) { 289 set_value(event, !is_element_in_timepicki($(event.target))); 290 } else { 291 var ele_lef = 0; 292 293 ele_next.css({ 294 "top": ele_hei + "px", 295 "left": ele_lef + "px" 296 }); 297 open_timepicki(); 298 } 299 } 300 }); 301 302 // open the modal when the user focuses on the input 303 ele.on('focus', open_timepicki); 304 305 // select all text in input when user focuses on it
306 inputs.on('focus', function() { 307 var input = $(this); 308 if (!input.is(ele)) { 309 input.select(); 310 } 311 }); 312 313 // allow user to increase and decrease numbers using arrow keys 314 inputs.on('keydown', function(e) { 315 var direction, input = $(this); 316 317 // UP 318 if (e.which === 38) { 319 if (settings.increase_direction === 'down') { 320 direction = 'prev'; 321 } else { 322 direction = 'next'; 323 } 324 // DOWN 325 } else if (e.which === 40) { 326 if (settings.increase_direction === 'down') { 327 direction = 'next'; 328 } else { 329 direction = 'prev'; 330 } 331 } 332 333 if (input.closest('.timepicker_wrap .time').length) { 334 change_time(null, direction); 335 } else if (input.closest('.timepicker_wrap .mins').length) { 336 change_mins(null, direction); 337 } else if (input.closest('.timepicker_wrap .meridian').length && settings.show_meridian) { 338 change_meri(null, direction); 339 } 340 }); 341 342 // close the modal when the time picker loses keyboard focus 343 inputs.on('blur', function() { 344 setTimeout(function() { 345 var focused_element = $(document.activeElement); 346 if (focused_element.is(':input') && !is_element_in_timepicki(focused_element)) { 347 set_value(); 348 close_timepicki(); 349 } 350 }, 0); 351 }); 352 353 function is_element_in_timepicki(jquery_element) { 354 return $.contains(ele_par[0], jquery_element[0]) || ele_par.is(jquery_element); 355 } 356 357 function set_value(event, close) { 358 // use input values to set the time 359 var tim = ele_next.find(".ti_tx input").val(); 360 var mini = ele_next.find(".mi_tx input").val(); 361 var meri = ""; 362 if(settings.show_meridian){ 363 meri = ele_next.find(".mer_tx input").val(); 364 } 365 366 if (tim.length !== 0 && mini.length !== 0 && (!settings.show_meridian || meri.length !== 0)) { 367 // store the value so we can set the initial value 368 // next time the picker is opened 369 ele.attr('data-timepicki-tim', tim); 370 ele.attr('data-timepicki-mini', mini); 371 372 if(settings.show_meridian){ 373 ele.attr('data-timepicki-meri', meri); 374 // set the formatted value 375 ele.val(settings.format_output(tim, mini, meri)); 376 }else{ 377 ele.val(settings.format_output(tim, mini)); 378 } 379 } 380 381 //Call user on_change callback function if set 382 if (settings.on_change !== null) { 383 settings.on_change(ele[0]); 384 } 385 386 if (close) { 387 close_timepicki(); 388 } 389 } 390 391 function open_timepicki() { 392 set_date(settings.start_time); 393 ele_next.fadeIn(); 394 if(!settings.input_writable) { 395 // focus on the first input and select its contents 396 var first_input = ele_next.find('input:visible').first(); 397 first_input.focus(); 398 } 399 // if the user presses shift+tab while on the first input, 400 // they mean to exit the time picker and go to the previous field 401 var first_input_exit_handler = function(e) { 402 if (e.which === 9 && e.shiftKey) { 403 first_input.off('keydown', first_input_exit_handler); 404 var all_form_elements = $(':input:visible:not(.timepicki-input)'); 405 var index_of_timepicki_input = all_form_elements.index(ele); 406 var previous_form_element = all_form_elements.get(index_of_timepicki_input-1); 407 previous_form_element.focus(); 408 } 409 }; 410 first_input.on('keydown', first_input_exit_handler); 411 } 412 413 function close_timepicki() { 414 ele_next.fadeOut(); 415 } 416 417 function set_date(start_time) { 418 var d, ti, mi, mer; 419 420 // if a value was already picked we will remember that value 421 if (ele.is('[data-timepicki-tim]')) { 422 ti = Number(ele.attr('data-timepicki-tim')); 423 mi = Number(ele.attr('data-timepicki-mini')); 424 if(settings.show_meridian){ 425 mer = ele.attr('data-timepicki-meri'); 426 } 427 // developer can specify a custom starting value 428 } else if (typeof start_time === 'object') { 429 ti = Number(start_time[0]); 430 mi = Number(start_time[1]); 431 if(settings.show_meridian){ 432 mer = start_time[2]; 433 } 434 // default is we will use the current time 435 } else { 436 d = new Date(); 437 ti = d.getHours(); 438 mi = d.getMinutes(); 439 mer = "AM"; 440 if (settings.show_meridian){ 441 if (ti == 0) { // midnight 442 ti = 12; 443 } else if (ti == 12) { // noon 444 mer = "PM"; 445 } else if (ti > 12) { 446 ti -= 12; 447 mer = "PM"; 448 } 449 } 450 } 451 452 if (ti < 10) { 453 ele_next.find(".ti_tx input").val("0" + ti); 454 } else { 455 ele_next.find(".ti_tx input").val(ti); 456 } 457 if (mi < 10) { 458 ele_next.find(".mi_tx input").val("0" + mi); 459 } else { 460 ele_next.find(".mi_tx input").val(mi); 461 } 462 if(settings.show_meridian){ 463 if (mer < 10) { 464 ele_next.find(".mer_tx input").val("0" + mer); 465 } else { 466 ele_next.find(".mer_tx input").val(mer); 467 } 468 } 469 } 470 471 function change_time(cur_ele, direction) { 472 var cur_cli = "time"; 473 var cur_time = Number(ele_next.find("." + cur_cli + " .ti_tx input").val()); 474 var ele_st = Number(settings.min_hour_value); 475 var ele_en = Number(settings.max_hour_value); 476 var step_size = Number(settings.step_size_hours); 477 if ((cur_ele && cur_ele.hasClass('action-next')) || direction === 'next') { 478 if (validateMaxTime(ele_next, cur_time + step_size, null) == false) 479 return; 480 481 if (cur_time + step_size > ele_en) { 482 var min_value = ele_st; 483 if (min_value < 10) { 484 min_value = '0' + min_value; 485 } else {
486 min_value = String(min_value); 487 } 488 ele_next.find("." + cur_cli + " .ti_tx input").val(min_value); 489 } else { 490 cur_time = cur_time + step_size; 491 if (cur_time < 10) { 492 cur_time = "0" + cur_time; 493 } 494 ele_next.find("." + cur_cli + " .ti_tx input").val(cur_time); 495 } 496 } else if ((cur_ele && cur_ele.hasClass('action-prev')) || direction === 'prev') { 497 if (validateMaxTime(ele_next, cur_time - step_size, null) == false) 498 return; 499 500 var minValue = Number(settings.min_hour_value) 501 if (cur_time - step_size < minValue) { 502 var max_value = ele_en; 503 if (validateMaxTime(ele_next, max_value, null) == false) 504 return; 505 506 if (max_value < 10) { 507 max_value = '0' + max_value; 508 } else { 509 max_value = String(max_value); 510 } 511 ele_next.find("." + cur_cli + " .ti_tx input").val(max_value); 512 } else { 513 cur_time = cur_time - step_size; 514 if (cur_time < 10) { 515 cur_time = "0" + cur_time; 516 } 517 ele_next.find("." + cur_cli + " .ti_tx input").val(cur_time); 518 } 519 } 520 } 521 522 function change_mins(cur_ele, direction) { 523 var cur_cli = "mins"; 524 var cur_mins = Number(ele_next.find("." + cur_cli + " .mi_tx input").val()); 525 var ele_st = 0; 526 var ele_en = 59; 527 var step_size = Number(settings.step_size_minutes); 528 if ((cur_ele && cur_ele.hasClass('action-next')) || direction === 'next') { 529 if (validateMaxTime(ele_next, null, cur_mins + step_size) == false) 530 return; 531 532 if (cur_mins + step_size > ele_en) { 533 ele_next.find("." + cur_cli + " .mi_tx input").val("00"); 534 if(settings.overflow_minutes){ 535 change_time(null, 'next'); 536 } 537 } else { 538 cur_mins = cur_mins + step_size; 539 if (cur_mins < 10) { 540 ele_next.find("." + cur_cli + " .mi_tx input").val("0" + cur_mins); 541 } else { 542 ele_next.find("." + cur_cli + " .mi_tx input").val(cur_mins); 543 } 544 } 545 } else if ((cur_ele && cur_ele.hasClass('action-prev')) || direction === 'prev') { 546 if (validateMaxTime(ele_next, null, cur_mins - step_size) == false) 547 return; 548 549 if (cur_mins - step_size <= -1) { 550 if (validateMaxTime(ele_next, null, ele_en + 1 - step_size) == false) 551 return; 552 553 ele_next.find("." + cur_cli + " .mi_tx input").val(ele_en + 1 - step_size); 554 if(settings.overflow_minutes){ 555 change_time(null, 'prev'); 556 } 557 } else { 558 cur_mins = cur_mins - step_size; 559 if (cur_mins < 10) { 560 ele_next.find("." + cur_cli + " .mi_tx input").val("0" + cur_mins); 561 } else { 562 ele_next.find("." + cur_cli + " .mi_tx input").val(cur_mins); 563 } 564 } 565 } 566 } 567 568 function change_meri(cur_ele, direction) { 569 var cur_cli = "meridian"; 570 var ele_st = 0; 571 var ele_en = 1; 572 var cur_mer = null; 573 cur_mer = ele_next.find("." + cur_cli + " .mer_tx input").val(); 574 if ((cur_ele && cur_ele.hasClass('action-next')) || direction === 'next') { 575 if (cur_mer == "AM") { 576 ele_next.find("." + cur_cli + " .mer_tx input").val("PM"); 577 } else { 578 ele_next.find("." + cur_cli + " .mer_tx input").val("AM"); 579 } 580 } else if ((cur_ele && cur_ele.hasClass('action-prev')) || direction === 'prev') { 581 if (cur_mer == "AM") { 582 ele_next.find("." + cur_cli + " .mer_tx input").val("PM"); 583 } else { 584 ele_next.find("." + cur_cli + " .mer_tx input").val("AM"); 585 } 586 } 587 } 588 589 // handle clicking on the arrow icons 590 var cur_next = ele_next.find(".action-next"); 591 var cur_prev = ele_next.find(".action-prev"); 592 $(cur_prev).add(cur_next).on("click", function() { 593 var cur_ele = $(this); 594 if (cur_ele.parent().attr("class") == "time") { 595 change_time(cur_ele); 596 } else if (cur_ele.parent().attr("class") == "mins") { 597 change_mins(cur_ele); 598 } else { 599 if(settings.show_meridian){ 600 change_meri(cur_ele); 601 } 602 } 603 }); 604 605 }); 606 }; 607 608}(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.