PageSourceSearch

https://leylandsmedicalcentre.nhs.uk/scripts/master-lower-public.js

js leylandsmedicalcentre.nhs.uk collected 2026-10-05 23:47:22 UTC 133,089 bytes, 3,560 lines download raw bytes

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('&nbsp;');    
219    $result_type.html('&nbsp;');
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.