1$(function () { 2 $(document).on("change", ".poll-widget [name=PollAnswer]", function () { 3 var $this = $(this); 4 var form = $this.closest("form"); 5 6 var data = form.serialize(); 7 var action = form.attr("action"); 8 9 $.post(action, data, function (results) { 10 if (results.Success) { 11 $this.closest(".poll-widget").remove(); 12 13 } else { 14 var $summary = $this 15 .prev(".fragment-editor") 16 .find(".error-summary"); 17 18 $summary.empty().removeClass("d-none"); 19 20 $(results.Errors).each(function (index, item) { 21 $summary.append($("<div>").text(item.Error)); 22 }); 23 } 24 }); 25 }); 26}); 27$(document).on("submit", ".fftWidget_Submit, .fftFullSurvey_Submit", function () { 28 event.preventDefault(); 29 let $form = $(this); 30 31 const siteKey = $form.find("input[name='sitekey']").val(); 32 grecaptcha.ready(function () { 33 try { 34 grecaptcha.execute(siteKey, { action: 'fftwidget' }).then(function (token) { 35 $form.find("[name='Captcha']").val(token); 36 37 const id = $form.data("id"); 38 const data = $form.serialize(); 39 const action = $form.attr("action"); 40 41 $.post(action, data, function (results) { 42 if (results.Success) { 43 $form.find(".form-group").removeClass("has-error"); 44 45 $("#fft-content-" + id).replaceWith("<div class='alert alert-success' role='alert'>Thank you for completing our Friends and Family Test.</div>"); 46 47 } else { 48 let $errMsg = ""; 49 $(results.Errors).each(function (index, item) { 50 $form.find("[name=" + item.Key + "]").closest(".form-group").addClass("has-error"); 51 $errMsg += "<div>" + item.Error + "</div>"; 52 }); 53 54 showFFTError($form, $errMsg); 55 } 56 }); 57 }) 58 } 59 catch (err) { 60 console.error(err); 61 showFFTError($form, "There was an error contacting reCAPTCHA, Please try again"); 62 } 63 }); 64}); 65 66function showFFTError($form, errMsg) { 67 let $summary = $form.find(".error-summary"); 68 69 $summary.empty().removeClass("d-none"); 70 $summary.html(errMsg); 71} 72 73$(document).ready(function () { 74 $('.fft-checkbox-question .fft-question-fields').each((i, el) => { 75 $(el) 76 .find('input[type="checkbox"]') 77 .on('change', () => { 78 const selectedValues = $(el) 79 .find('input[type="checkbox"]:checked') 80 .map((i, el) => $(el).val()); 81 82 console.log(selectedValues); 83 84 const answer = selectedValues.toArray().join('|'); 85 $(el).find('input[type="hidden"]').val(answer); 86 }); 87 }); 88}); 89$(document).on("submit", ".newsletter-signup", function () { 90 event.preventDefault(); 91 let $form = $(this); 92 const siteKey = $form.find("input[name='sitekey']").val(); 93 94 grecaptcha.ready(function () { 95 try { 96 grecaptcha.execute(siteKey, { action: 'newsletterwidget' }).then(function (token) { 97 $form.find("[name='Captcha']").val(token); 98 const data = $form.serialize(); 99 const action = $form.attr("action"); 100 101 $.ajax({
102 url: action, 103 data: data, 104 type: "POST", 105 cache: false, 106 success: function (data) { 107 if (data.Success === true) { 108 let $formWrapper = $form.closest(".newslettersignupform"); 109 $formWrapper.parent().find(".alert-success").removeClass("d-none"); 110 $formWrapper.hide(); 111 } 112 else { 113 let $errMsg = ""; 114 $(data.Errors).each(function (index, item) { 115 $errMsg += "<div>" + item.Error + "</div>"; 116 }); 117 118 if ($errMsg.length > 0) { 119 showNewsletterError($form, $errMsg); 120 } 121 } 122 } 123 }); 124 }) 125 } 126 catch (err) { 127 console.error(err); 128 showNewsletterError($form, "There was an error contacting reCAPTCHA, Please try again"); 129 } 130 }); 131}); 132 133function showNewsletterError($form, errMsg) { 134 let $error = $form.find(".error-group"); 135 $error.removeClass("d-none"); 136 $error.replaceWith("<div class='alert alert-danger error-group' role='alert'>" + errMsg +"</div>"); 137} 138$( document ).ready(function() { 139 wt1 = $(this).find('.bmiTable'); 140 wt1.find('.weight_lb').hide(); 141 wt1.find(".weight_kg label").html('kg'); 142 wt1.find(".weight_kg input").attr('aria-label', 'Weight (kilograms)'); 143 144 wt1.find(".height_in" ).hide(); 145 wt1.find(".height_m label").html('m'); 146 wt1.find(".height_m input").attr('aria-label', 'Height (metres)'); 147 148 wt1.find("div[class^='bmiType']").hide(); 149}); 150 151$(document).on("click", ".wgtKilos", function() { 152 wt1 = $(this).closest('.bmiTable'); 153 wt1.find(".weight_lb").hide(); 154 wt1.find(".weight_kg label").html('kg'); 155 wt1.find(".weight_kg input").attr('aria-label', 'Weight (kilograms)'); 156}); 157 158$(document).on("click", ".wgtPounds", function() { 159 wt1 = $(this).closest('.bmiTable'); 160 wt1.find(".weight_lb").show(); 161 wt1.find(".weight_kg label").html('st'); 162 wt1.find(".weight_kg input").attr('aria-label', 'Weight (stones)'); 163}); 164 165$(document).on("click", ".hgtMetres", function() { 166 wt1 = $(this).closest('.bmiTable'); 167 wt1.find(".height_in").hide(); 168 wt1.find(".height_m label").html('m'); 169 wt1.find(".height_m input").attr('aria-label', 'Height (metres)'); 170}); 171 172$(document).on("click", ".hgtFeet", function() { 173 wt1 = $(this).closest('.bmiTable'); 174 wt1.find(".height_in").show(); 175 wt1.find(".height_m label").html('ft'); 176 wt1.find(".height_m input").attr('aria-label', 'Height (feet)'); 177}); 178 179 180// Convert Inches to Centimeters 181function convertHeight(feet, inches) { 182 var mt = (parseInt(feet * 12) + parseInt(inches)) * 0.0254; 183 return mt; 184} 185 186// Convert Pounds to Kilograms 187function convertWeight(stones, pounds) { 188 var wt = (parseInt(stones * 14) + parseInt(pounds))/2.20462262185; 189 return wt; 190} 191 192// Find Unit of Measurement 193function findUnit(unit) { 194 var output; 195 for (i=0; i<unit.length; i++) { 196 if(unit[i].checked === true) { 197 output = unit[i].value; 198 } 199 } 200 return output; 201} 202 203 204$(document).on("click", ".reset", function(){ 205 wt1 = $(this).closest('.bmiTable'); 206 207 var $result_bmi = wt1.find('.bmi_result'); 208 var $result_type = wt1.find('.bmi_type'); 209 210 wt1.find('.weight_kg input').val(''); 211 wt1.find('.weight_lb input').val(''); 212 wt1.find('.height_m input').val(''); 213 wt1.find('.height_in input').val(''); 214 215 $result_bmi = wt1.find('.bmi_result'); 216 $result_type = wt1.find("div[class^='bmiType']") 217 218 $result_bmi.html(' '); 219 $result_type.html(' '); 220 $result_bmi.hide(); 221 $result_type.hide(); 222}); 223 224 225$(document).on("click", ".calculate", function(){ 226 wt1 = $(this).closest('.bmiTable'); 227 var weight = wt1.find('.weight_kg input').val(); 228 var weight2 = wt1.find('.weight_lb input').val(); 229 var height = wt1.find('.height_m input').val(); 230 var height2 = wt1.find('.height_in input').val(); 231 var weightUnit = $('input[name=weightUnit]:checked').val(); 232 var heightUnit = $('input[name=heightUnit]:checked').val(); 233 var w; 234 var hasError = 0; 235 236 if (weight == '' || height == '') { 237 hasError = 1; 238 } else if (!$.isNumeric(weight) || !$.isNumeric(height)) { 239 hasError = 2; 240 } 241 242 243 if (hasError == 0 && weightUnit == 2) { 244 if (!$.isNumeric(weight2)) { 245 if (weight2 == '') { 246 weight2 = 0; 247 } 248 else { 249 hasError = 2; 250 } 251 } 252 } 253 254 if (hasError == 0 && heightUnit == 2) { 255 if ( !$.isNumeric(height2)) { 256 if (height2 == '') { 257 height2 = 0; 258 } 259 else { 260 hasError = 2; 261 } 262 } 263 } 264 265 if (hasError == 0) { 266 // If measurement is in pounds, convert. 267 if (weightUnit == 2) { 268 w = convertWeight(weight, weight2); 269 } else { 270 w = weight; 271 } 272 273 // If height measurement is in inches, convert. 274 if (heightUnit == 2) { 275 h = convertHeight(height, height2); 276 } else { 277 h = height; 278 }; 279 280 var bmi = (w / (h * h)).toFixed(1) 281 var type = ""; 282 var ctype = 3; 283 284 if (bmi < 15) { 285 type = "Very severely underweight"; 286 ctype = 1; 287 } else if (bmi <= 16) { 288 type = "Severely underweight"; 289 ctype = 2; 290 } else if (bmi <= 18.4) { 291 type = "Underweight"; 292 ctype = 3; 293 } else if (bmi <= 24.9) { 294 type = "Normal"; 295 ctype = 4; 296 } else if (bmi <= 29.9) { 297 type = "Overweight"; 298 ctype = 5; 299 } else if (bmi <= 34.9) { 300 type = "Obese Class I (Moderately obese)"; 301 ctype = 6; 302 } else if (bmi <= 39.9) { 303 type = "Obese Class II (Severely obese)"; 304 ctype = 7; 305 } else { 306 type = "Obese Class III (Very severely obese)"; 307 ctype = 8; 308 } 309 310 $result_bmi = wt1.find('.bmi_result'); 311 $result_bmi.text('Your BMI is: ' + bmi); 312 $result_bmi.slideDown('fast'); 313 314 $result_type = wt1.find("div[class^='bmiType']") 315 $result_type.text(type); 316 $result_type.removeClass(); 317 $result_type.addClass('bmiType' + ctype); 318 $result_type.slideDown('fast'); 319 } 320 else { 321 errMsg = ""; 322 switch (hasError) { 323 case 1: 324 errMsg = 'Please enter weight and height'; 325 break; 326 case 2: 327 errMsg = 'Please enter valid weight and height'; 328 break; 329 default: 330 errMsg = 'Please enter weight and height'; 331 break; 332 } 333 334 $warning = wt1.parent().find('.bmi_warnings'); 335 $warning.text(errMsg); 336 $warning.addClass('bmiWarning'); 337 $warning.slideDown('fast'); 338 setTimeout(function(){ $warning.slideUp('slow'); }, 4000); 339 } 340}); 341$(document).ready(function () { 342 $("#searchForm2").find(":submit").on("click", searchFormSubmitHandler); 343 344 function searchFormSubmitHandler(event) { 345 event.preventDefault(); 346 event.stopImmediatePropagation(); 347 348 var $form = $(this).closest("form"); 349 var inputLen = $form.find("#fullSearchText").val().length; 350 351 if (inputLen === 0) // Stop empty search 352 { 353 $form.find("#fullSearchText").attr("placeholder", "Search"); 354 return; 355 } 356 357 var $button = $form.find(":submit"); 358 var $buttonIcon = $button.find("i"); 359 $buttonIcon.removeClass("fa-search").addClass("fa-spinner fa-spin"); 360 361 let siteKey = $("#reCaptchaSiteKey").val(); 362 363 grecaptcha.ready(function () { 364 grecaptcha.execute(siteKey, { action: 'search' }).then(function (token) { 365 $form.find("#searchCaptcha").val(token); 366 $form.trigger("submit"); 367 }); 368 }); 369 } 370 371 function whichTransitionEvent() { 372 //This method is here because chrome supports 'transition' and 'webkitTransitionEnd' 373 //so the jquery event would trigger twice if i just put all the event names into a single .one() call 374 var t, 375 el = document.createElement("fakeelement"); 376 377 var transitions = { 378 "transition": "transitionend", 379 "OTransition": "oTransitionEnd", 380 "MozTransition": "transitionend", 381 "WebkitTransition": "webkitTransitionEnd" 382 } 383 384 for (t in transitions) { 385 if (el.style[t] !== undefined) { 386 return transitions[t]; 387 } 388 } 389 } 390 391 $('.search-btn, .dropdown-item-search').click(function () { 392 var $this = $(this); 393 $this.attr('aria-pressed', function (_, attr) { 394 return attr === "true" ? "false" : "true"; 395 }).attr('aria-expanded', function (_, attr) { 396 return attr === "true" ? "false" : "true"; 397 }); 398 399 var searchBar = $('.searchbar') 400 .attr('aria-hidden', function (_, attr) { 401 return attr === "true" ? "false" : "true"; 402 }) 403 .toggleClass("open") 404 .one(whichTransitionEvent(), function () { 405 searchBar 406 .find("#fullSearchText") 407 .focus(); 408 }); 409 });
410 411 $('.close-search-btn').click(function () { 412 $('.searchbar') 413 .attr('aria-hidden', "true") 414 .removeClass("open"); 415 416 $(".search-btn, .dropdown-item-search") 417 .attr("aria-pressed", "false") 418 .attr("aria-expanded", "false") 419 .focus(); 420 }); 421}); 422 423/*! 424 * pickadate.js v3.6.4, 2019/05/25 425 * By Amsul, http://amsul.ca 426 * Hosted on http://amsul.github.io/pickadate.js 427 * Licensed under MIT 428 */ 429 430(function (factory) { 431 432 // AMD. 433 if (typeof define == 'function' && define.amd) 434 define('picker', ['jquery'], factory) 435 436 // Node.js/browserify. 437 else if (typeof exports == 'object') 438 module.exports = factory(require('jquery')) 439 440 // Browser globals. 441 else if (typeof window == 'object') 442 window.Picker = factory(jQuery) 443 444 else this.Picker = factory(jQuery) 445}(function ($) { 446 447 var $window = $(window) 448 var $document = $(document) 449 var $html = $(document.documentElement) 450 var supportsTransitions = document.documentElement.style.transition != null 451 452 453 /** 454 * The picker constructor that creates a blank picker. 455 */ 456 function PickerConstructor(ELEMENT, NAME, COMPONENT, OPTIONS) { 457 458 // If there�s no element, return the picker constructor. 459 if (!ELEMENT) return PickerConstructor 460 461 462 var 463 IS_DEFAULT_THEME = false, 464 465 466 // The state of the picker. 467 STATE = { 468 id: ELEMENT.id || 'P' + Math.abs(~~(Math.random() * new Date())), 469 handlingOpen: false, 470 }, 471 472 473 // Merge the defaults and options passed. 474 SETTINGS = COMPONENT ? $.extend(true, {}, COMPONENT.defaults, OPTIONS) : OPTIONS || {}, 475 476 477 // Merge the default classes with the settings classes. 478 CLASSES = $.extend({}, PickerConstructor.klasses(), SETTINGS.klass), 479 480 481 // The element node wrapper into a jQuery object. 482 $ELEMENT = $(ELEMENT), 483 484 485 // Pseudo picker constructor. 486 PickerInstance = function () { 487 return this.start() 488 }, 489 490 491 // The picker prototype. 492 P = PickerInstance.prototype = { 493 494 constructor: PickerInstance, 495 496 $node: $ELEMENT, 497 498 499 /** 500 * Initialize everything 501 */ 502 start: function () { 503 504 // If it�s already started, do nothing. 505 if (STATE && STATE.start) return P 506 507 508 // Update the picker states. 509 STATE.methods = {} 510 STATE.start = true 511 STATE.open = false 512 STATE.type = ELEMENT.type 513 514 515 // Confirm focus state, convert into text input to remove UA stylings, 516 // and set as readonly to prevent keyboard popup. 517 ELEMENT.autofocus = ELEMENT == getActiveElement() 518 ELEMENT.readOnly = !SETTINGS.editable 519 SETTINGS.id = ELEMENT.id = ELEMENT.id || STATE.id 520 if (ELEMENT.type != 'text') { 521 ELEMENT.type = 'text' 522 } 523 524 // Create a new picker component with the settings. 525 P.component = new COMPONENT(P, SETTINGS) 526 527 528 // Create the picker root and then prepare it. 529 P.$root = $('<div class="' + CLASSES.picker + '" id="' + ELEMENT.id + '_root" />') 530 prepareElementRoot() 531 532 533 // Create the picker holder and then prepare it. 534 P.$holder = $(createWrappedComponent()).appendTo(P.$root) 535 prepareElementHolder() 536 537 538 // If there�s a format for the hidden input element, create the element. 539 if (SETTINGS.formatSubmit) { 540 prepareElementHidden() 541 } 542 543 544 // Prepare the input element. 545 prepareElement() 546 547 548 // Insert the hidden input as specified in the settings. 549 if (SETTINGS.containerHidden) $(SETTINGS.containerHidden).append(P._hidden) 550 else $ELEMENT.after(P._hidden) 551 552 553 // Insert the root as specified in the settings. 554 if (SETTINGS.container) $(SETTINGS.container).append(P.$root) 555 else $ELEMENT.after(P.$root) 556 557 558 // Bind the default component and settings events. 559 P.on({ 560 start: P.component.onStart, 561 render: P.component.onRender, 562 stop: P.component.onStop, 563 open: P.component.onOpen, 564 close: P.component.onClose, 565 set: P.component.onSet 566 }).on({ 567 start: SETTINGS.onStart, 568 render: SETTINGS.onRender, 569 stop: SETTINGS.onStop, 570 open: SETTINGS.onOpen, 571 close: SETTINGS.onClose, 572 set: SETTINGS.onSet 573 }) 574 575 576 // Once we�re all set, check the theme in use. 577 IS_DEFAULT_THEME = isUsingDefaultTheme(P.$holder[0]) 578 579 580 // If the element has autofocus, open the picker. 581 if (ELEMENT.autofocus) { 582 P.open() 583 } 584 585 586 // Trigger queued the �start� and �render� events. 587 return P.trigger('start').trigger('render') 588 }, //start 589 590 591 /**
592 * Render a new picker 593 */ 594 render: function (entireComponent) { 595 596 // Insert a new component holder in the root or box. 597 if (entireComponent) { 598 P.$holder = $(createWrappedComponent()) 599 prepareElementHolder() 600 P.$root.html(P.$holder) 601 } 602 else P.$root.find('.' + CLASSES.box).html(P.component.nodes(STATE.open)) 603 604 // Trigger the queued �render� events. 605 return P.trigger('render') 606 }, //render 607 608 609 /** 610 * Destroy everything 611 */ 612 stop: function () { 613 614 // If it�s already stopped, do nothing. 615 if (!STATE.start) return P 616 617 // Then close the picker. 618 P.close() 619 620 // Remove the hidden field. 621 if (P._hidden) { 622 P._hidden.parentNode.removeChild(P._hidden) 623 } 624 625 // Remove the root. 626 P.$root.remove() 627 628 // Remove the input class, remove the stored data, and unbind 629 // the events (after a tick for IE - see `P.close`). 630 $ELEMENT.removeClass(CLASSES.input).removeData(NAME) 631 setTimeout(function () { 632 $ELEMENT.off('.' + STATE.id) 633 }, 0) 634 635 // Restore the element state 636 ELEMENT.type = STATE.type 637 ELEMENT.readOnly = false 638 639 // Trigger the queued �stop� events. 640 P.trigger('stop') 641 642 // Reset the picker states. 643 STATE.methods = {} 644 STATE.start = false 645 646 return P 647 }, //stop 648 649 650 /** 651 * Open up the picker 652 */ 653 open: function (dontGiveFocus) { 654 655 // If it�s already open, do nothing. 656 if (STATE.open) return P 657 658 // Add the �active� class. 659 $ELEMENT.addClass(CLASSES.active) 660 661 // * A Firefox bug, when `html` has `overflow:hidden`, results in 662 // killing transitions :(. So add the �opened� state on the next tick. 663 // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=625289 664 setTimeout(function () { 665 666 // Add the �opened� class to the picker root. 667 P.$root.addClass(CLASSES.opened) 668 aria(P.$root[0], 'hidden', false) 669 670 }, 0) 671 672 // If we have to give focus, bind the element and doc events. 673 if (dontGiveFocus !== false) { 674 675 // Set it as open. 676 STATE.open = true 677 678 // Prevent the page from scrolling. 679 if (IS_DEFAULT_THEME) { 680 $('body'). 681 css('overflow', 'hidden'). 682 css('padding-right', '+=' + getScrollbarWidth()) 683 } 684 685 // Pass focus to the root element�s jQuery object. 686 focusPickerOnceOpened() 687 688 // Bind the document events. 689 $document.on('click.' + STATE.id + ' focusin.' + STATE.id, function (event) { 690 // If the picker is currently midway through processing 691 // the opening sequence of events then don't handle clicks 692 // on any part of the DOM. This is caused by a bug in Chrome 73 693 // where a click event is being generated with the incorrect 694 // path in it. 695 // In short, if someone does a click that finishes after the 696 // new element is created then the path contains only the 697 // parent element and not the input element itself. 698 if (STATE.handlingOpen) { 699 return; 700 } 701 702 var target = getRealEventTarget(event, ELEMENT) 703 704 // If the target of the event is not the element, close the picker picker. 705 // * Don�t worry about clicks or focusins on the root because those don�t bubble up. 706 // Also, for Firefox, a click on an `option` element bubbles up directly 707 // to the doc. So make sure the target wasn't the doc. 708 // * In Firefox stopPropagation() doesn�t prevent right-click events from bubbling, 709 // which causes the picker to unexpectedly close when right-clicking it. So make 710 // sure the event wasn�t a right-click. 711 // * In Chrome 62 and up, password autofill causes a simulated focusin event which 712 // closes the picker. 713 if (!event.isSimulated && target != ELEMENT && target != document && event.which != 3) { 714 715 // If the target was the holder that c
715overs the screen, 716 // keep the element focused to maintain tabindex. 717 P.close(target === P.$holder[0]) 718 } 719 720 }).on('keydown.' + STATE.id, function (event) { 721 722 var 723 // Get the keycode. 724 keycode = event.keyCode, 725 726 // Translate that to a selection change. 727 keycodeToMove = P.component.key[keycode], 728 729 // Grab the target. 730 target = getRealEventTarget(event, ELEMENT) 731 732 733 // On escape, close the picker and give focus. 734 if (keycode == 27) { 735 P.close(true) 736 } 737 738 739 // Check if there is a key movement or �enter� keypress on the element. 740 else if (target == P.$holder[0] && (keycodeToMove || keycode == 13)) { 741 742 // Prevent the default action to stop page movement. 743 event.preventDefault() 744 745 // Trigger the key movement action. 746 if (keycodeToMove) { 747 PickerConstructor._.trigger(P.component.key.go, P, [PickerConstructor._.trigger(keycodeToMove)]) 748 } 749 750 // On �enter�, if the highlighted item isn�t disabled, set the value and close. 751 else if (!P.$root.find('.' + CLASSES.highlighted).hasClass(CLASSES.disabled)) { 752 P.set('select', P.component.item.highlight) 753 if (SETTINGS.closeOnSelect) { 754 P.close(true) 755 } 756 } 757 } 758 759 760 // If the target is within the root and �enter� is pressed, 761 // prevent the default action and trigger a click on the target instead. 762 else if ($.contains(P.$root[0], target) && keycode == 13) { 763 event.preventDefault() 764 target.click() 765 } 766 }) 767 } 768 769 // Trigger the queued �open� events. 770 return P.trigger('open') 771 }, //open 772 773 774 /** 775 * Close the picker 776 */ 777 close: function (giveFocus) { 778 779 // If we need to give focus, do it before changing states. 780 if (giveFocus) { 781 if (SETTINGS.editable) { 782 ELEMENT.focus() 783 } 784 else { 785 // ....ah yes! It would�ve been incomplete without a crazy workaround for IE :| 786 // The focus is triggered *after* the close has completed - causing it 787 // to open again. So unbind and rebind the event at the next tick. 788 P.$holder.off('focus.toOpen').focus() 789 setTimeout(function () { 790 P.$holder.on('focus.toOpen', handleFocusToOpenEvent) 791 }, 0) 792 } 793 } 794 795 // Remove the �active� class. 796 $ELEMENT.removeClass(CLASSES.active) 797 798 // * A Firefox bug, when `html` has `overflow:hidden`, results in 799 // killing transitions :(. So remove the �opened� state on the next tick. 800 // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=625289 801 setTimeout(function () { 802 803 // Remove the �opened� and �focused� class from the picker root. 804 P.$root.removeClass(CLASSES.opened + ' ' + CLASSES.focused) 805 aria(P.$root[0], 'hidden', true) 806 807 }, 0) 808 809 // If it�s already closed, do nothing more. 810 if (!STATE.open) return P 811 812 // Set it as closed. 813 STATE.open = false 814 815 // Allow the page to scroll. 816 if (IS_DEFAULT_THEME) { 817 $('body'). 818 css('overflow', ''). 819 css('padding-right', '-=' + getScrollbarWidth()) 820 } 821 822 // Unbind the document events. 823 $document.off('.' + STATE.id) 824 825 // Trigger the queued �close� events. 826 return P.trigger('close') 827 }, //close 828 829 830 /** 831 * Clear the values 832 */ 833 clear: function (options) { 834 return P.set('clear', null, options) 835 }, //clear 836 837 838 /** 839 * Set something 840 */ 841 set: function (thing, value, options) { 842 843 var thingItem, thingValue, 844 thingIsObject = $.isPlainObject(thing), 845 thingObject = thingIsObject ? thing : {} 846 847 // Make sure we have usable options. 848 options = thingIsObject && $.isPlainObject(value) ? value : options || {} 849 850 if (thing) { 851 852 // If the thing isn�t an object, make it one. 853 if (!thingIsObject) { 854 thingObject[thing] = value 855 } 856 857 // Go through the things of items to set. 858 for (thingItem in thingObject) { 859 860 // Grab the value of the thing. 861 thingValue = thingObject[thingItem] 862 863 // First, if the item exists and there�s a value, set it. 864 if (thingItem in P.component.item) { 865 if (thingValue === undefined) thingValue = null 866 P.component.set(thingItem, thingValue, options) 867 } 868 869 // Then, check to update the element value and broadcast a change. 870 if ((thingItem == 'select' || thingItem == 'clear') && SETTINGS.updateInput) { 871 $ELEMENT. 872 val(thingItem == 'clear' ? '' : P.get(thingItem, SETTINGS.format)). 873 trigger('change') 874 } 875 } 876 877 // Render a new picker. 878 P.render() 879 } 880 881 // When the method isn�t muted, trigger queued �set� events and pass the `thingObject`. 882 return options.muted ? P : P.trigger('set', thingObject) 883 }, //set 884 885 886 /** 887 * Get something 888 */ 889 get: function (thing, format) { 890 891 // Make sure there�s something to get. 892 thing = thing || 'value' 893 894 // If a picker state exists, return that. 895 if (STATE[thing] != null) { 896 return STATE[thing] 897 } 898 899 // Return the submission value, if that. 900 if (thing == 'valueSubmit') { 901 if (P._hidden) { 902 return P._hidden.value 903 } 904 thing = 'value' 905 } 906 907 // Return the value, if that. 908 if (thing == 'value') { 909 return ELEMENT.value 910 } 911 912 // Check if a component item exists, return that. 913 if (thing in P.component.item) { 914 if (typeof format == 'string') { 915 var thingValue = P.component.get(thing) 916 return thingValue ? 917 PickerConstructor._.trigger( 918 P.component.formats.toString, 919 P.component, 920 [format, thingValue] 921 ) : '' 922 } 923 return P.component.get(thing) 924 } 925 }, //get 926 927 928 929 /** 930 * Bind events on the things. 931 */ 932 on: function (thing, method, internal) { 933 934 var thingName, thingMethod, 935 thingIsObject = $.isPlainObject(thing), 936 thingObject = thingIsObject ? thing : {} 937 938 if (thing) { 939 940 // If the thing isn�t an object, make it one. 941 if (!thingIsObject) { 942 thingObject[thing] = method 943 } 944 945 // Go through the things to bind to. 946 for (thingName in thingObject) { 947 948 // Grab the method of the thing. 949 thingMethod = thingObject[thingName] 950 951 // If it was an internal binding, prefix it. 952 if (internal) { 953 thingName = '_' + thingName 954 } 955 956 // Make sure the thing methods collection exists. 957 STATE.methods[thingName] = STATE.methods[thingName] || [] 958 959 // Add the method to the relative method collection. 960 STATE.methods[thingName].push(thingMethod) 961 } 962 } 963 964 return P 965 }, //on 966 967 968 969 /** 970 * Unbind events on the things. 971 */ 972 off: function () { 973 var i, thingName, 974 names = arguments; 975 for (i = 0, namesCount = names.length; i < namesCount; i += 1) { 976 thingName = names[i] 977 if (thingName in STATE.methods) { 978 delete STATE.methods[thingName] 979 } 980 } 981 return P 982 }, 983 984 985 /** 986 * Fire off method events. 987 */ 988 trigger: function (name, data) { 989 var _trigger = function (name) { 990 var methodList = STATE.methods[name] 991 if (methodList) { 992 methodList.map(function (method) { 993 PickerConstructor._.trigger(method, P, [data]) 994 }) 995 } 996 } 997 _trigger('_' + name) 998 _trigger(name) 999 return P 1000 }
1000 //trigger 1001 } //PickerInstance.prototype 1002 1003 1004 /** 1005 * Wrap the picker holder components together. 1006 */ 1007 function createWrappedComponent() { 1008 1009 // Create a picker wrapper holder 1010 return PickerConstructor._.node('div', 1011 1012 // Create a picker wrapper node 1013 PickerConstructor._.node('div', 1014 1015 // Create a picker frame 1016 PickerConstructor._.node('div', 1017 1018 // Create a picker box node 1019 PickerConstructor._.node('div', 1020 1021 // Create the components nodes. 1022 P.component.nodes(STATE.open), 1023 1024 // The picker box class 1025 CLASSES.box 1026 ), 1027 1028 // Picker wrap class 1029 CLASSES.wrap 1030 ), 1031 1032 // Picker frame class 1033 CLASSES.frame 1034 ), 1035 1036 // Picker holder class 1037 CLASSES.holder, 1038 1039 'tabindex="-1"' 1040 ) //endreturn 1041 } //createWrappedComponent 1042 1043 /** 1044 * Prepare the input element with all bindings. 1045 */ 1046 function prepareElement() { 1047 1048 $ELEMENT. 1049 1050 // Store the picker data by component name. 1051 data(NAME, P). 1052 1053 // Add the �input� class name. 1054 addClass(CLASSES.input). 1055 1056 // If there�s a `data-value`, update the value of the element. 1057 val($ELEMENT.data('value') ? 1058 P.get('select', SETTINGS.format) : 1059 ELEMENT.value 1060 ). 1061 1062 // On focus/click, open the picker. 1063 on('focus.' + STATE.id + ' click.' + STATE.id, 1064 function (event) { 1065 event.preventDefault() 1066 P.open() 1067 } 1068 ) 1069 1070 // Mousedown handler to capture when the user starts interacting 1071 // with the picker. This is used in working around a bug in Chrome 73. 1072 .on('mousedown', function () { 1073 STATE.handlingOpen = true; 1074 var handler = function () { 1075 // By default mouseup events are fired before a click event. 1076 // By using a timeout we can force the mouseup to be handled 1077 // after the corresponding click event is handled. 1078 setTimeout(function () { 1079 $(document).off('mouseup', handler); 1080 STATE.handlingOpen = false; 1081 }, 0); 1082 }; 1083 $(document).on('mouseup', handler); 1084 }); 1085 1086 1087 // Only bind keydown events if the element isn�t editable. 1088 if (!SETTINGS.editable) { 1089 1090 $ELEMENT. 1091 1092 // Handle keyboard event based on the picker being opened or not. 1093 on('keydown.' + STATE.id, handleKeydownEvent) 1094 } 1095 1096 1097 // Update the aria attributes. 1098 aria(ELEMENT, { 1099 haspopup: true, 1100 readonly: false, 1101 owns: ELEMENT.id + '_root' 1102 }) 1103 } 1104 1105 1106 /** 1107 * Prepare the root picker element with all bindings. 1108 */ 1109 function prepareElementRoot() { 1110 aria(P.$root[0], 'hidden', true) 1111 } 1112 1113 1114 /** 1115 * Prepare the holder picker element with all bindings. 1116 */ 1117 function prepareElementHolder() { 1118 1119 P.$holder. 1120 1121 on({ 1122 1123 // For iOS8. 1124 keydown: handleKeydownEvent, 1125 1126 'focus.toOpen': handleFocusToOpenEvent, 1127 1128 blur: function () { 1129 // Remove the �target� class. 1130 $ELEMENT.removeClass(CLASSES.target) 1131 }, 1132 1133 // When something within the holder is focused, stop from bubbling 1134 // to the doc and remove the �focused� state from the root. 1135 focusin: function (event) { 1136 P.$root.removeClass(CLASSES.focused) 1137 event.stopPropagation() 1138 }, 1139 1140 // When something within the holder is clicked, stop it 1141 // from bubbling to the doc. 1142 'mousedown click': function (event) { 1143 1144 var target = getRealEventTarget(event, ELEMENT) 1145 1146 // Make sure the target isn�t the root holder so it can bubble up. 1147 if (target != P.$holder[0]) { 1148 1149 event.stopPropagation() 1150 1151 // * For mousedown events, cancel the default action in order to 1152 // prevent cases where focus is shifted onto external elements 1153 // when using things like jQuery mobile or MagnificPopup (ref: #249 & #120). 1154 // Also, for Firefox, don�t prevent action on the `option` element. 1155 if (event.type == 'mousedown' && !$(target).is('input, select, textarea, button, option')) { 1156 1157 event.preventDefault() 1158 1159 // Re-focus onto the holder so that users can click away 1160 // from elements focused within the picker. 1161 P.$holder.eq(0).focus() 1162 } 1163 } 1164 } 1165 1166 }). 1167 1168 // If there�s a click on an actionable element, carry out the actions.
1169 on('click', '[data-pick], [data-nav], [data-clear], [data-close]', function () { 1170 1171 var $target = $(this), 1172 targetData = $target.data(), 1173 targetDisabled = $target.hasClass(CLASSES.navDisabled) || $target.hasClass(CLASSES.disabled), 1174 1175 // * For IE, non-focusable elements can be active elements as well 1176 // (http://stackoverflow.com/a/2684561). 1177 activeElement = getActiveElement() 1178 activeElement = activeElement && ((activeElement.type || activeElement.href) ? activeElement : null); 1179 1180 // If it�s disabled or nothing inside is actively focused, re-focus the element. 1181 if (targetDisabled || activeElement && !$.contains(P.$root[0], activeElement)) { 1182 P.$holder.eq(0).focus() 1183 } 1184 1185 // If something is superficially changed, update the `highlight` based on the `nav`. 1186 if (!targetDisabled && targetData.nav) { 1187 P.set('highlight', P.component.item.highlight, { nav: targetData.nav }) 1188 } 1189 1190 // If something is picked, set `select` then close with focus. 1191 else if (!targetDisabled && 'pick' in targetData) { 1192 P.set('select', targetData.pick) 1193 if (SETTINGS.closeOnSelect) { 1194 P.close(true) 1195 } 1196 } 1197 1198 // If a �clear� button is pressed, empty the values and close with focus. 1199 else if (targetData.clear) { 1200 P.clear() 1201 if (SETTINGS.closeOnClear) { 1202 P.close(true) 1203 } 1204 } 1205 1206 else if (targetData.close) { 1207 P.close(true) 1208 } 1209 1210 }) //P.$holder 1211 1212 } 1213 1214 1215 /** 1216 * Prepare the hidden input element along with all bindings. 1217 */ 1218 function prepareElementHidden() { 1219 1220 var name 1221 1222 if (SETTINGS.hiddenName === true) { 1223 name = ELEMENT.name 1224 ELEMENT.name = '' 1225 } 1226 else { 1227 name = [ 1228 typeof SETTINGS.hiddenPrefix == 'string' ? SETTINGS.hiddenPrefix : '', 1229 typeof SETTINGS.hiddenSuffix == 'string' ? SETTINGS.hiddenSuffix : '_submit' 1230 ] 1231 name = name[0] + ELEMENT.name + name[1] 1232 } 1233 1234 P._hidden = $( 1235 '<input ' + 1236 'type=hidden ' + 1237 1238 // Create the name using the original input�s with a prefix and suffix. 1239 'name="' + name + '"' + 1240 1241 // If the element has a value, set the hidden value as well. 1242 ( 1243 $ELEMENT.data('value') || ELEMENT.value ? 1244 ' value="' + P.get('select', SETTINGS.formatSubmit) + '"' : 1245 '' 1246 ) + 1247 '>' 1248 )[0] 1249 1250 $ELEMENT. 1251 1252 // If the value changes, update the hidden input with the correct format. 1253 on('change.' + STATE.id, function () { 1254 P._hidden.value = ELEMENT.value ? 1255 P.get('select', SETTINGS.formatSubmit) : 1256 '' 1257 }) 1258 } 1259 1260 1261 // Wait for transitions to end before focusing the holder. Otherwise, while 1262 // using the `container` option, the view jumps to the container. 1263 function focusPickerOnceOpened() { 1264 1265 if (IS_DEFAULT_THEME && supportsTransitions) { 1266 P.$holder.find('.' + CLASSES.frame).one('transitionend', function () { 1267 P.$holder.eq(0).focus() 1268 }) 1269 } 1270 else { 1271 setTimeout(function () { 1272 P.$holder.eq(0).focus() 1273 }, 0) 1274 } 1275 } 1276 1277 1278 function handleFocusToOpenEvent(event) { 1279 1280 // Stop the event from propagating to the doc. 1281 event.stopPropagation() 1282 1283 // Add the �target� class. 1284 $ELEMENT.addClass(CLASSES.target) 1285 1286 // Add the �focused� class to the root. 1287 P.$root.addClass(CLASSES.focused) 1288 1289 // And then finally open the picker. 1290 P.open() 1291 } 1292 1293 1294 // For iOS8. 1295 function handleKeydownEvent(event) { 1296 1297 var keycode = event.keyCode,
1298 1299 // Check if one of the delete keys was pressed. 1300 isKeycodeDelete = /^(8|46)$/.test(keycode) 1301 1302 // For some reason IE clears the input value on �escape�. 1303 if (keycode == 27) { 1304 P.close(true) 1305 return false 1306 } 1307 1308 // Check if `space` or `delete` was pressed or the picker is closed with a key movement. 1309 if (keycode == 32 || isKeycodeDelete || !STATE.open && P.component.key[keycode]) { 1310 1311 // Prevent it from moving the page and bubbling to doc. 1312 event.preventDefault() 1313 event.stopPropagation() 1314 1315 // If `delete` was pressed, clear the values and close the picker. 1316 // Otherwise open the picker. 1317 if (isKeycodeDelete) { P.clear().close() } 1318 else { P.open() } 1319 } 1320 } 1321 1322 1323 // Return a new picker instance. 1324 return new PickerInstance() 1325 } //PickerConstructor 1326 1327 1328 1329 /** 1330 * The default classes and prefix to use for the HTML classes. 1331 */ 1332 PickerConstructor.klasses = function (prefix) { 1333 prefix = prefix || 'picker' 1334 return { 1335 1336 picker: prefix, 1337 opened: prefix + '--opened', 1338 focused: prefix + '--focused', 1339 1340 input: prefix + '__input', 1341 active: prefix + '__input--active', 1342 target: prefix + '__input--target', 1343 1344 holder: prefix + '__holder', 1345 1346 frame: prefix + '__frame', 1347 wrap: prefix + '__wrap', 1348 1349 box: prefix + '__box' 1350 } 1351 } //PickerConstructor.klasses 1352 1353 1354 1355 /** 1356 * Check if the default theme is being used. 1357 */ 1358 function isUsingDefaultTheme(element) { 1359 1360 var theme, 1361 prop = 'position' 1362 1363 // For IE. 1364 if (element.currentStyle) { 1365 theme = element.currentStyle[prop] 1366 } 1367 1368 // For normal browsers. 1369 else if (window.getComputedStyle) { 1370 theme = getComputedStyle(element)[prop] 1371 } 1372 1373 return theme == 'fixed' 1374 } 1375 1376 1377 1378 /** 1379 * Get the width of the browser�s scrollbar. 1380 * Taken from: https://github.com/VodkaBears/Remodal/blob/master/src/jquery.remodal.js 1381 */ 1382 function getScrollbarWidth() { 1383 1384 if ($html.height() <= $window.height()) { 1385 return 0 1386 } 1387 1388 var $outer = $('<div style="visibility:hidden;width:100px" />'). 1389 appendTo('body') 1390 1391 // Get the width without scrollbars. 1392 var widthWithoutScroll = $outer[0].offsetWidth 1393 1394 // Force adding scrollbars. 1395 $outer.css('overflow', 'scroll') 1396 1397 // Add the inner div. 1398 var $inner = $('<div style="width:100%" />').appendTo($outer) 1399 1400 // Get the width with scrollbars. 1401 var widthWithScroll = $inner[0].offsetWidth 1402 1403 // Remove the divs. 1404 $outer.remove() 1405 1406 // Return the difference between the widths. 1407 return widthWithoutScroll - widthWithScroll 1408 } 1409 1410 1411 1412 /** 1413 * Get the target element from the event. 1414 * If ELEMENT is supplied and present in the event path (ELEMENT is ancestor of the target), 1415 * returns ELEMENT instead 1416 */ 1417 function getRealEventTarget(event, ELEMENT) { 1418 1419 var path = [] 1420 1421 if (event.path) { 1422 path = event.path 1423 } 1424 1425 if (event.originalEvent && event.originalEvent.path) { 1426 path = event.originalEvent.path 1427 } 1428 1429 if (path && path.length > 0) { 1430 if (ELEMENT && path.indexOf(ELEMENT) >= 0) { 1431 return ELEMENT 1432 } else { 1433 return path[0] 1434 } 1435 } 1436 1437 return event.target 1438 } 1439 1440 /** 1441 * PickerConstructor helper methods. 1442 */ 1443 PickerConstructor._ = { 1444 1445 /** 1446 * Create a group of nodes. Expects: 1447 * ` 1448 { 1449 min: {Integer}, 1450 max: {Integer}, 1451 i: {Integer}, 1452 node: {String}, 1453 item: {Function} 1454 } 1455 * ` 1456 */ 1457 group: function (groupObject) { 1458 1459 var 1460 // Scope for the looped object 1461 loopObjectScope, 1462 1463 // Create the nodes list 1464 nodesList = '', 1465 1466 // The counter starts from the `min` 1467 counter = PickerConstructor._.trigger(groupObject.min, groupObject) 1468 1469 1470 // Loop from the `min` to `max`, incrementing by `i` 1471 for (; counter <= PickerConstructor._.trigger(groupObject.max, groupObject, [counter]); counter += groupObject.i) { 1472 1473 // Trigger the `item` function within scope of the object 1474 loopObjectScope = PickerConstructor._.trigger(groupObject.item, groupObject, [counter]) 1475 1476 // Splice the subgroup and create nodes out of the sub nodes 1477 nodesList += PickerConstructor._.node( 1478 groupObject.node, 1479 loopObjectScope[0], // the node 1480 loopObjectScope[1], // the classes 1481 loopObjectScope[2] // the attributes 1482 ) 1483 } 1484 1485 // Return the list of nodes 1486 return nodesList 1487 }, //group 1488 1489 1490 /** 1491 * Create a dom node string 1492 */ 1493 node: function (wrapper, item, klass, attribute) { 1494 1495 // If the item is false-y, just return an empty string 1496 if (!item) return '' 1497 1498 // If the item is an array, do a join 1499 item = $.isArray(item) ? item.join('') : item 1500 1501 // Check for the class 1502 klass = klass ? ' class="' + klass + '"' : '' 1503 1504 // Check for any attributes 1505 attribute = attribute ? ' ' + attribute : '' 1506 1507 // Return the wrapped item 1508 return '<' + wrapper + klass + attribute + '>' + item + '</' + wrapper + '>' 1509 }, //node 1510 1511 1512 /** 1513 * Lead numbers below 10 with a zero. 1514 */ 1515 lead: function (number) { 1516 return (number < 10 ? '0' : '') + number 1517 }, 1518 1519 1520 /** 1521 * Trigger a function otherwise return the value. 1522 */ 1523 trigger: function (callback, scope, args) { 1524 return typeof callback == 'function' ? callback.apply(scope, args || []) : callback 1525 }, 1526 1527 1528 /** 1529 * If the second character is a digit, length is 2 otherwise 1. 1530 */ 1531 digits: function (string) { 1532 return (/\d/).test(string[1]) ? 2 : 1 1533 }, 1534 1535 1536 /** 1537 * Tell if something is a date object. 1538 */ 1539 isDate: function (value) { 1540 return {}.toString.call(value).indexOf('Date') > -1 && this.isInteger(value.getDate()) 1541 }, 1542 1543 1544 /** 1545 * Tell if something is an integer. 1546 */ 1547 isInteger: function (value) { 1548 return {}.toString.call(value).indexOf('Number') > -1 && value % 1 === 0 1549 }, 1550 1551 1552 /** 1553 * Create ARIA attribute strings. 1554 */ 1555 ariaAttr: ariaAttr 1556 } //PickerConstructor._ 1557 1558 1559 1560 /** 1561 * Extend the picker with a component and defaults. 1562 */ 1563 PickerConstructor.extend = function (name, Component) { 1564 1565 // Extend jQuery. 1566 $.fn[name] = function (options, action) { 1567 1568 // Grab the component data. 1569 var componentData = this.data(name) 1570 1571 // If the picker is requested, return the data object. 1572 if (options == 'picker') { 1573 return componentData 1574 } 1575 1576 // If the component data exists and `options` is a string, carry out the action. 1577 if (componentData && typeof options == 'string') { 1578 return PickerConstructor._.trigger(componentData[options], componentData, [action]) 1579 } 1580 1581 // Otherwise go through each matched element and if the component 1582 // doesn�t exist, create a new picker using `this` element 1583 // and merging the defaults and options with a deep copy. 1584 return this.each(function () { 1585 var $this = $(this) 1586 if (!$this.data(name)) { 1587 new PickerConstructor(this, name, Component, options) 1588 } 1589 }) 1590 } 1591 1592 // Set the defaults. 1593 $.fn[name].defaults = Component.defaults 1594 } //PickerConstructor.extend 1595 1596 1597 1598 function aria(element, attribute, value) { 1599 if ($.isPlainObject(attribute)) { 1600 for (var key in attribute) { 1601 ariaSet(element, key, attribute[key]) 1602 } 1603 } 1604 else { 1605 ariaSet(element, attribute, value) 1606 } 1607 } 1608 function ariaSet(element, attribute, value) { 1609 element.setAttribute( 1610 (attribute == 'role' ? '' : 'aria-') + attribute, 1611 value 1612 ) 1613 } 1614 function ariaAttr(attribute, data) { 1615 if (!$.isPlainObject(attribute)) { 1616 attribute = { attribute: data } 1617 } 1618 data = '' 1619 for (var key in attribute) { 1620 var attr = (key == 'role' ? '' : 'aria-') + key, 1621 attrVal = attribute[key] 1622 data += attrVal == null ? '' : attr + '="' + attribute[key] + '"' 1623 } 1624 return data 1625 } 1626 1627 // IE8 bug throws an error for activeElements within iframes. 1628 function getActiveElement() { 1629 try { 1630 return document.activeElement 1631 } catch (err) { } 1632 } 1633 1634 1635 1636 // Expose the picker constructor. 1637 return PickerConstructor 1638 1639 1640})); 1641/*! 1642 * Date picker for pickadate.js v3.6.4 1643 * http://amsul.github.io/pickadate.js/date.htm 1644 */ 1645 1646(function (factory) { 1647 1648 // AMD. 1649 if (typeof define == 'function' && define.amd) 1650 define(['./picker', 'jquery'], factory) 1651 1652 // Node.js/browserify. 1653 else if (typeof exports == 'object') 1654 module.exports = factory(require('./picker.js'), require('jquery')) 1655 1656 // Browser globals. 1657 else factory(Picker, jQuery) 1658 1659}(function (Picker, $) { 1660 1661 1662 /** 1663 * Globals and constants 1664 */ 1665 var DAYS_IN_WEEK = 7, 1666 WEEKS_IN_CALENDAR = 6, 1667 _ = Picker._ 1668 1669 1670 1671 /** 1672 * The date picker constructor 1673 */ 1674 function DatePicker(picker, settings) { 1675 1676 var calendar = this, 1677 element = picker.$node[0], 1678 elementValue = element.value, 1679 elementDataValue = picker.$node.data('value'), 1680 valueString = elementDataValue || elementValue, 1681 formatString = elementDataValue ? settings.formatSubmit : settings.format, 1682 isRTL = function () { 1683 1684 return element.currentStyle ? 1685 1686 // For IE. 1687 element.currentStyle.direction == 'rtl' : 1688 1689 // For normal browsers. 1690 getComputedStyle(picker.$root[0]).direction == 'rtl'
1691 } 1692 1693 calendar.settings = settings 1694 calendar.$node = picker.$node 1695 1696 // The queue of methods that will be used to build item objects. 1697 calendar.queue = { 1698 min: 'measure create', 1699 max: 'measure create', 1700 now: 'now create', 1701 select: 'parse create validate', 1702 highlight: 'parse navigate create validate', 1703 view: 'parse create validate viewset', 1704 disable: 'deactivate', 1705 enable: 'activate' 1706 } 1707 1708 // The component's item object. 1709 calendar.item = {} 1710 1711 calendar.item.clear = null 1712 calendar.item.disable = (settings.disable || []).slice(0) 1713 calendar.item.enable = -(function (collectionDisabled) { 1714 return collectionDisabled[0] === true ? collectionDisabled.shift() : -1 1715 })(calendar.item.disable) 1716 1717 calendar. 1718 set('min', settings.min). 1719 set('max', settings.max). 1720 set('now') 1721 1722 // When there�s a value, set the `select`, which in turn 1723 // also sets the `highlight` and `view`. 1724 if (valueString) { 1725 calendar.set('select', valueString, { 1726 format: formatString, 1727 defaultValue: true 1728 }) 1729 } 1730 1731 // If there�s no value, default to highlighting �today�. 1732 else { 1733 calendar. 1734 set('select', null). 1735 set('highlight', calendar.item.now) 1736 } 1737 1738 1739 // The keycode to movement mapping. 1740 calendar.key = { 1741 40: 7, // Down 1742 38: -7, // Up 1743 39: function () { return isRTL() ? -1 : 1 }, // Right 1744 37: function () { return isRTL() ? 1 : -1 }, // Left 1745 go: function (timeChange) { 1746 var highlightedObject = calendar.item.highlight, 1747 targetDate = new Date(highlightedObject.year, highlightedObject.month, highlightedObject.date + timeChange) 1748 calendar.set( 1749 'highlight', 1750 targetDate, 1751 { interval: timeChange } 1752 ) 1753 this.render() 1754 } 1755 } 1756 1757 1758 // Bind some picker events. 1759 picker. 1760 on('render', function () { 1761 picker.$root.find('.' + settings.klass.selectMonth).on('change', function () { 1762 var value = this.value 1763 if (value) { 1764 picker.set('highlight', [picker.get('view').year, value, picker.get('highlight').date]) 1765 picker.$root.find('.' + settings.klass.selectMonth).trigger('focus') 1766 } 1767 }) 1768 picker.$root.find('.' + settings.klass.selectYear).on('change', function () { 1769 var value = this.value 1770 if (value) { 1771 picker.set('highlight', [value, picker.get('view').month, picker.get('highlight').date]) 1772 picker.$root.find('.' + settings.klass.selectYear).trigger('focus') 1773 } 1774 }) 1775 }, 1). 1776 on('open', function () { 1777 var includeToday = '' 1778 if (calendar.disabled(calendar.get('now'))) { 1779 includeToday = ':not(.' + settings.klass.buttonToday + ')' 1780 } 1781 picker.$root.find('button' + includeToday + ', select').attr('disabled', false) 1782 }, 1). 1783 on('close', function () { 1784 picker.$root.find('button, select').attr('disabled', true) 1785 }, 1) 1786 1787 } //DatePicker 1788 1789 1790 /** 1791 * Set a datepicker item object. 1792 */ 1793 DatePicker.prototype.set = function (type, value, options) { 1794 1795 var calendar = this, 1796 calendarItem = calendar.item 1797 1798 // If the value is `null` just set it immediately. 1799 if (value === null) { 1800 if (type == 'clear') type = 'select' 1801 calendarItem[type] = value 1802 return calendar 1803 } 1804 1805 // Otherwise go through the queue of methods, and invoke the functions. 1806 // Update this as the time unit, and set the final value as this item. 1807 // * In the case of `enable`, keep the queue but set `disable` instead. 1808 // And in the case of `flip`, keep the queue but set `enable` instead. 1809 calendarItem[(type == 'enable' ? 'disable' : type == 'flip' ? 'enable' : type)] = calendar.queue[type].split(' ').map(function (method) { 1810 value = calendar[method](type, value, options) 1811 return value 1812 }).pop() 1813 1814 // Check if we need to cascade through more updates. 1815 if (type == 'select') { 1816 calendar.set('highlight', calendarItem.select, options) 1817 } 1818 else if (type == 'highlight') { 1819 calendar.set('view', calendarItem.highlight, options) 1820 } 1821 else if (type.match(/^(flip|min|max|disable|enable)$/)) { 1822 if (calendarItem.select && calendar.disabled(calendarItem.select)) { 1823 calendar.set('select', calendarItem.select, options) 1824 } 1825 if (calendarItem.highlight && calendar.disabled(calendarItem.highlight)) { 1826 calendar.set('highlight', calendarItem.highlight, options) 1827 } 1828 } 1829 1830 return calendar 1831 } //DatePicker.prototype.set 1832 1833 1834 /** 1835 * Get a datepicker item object. 1836 */ 1837 DatePicker.prototype.get = function (type) { 1838 return this.item[type] 1839 } //DatePicker.prototype.get 1840 1841 1842 /** 1843 * Create a picker date object. 1844 */ 1845 DatePicker.prototype.create = function (type, value, options) { 1846 1847 var isInfiniteValue, 1848 calendar = this 1849 1850 // If there�s no value, use the type as the value. 1851 value = value === undefined ? type : value 1852 1853 1854 // If it�s infinity, update the value. 1855 if (value == -Infinity || value == Infinity) { 1856 isInfiniteValue = value 1857 } 1858 1859 // If it�s an object, use the native date object. 1860 else if ($.isPlainObject(value) && _.isInteger(value.pick)) { 1861 value = value.obj 1862 } 1863 1864 // If it�s an array, convert it into a date and make sure 1865 // that it�s a valid date � otherwise default to today. 1866 else if ($.isArray(value)) { 1867 value = new Date(value[0], value[1], value[2]) 1868 value = _.isDate(value) ? value : calendar.create().obj 1869 } 1870 1871 // If it�s a number or date object, make a normalized date. 1872 else if (_.isInteger(value) || _.isDate(value)) { 1873 value = calendar.normalize(new Date(value), options) 1874 } 1875 1876 // If it�s a literal true or any other case, set it to now. 1877 else /*if ( value === true )*/ { 1878 value = calendar.now(type, value, options) 1879 } 1880 1881 // Return the compiled object. 1882 return { 1883 year: isInfiniteValue || value.getFullYear(), 1884 month: isInfiniteValue || value.getMonth(), 1885 date: isInfiniteValue || value.getDate(), 1886 day: isInfiniteValue || value.getDay(), 1887 obj: isInfiniteValue || value, 1888 pick: isInfiniteValue || value.getTime() 1889 } 1890 } //DatePicker.prototype.create 1891 1892 1893 /** 1894 * Create a range limit object using an array, date object, 1895 * literal �true�, or integer relative to another time. 1896 */ 1897 DatePicker.prototype.createRange = function (from, to) { 1898 1899 var calendar = this, 1900 createDate = function (date) { 1901 if (date === true || $.isArray(date) || _.isDate(date)) { 1902 return calendar.create(date) 1903 } 1904 return date 1905 } 1906 1907 // Create objects if possible. 1908 if (!_.isInteger(from)) { 1909 from = createDate(from) 1910 } 1911 if (!_.isInteger(to)) { 1912 to = createDate(to) 1913 } 1914 1915 // Create relative dates. 1916 if (_.isInteger(from) && $.isPlainObject(to)) { 1917 from = [to.year, to.month, to.date + from]; 1918 } 1919 else if (_.isInteger(to) && $.isPlainObject(from)) { 1920 to = [from.year, from.month, from.date + to]; 1921 } 1922 1923 return { 1924 from: createDate(from), 1925 to: createDate(to) 1926 } 1927 } //DatePicker.prototype.createRange 1928 1929 1930 /** 1931 * Check if a date unit falls within a date range object. 1932 */ 1933 DatePicker.prototype.withinRange = function (range, dateUnit) { 1934 range = this.createRange(range.from, range.to) 1935 return dateUnit.pick >= range.from.pick && dateUnit.pick <= range.to.pick 1936 } 1937 1938 1939 /** 1940 * Check if two date range objects overlap. 1941 */ 1942 DatePicker.prototype.overlapRanges = function (one, two) { 1943 1944 var calendar = this 1945 1946 // Convert the ranges into comparable dates. 1947 one = calendar.createRange(one.from, one.to) 1948 two = calendar.createRange(two.from, two.to) 1949 1950 return calendar.withinRange(one, two.from) || calendar.withinRange(one, two.to) || 1951 calendar.withinRange(two, one.from) || calendar.withinRange(two, one.to) 1952 } 1953 1954 1955 /** 1956 * Get the date today. 1957 */ 1958 DatePicker.prototype.now = function (type, value, options) { 1959 value = new Date() 1960 if (options && options.rel) { 1961 value.setDate(value.getDate() + options.rel) 1962 } 1963 return this.normalize(value, options) 1964 } 1965 1966 1967 /** 1968 * Navigate to next/prev month. 1969 */ 1970 DatePicker.prototype.navigate = function (type, value, options) { 1971 1972 var targetDateObject, 1973 targetYear, 1974 targetMonth, 1975 targetDate, 1976 isTargetArray = $.isArray(value), 1977 isTargetObject = $.isPlainObject(value), 1978 viewsetObject = this.item.view/*, 1979 safety = 100*/ 1980 1981 1982 if (isTargetArray || isTargetObject) { 1983 1984 if (isTargetObject) { 1985 targetYear = value.year 1986 targetMonth = value.month 1987 targetDate = value.date 1988 } 1989 else { 1990 targetYear = +value[0] 1991 targetMonth = +value[1] 1992 targetDate = +value[2] 1993 } 1994 1995 // If we�re navigating months but the view is in a different 1996 // month, navigate to the view�s year and month. 1997 if (options && options.nav && viewsetObject && viewsetObject.month !== targetMonth) { 1998 targetYear = viewsetObject.year 1999 targetMonth = viewsetObject.month 2000 } 2001 2002 // Figure out the expected target year and month. 2003 targetDateObject = new Date(targetYear, targetMonth + (options && options.nav ? options.nav : 0), 1) 2004 targetYear = targetDateObject.getFullYear() 2005 targetMonth = targetDateObject.getMonth() 2006 2007 // If the month we�re going to doesn�t have enough days, 2008 // keep decreasing the date until we reach the month�s last date. 2009 while ( /*safety &&*/ new Date(targetYear, targetMonth, targetDate).getMonth() !== targetMonth) { 2010 targetDate -= 1 2011 /*safety -= 1 2012 if ( !safety ) { 2013 throw 'Fell into an infinite loop while navigating to ' + new Date( targetYear, targetMonth, targetDate ) + '.' 2014 }*/ 2015 } 2016 2017 value = [targetYear, targetMonth, targetDate] 2018 } 2019 2020 return value 2021 } //DatePicker.prototype.navigate 2022 2023 2024 /** 2025 * Normalize a date by setting the hours to midnight. 2026 */ 2027 DatePicker.prototype.normalize = function (value/*, options*/) { 2028 value.setHours(0, 0, 0, 0) 2029 return value 2030 } 2031 2032 2033 /** 2034 * Measure the range of dates. 2035 */ 2036 DatePicker.prototype.measure = function (type, value/*, options*/) { 2037 2038 var calendar = this 2039 2040 // If it's an integer, get a date relative to today. 2041 if (_.isInteger(value)) { 2042 value = calendar.now(type, value, { rel: value }) 2043 } 2044 2045 // If it�s anything false-y, remove the limits. 2046 else if (!value) { 2047 value = type == 'min' ? -Infinity : Infinity 2048 } 2049 2050 // If it�s a string, parse it. 2051 else if (typeof value == 'string') { 2052 value = calendar.parse(type, value) 2053 } 2054 2055 return value 2056 } ///DatePicker.prototype.measure 2057 2058 2059 /** 2060 * Create a viewset object based on navigation. 2061 */ 2062 DatePicker.prototype.viewset = function (type, dateObject/*, options*/) { 2063 return this.create([dateObject.year, dateObject.month, 1]) 2064 } 2065 2066 2067 /** 2068 * Validate a date as enabled and shift if needed. 2069 */ 2070 DatePicker.prototype.validate = function (type, dateObject, options) { 2071 2072 var calendar = this, 2073 2074 // Keep a reference to the original date. 2075 originalDateObject = dateObject, 2076 2077 // Make sure we have an interval. 2078 interval = options && options.interval ? options.interval : 1, 2079 2080 // Check if the calendar enabled dates are inverted. 2081 isFlippedBase = calendar.item.enable === -1, 2082 2083 // Check if we have any enabled dates after/before now. 2084 hasEnabledBeforeTarget, hasEnabledAfterTarget, 2085 2086 // The min & max limits. 2087 minLimitObject = calendar.item.min, 2088 maxLimitObject = calendar.item.max, 2089 2090 // Check if we�ve reached the limit during shifting. 2091 reachedMin, reachedMax, 2092 2093 // Check if the calendar is inverted and at least one weekday is enabled. 2094 hasEnabledWeekdays = isFlippedBase && calendar.item.disable.filter(function (value) { 2095 2096 // If there�s a date, check where it is relative to the target. 2097 if ($.isArray(value)) { 2098 var dateTime = calendar.create(value).pick 2099 if (dateTime < dateObject.pick) hasEnabledBeforeTarget = true 2100 else if (dateTime > dateObject.pick) hasEnabledAfterTarget = true 2101 } 2102 2103 // Return only integers for enabled weekdays. 2104 return _.isInteger(value) 2105 }).length/*, 2106 2107 safety = 100*/ 2108 2109 2110 2111 // Cases to validate for: 2112 // [1] Not inverted and date disabled. 2113 // [2] Inverted and some dates enabled. 2114 // [3] Not inverted and out of range. 2115 // 2116 // Cases to **not** validate for: 2117 // � Navigating months. 2118 // � Not inverted and date enabled. 2119 // � Inverted and all dates disabled. 2120 // � ..and anything else.
2121 if (!options || (!options.nav && !options.defaultValue)) if ( 2122 /* 1 */ (!isFlippedBase && calendar.disabled(dateObject)) || 2123 /* 2 */ (isFlippedBase && calendar.disabled(dateObject) && (hasEnabledWeekdays || hasEnabledBeforeTarget || hasEnabledAfterTarget)) || 2124 /* 3 */ (!isFlippedBase && (dateObject.pick <= minLimitObject.pick || dateObject.pick >= maxLimitObject.pick)) 2125 ) { 2126 2127 2128 // When inverted, flip the direction if there aren�t any enabled weekdays 2129 // and there are no enabled dates in the direction of the interval. 2130 if (isFlippedBase && !hasEnabledWeekdays && ((!hasEnabledAfterTarget && interval > 0) || (!hasEnabledBeforeTarget && interval < 0))) { 2131 interval *= -1 2132 } 2133 2134 2135 // Keep looping until we reach an enabled date. 2136 while ( /*safety &&*/ calendar.disabled(dateObject)) { 2137 2138 /*safety -= 1 2139 if ( !safety ) { 2140 throw 'Fell into an infinite loop while validating ' + dateObject.obj + '.' 2141 }*/ 2142 2143 2144 // If we�ve looped into the next/prev month with a large interval, return to the original date and flatten the interval. 2145 if (Math.abs(interval) > 1 && (dateObject.month < originalDateObject.month || dateObject.month > originalDateObject.month)) { 2146 dateObject = originalDateObject 2147 interval = interval > 0 ? 1 : -1 2148 } 2149 2150 2151 // If we�ve reached the min/max limit, reverse the direction, flatten the interval and set it to the limit. 2152 if (dateObject.pick <= minLimitObject.pick) { 2153 reachedMin = true 2154 interval = 1 2155 dateObject = calendar.create([ 2156 minLimitObject.year, 2157 minLimitObject.month, 2158 minLimitObject.date + (dateObject.pick === minLimitObject.pick ? 0 : -1) 2159 ]) 2160 } 2161 else if (dateObject.pick >= maxLimitObject.pick) { 2162 reachedMax = true 2163 interval = -1 2164 dateObject = calendar.create([ 2165 maxLimitObject.year, 2166 maxLimitObject.month, 2167 maxLimitObject.date + (dateObject.pick === maxLimitObject.pick ? 0 : 1) 2168 ]) 2169 } 2170 2171 2172 // If we�ve reached both limits, just break out of the loop. 2173 if (reachedMin && reachedMax) { 2174 break 2175 } 2176 2177 2178 // Finally, create the shifted date using the interval and keep looping. 2179 dateObject = calendar.create([dateObject.year, dateObject.month, dateObject.date + interval]) 2180 } 2181 2182 } //endif 2183 2184 2185 // Return the date object settled on. 2186 return dateObject 2187 } //DatePicker.prototype.validate 2188 2189 2190 /** 2191 * Check if a date is disabled. 2192 */ 2193 DatePicker.prototype.disabled = function (dateToVerify) { 2194 2195 var 2196 calendar = this, 2197 2198 // Filter through the disabled dates to check if this is one. 2199 isDisabledMatch = calendar.item.disable.filter(function (dateToDisable) { 2200 2201 // If the date is a number, match the weekday with 0index and `firstDay` check. 2202 if (_.isInteger(dateToDisable)) { 2203 return dateToVerify.day === (calendar.settings.firstDay ? dateToDisable : dateToDisable - 1) % 7 2204 } 2205 2206 // If it�s an array or a native JS date, create and match the exact date. 2207 if ($.isArray(dateToDisable) || _.isDate(dateToDisable)) { 2208 return dateToVerify.pick === calendar.create(dateToDisable).pick 2209 } 2210 2211 // If it�s an object, match a date within the �from� and �to� range. 2212 if ($.isPlainObject(dateToDisable)) { 2213 return calendar.withinRange(dateToDisable, dateToVerify) 2214 } 2215 }) 2216 2217 // If this date matches a disabled date, confirm it�s not inverted. 2218 isDisabledMatch = isDisabledMatch.length && !isDisabledMatch.filter(function (dateToDisable) { 2219 return $.isArray(dateToDisable) && dateToDisable[3] == 'inverted' || 2220 $.isPlainObject(dateToDisable) && dateToDisable.inverted 2221 }).length 2222 2223 // Check the calendar �enabled� flag and respectively flip the 2224 // disabled state. Then also check if it�s beyond the min/max limits. 2225 return calendar.item.enable === -1 ? !isDisabledMatch : isDisabledMatch || 2226 dateToVerify.pick < calendar.item.min.pick || 2227 dateToVerify.pick > calendar.item.max.pick 2228 2229 } //DatePicker.prototype.disabled 2230 2231 2232 /** 2233 * Parse a string into a usable type. 2234 */ 2235 DatePicker.prototype.parse = function (type, value, options) { 2236 2237 var calendar = this, 2238 parsingObject = {} 2239 2240 // If it�s already parsed, we�re good. 2241 if (!value || typeof value != 'string') { 2242 return value 2243 } 2244 2245 // We need a `.format` to parse the value with. 2246 if (!(options && options.format)) { 2247 options = options || {} 2248 options.format = calendar.settings.format 2249 } 2250 2251 // Convert the format into an array and then map through it. 2252 calendar.formats.toArray(options.format).map(function (label) { 2253 2254 var 2255 // Grab the formatting label. 2256 formattingLabel = calendar.formats[label], 2257 2258 // The format length is from the formatting label function or the 2259 // label length without the escaping exclamation (!) mark. 2260 formatLength = formattingLabel ? _.trigger(formattingLabel, calendar, [value, parsingObject]) : label.replace(/^!/, '').length 2261 2262 // If there's a format label, split the value up to the format length. 2263 // Then add it to the parsing object with appropriate label. 2264 if (formattingLabel) { 2265 parsingObject[label] = value.substr(0, formatLength) 2266 } 2267 2268 // Update the value as the substring from format length to end. 2269 value = value.substr(formatLength) 2270 }) 2271 2272 // Compensate for month 0index. 2273 return [ 2274 parsingObject.yyyy || parsingObject.yy, 2275 +(parsingObject.mm || parsingObject.m) - 1, 2276 parsingObject.dd || parsingObject.d 2277 ] 2278 } //DatePicker.prototype.parse 2279 2280 2281 /** 2282 * Various formats to display the object in. 2283 */ 2284 DatePicker.prototype.formats = (function () { 2285 2286 // Return the length of the first word in a collection. 2287 function getWordLengthFromCollection(string, collection, dateObject) { 2288 2289 // Grab the first word from the string. 2290 // Regex pattern from http://stackoverflow.com/q/150033 2291 var word = string.match(/[^\x00-\x7F]+|[a-zA-Z0-9_\u0080-\u00FF]+/)[0] 2292 2293 // If there's no month index, add it to the date object 2294 if (!dateObject.mm && !dateObject.m) { 2295 dateObject.m = collection.indexOf(word) + 1 2296 } 2297 2298 // Return the length of the word. 2299 return word.length 2300 } 2301 2302 // Get the length of the first word in a string. 2303 function getFirstWordLength(string) { 2304 return string.match(/[a-zA-Z0-9_\u0080-\u00FF]+/)[0].length 2305 } 2306 2307 return { 2308 2309 d: function (string, dateObject) { 2310 2311 // If there's string, then get the digits length. 2312 // Otherwise return the selected date. 2313 return string ? _.digits(string) : dateObject.date 2314 }, 2315 dd: function (string, dateObject) { 2316 2317 // If there's a string, then the length is always 2. 2318 // Otherwise return the selected date with a leading zero. 2319 return string ? 2 : _.lead(dateObject.date) 2320 }, 2321 ddd: function (string, dateObject) { 2322 2323 // If there's a string, then get the length of the first word. 2324 // Otherwise return the short selected weekday. 2325 return string ? getFirstWordLength(string) : this.settings.weekdaysShort[dateObject.day] 2326 }, 2327 dddd: function (string, dateObject) { 2328 2329 // If there's a string, then get the length of the first word. 2330 // Otherwise return the full selected weekday. 2331 return string ? getFirstWordLength(string) : this.settings.weekdaysFull[dateObject.day] 2332 }, 2333 m: function (string, dateObject) { 2334 2335 // If there's a string, then get the length of the digits 2336 // Otherwise return the selected month with 0index compensation. 2337 return string ? _.digits(string) : dateObject.month + 1 2338 }, 2339 mm: function (string, dateObject) { 2340 2341 // If there's a string, then the length is always 2. 2342 // Otherwise return the selected month with 0index and leading zero. 2343 return string ? 2 : _.lead(dateObject.month + 1) 2344 }, 2345 mmm: function (string, dateObject) { 2346 2347 var collection = this.settings.monthsShort 2348 2349 // If there's a string, get length of the relevant month from the short 2350 // months collection. Otherwise return the selected month from that c
2350ollection. 2351 return string ? getWordLengthFromCollection(string, collection, dateObject) : collection[dateObject.month] 2352 }, 2353 mmmm: function (string, dateObject) { 2354 2355 var collection = this.settings.monthsFull 2356 2357 // If there's a string, get length of the relevant month from the full 2358 // months collection. Otherwise return the selected month from that collection. 2359 return string ? getWordLengthFromCollection(string, collection, dateObject) : collection[dateObject.month] 2360 }, 2361 yy: function (string, dateObject) { 2362 2363 // If there's a string, then the length is always 2. 2364 // Otherwise return the selected year by slicing out the first 2 digits. 2365 return string ? 2 : ('' + dateObject.year).slice(2) 2366 }, 2367 yyyy: function (string, dateObject) { 2368 2369 // If there's a string, then the length is always 4. 2370 // Otherwise return the selected year. 2371 return string ? 4 : dateObject.year 2372 }, 2373 2374 // Create an array by splitting the formatting string passed. 2375 toArray: function (formatString) { return formatString.split(/(d{1,4}|m{1,4}|y{4}|yy|!.)/g) }, 2376 2377 // Format an object into a string using the formatting options. 2378 toString: function (formatString, itemObject) { 2379 var calendar = this 2380 return calendar.formats.toArray(formatString).map(function (label) { 2381 return _.trigger(calendar.formats[label], calendar, [0, itemObject]) || label.replace(/^!/, '') 2382 }).join('') 2383 } 2384 } 2385 })() //DatePicker.prototype.formats 2386 2387 2388 2389 2390 /** 2391 * Check if two date units are the exact. 2392 */ 2393 DatePicker.prototype.isDateExact = function (one, two) { 2394 2395 var calendar = this 2396 2397 // When we�re working with weekdays, do a direct comparison. 2398 if ( 2399 (_.isInteger(one) && _.isInteger(two)) || 2400 (typeof one == 'boolean' && typeof two == 'boolean') 2401 ) { 2402 return one === two 2403 } 2404 2405 // When we�re working with date representations, compare the �pick� value. 2406 if ( 2407 (_.isDate(one) || $.isArray(one)) && 2408 (_.isDate(two) || $.isArray(two)) 2409 ) { 2410 return calendar.create(one).pick === calendar.create(two).pick 2411 } 2412 2413 // When we�re working with range objects, compare the �from� and �to�. 2414 if ($.isPlainObject(one) && $.isPlainObject(two)) { 2415 return calendar.isDateExact(one.from, two.from) && calendar.isDateExact(one.to, two.to) 2416 } 2417 2418 return false 2419 } 2420 2421 2422 /** 2423 * Check if two date units overlap. 2424 */ 2425 DatePicker.prototype.isDateOverlap = function (one, two) { 2426 2427 var calendar = this, 2428 firstDay = calendar.settings.firstDay ? 1 : 0 2429 2430 // When we�re working with a weekday index, compare the days. 2431 if (_.isInteger(one) && (_.isDate(two) || $.isArray(two))) { 2432 one = one % 7 + firstDay 2433 return one === calendar.create(two).day + 1 2434 } 2435 if (_.isInteger(two) && (_.isDate(one) || $.isArray(one))) { 2436 two = two % 7 + firstDay 2437 return two === calendar.create(one).day + 1 2438 } 2439 2440 // When we�re working with range objects, check if the ranges overlap. 2441 if ($.isPlainObject(one) && $.isPlainObject(two)) { 2442 return calendar.overlapRanges(one, two) 2443 } 2444 2445 return false 2446 } 2447 2448 2449 /** 2450 * Flip the �enabled� state. 2451 */ 2452 DatePicker.prototype.flipEnable = function (val) { 2453 var itemObject = this.item 2454 itemObject.enable = val || (itemObject.enable == -1 ? 1 : -1) 2455 } 2456 2457 2458 /** 2459 * Mark a collection of dates as �disabled�. 2460 */ 2461 DatePicker.prototype.deactivate = function (type, datesToDisable) { 2462 2463 var calendar = this, 2464 disabledItems = calendar.item.disable.slice(0) 2465 2466 2467 // If we�re flipping, that�s all we need to do. 2468 if (datesToDisable == 'flip') { 2469 calendar.flipEnable() 2470 } 2471 2472 else if (datesToDisable === false) { 2473 calendar.flipEnable(1) 2474 disabledItems = [] 2475 } 2476 2477 else if (datesToDisable === true) { 2478 calendar.flipEnable(-1) 2479 disabledItems = [] 2480 } 2481 2482 // Otherwise go through the dates to disable. 2483 else { 2484 2485 datesToDisable.map(function (unitToDisable) { 2486 2487 var matchFound 2488 2489 // When we have disabled items, check for matches. 2490 // If something is matched, immediately break out. 2491 for (var index = 0; index < disabledItems.length; index += 1) { 2492 if (calendar.isDateExact(unitToDisable, disabledItems[index])) { 2493 matchFound = true 2494 break 2495 } 2496 } 2497 2498 // If nothing was found, add the validated unit to the collection. 2499 if (!matchFound) { 2500 if ( 2501 _.isInteger(unitToDisable) || 2502 _.isDate(unitToDisable) || 2503 $.isArray(unitToDisable) || 2504 ($.isPlainObject(unitToDisable) && unitToDisable.from && unitToDisable.to) 2505 ) { 2506 disabledItems.push(unitToDisable) 2507 } 2508 } 2509 }) 2510 } 2511 2512 // Return the updated collection. 2513 return disabledItems 2514 } //DatePicker.prototype.deactivate 2515 2516 2517 /** 2518 * Mark a collection of dates as �enabled�. 2519 */ 2520 DatePicker.prototype.activate = function (type, datesToEnable) { 2521 2522 var calendar = this, 2523 disabledItems = calendar.item.disable, 2524 disabledItemsCount = disabledItems.length 2525 2526 // If we�re flipping, that�s all we need to do. 2527 if (datesToEnable == 'flip') { 2528 calendar.flipEnable() 2529 } 2530 2531 else if (datesToEnable === true) { 2532 calendar.flipEnable(1) 2533 disabledItems = [] 2534 } 2535 2536 else if (datesToEnable === false) { 2537 calendar.flipEnable(-1) 2538 disabledItems = [] 2539 } 2540 2541 // Otherwise go through the disabled dates. 2542 else { 2543 2544 datesToEnable.map(function (unitToEnable) { 2545 2546 var matchFound, 2547 disabledUnit, 2548 index, 2549 isExactRange 2550 2551 // Go through the disabled items and try to find a match. 2552 for (index = 0; index < disabledItemsCount; index += 1) { 2553 2554 disabledUnit = disabledItems[index] 2555 2556 // When an exact match is found, remove it from the collection. 2557 if (calendar.isDateExact(disabledUnit, unitToEnable)) { 2558 matchFound = disabledItems[index] = null 2559 isExactRange = true 2560 break 2561 } 2562 2563 // When an overlapped match is found, add the �inverted� state to it. 2564 else if (calendar.isDateOverlap(disabledUnit, unitToEnable)) { 2565 if ($.isPlainObject(unitToEnable)) { 2566 unitToEnable.inverted = true 2567 matchFound = unitToEnable 2568 } 2569 else if ($.isArray(unitToEnable)) { 2570 matchFound = unitToEnable 2571 if (!matchFound[3]) matchFound.push('inverted') 2572 } 2573 else if (_.isDate(unitToEnable)) { 2574 matchFound = [unitToEnable.getFullYear(), unitToEnable.getMonth(), unitToEnable.getDate(), 'inverted'] 2575 } 2576 break 2577 } 2578 } 2579 2580 // If a match was found, remove a previous duplicate entry. 2581 if (matchFound) for (index = 0; index < disabledItemsCount; index += 1) { 2582 if (calendar.isDateExact(disabledItems[index], unitToEnable)) { 2583 disabledItems[index] = null 2584 break 2585 } 2586 } 2587 2588 // In the event that we�re dealing with an exact range of dates, 2589 // make sure there are no �inverted� dates because of it. 2590 if (isExactRange) for (index = 0; index < disabledItemsCount; index += 1) { 2591 if (calendar.isDateOverlap(disabledItems[index], unitToEnable)) { 2592 disabledItems[index] = null 2593 break 2594 } 2595 } 2596 2597 // If something is still matched, add it into the collection. 2598 if (matchFound) { 2599 disabledItems.push(matchFound) 2600 } 2601 }) 2602 } 2603 2604 // Return the updated collection. 2605 return disabledItems.filter(function (val) { return val != null }) 2606 } //DatePicker.prototype.activate 2607 2608 2609 /** 2610 * Create a string for the nodes in the picker. 2611 */ 2612 DatePicker.prototype.nodes = function (isOpen) { 2613 2614 var 2615 calendar = this, 2616 settings = calendar.settings, 2617 calendarItem = calendar.item, 2618 nowObject = calendarItem.now, 2619 selectedObject = calendarItem.select, 2620 highlightedObject = calendarItem.highlight, 2621 viewsetObject = calendarItem.view, 2622 disabledCollection = calendarItem.disable, 2623 minLimitObject = calendarItem.min, 2624 maxLimitObject = calendarItem.max, 2625 2626 2627 // Create the calendar table head using a copy of weekday labels collection. 2628 // * We do a copy so we don't mutate the original array. 2629 tableHead = (function (collection, fullCollection) { 2630 2631 // If the first day should be Monday, move Sunday to the end. 2632 if (settings.firstDay) { 2633 collection.push(collection.shift()) 2634 fullCollection.push(fullCollection.shift()) 2635 } 2636 2637 // Create and return the table head group.
2638 return _.node( 2639 'thead', 2640 _.node( 2641 'tr', 2642 _.group({ 2643 min: 0, 2644 max: DAYS_IN_WEEK - 1, 2645 i: 1, 2646 node: 'th', 2647 item: function (counter) { 2648 return [ 2649 collection[counter], 2650 settings.klass.weekdays, 2651 'scope=col title="' + fullCollection[counter] + '"' 2652 ] 2653 } 2654 }) 2655 ) 2656 ) //endreturn 2657 })((settings.showWeekdaysFull ? settings.weekdaysFull : settings.weekdaysShort).slice(0), settings.weekdaysFull.slice(0)), //tableHead 2658 2659 2660 // Create the nav for next/prev month. 2661 createMonthNav = function (next) { 2662 2663 // Otherwise, return the created month tag. 2664 return _.node( 2665 'div', 2666 ' ', 2667 settings.klass['nav' + (next ? 'Next' : 'Prev')] + ( 2668 2669 // If the focused month is outside the range, disabled the button. 2670 (next && viewsetObject.year >= maxLimitObject.year && viewsetObject.month >= maxLimitObject.month) || 2671 (!next && viewsetObject.year <= minLimitObject.year && viewsetObject.month <= minLimitObject.month) ? 2672 ' ' + settings.klass.navDisabled : '' 2673 ), 2674 'data-nav=' + (next || -1) + ' ' + 'tabindex="0" ' + 2675 _.ariaAttr({ 2676 role: 'button', 2677 controls: calendar.$node[0].id + '_table' 2678 }) + ' ' + 2679 'title="' + (next ? settings.labelMonthNext : settings.labelMonthPrev) + '"' 2680 ) //endreturn 2681 }, //createMonthNav 2682 2683 2684 // Create the month label. 2685 createMonthLabel = function () { 2686 2687 var monthsCollection = settings.showMonthsShort ? settings.monthsShort : settings.monthsFull 2688 2689 // If there are months to select, add a dropdown menu. 2690 if (settings.selectMonths) { 2691 2692 return _.node('select', 2693 _.group({ 2694 min: 0, 2695 max: 11, 2696 i: 1, 2697 node: 'option', 2698 item: function (loopedMonth) { 2699 2700 return [ 2701 2702 // The looped month and no classes. 2703 monthsCollection[loopedMonth], 0, 2704 2705 // Set the value and selected index. 2706 'value=' + loopedMonth + 2707 (viewsetObject.month == loopedMonth ? ' selected' : '') + 2708 ( 2709 ( 2710 (viewsetObject.year == minLimitObject.year && loopedMonth < minLimitObject.month) || 2711 (viewsetObject.year == maxLimitObject.year && loopedMonth > maxLimitObject.month) 2712 ) ? 2713 ' disabled' : '' 2714 ) 2715 ] 2716 } 2717 }), 2718 settings.klass.selectMonth, 2719 (isOpen ? '' : 'disabled') + ' ' + 2720 _.ariaAttr({ controls: calendar.$node[0].id + '_table' }) + ' ' + 2721 'title="' + settings.labelMonthSelect + '"' 2722 ) 2723 } 2724 2725 // If there's a need for a month selector
2726 return _.node('div', monthsCollection[viewsetObject.month], settings.klass.month) 2727 }, //createMonthLabel 2728 2729 2730 // Create the year label. 2731 createYearLabel = function () { 2732 2733 var focusedYear = viewsetObject.year, 2734 2735 // If years selector is set to a literal "true", set it to 5. Otherwise 2736 // divide in half to get half before and half after focused year. 2737 numberYears = settings.selectYears === true ? 5 : ~~(settings.selectYears / 2) 2738 2739 // If there are years to select, add a dropdown menu. 2740 if (numberYears) { 2741 2742 var 2743 minYear = minLimitObject.year, 2744 maxYear = maxLimitObject.year, 2745 lowestYear = focusedYear - numberYears, 2746 highestYear = focusedYear + numberYears 2747 2748 // If the min year is greater than the lowest year, increase the highest year 2749 // by the difference and set the lowest year to the min year. 2750 if (minYear > lowestYear) { 2751 highestYear += minYear - lowestYear 2752 lowestYear = minYear 2753 } 2754 2755 // If the max year is less than the highest year, decrease the lowest year 2756 // by the lower of the two: available and needed years. Then set the 2757 // highest year to the max year. 2758 if (maxYear < highestYear) { 2759 2760 var availableYears = lowestYear - minYear, 2761 neededYears = highestYear - maxYear 2762 2763 lowestYear -= availableYears > neededYears ? neededYears : availableYears 2764 highestYear = maxYear 2765 } 2766 2767 return _.node('select', 2768 _.group({ 2769 min: lowestYear, 2770 max: highestYear, 2771 i: 1, 2772 node: 'option', 2773 item: function (loopedYear) { 2774 return [ 2775 2776 // The looped year and no classes. 2777 loopedYear, 0, 2778 2779 // Set the value and selected index. 2780 'value=' + loopedYear + (focusedYear == loopedYear ? ' selected' : '') 2781 ] 2782 } 2783 }), 2784 settings.klass.selectYear, 2785 (isOpen ? '' : 'disabled') + ' ' + _.ariaAttr({ controls: calendar.$node[0].id + '_table' }) + ' ' + 2786 'title="' + settings.labelYearSelect + '"' 2787 ) 2788 } 2789 2790 // Otherwise just return the year focused 2791 return _.node('div', focusedYear, settings.klass.year) 2792 } //createYearLabel 2793 2794 2795 // Create and return the entire calendar. 2796 return _.node( 2797 'div', 2798 (settings.selectYears ? createYearLabel() + createMonthLabel() : createMonthLabel() + createYearLabel()) + 2799 createMonthNav() + createMonthNav(1), 2800 settings.klass.header 2801 ) + _.node( 2802 'table', 2803 tableHead + 2804 _.node( 2805 'tbody', 2806 _.group({ 2807 min: 0, 2808 max: WEEKS_IN_CALENDAR - 1, 2809 i: 1, 2810 node: 'tr', 2811 item: function (rowCounter) { 2812 2813 // If Monday is the first day and the month starts on Sunday, shift the date back a week. 2814 var shiftDateBy = settings.firstDay && calendar.create([viewsetObject.year, viewsetObject.month, 1]).day === 0 ? -7 : 0 2815 2816 return [ 2817 _.group({ 2818 min: DAYS_IN_WEEK * rowCounter - viewsetObject.day + shiftDateBy + 1, // Add 1 for weekday 0index 2819 max: function () { 2820 return this.min + DAYS_IN_WEEK - 1 2821 }, 2822 i: 1, 2823 node: 'td', 2824 item: function (targetDate) { 2825 2826 // Convert the time date from a relative date to a target date. 2827 targetDate = calendar.create([viewsetObject.year, viewsetObject.month, targetDate + (settings.firstDay ? 1 : 0)]) 2828 2829 var isSelected = selectedObject && selectedObject.pick == targetDate.pick, 2830 isHighlighted = highlightedObject && highlightedObject.pick == targetDate.pick, 2831 isDisabled = disabledCollection && calendar.disabled(targetDate) || targetDate.pick < minLimitObject.pick || targetDate.pick > maxLimitObject.pick, 2832 formattedDate = _.trigger(calendar.formats.toString, calendar, [settings.format, targetDate]), 2833 calendarNodeUniqueId = settings.id + '_' + targetDate.pick 2834 2835 return [ 2836 _.node( 2837 'div', 2838 targetDate.date, 2839 (function (klasses) { 2840 2841 // Add the `infocus` or `outfocus` classes based on month in view. 2842 klasses.push(viewsetObject.month == targetDate.month ? settings.klass.infocus : settings.klass.outfocus) 2843 2844 // Add the `today` class if needed. 2845 if (nowObject.pick == targetDate.pick) { 2846 klasses.push(settings.klass.now) 2847 } 2848 2849 // Add the `selected` class if something's selected and the time matches. 2850 if (isSelected) { 2851 klasses.push(settings.klass.selected) 2852 } 2853 2854 // Add the `highlighted` class if something's highlighted and the time matches. 2855 if (isHighlighted) { 2856 klasses.push(settings.klass.highlighted) 2857 } 2858 2859 // Add the `disabled` class if something's disabled and the object matches. 2860 if (isDisabled) { 2861 klasses.push(settings.klass.disabled) 2862 } 2863 2864 return klasses.join(' ') 2865 })([settings.klass.day]), 2866 'data-pick=' + targetDate.pick + ' id=' + calendarNodeUniqueId + ' tabindex="0" ' + _.ariaAttr({ 2867 role: 'gridcell', 2868 label: formattedDate, 2869 selected: isSelected && calendar.$node.val() === formattedDate ? true : null, 2870 activedescendant: isHighlighted ? targetDate.pick : null, 2871 disabled: isDisabled ? true : null 2872 }) 2873 ), 2874 '' 2875 ] //endreturn 2876 } 2877 }) 2878 ] //endreturn 2879 } 2880 }) 2881 ), 2882 settings.klass.table, 2883 'id="' + calendar.$node[0].id + '_table' + '" ' + _.ariaAttr({ 2884 role: 'grid', 2885 controls: calendar.$node[0].id, 2886 readonly: true 2887 }) 2888 ) + 2889 2890 // * For Firefox forms to submit, make sure to set the buttons� `type` attributes as �button�. 2891 _.node( 2892 'div', 2893 _.node('button', settings.today, settings.klass.buttonToday,
2894 'type=button data-pick=' + nowObject.pick + 2895 (isOpen && !calendar.disabled(nowObject) ? '' : ' disabled') + ' ' + 2896 _.ariaAttr({ controls: calendar.$node[0].id })) + 2897 _.node('button', settings.clear, settings.klass.buttonClear, 2898 'type=button data-clear=1' + 2899 (isOpen ? '' : ' disabled') + ' ' + 2900 _.ariaAttr({ controls: calendar.$node[0].id })) + 2901 _.node('button', settings.close, settings.klass.buttonClose, 2902 'type=button data-close=true ' + 2903 (isOpen ? '' : ' disabled') + ' ' + 2904 _.ariaAttr({ controls: calendar.$node[0].id })), 2905 settings.klass.footer 2906 ) //endreturn 2907 } //DatePicker.prototype.nodes 2908 2909 2910 2911 2912 /** 2913 * The date picker defaults. 2914 */ 2915 DatePicker.defaults = (function (prefix) { 2916 2917 return { 2918 2919 // The title label to use for the month nav buttons 2920 labelMonthNext: 'Next month', 2921 labelMonthPrev: 'Previous month', 2922 2923 // The title label to use for the dropdown selectors 2924 labelMonthSelect: 'Select a month', 2925 labelYearSelect: 'Select a year', 2926 2927 // Months and weekdays 2928 monthsFull: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], 2929 monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], 2930 weekdaysFull: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], 2931 weekdaysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], 2932 2933 // Today and clear 2934 today: 'Today', 2935 clear: 'Clear', 2936 close: 'Close', 2937 2938 // Picker close behavior 2939 closeOnSelect: true, 2940 closeOnClear: true, 2941 2942 // Update input value on select/clear 2943 updateInput: true, 2944 2945 // The format to show on the `input` element 2946 format: 'd mmmm, yyyy', 2947 2948 // Classes 2949 klass: { 2950 2951 table: prefix + 'table', 2952 2953 header: prefix + 'header', 2954 2955 navPrev: prefix + 'nav--prev', 2956 navNext: prefix + 'nav--next', 2957 navDisabled: prefix + 'nav--disabled', 2958 2959 month: prefix + 'month', 2960 year: prefix + 'year', 2961 2962 selectMonth: prefix + 'select--month', 2963 selectYear: prefix + 'select--year', 2964 2965 weekdays: prefix + 'weekday', 2966 2967 day: prefix + 'day', 2968 disabled: prefix + 'day--disabled', 2969 selected: prefix + 'day--selected', 2970 highlighted: prefix + 'day--highlighted', 2971 now: prefix + 'day--today', 2972 infocus: prefix + 'day--infocus', 2973 outfocus: prefix + 'day--outfocus', 2974 2975 footer: prefix + 'footer', 2976 2977 buttonClear: prefix + 'button--clear', 2978 buttonToday: prefix + 'button--today', 2979 buttonClose: prefix + 'button--close' 2980 } 2981 } 2982 })(Picker.klasses().picker + '__') 2983 2984 2985 2986 2987 2988 /** 2989 * Extend the picker to add the date picker. 2990 */ 2991 Picker.extend('pickadate', DatePicker) 2992 2993 2994})); 2995function LoadMenuData(id, mwId) { 2996 if (id != "") { 2997 var url = "MenuWidget/GetMenuItems/?menuId=" + id; 2998 var panelId = '#menuPanel' + mwId; 2999 $(panelId).html('<i class="fa fa-spinner fa-spin"></i> Loading...'); 3000 3001 $.ajax({
3002 type: "GET", 3003 url: url, 3004 success: function (result) { 3005 $(panelId).html(result); 3006 $(panelId).closest('form').find('#MenuId').val(id); 3007 } 3008 }); 3009 } 3010} 3011$(document).ready(function () { 3012 $(".surgeryPicker").on('change', function (e) { 3013 e.preventDefault(); 3014 3015 var id = $(this).attr("data-id"); 3016 var url = $(this).attr("data-url"); 3017 var otDivID = "#openingTimes_" + id; 3018 var otSelecterID = "#siteId_" + id + " option:selected"; 3019 3020 var $frm = $(this).find(otDivID); 3021 3022 var selectedSite = $(otSelecterID).val(); 3023 3024 LoadOpeningTimes(selectedSite, otDivID, url); 3025 }); 3026}); 3027 3028function LoadOpeningTimes(siteId, divId, url) { 3029 $.ajax({ 3030 url: url, 3031 type: 'POST', 3032 data: { siteId: siteId }, 3033 success: function (r) { 3034 $(divId).html(r); 3035 } 3036 }); 3037} 3038$(document).ready(function () { 3039 3040 // Essentially pass all elements with a title attribute set into a function that sets up the tooltips 3041 // Exceptions: 3042 // - the google maps iframes need a title for accessibility 3043 // - any element who's class starts with 'picker' (I think this is for date pickers) 3044 init($('[title]:not([class^="picker"],iframe)')); 3045}); 3046 3047function init(element) { 3048 //debugger; 3049 // loop round each element and set up tooltip 3050 $(element).each(function (index, item) { 3051 var $this = $(this); 3052 3053 $this.attr("data-toggle", "tooltip"); 3054 3055 // in cases where tooltip needs to be positioned other than bottom could do something like this: 3056 // if (":contains(static-icons)") { // Set position } 3057 // where static-icons is a class. For now bottom is the default position 3058 var placement = $this.attr("data-placement"); 3059 if (placement != "top" && placement != "bottom" && placement != "left" && placement != "right") { 3060 $this.attr("data-placement", "bottom"); 3061 } 3062 3063 //tooltips object placed inside an .image-widget-container will not be visible because overflow-y:hidden is set 3064 //to get around this we need to move the tool top into the .widget-item-container to do this we need to give the .widget-item-container an unique id 3065 if ($this.hasClass("image-widget-image")) { 3066 var containerId = "widget_tooltip_container_" + index; 3067 3068 $this.closest(".widget-item-container").attr("id", containerId); 3069 $this 3070 .attr("data-container", "#" + containerId) 3071 .attr("data-placement", "top"); 3072 //the slideshow widget has to do something similar 3073 } else if ($this.hasClass("slideshow-image")) { 3074 var containerId = "#" + $this.closest(".carousel").attr("id"); 3075 $this.attr("data-container", containerId); 3076 } 3077 }); 3078 3079 // Set trigger to hover otherwise current tooltip will stay after a button has been clicked 3080 $('[data-toggle="tooltip"]').tooltip({ 3081 trigger: 'hover', 3082 delay: { "show": 1000, "hide": 50 } 3083 }); 3084} 3085/*! lozad.js - v1.9.0 - 2019-02-09 3086* https://github.com/ApoorvSaxena/lozad.js 3087* Copyright (c) 2019 Apoorv Saxena; Licensed MIT */ 3088!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.lozad=e()}(this,function(){"use strict";var g=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var r=arguments[e];for(var o in r)Object.prototype.hasOwnProperty.call(r,o)&&(t[o]=r[o])}return t},n="undefined"!=typeof document&&document.documentMode,l={rootMargin:"0px",threshold:0,load:function(t){if("picture"===t.nodeName.toLowerCase()){var e=document.createElement("img");n&&t.getAttribute("data-iesrc")&&(e.src=t.getAttribute("data-iesrc")),t.getAttribute("data-alt")&&(e.alt=t.getAttribute("data-alt")),t.appendChild(e)}if("video"===t.nodeName.toLowerCase()&&!t.getAttribute("data-src")&&t.children){for(var r=t.children,o=void 0,a=0;a<=r.length-1;a++)(o=r[a].getAttribute("data-src"))&&(r[a].src=o);t.load()}t.getAttribute("data-src")&&(t.src=t.getAttribute("data-src")),t.getAttribute("data-srcset")&&t.setAttribute("srcset",t.getAttribute("data-srcset")),t.getAttribute("data-background-image")&&(t.style.backgroundImage="url('"+t.getAttribute("data-background-image")+"')"),t.getAttribute("data-toggle-class")&&t.classList.toggle(t.getAttribute("data-toggle-class"))},loaded:function(){}}; 3089/** 3090 * Detect IE browser 3091 * @const {boolean} 3092 * @private 3093 */function f(t){t.setAttribute("data-loaded",!0)}var b=function(t){return"true"===t.getAttribute("data-loaded")};return function(){var r,o,a=0<arguments.length&&void 0!==arguments[0]?arguments[0]:".lozad",t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{},e=g({},l,t),n=e.root,i=e.rootMargin,d=e.threshold,c=e.load,u=e.loaded,s=void 0;return window.IntersectionObserver&&(s=new IntersectionObserver((r=c,o=u,function(t,e){t.forEach(function(t){(0<t.intersectionRatio||t.isIntersecting)&&(e.unobserve(t.target),b(t.target)||(r(t.target),f(t.target),o(t.target)))})}),{root:n,rootMargin:i,threshold:d})),{observe:function(){for(var t=function(t){var e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:document;return t instanceof Element?[t]:t instanceof NodeList?t:e.querySelectorAll(t)}(a,n),e=0;e<t.length;e++)b(t[e])||(s?s.observe(t[e]):(c(t[e]),f(t[e]),u(t[e])))},triggerLoad:function(t){b(t)||(c(t),f(t),u(t))},observer:s}}}); 3094class CookieBase { 3095 constructor() { 3096 Cookies.json = true; 3097 } 3098 3099 get actionTypeEnum() { 3100 return { 3101 Accept: 'accept',
3102 Reject: 'reject' 3103 }; 3104 }; 3105 3106 get categoryTypeEnum() { 3107 return { 3108 Performance: 'performance', 3109 Targeting: 'targeting', 3110 Functionality: 'functionality' 3111 }; 3112 }; 3113 3114 get cookieCategories() { 3115 return { 3116 Functional: ['googtrans'], 3117 Performance: ['_ga', '_gat', '_gid', '_gat_UA-65152898-10'] 3118 }; 3119 }; 3120 3121 createCookieObject(action, categories) { 3122 let cookie = new Object(); 3123 cookie.action = action; 3124 cookie.categories = categories; 3125 3126 Cookies.set('CookieConsent', cookie, { expires: 365, secure: true }); 3127 3128 return cookie; 3129 }; 3130 3131 getCookieConsent(cookie) { 3132 const container = this; 3133 if (cookie.action === this.actionTypeEnum.Accept) { 3134 $.each(cookie.categories, 3135 function (index, category) { 3136 const tag = $("*[data-cookiecategory='" + category + "']"); 3137 tag.each(function () { 3138 const self = this; 3139 const async = !!self.src.includes('analytics'); 3140 container.reloadJavascript(self.src, 'text/javascript', category, async); 3141 }); 3142 }); 3143 } 3144 }; 3145 3146 reloadJavascript(src, type, data, async) { 3147 let head = document.getElementsByTagName('head')[0]; 3148 let script = document.createElement('script'); 3149 script.async = async; 3150 script.src = src; 3151 script.type = type; 3152 script.setAttribute('data-cookiecategory', data); 3153 3154 head.appendChild(script); 3155 }; 3156 3157 removeCookies(cookies) { 3158 $.each(cookies, 3159 function (index, value) { 3160 Cookies.remove(value); 3161 }); 3162 } 3163} 3164$(document).ready(function () { 3165 const cb = new CookieBanner(); 3166 cb.init(); 3167 3168 $('#cookie-banner-accept').on("click", function () { 3169 cb.acceptCookies(); 3170 }); 3171 3172 $('#cookie-banner-reject').on("click", function () { 3173 cb.rejectCookies(); 3174 }); 3175 3176}); 3177 3178class CookieBanner extends CookieBase { 3179 3180 init() { 3181 // don't show the pop up if we are on the cookie policy page 3182 if (this.isCookiePolicyPath()) { 3183 this.hideCookieConsent(); 3184 } 3185 3186 const cookie = Cookies.get('CookieConsent'); 3187 if (cookie === undefined) { 3188 this.showCookieConsent(); 3189 } else { 3190 this.getCookieConsent(cookie); 3191 this.hideCookieConfirmation(); 3192 this.hideCookieConsent(); 3193 } 3194 }; 3195 3196 isCookiePolicyPath() { 3197 const url = window.location.href; 3198 const pathname = new URL(url).pathname.toLowerCase(); 3199 3200 return pathname.indexOf('cookiepolicy') > -1; 3201 }; 3202 3203 showCookieConsent() { 3204 $('#cookie-banner').show(); 3205 // need to raise this as some templates don't need the main window adjusting/resizing to fit the banner 3206 if (document.onCookieConsent) { 3207 document.onCookieConsent(); 3208 } 3209 }; 3210 3211 hideCookieConsent() { 3212 $('#cookie-banner').remove(); 3213 // need to raise this as some templates don't need the main window adjusting/resizing to fit the banner 3214 if (document.onCookieConsent) { 3215 document.onCookieConsent(); 3216 } 3217 }; 3218 3219 showCookieConfirmation() { 3220 $("#cookie-banner-confirmation").show(); 3221 }; 3222 3223 hideCookieConfirmation() { 3224 $('#cookie-banner-confirmation').remove(); 3225 } 3226 3227 acceptCookies() { 3228 const categories = [this.categoryTypeEnum.Performance, this.categoryTypeEnum.Functionality, this.categoryTypeEnum.Targeting]; 3229 const cookie = this.createCookieObject(this.actionTypeEnum.Accept, categories); 3230 3231 this.getCookieConsent(cookie); 3232 this.showCookieConfirmation(); 3233 this.hideCookieConsent(); 3234 }; 3235 3236 rejectCookies() { 3237 const cookies = this.cookieCategories.Functional.concat(this.cookieCategories.Performance); 3238 this.removeCookies(cookies); 3239 this.createCookieObject(this.actionTypeEnum.Reject, []); 3240 this.showCookieConfirmation(); 3241 this.hideCookieConsent(); 3242 }; 3243};
3244$(document).ready(function () { 3245 if ($("#cookiepolicy-wrapper").is(':visible')) { 3246 const cp = new CookiePolicy(); 3247 cp.setCategories(); 3248 3249 $('#cookiePolicy-button-save').on("click", function () { 3250 cp.updateCookies(); 3251 }); 3252 } 3253}); 3254 3255class CookiePolicy extends CookieBase { 3256 3257 setCategories() { 3258 const cookie = Cookies.get('CookieConsent'); 3259 if (cookie === undefined) { 3260 return; 3261 }; 3262 3263 $.each(cookie.categories, 3264 function (index, category) { 3265 $("input[value='" + category + "']").prop('checked', true); 3266 }); 3267 } 3268 3269 updateCookies() { 3270 let acceptedCategories = []; 3271 let rejectedCategories = []; 3272 3273 $('#cookie-policy-checkboxes input:checkbox').each(function () { 3274 const isChecked = $(this).is(":checked") 3275 if (isChecked) 3276 acceptedCategories.push($(this).val()) 3277 else 3278 rejectedCategories.push($(this).val()); 3279 }); 3280 3281 const cookie = acceptedCategories.length 3282 ? this.createCookieObject(this.actionTypeEnum.Accept, acceptedCategories) 3283 : this.createCookieObject(this.actionTypeEnum.Reject, []); 3284 3285 const container = this; 3286 $.each(rejectedCategories, 3287 function (index, category) { 3288 switch (category) { 3289 case container.categoryTypeEnum.Performance: 3290 container.removeCookies(container.cookieCategories.Performance); 3291 break; 3292 case container.categoryTypeEnum.Functionality: 3293 container.removeCookies(container.cookieCategories.Functionality); 3294 break; 3295 } 3296 }); 3297 3298 this.getCookieConsent(cookie); 3299 3300 swal({ 3301 title: 'Good job!', 3302 text: 'You have successfully updated your cookie preferences', 3303 icon: 'success' 3304 }); 3305 } 3306} 3307$(window).resize(function () { 3308 toggleAriaHidden(); 3309}); 3310 3311$(document).ready(function () { 3312 toggleAriaHidden(); 3313}); 3314 3315function toggleAriaHidden() { 3316 var screenWidth = $(window).width(); 3317 3318 if (screenWidth < 768) { 3319 $(".d-none .d-sm-block").attr("aria-hidden", "true"); 3320 $(".hidden-sm, .hidden-md, .hidden-lg").attr("aria-hidden", "false"); 3321 $(".d-xs-block, .d-xs-inline, .d-xs-inline-block").attr("aria-hidden", "false"); 3322 $(".d-sm-block, .d-sm-inline, .d-sm-inline-block, .d-md-block, .d-md-inline, .d-md-inline-block, .d-lg-block, .d-lg-inline, .d-lg-inline-block").attr("aria-hidden", "true"); 3323 } else if (screenWidth >= 768 && screenWidth < 992) { 3324 $(".d-sm-none .d-md-block").attr("aria-hidden", "true"); 3325 $(".hidden-xs, .hidden-md, .hidden-lg").attr("aria-hidden", "false"); 3326 $(".d-sm-block, .d-sm-inline, .d-sm-inline-block").attr("aria-hidden", "false"); 3327 $(".d-xs-block, .d-xs-inline, .d-xs-inline-block, .d-md-block, .d-md-inline, .d-md-inline-block, .d-lg-block, .d-lg-inline, .d-lg-inline-block").attr("aria-hidden", "true"); 3328 } else if (screenWidth >= 992 && screenWidth < 1200) { 3329 $(".hidden-md").attr("aria-hidden", "true"); 3330 $(".hidden-xs, .hidden-sm, .hidden-lg").attr("aria-hidden", "false"); 3331 $(".d-md-block, .d-md-inline, .d-md-inline-block").attr("aria-hidden", "false"); 3332 $(".d-xs-block, .d-xs-inline, .d-xs-inline-block, .d-sm-block, .d-sm-inline, .d-sm-inline-block, .d-lg-block, .d-lg-inline, .d-lg-inline-block").attr("aria-hidden", "true"); 3333 } else if (screenWidth >= 1200) { 3334 $(".hidden-lg").attr("aria-hidden", "true"); 3335 $(".hidden-xs, .hidden-sm, .hidden-md").attr("aria-hidden", "false"); 3336 $(".d-lg-block, .d-lg-inline, .d-lg-inline-block").attr("aria-hidden", "false"); 3337 $(".d-sm-block, .d-xs-inline, .d-xs-inline-block, .d-sm-block, .d-sm-inline, .d-sm-inline-block, .d-md-block, .d-md-inline, .d-md-inline-block").attr("aria-hidden", "true"); 3338 } 3339} 3340function DeleteMapFile(id, fId) { 3341 if (id != "") { 3342 var url = "BoundaryWidget/DeleteFile/?boundaryWidgetId=" + id; 3343 3344 swal({ 3345 title: 'Are you sure?', 3346 text: 'This will delete the boundary widget file.', 3347 icon: 'warning',
3348 buttons: ['No', 'Yes'] 3349 }).then(function (yes) { 3350 if (yes) { 3351 $.ajax({ 3352 type: "DELETE", 3353 url: url, 3354 success: function (result) { 3355 if (result.Success) { 3356 $("#fileNameDiv").html(""); 3357 $("#FileId").val(null); 3358 } else { 3359 $(".error-summary").removeClass("d-none"); 3360 $(".error-summary").html("Error deleting current file"); 3361 } 3362 } 3363 }); 3364 } 3365 }); 3366 3367 } 3368} 3369 3370var boundaryMap = function (id, isKml, filePath) { 3371 let _this = this; 3372 this.id = id; 3373 this.isKml = isKml; 3374 this.filePath = filePath; 3375 this.map = null; 3376 this.marker = null; 3377 const geometryTypes = ["Polygon", "MultiPolygon"]; 3378 3379 this.checkAddress = function checkAddress(latlng) { 3380 let containsLoc = false; 3381 this.map.data.forEach(function (feature) { 3382 const boundary = feature.getGeometry(); 3383 const boundaryType = boundary.getType(); 3384 if (geometryTypes.includes(boundaryType) && containsLoc == false) { 3385 containsLoc = _this.containsLocation(latlng, boundary); 3386 } 3387 }); 3388 return containsLoc; 3389 } 3390 3391 this.containsLocation = function (latlng, boundary) { 3392 let contains = false; 3393 const boundaryType = boundary.getType(); 3394 3395 // if boundary is a 'multipolygon' then recurse down till we find a polygon 3396 if (boundaryType == geometryTypes[1]) { 3397 const polyLength = boundary.getLength(); 3398 for (index = 0; index < polyLength; index++) { 3399 const multiPoly = boundary.getAt(index); 3400 contains = this.containsLocation(latlng, multiPoly); 3401 } 3402 } 3403 3404 if (contains) 3405 return true; 3406 const poly = new google.maps.Polygon({ paths: boundary.getAt(0).getArray() }) 3407 return google.maps.geometry.poly.containsLocation(latlng, poly); 3408 } 3409 3410 3411 this.reloadBounds = function (markerLatlng = null) { 3412 let bounds = new google.maps.LatLngBounds(); 3413 this.map.data.forEach(function (feature) { 3414 3415 let geo = feature.getGeometry(); 3416 3417 geo.forEachLatLng(function (LatLng) { 3418 bounds.extend(LatLng); 3419 }); 3420 }); 3421 3422 if (markerLatlng != null) { 3423 bounds.extend(markerLatlng); 3424 } 3425 // fit data to bounds 3426 this.map.fitBounds(bounds); 3427 } 3428 3429 this.resetMap = function () { 3430 const widget = $("#boundarywidget_" + this.id) 3431 widget.find('.boundary-error').addClass('d-none'); 3432 widget.find('.boundary-yes').addClass('d-none'); 3433 widget.find('.boundary-no').addClass('d-none'); 3434 3435 if (this.marker != null) { 3436 this.marker.setMap(null); 3437 } 3438 this.map.data.setStyle({ 3439 fillColor: 'dodgerblue', 3440 strokeColor: 'dodgerblue', 3441 strokeWeight: 3 3442 }); 3443 3444 this.reloadBounds(); 3445 } 3446 3447 this.search = function () { 3448 this.resetMap(); 3449 const widget = $("#boundarywidget_" + this.id) 3450 const postcode = widget.find(".postcode-search").val(); 3451 3452 var geocoder = new google.maps.Geocoder(); 3453 geocoder.geocode({ 3454 'address': postcode, 3455 'region': 'uk' 3456 }, function (results, status) { 3457 if (status == google.maps.GeocoderStatus.OK) { 3458 const latlng = { lat: results[0].geometry.location.lat(), lng: results[0].geometry.location.lng() }; 3459 3460 _this.marker = new google.maps.Marker({ 3461 position: latlng, 3462 map: _this.map, 3463 }); 3464 3465 if (_this.checkAddress(latlng)) { 3466 widget.find('.boundary-yes').removeClass('d-none'); 3467 _this.map.data.setStyle({ 3468 fillColor: 'green', 3469 strokeColor: 'green', 3470 strokeWeight: 3 3471 }); 3472 } else { 3473 widget.find('.boundary-no').removeClass('d-none'); 3474 _this.map.data.setStyle({ 3475 fillColor: 'red', 3476 strokeColor: 'red', 3477 strokeWeight: 3 3478 }); 3479 } 3480 _this.reloadBounds(latlng); 3481 } else { 3482 widget.find('.boundary-error').removeClass('d-none'); 3483 _this.reloadBounds(null); 3484 } 3485 }) 3486 } 3487 3488 this.initMap = function () { 3489 $("#boundarywidget_" + this.id).find(".search-btn").on("click", function () { 3490 _this.search(); 3491 }) 3492 3493 $("#boundarywidget_" + this.id).find(".postcode-search").keypress(function (e) { 3494 if (e.keyCode === 13) { 3495 _this.search(); 3496 } 3497 }) 3498 3499 this.map = new google.maps.Map($("#boundarywidget_" + this.id).find(".map")[0]); 3500 3501 this.map.data.setStyle({ 3502 fillColor: 'dodgerblue', 3503 strokeColor: 'dodgerblue', 3504 strokeWeight: 3 3505 }); 3506 3507 if (this.isKml) { 3508 3509 //reading the kml file ourselves and adding the coordinates because easy to debug issues & able to pull from map what we want to add 3510 $.ajax({
3511 url: this.filePath, 3512 dataType: 'html', 3513 type: 'get', 3514 success: function (results) { 3515 let parser = new DOMParser() 3516 let xmlDoc = parser.parseFromString(results, "text/xml") 3517 let googlePolygons = [] 3518 3519 //make sure is a kml file 3520 if (xmlDoc.documentElement.nodeName == "kml") { 3521 for (const item of xmlDoc.getElementsByTagName('Placemark')) { 3522 3523 let polygons = item.getElementsByTagName('Polygon') 3524 3525 //at the moment we only deal with polygons 3526 if (polygons.length > 0) { 3527 3528 for (const polygon of polygons) { 3529 var coords = polygon.getElementsByTagName('coordinates')[0].childNodes[0]; 3530 3531 if (coords) { 3532 for (const point of coords.nodeValue.trim().split("\n")) { 3533 let coord = point.split(",") 3534 googlePolygons.push({ lat: +coord[1], lng: +coord[0] }) 3535 } 3536 } 3537 3538 //add to map 3539 _this.map.data.add({ 3540 geometry: new google.maps.Data.Polygon([ 3541 googlePolygons 3542 ]) 3543 }); 3544 } 3545 } 3546 } 3547 _this.reloadBounds(); 3548 } 3549 } 3550 }); 3551 3552 } else { 3553 if (this.filePath.length > 0) { 3554 this.map.data.loadGeoJson(this.filePath, null, function () { _this.reloadBounds(); }); 3555 } 3556 } 3557 } 3558 3559 this.initMap(); 3560};
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.