

(function () {
    if (!ko) {
        throw new Error('knockout.dotgolf.extensions.js requires Knockout to be loaded first.');
        return;
    }


    //DFH 2012-07-06 adds our standard plural() function so that you can bind to observable strings, formatting them at the same time.
    //Example usage:
    //
    //   <span data-bind="text: rowsAffected.plural('{0} row[s] affected, so {0} [person/people] may be unhappy.')"></span>
    //
    ko.observable.fn.plural = function (formatString, zeroText) {
        return ko.computed(function () {
            var val = this();

            return formatString.plural(val, zeroText);
        }, this);
    };

    ko.observableArray.fn.liveitems = function () {
        return ko.utils.arrayFilter(this(), function (item) {
            if (typeof item._destroy == 'function')
                return !item._destroy();

            return !item._destroy;
        });
    };

    //DFH 2012-07-24 adds a groupby() function for knockout observable arrays.  Grabbed this from http://stackoverflow.com/questions/9877301/knockoutjs-observablearray-data-grouping.
    //The cool thing is that this can groupby based on a property on the object.
    //
    //Example usage:
    //
    //  this.people = ko.observableArray([
    //     new Person("Jimmy", "Friend"),
    //     new Person("George", "Friend"),
    //     new Person("Zippy", "Enemy")
    //  ]).groupby('type');
    //
    ko.observableArray.fn.groupby = function (prop, excludeDestroyed) {
        var target = this;

        if (target.index && target.index[prop]) return target; //Group by already set up, bail out.

        target.index = {};
        target.index[prop] = ko.observable({});

        ko.computed(function () {
            //rebuild index
            var propIndex = {};

            ko.utils.arrayForEach(excludeDestroyed ? target.liveitems() : target(), function (item) {
                var key = '' + ko.utils.unwrapObservable(item[prop]);
                if (key) {
                    propIndex[key] = propIndex[key] || [];
                    propIndex[key].push(item);
                }
            });

            target.index[prop](propIndex);
        });

        return target;
    };

    ko.observableArray.fn.distinct = function (prop, excludeDestroyed) {
        var target = this.groupby(prop, excludeDestroyed);
        var distinctArray = ko.observableArray();

        ko.computed(function () {
            distinctArray([]);
            $.each(target.index[prop](), function (idx, item) {
                distinctArray.push(item[0]);
            });
        });

        return distinctArray;
    };

    //DFH 2015-09-15 Computes the sum of any property on an observable array.  You can invoke it two ways:
    //
    //  myArray.sum('Amount');  //Returns the sum of the Amount property across all items
    //OR
    //  myArray.sum(function() { return this.Amount(); });  //Uses an inline function to do the same - useful for conversions/parsing of properties.
    //
    ko.observableArray.fn.sum = function (propertyNameOrFunction) {
        var target = this;

        return ko.computed(function () {
            var total = 0;

            ko.utils.arrayForEach(target.liveitems(), function (item) {

                var val = null;
                if (typeof propertyNameOrFunction != 'function')
                    val = +ko.utils.unwrapObservable(item[propertyNameOrFunction]);
                else
                    val = +propertyNameOrFunction.apply(item);

                if (val)
                    total += val;
            });

            return total;
        });
    };

    //DFH 2012-07-10 adds a timepicker binding so you can have time pickers in your views.  I have pilfered
    //some of this from the interwebz - http://stackoverflow.com/questions/6399078/knockoutjs-databind-with-jquery-ui-datepicker
    //Example usage:
    //
    //  <input type="text" data-bind="timepicker: StartTime" />
    //
    ko.bindingHandlers.timepicker = {
        init: function (element, valueAccessor, allBindingsAccessor) {
            //initialize datepicker with some optional options
            var options = allBindingsAccessor().timepickerOptions || {
                ampm: false,
                showAnim: 'fadeIn',
                minute: 00,
                hourGrid: 6,
                minuteGrid: 15,
                addSliderAccess: true,
                sliderAccessArgs: { touchonly: false },
                showOn: "both",
                buttonImage: "/img/clock.png",
                buttonImageOnly: true
            };

            if (!$(element).hasClass('hasDatepicker')) {
                $(element).addClass('isTimePicker').timepicker(options);

                //handle the field changing
                ko.utils.registerEventHandler(element, "change", function () {
                    var observable = valueAccessor();
                    observable($(element).timepicker("getDate").val());
                });

                //handle disposal (if KO removes by the template binding)
                ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                    $(element).timepicker("destroy");
                });
            }
        },
        update: function (element, valueAccessor) {
            var value = ko.utils.unwrapObservable(valueAccessor());

            $(element).val(value);

            if ($(element).hasClass('hasDatepicker')) {
                var current = $(element).timepicker("getDate");

                if (value != current.val())
                    $(element).timepicker("setDate", dgStringToDateTime(value));
            }
        }
    };

    //DFH 2012-07-10 adds a datepicker binding so you can have date pickers in your views.  Adapted from the timepicker binding above.
    //Example usage:
    //
    //  <input type="text" data-bind="datepicker: ResignedDate" />
    //
    ko.bindingHandlers.datepicker = {
        init: function (element, valueAccessor, allBindingsAccessor) {

            //initialize datepicker with some optional options
            var options = $.extend({
                /*minDate: 0,*/
                dateFormat: 'd/mm/yy',
                showOn: 'both',
                buttonImage: '/img/calendar_view_week.png',
                buttonImageOnly: true,
                showAnim: 'fadeIn'
            }, allBindingsAccessor().datepickerOptions || {});

            //DFH 2012-10-19 Special hacks for Internet Exploder to make the popup disappear once the user clicks.
            var oldOnClose = options.onClose;
            options.onClose = function (dateText, inst) {
                if (typeof oldOnClose == 'function') oldOnClose(dateText, inst);
                $(this).change();
            };
            var oldOnSelect = options.onSelect;
            options.onSelect = function (dateText, inst) {
                if (typeof oldOnSelect == 'function') oldOnSelect(dateText, inst);
                $(this).blur();
            };

            if (!$(element).hasClass('hasDatepicker')) {

                $(element).addClass('isDatePicker').before('<span class="date-picker-day-span"></span>').datepicker(options);

                //handle the field changing
                ko.utils.registerEventHandler(element, "change", function () {
                    var observable = valueAccessor();
                    observable($(element).datepicker("getDate"));
                });

                //handle disposal (if KO removes by the template binding)
                ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                    $(element).datepicker("destroy");
                });
            }
            
            if (typeof allBindingsAccessor().disable == 'function') {
                $(element).datepicker(allBindingsAccessor().disable() ? 'disable' : 'enable');
                allBindingsAccessor().disable.subscribe(function () {
                    $(element).datepicker(allBindingsAccessor().disable() ? 'disable' : 'enable');
                });
            }
        },
        update: function (element, valueAccessor) {
            var value = ko.utils.unwrapObservable(valueAccessor());

            //$(element).val(value+'');
            if ($(element).hasClass('hasDatepicker')) {
                var current = $(element).datepicker('getDate');

                if (value != current) {
                    $(element).datepicker('setDate', value);

                    var theDate = new Date(value);
                    var str = $.datepicker.formatDate('D', theDate);
                    var full = $.datepicker.formatDate('DD, MM d, yy', theDate);
                    $(element).attr('title', full).prev('.date-picker-day-span').text(str);
                }
            }
        }
    };

    //DFH 2012-07-10 Automatically adds a LABEL element after your checkboxes, linking
    //it with an ID (which is autogenerated if not specified on the input element).
    //
    //Example usage:
    //
    //  <input type="checkbox" data-bind="label: 'This text goes on the label'" />
    //
    ko.bindingHandlers.label = {
        init: function (element, valueAccessor, allBindingsAccessor) {
            ko.bindingHandlers.label.counter = ko.bindingHandlers.label.counter || 0;
            element.id = element.id || 'ko_ux_id_' + (++ko.bindingHandlers.label.counter);
            var lbl = $('<LABEL/>').text(valueAccessor()).attr('for', element.id);
            $(element).after(lbl);
        },
        update: function (element, valueAccessor) {
            var lbl = $(element).next('LABEL');
            lbl.text(valueAccessor());
        }
    };

    //DFH 2012-07-31 Like the visible binding, but rather fades the element(s) in question
    //in or out when the property changes.  Loosely based on some article I found on the net.
    ko.bindingHandlers.fadeVisible = {
        init: function (element, valueAccessor) {
            // Initially set the element to be instantly visible/hidden depending on the value
            var value = valueAccessor();
            $(element).toggle(ko.utils.unwrapObservable(value)); // Use "unwrapObservable" so we can handle values that may or may not be observable
        },
        update: function (element, valueAccessor) {
            // Whenever the value subsequently changes, slowly fade the element in or out
            var value = valueAccessor();
            ko.utils.unwrapObservable(value) ? $(element).fadeIn() : $(element).fadeOut();
        }
    };

    //KDB 2012-09-07 Like the visible binding, but rather slides the element(s) in question
    //in or out when the property changes.  Exact copy from the koJS website.
    //
    //Example usage:
    //
    //  <div data-bind="slideVisible: trueOrfalse, slideDuration:600">This div will slide down or op</div>
    //
    ko.bindingHandlers.slideVisible = {
        update: function (element, valueAccessor, allBindingsAccessor) {
            // First get the latest data that we're bound to
            var value = valueAccessor(), allBindings = allBindingsAccessor();

            // Next, whether or not the supplied model property is observable, get its current value
            var valueUnwrapped = ko.utils.unwrapObservable(value);

            // Grab some more data from another binding property
            var duration = allBindings.slideDuration || 400; // 400ms is default duration unless otherwise specified
            var scrollTo = allBindings.scrollTo; // Scroll to object with params
            var callback = function () { return; };
            if (scrollTo) {
                $.extend(scrollTo, { container: window, target: element, duration: 500 });
                callback = function() {
                    $(scrollTo.container).scrollTo(scrollTo.target, scrollTo.duration);
                };
            }

            // Now manipulate the DOM element
            if (valueUnwrapped == true)
                $(element).slideDown(duration, callback); // Make the element visible
            else
                $(element).slideUp(duration);   // Make the element invisible
        }
    };

    //KDB 2013-06-04 Alternates the given CSS Class
    //
    // Params:
    // 
    // trigger - A Boolean observable that will trigger the start
    // speed - The speed in milliseconds to alternate at
    // delay - Milliseconds to delay the start 
    // duration - Time in milliseconds to toggle the class for, default = 0 = forever
    //
    //Example usage:
    //
    //  <div class="normal" data-bind="alternateCssClass: 'flash-on', trigger: trigger, speed: speed, delay: 3000"></div>
    //

    ko.bindingHandlers.alternateCssClass = {
        init: function (element, valueAccessor, allBindingsAccessor) {
            
            // First get the latest data that we're bound to
            var value = valueAccessor(), allBindings = allBindingsAccessor();

            // Next, whether or not the supplied model property is observable, get its current value
            var valueUnwrapped = ko.utils.unwrapObservable(value);

            // Grab some more data from another binding property
            var trigger = allBindings.trigger || false;
            var triggerUnwrapped = ko.utils.unwrapObservable(trigger);
            if (typeof ko.utils.unwrapObservable(trigger) !== 'boolean') {
                console.error('alternateCssClass requires a trigger of type boolean to function correctly!');
                return;
            }
            var delay = allBindings.delay || 0; // 0ms is default delay unless otherwise specified
            var speed = allBindings.speed || 500; // 500ms is default speed unless otherwise specified
            var duration = allBindings.duration || 0; // 0ms is default duration unless otherwise specified
            
            var alternateTimer = null;
            var durationTimer = null;
            var alternateTimerStart = function (delayStart) {
                alternateTimer = setTimeout(
                    function () {
                        //console.log('class alternated');
                        $(element).toggleClass(valueUnwrapped);
                        alternateTimerStart();
                        if (!durationTimer && ko.utils.unwrapObservable(duration) > 0) {
                            durationTimer = setTimeout(function () {
                                if (ko.isObservable(trigger)) {
                                    trigger(false);
                                } else {
                                    alternateTimerStop();
                                }
                            }, ko.utils.unwrapObservable(duration));
                        }
                    },
                    delayStart ? delayStart : ko.utils.unwrapObservable(speed));
            };

            var alternateTimerStop = function () {
                $(element).removeClass(valueUnwrapped);
                clearTimeout(alternateTimer);
                alternateTimer = null;
                clearTimeout(durationTimer);
                durationTimer = null;
            };

            if (ko.isObservable(trigger)) {               
                trigger.subscribe(function (triggerVal) {
                    if (triggerVal && !alternateTimer) {
                        //console.log('alternateCssClass Start');
                        alternateTimerStart(ko.utils.unwrapObservable(delay));
                    } else {
                        //console.log('alternateCssClass Stop');
                        alternateTimerStop();
                    }
                });
                
                //start the time if the default is true
                if (triggerUnwrapped) {
                    alternateTimerStart(ko.utils.unwrapObservable(delay));
                }
                
            } else if (trigger) {
                alternateTimerStart(ko.utils.unwrapObservable(delay));
            }
        }
    };

    //KDB 2012-09-10 Styles a checkbox or radio with the fancy styles
    //
    //Example usage:
    //
    //  <label for="myTextbox">
    //      My Radio Label
    //      <input type="radio" value="some-value-a" id="myTextbox" data-bind="fancyControl: <observable>" />
    //  </label>
    //
    //  IMPORTANT NOTE: If you are binding to a set of radios, you will need to return
    //  the corresponding VALUE from your observable, NOT true or false as with a checkbox.
    //
    if (ko.version >= '3.4') {
        ko.bindingHandlers['fancyControl'] = {
            'after': ['value', 'attr'],
            'init': function (element, valueAccessor, allBindings) {
                if ($(element).attr('type') !== 'radio' && $(element).attr('type') !== 'checkbox') return;

                if ($(element).attr('type') == 'radio' && $(element).closest('.fancyControlGroup').length != 0) {
                    var controlGroup = $(element).closest('.fancyControlGroup').attr('fancyControlGroupId');
                    var controlId = $(element).attr('id');
                    $(element).closest('label').attr('for', controlGroup + '_' + controlId);
                    $(element).attr('id', controlGroup + '_' + controlId).attr('name', controlGroup);
                }

                var checkedValue = ko.pureComputed(function () {
                    // Treat "value" like "checkedValue" when it is included with "checked" binding
                    if (allBindings['has']('checkedValue')) {
                        return ko.utils.unwrapObservable(allBindings.get('checkedValue'));
                    } else if (allBindings['has']('value')) {
                        return ko.utils.unwrapObservable(allBindings.get('value'));
                    }

                    return element.value;
                });

                function updateModel() {
                    // This updates the model value from the view value.
                    // It runs in response to DOM events (click) and changes in checkedValue.
                    var isChecked = element.checked,
                        elemValue = useCheckedValue ? checkedValue() : isChecked;

                    // When we're first setting up this computed, don't change any model state.
                    if (ko.computedContext.isInitial()) {
                        return;
                    }

                    // We can ignore unchecked radio buttons, because some other radio
                    // button will be getting checked, and that one can take care of updating state.
                    if (isRadio && !isChecked) {
                        return;
                    }

                    if (valueIsArray) {
                        var writableValue = rawValueIsNonArrayObservable ? valueAccessor.peek() : valueAccessor;
                        if (oldElemValue !== elemValue) {
                            // When we're responding to the checkedValue changing, and the element is
                            // currently checked, replace the old elem value with the new elem value
                            // in the model array.
                            if (isChecked) {
                                ko.utils.addOrRemoveItem(writableValue, elemValue, true);
                                ko.utils.addOrRemoveItem(writableValue, oldElemValue, false);
                            }

                            oldElemValue = elemValue;
                        } else {
                            // When we're responding to the user having checked/unchecked a checkbox,
                            // add/remove the element value to the model array.
                            ko.utils.addOrRemoveItem(writableValue, elemValue, isChecked);
                        }
                        if (rawValueIsNonArrayObservable && ko.isWriteableObservable(valueAccessor)) {
                            valueAccessor(writableValue);
                        }
                    } else {
                        if (ko.isWriteableObservable(valueAccessor()) && (valueAccessor().peek() !== elemValue)) {
                            valueAccessor()(elemValue);
                        }
                    }

                };

                function updateView() {
                    valueAccessor();
                    if ($(element).attr('type') == 'radio' && $(element).closest('.fancyControlGroup').length != 0) {
                        var controlGroup = $(element).closest('.fancyControlGroup').attr('fancyControlGroupId');
                        var controlId = $(element).attr('id');
                        $(element).closest('label').attr('for', controlGroup + '_' + controlId);
                        $(element).attr('id', controlGroup + '_' + controlId).attr('name', controlGroup);
                    }
                    if ($(element).attr('type') == 'radio' || $(element).attr('type') == 'checkbox') {
                        // This updates the view value from the model value.
                        // It runs in response to changes in the bound (checked) value.
                        var modelValue = ko.utils.unwrapObservable(valueAccessor());

                        if (valueIsArray) {
                            // When a checkbox is bound to an array, being checked represents its value being present in that array
                            element.checked = ko.utils.arrayIndexOf(modelValue, checkedValue()) >= 0;
                        } else if (isCheckbox) {
                            // When a checkbox is bound to any other value (not an array), being checked represents the value being trueish
                            element.checked = modelValue;
                        } else {
                            // For radio buttons, being checked means that the radio button's value corresponds to the model value
                            element.checked = (checkedValue() == modelValue);
                        }
                        $(element).attr('type') == 'radio' ? dgSetFancyRadio($(element)) : dgSetFancyCheckBox($(element));
                    }

                };

                var isCheckbox = element.type == "checkbox",
                    isRadio = element.type == "radio";

                // Only bind to check boxes and radio buttons
                if (!isCheckbox && !isRadio) {
                    return;
                }

                var rawValue = valueAccessor(),
                    valueIsArray = isCheckbox && (ko.utils.unwrapObservable(rawValue) instanceof Array),
                    rawValueIsNonArrayObservable = !(valueIsArray && rawValue.push && rawValue.splice),
                    oldElemValue = valueIsArray ? checkedValue() : undefined,
                    useCheckedValue = isRadio || valueIsArray;

                // IE 6 won't allow radio buttons to be selected unless they have a name
                if (isRadio && !element.name)
                    ko.bindingHandlers['uniqueName']['init'](element, function () { return true });

                // Set up two computeds to update the binding:

                // The first responds to changes in the checkedValue value and to element clicks
                ko.computed(updateModel, null, { disposeWhenNodeIsRemoved: element });
                ko.utils.registerEventHandler(element, "click", updateModel);

                // The second responds to changes in the model value (the one associated with the checked binding)
                ko.computed(updateView, null, { disposeWhenNodeIsRemoved: element });

                rawValue = undefined;

                $(element).closest('label').addClass($(element).attr('type') == 'radio' ? 'ko fancy_radio' : 'ko fancy_check');

                if (allBindings().disable !== undefined && !ko.isObservable(allBindings().disable))
                    throw new Error('disable property on fancyControl binding MUST be an observable.');

                if (ko.isObservable(allBindings().disable)) {
                    $(element).attr('disabled', !!allBindings().disable());
                    allBindings().disable.subscribe(function () {
                        $(element).attr('disabled', !!allBindings().disable());
                        $(element).attr('type') == 'radio' ? dgSetFancyRadio($(element)) : dgSetFancyCheckBox($(element));
                    });
                }
            }
        };
    } else {
        ko.bindingHandlers.fancyControl = {
            init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
                if ($(element).attr('type') == 'radio' || $(element).attr('type') == 'checkbox') {
                    //KDB 2013-05-10 Check for radio button grouping
                    if ($(element).attr('type') == 'radio' && $(element).closest('.fancyControlGroup').length != 0) {
                        var controlGroup = $(element).closest('.fancyControlGroup').attr('fancyControlGroupId');
                        var controlId = $(element).attr('id');
                        $(element).closest('label').attr('for', controlGroup + '_' + controlId);
                        $(element).attr('id', controlGroup + '_' + controlId).attr('name', controlGroup);
                    }
                    ko.bindingHandlers.checked.init(element, valueAccessor, allBindingsAccessor, viewModel);
                    $(element).closest('label').addClass($(element).attr('type') == 'radio' ? 'ko fancy_radio' : 'ko fancy_check');

                    if (allBindingsAccessor().disable !== undefined && !ko.isObservable(allBindingsAccessor().disable))
                        throw new Error('disable property on fancyControl binding MUST be an observable.');

                    if (ko.isObservable(allBindingsAccessor().disable)) {
                        $(element).attr('disabled', allBindingsAccessor().disable());
                        allBindingsAccessor().disable.subscribe(function () {
                            $(element).attr('disabled', allBindingsAccessor().disable());
                            $(element).attr('type') == 'radio' ? dgSetFancyRadio($(element)) : dgSetFancyCheckBox($(element));
                        });
                    }
                }
            },
            update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
                //KDB 2013-05-10 Handle radio button grouping changes
                if ($(element).attr('type') == 'radio' && $(element).closest('.fancyControlGroup').length != 0) {
                    var controlGroup = $(element).closest('.fancyControlGroup').attr('fancyControlGroupId');
                    var controlId = $(element).attr('id');
                    $(element).closest('label').attr('for', controlGroup + '_' + controlId);
                    $(element).attr('id', controlGroup + '_' + controlId).attr('name', controlGroup);
                }
                if ($(element).attr('type') == 'radio' || $(element).attr('type') == 'checkbox') {
                    ko.bindingHandlers.checked.update(element, valueAccessor, allBindingsAccessor, viewModel);
                    $(element).attr('type') == 'radio' ? dgSetFancyRadio($(element)) : dgSetFancyCheckBox($(element));
                }
            }
        };
    }
    
    //KDB 2013-05-10 Dynamic grouping for fancy radio's
    //
    //Example usage:
    //
    //  <div data-bind="fancyControlGroup: <uniqueGroupId_observable>">
    //      <fancyRadio>...
    //      <fancyRadio>...
    //      <fancyRadio>...
    //  </div>
    //
    //
    ko.bindingHandlers.fancyControlGroup = {
        init: function (element, valueAccessor) {
            $(element).addClass('fancyControlGroup').attr('fancyControlGroupId', 'FCG_'+(ko.isObservable(valueAccessor) ? valueAccessor() : valueAccessor()));
        },
        update: function (element, valueAccessor) {
            $(element).attr('fancyControlGroupId', 'FCG_' + (ko.isObservable(valueAccessor) ? valueAccessor() : valueAccessor()));
        }
    };

    //MSK 2020-10-13 Creates a country dropdown
    //
    //Example usage:
    //  Basic (passing in a selectedCountryId observable only):
    //   <div data-bind="countrySelect: <observable>"></div> )
    //
    //  Custom (passing in an options containing countrySelect properties to set):
    //   <div data-bind="countrySelect: { selectedCountryId: <observable>, excludeDataSharingCountries: true }"></div>
    //
    ko.bindingHandlers.countrySelect = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            if (typeof $(document).countrySelect != 'function')
                throw new Error('countrySelect binding error: The countrySelect binding requires a script reference to the "dotgolf.country-selector.js" file.');

            var options = valueAccessor();
            if (ko.isObservable(options))
                options = { selectedCountryId: valueAccessor() };

            $(element).countrySelect(options);
            return { controlsDescendantBindings: true };
        }
    };

    //MSK 2021-09-29 Creates a region dropdown
    //
    //Example usage:
    //  Basic (passing in a selectedRegionId observable only):
    //   <div data-bind="regionSelect: <observable>"></div> 
    //  (regions will be from the current system county)
    //
    //  Custom (passing in an options containing regionSelect properties to set):
    //   <div data-bind="regionSelect: { selectedRegionId: <observable>, selectedCountryId: <observable> }"></div>
    //
    ko.bindingHandlers.regionSelect = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            if (typeof $(document).regionSelect != 'function')
                throw new Error('regionSelect binding error: The regionSelect binding requires a script reference to the "dotgolf.region-selector.js" file.');

            var options = valueAccessor();
            if (ko.isObservable(options))
                options = { selectedRegionId: valueAccessor() };

            $(element).regionSelect(options);
            return { controlsDescendantBindings: true };
        }
    };

    //DFH 2012-09-26 This extender creates a two-way binding from a string based DOM value to a boolean observable.
    //Pretty useful if you have a SELECT with two values, 0 and 1, and a backing observable which is a bool.
    //
    //Example usage:
    //
    //  <SELECT data-bind="value: myBoolObservable.extend({ asBool:true })">
    //    <OPTION value="0">Some Text (NO)</OPTION>
    //    <OPTION value="1">Some Text (YES)</OPTION>
    //  </SELECT>
    //
    ko.extenders.asBool = function (target, options) {
        //options unused right now.

        var result = ko.computed({
            read: function () {
                return (target()) == true;
            },
            write: function (newValue) {
                if (newValue == 'true' || newValue == "1" || newValue == "yes" || newValue == "y" || newValue == "Y") {
                    if (!target()) target(true);
                } else {
                    if (target()) target(false);
                }
            }
        });

        //return the new computed observable
        return result;
    };

    //GTC 2018-05-16 This extender displays "Yes" for true and "No" for false.    
    //
    //Example usage:
    //
    //  <td data-bind="YesNo: HasPaid"></td>
    //
    ko.bindingHandlers.YesNo = {
        update: function (element, valueAccessor) {
            // defaults to false
            var val = ko.utils.unwrapObservable(valueAccessor()) || false;

            if (val)
                $(element).text("Yes");
            else
                $(element).text("No");
        }
    }

    //DFH 2013-01-09 This extender creates a two-way binding which inverts the underlying boolean observable value.
    //This is useful when working with visible or disabled bindings where your model observable has the inverted meaning.
    //
    //Example:
    //
    //  <blah data-bind="visible: $root.hidden.extend({ invert: true})"></blah>
    //
    ko.extenders.invert = function (target, options) {
        var result = ko.computed({
            read: function() {
                return !target();
            },
            write: function(newValue) {
                target(!newValue);
            }
        });

        //return the new computed observable
        return result;
    };


    //KDB: qTip Validation Messages
    ko.bindingHandlers.qtipValMessage = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            var observable = valueAccessor(), $element = $(element);
            if (observable.isValid) {
                observable.isValid.subscribe(function (valid) {
                    $element.toggleClass('invalid', !valid);
                    $element.toggleClass('valid', valid);
                    if (!valid) {
                        if (typeof $element.data('qtip') === 'object')
                            $element.qtip('destroy');

                        $element.qtip({
                            overwrite: true,
                            show: {
                                when: !valid,
                                ready: true
                            },
                            hide: false,
                            content: ko.unwrap(observable.error),
                            position: {
                                corner: {
                                    target: 'rightMiddle',
                                    tooltip: 'leftMiddle'
                                }

                            },
                            style: {
                                name: 'red',
                                tip: {
                                    corner: 'leftMiddle', // We declare our corner within the object using the corner sub-option
                                    size: {
                                        x: 5, // Be careful that the x and y values refer to coordinates on screen, not height or width.
                                        y: 10 // Depending on which corner your tooltip is at, x and y could mean either height or width!
                                    }
                                },
                                border: {
                                    width: 2
                                }
                            }
                        });
                    } else {
                        if (typeof $element.data('qtip') === 'object') {
                            $element.qtip('hide');
                        }
                    }
                });
            }
        }
    };

    //KDB: qTip Validation Messages
    ko.bindingHandlers.qtipValIcon = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            var observable = valueAccessor(), $element = $(element);
            $element.addClass('qtip-validator');
            if (observable.isValid) {
                observable.isValid.subscribe(function (valid) {
                    $element.toggleClass('invalid', !valid);
                    $element.toggleClass('valid', valid);
                    if (!valid) {
                        $element.qtip({
                            overwrite: true,
                            show: {
                                when: 'click'
                            },
                            hide: { when: 'inactive', delay: 3000 },
                            content: ko.unwrap(observable.error),
                            position: {
                                corner: {
                                    target: 'rightMiddle',
                                    tooltip: 'leftMiddle'
                                }

                            },
                            style: {
                                name: 'red',
                                tip: {
                                    corner: 'leftMiddle', // We declare our corner within the object using the corner sub-option
                                    size: {
                                        x: 10, // Be careful that the x and y values refer to coordinates on screen, not height or width.
                                        y: 10 // Depending on which corner your tooltip is at, x and y could mean either height or width!
                                    }
                                },
                                border: {
                                    width: 2
                                }
                            }
                        });
                    } else {
                        if (typeof $element.data('qtip') === 'object') {
                            $element.qtip('destroy');
                        }
                    }
                });
            }
        }
    };

    //KDB: qTip Validation Messages
    ko.bindingHandlers.qtipValGroup = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            var observable = valueAccessor(), $element = $(element);
            if (observable.isValid) {
                observable.isValid.subscribe(function (valid) {
                    $element.toggleClass('invalid', !valid);
                    $element.toggleClass('valid', valid);
                    if (!valid) {
                        $element.qtip({
                            overwrite: true,
                            show: {
                                when: 'click'
                            },
                            hide: { when: 'inactive', delay: 3000 },
                            content: observable.error,
                            position: {
                                corner: {
                                    target: 'rightMiddle',
                                    tooltip: 'leftMiddle'
                                }

                            },
                            style: {
                                name: 'red',
                                tip: {
                                    corner: 'leftMiddle', // We declare our corner within the object using the corner sub-option
                                    size: {
                                        x: 5, // Be careful that the x and y values refer to coordinates on screen, not height or width.
                                        y: 10 // Depending on which corner your tooltip is at, x and y could mean either height or width!
                                    }
                                },
                                border: {
                                    width: 2
                                }
                            }
                        });
                    } else {
                        if (typeof $element.data('qtip') === 'object') {
                            $element.qtip('destroy');
                        }
                    }
                });
            }
        }
    };


    //KDB: Adds an invalid or valid class to any element that fails or passes validation
    ko.bindingHandlers.cssValGroup = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            var observable = valueAccessor(), $element = $(element);
            if (observable.isValid) {
                observable.isValid.subscribe(function (valid) {
                    $element.toggleClass('invalid', !valid);
                    $element.toggleClass('valid', valid);
                });
            }
        }
    };

    //KDB: custom input filter based on regex
    ko.bindingHandlers.filterRegEx = {
        init: function (element, valueAccessor) {
            $(element).filter_input({ regex: valueAccessor(), live: true });
        }
    };

    //KDB: only numbers are allowed
    ko.bindingHandlers.filterNumber = {
        init: function (element, valueAccessor) {
            if (valueAccessor())
                $(element).filter_input({ regex: '[0-9]', live: true });
        }
    };

    //KDB: only AlphaNumeric characters are allowed
    ko.bindingHandlers.filterAlpha = {
        init: function (element, valueAccessor) {
            if (valueAccessor())
                $(element).filter_input({ regex: '[a-zA-Z]', live: true });
        }
    };

    //KDB: only AlphaNumeric characters are allowed
    ko.bindingHandlers.filterAlphaNumeric = {
        init: function (element, valueAccessor) {
            if (valueAccessor())
                $(element).filter_input({ regex: '[a-zA-Z0-9]', live: true });
        }
    };

    //KDB: only AlphaNumeric characters are allowed
    ko.bindingHandlers.filterHandicap = {
        init: function (element, valueAccessor) {
            if (valueAccessor())
                $(element).filter_input({ regex: '[\+0-9\.-]', live: true });
        }
    };

    //KDB 2012-10-10 Bind to the Course select control
    //
    //Example usage:
    //
    //      <div data-bind="courseSelect: [COURSE_ID]" /div>
    //
    ko.bindingHandlers.courseSelect = {
        init: function (element, valueAccessor) {
            var theValue = valueAccessor();
            $(element).courseSelector({
                disableSearch: ko.observable(true),
                homeCoursesOnly: ko.observable(true),
                selectedCourse: ko.computed({
                    'read': function () {
                        return { CourseId: theValue() };
                    },
                    'write': function (newObj) {
                        theValue(newObj.CourseId);
                    }
                })
            });
            return { controlsDescendantBindings: true };
        }
    };


    //KDB 2012-10-10 Bind to the Marker select control
    //
    //Example usage:
    //
    //      <div data-bind="markerSelect: [MARKER_ID]" /div>
    //
    ko.bindingHandlers.markerSelect = {
        init: function (element, valueAccessor) {
            var _courseName = ko.observable('');
            var _marker = ko.observable(); 
            $(element).courseMarkerSelector({
                selectedMarker: ko.computed({
                    'read': function () {
                        _marker();
                        return {
                            //NZCRDataId
                            MarkerId: valueAccessor().markerId(),
                            PlayDate: valueAccessor().date(),
                            onInvalidMarker: function () {
                                valueAccessor().markerId(null);
                            }
                        };
                    },
                    'write': function (newObj) {
                        valueAccessor().markerId(newObj ? newObj.MarkerId : null);
                        _marker(newObj); 
                    }
                }),
                playDate: valueAccessor().date,
                filterGender: valueAccessor().gender,
                filterNineHole: valueAccessor().nineHole,
                markerSelectOnly: valueAccessor().allowCourseSelect ? false : true,
                homeCoursesOnly: valueAccessor().homeCoursesOnly ? valueAccessor().homeCoursesOnly : ko.observable(true),
                selectedCourse: ko.computed({
                    'read': function () {
                        return { CourseId: valueAccessor().courseId(), CourseName: _courseName() };
                    },
                    'write': function (newObj) {
                        valueAccessor().courseId(newObj.CourseId);
                        _courseName(newObj.Name);
                    }
                }),
                onReady: valueAccessor().onReady,
                disabled: valueAccessor().disabled,
                clearOnInvalidMarker: valueAccessor().clearOnInvalidMarker,
                markerDefaultData: valueAccessor().markerDefaultData,
                templateFileName: valueAccessor().templateFileName,
                filterByMarkerIDs: valueAccessor().filterByMarkerIDs,
                onSelected: valueAccessor().onSelected,
                includeClubs: valueAccessor().includeClubs
            });

            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                $(element).courseMarkerSelector("cancelLoad");
            });
            return { controlsDescendantBindings: true };
        }
    };

    ko.bindingHandlers.playerDetailsControl = {
        init: function (element, valueAccessor) {
            $(element).playerDetails(valueAccessor());
            return { controlsDescendantBindings: true };
        }
    };

    ko.bindingHandlers.feeSelectorControl = {
        init: function (element, valueAccessor) {
            var val = valueAccessor();
            $(element).feeSelector(val.props, val.options);
            return { controlsDescendantBindings: true };
        }
    };

    ko.bindingHandlers.competitionSelectControl = {
        init: function (element, valueAccessor) {
            var val = valueAccessor();
            $(element).competitionSelect(val.props, val.options);

            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                $(element).competitionSelect("dispose");
            });
            return { controlsDescendantBindings: true };
        }
    };
    
    ko.bindingHandlers.feeSummaryControl = {
        init: function (element, valueAccessor) {
            $(element).feeSummary(valueAccessor());
            return { controlsDescendantBindings: true };
        }
    };
    
    ko.bindingHandlers.compositeMarkerSelectorControl = {
        init: function (element, valueAccessor) {
            $(element).compositeMarkerSelector(valueAccessor());
            return { controlsDescendantBindings: true };
        }
    };

    //KDB 2012-10-26 Textbox watermark
    //
    //Example usage:
    //
    //      <input type="text" data-bind="value: [OBSERVABLE], watermark: 'Your first name'" />
    //
    ko.bindingHandlers.watermark = {
        init: function (element, valueAccessor, allBindingsAccessor) {
            var value = valueAccessor();
            var defaultWatermark = ko.utils.unwrapObservable(value);
            var $element = $(element);

            setTimeout(function () {
                $element.val(defaultWatermark);
            }, 0);

            $element.focus(
                function () {
                    if ($element.val() === defaultWatermark) {
                        $element.val("");
                    }
                }).blur(function () {
                    if ($element.val() === '') {
                        $element.val(defaultWatermark);
                    }
                });
        }
    };
    
    //
    // PEC - 2013-05-27
    // Example usage:
    // <input type="text" data-bind="value: [OBSERVABLE], selectText: true" />
    // This will set focus and select all the text in the textbox
    ko.bindingHandlers.selectText = {
        init: function (element, valueAccessor, allBindingsAccessor) {
            var value = valueAccessor();
            var selectAllValue = ko.utils.unwrapObservable(value);
            var $element = $(element);

            if (selectAllValue) {
                setTimeout(function() {
                    $element.focus().select();
                }, 0);
            }
        }
    };
    
    //DFH 2013-03-20 This extender prevents binding of any child elements
    //Useful when binding on demand e.g. only bind on mouse over
    //
    //
    ko.bindingHandlers.stopBinding = {
        init: function (element, valueAccessor) {

            var value = valueAccessor();
            if (ko.isObservable(value))
                value = ko.utils.unwrapObservable(valueAccessor());

            if (!value)
                $(element).removeAttr('data-bind');

            return { controlsDescendantBindings: value };
        }
    };
    ko.virtualElements.allowedBindings.stopBinding = true;


    //DFH 2015-04-15 This extender allows you to do a tablesorter-like heading click to sort on any table that is bound to a KO observable array.
    //This was heavily modified from someone else's code online, but I can't find the link.
    var allSorts = [];
    ko.bindingHandlers.sort = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            element.style.cursor = 'pointer';
            $(element).addClass('canbesorted');


            element.onclick = function () {
                var value = valueAccessor();
                var prop = value.prop;
                var data = value.arr;

                $('.sorted').not(element).removeClass('sorted').removeClass('up').removeClass('down');
                $(element).addClass('sorted');

                var asc = false;
                if ($(element).hasClass('down') || $(element).hasClass('up'))
                    asc = $(element).hasClass('down');
                asc = !asc;
                if (asc)
                    $(element).removeClass('up').addClass('down');
                else
                    $(element).addClass('up').removeClass('down');

                data.sort(function (left, right) {
                    var rec1 = left;
                    var rec2 = right;

                    if (!asc) {
                        rec1 = right;
                        rec2 = left;
                    }

                    var props = prop.split('.');
                    for (var i=0; i<props.length; i++) {
                        var propName = props[i];
                        var parenIndex = propName.indexOf('()');
                        if (parenIndex > 0) {
                            propName = propName.substring(0, parenIndex);
                            rec1 = rec1[propName]();
                            rec2 = rec2[propName]();
                        } else {
                            rec1 = rec1[propName];
                            rec2 = rec2[propName];
                        }
                    }

                    //Check for numbers only sorts:
                    var re = new RegExp(/^[+-]?\d+([.]\d+){0,1}$/);
                    if (re.exec('' + rec1) && re.exec('' + rec2))
                        return +rec1 == +rec2 ? 0 : +rec1 < +rec2 ? -1 : 1;

                    var r = ('' + rec1).toUpperCase() == ('' + rec2).toUpperCase() ? 0 : ('' + rec1).toUpperCase() < ('' + rec2).toUpperCase() ? -1 : 1;
                    if (r != 0) return r;

                    return rec1 == rec2 ? 0 : rec1 < rec2 ? -1 : 1;
                });
            };
        }
    };


    //DFH 2012-08-10 This extender creates two-way bindings for values in your viewmodel
    //based on the .NET standard format strings.  I haven't implemented all of them yet, but
    //for now we have money and floats.
    //
    //Example usage:
    //  data-bind="value: AmountPercent.extend({ format: 'P0' })"
    //
    ko.extenders.format = function (target, formatString) {
        var formatDecimal = function (val, mapping) {
            if (val == null) return null;
            val = isNaN(val) ? 0 : ((mapping.multiplier || 1) * parseFloat(+val));
            return val.toFixed(mapping.precision || 0) + (mapping.suffix || '');
        };
        var parseDecimal = function (str, mapping) {
            if (str == null || str == '') return null;
            str = ('' + str).replace((mapping.suffix || ''), '');
            return (isNaN(str) ? 0 : parseFloat(+str)).toFixed(mapping.precision) / (mapping.multiplier || 1);
        };
        var formatMoney = function (val, mapping) {
            //DFH Adapted from dgNumberToMoney; this version supports different precisions.
            if (mapping.precision=='' || mapping.precision == null || mapping.precision == undefined) mapping.precision = 2;
            var i = isNaN(val) ? 0 : ((mapping.multiplier || 1) * parseFloat(+val));
            if (isNaN(i)) { i = 0.0; }
            var minus = '';
            if (i < 0) { minus = '-'; }
            i = Math.abs(i);
            i = parseInt((i + (.5 / Math.pow(10, mapping.precision))) * Math.pow(10, mapping.precision));
            i = i / Math.pow(10,mapping.precision);

            var str = minus + new String(i) + (mapping.suffix || '');
            if (mapping.precision > 0) {
                if (str.indexOf('.') == -1) str += '.';
                while (str.indexOf('.') > str.length - mapping.precision -1)
                    str += '0';
            }
            return str;
        };
        var parseMoney = function (val, mapping) {
            if (mapping.precision == '') mapping.precision = 2;
            return parseDecimal(('' + val).replace('$', ''), {
                precision: mapping.precision
            });
        };

        var mappings = [{
            regex: '^[pP]([0-9]?)$',
            format: formatDecimal,
            parse: parseDecimal,
            multiplier: 100.0,
            suffix: ''
        }, {
            regex: '^[fF]([0-9]?)$',
            format: formatDecimal,
            parse: parseDecimal,
            suffix: ''
        }, {
            regex: '^[C]([0-9]?)$',
            format: formatMoney,
            parse: parseMoney
        }];

        var mapping = null;
        for (var i = 0; i < mappings.length; i++) {
            var match = new RegExp(mappings[i].regex).exec(formatString);
            if (match) {
                mapping = mappings[i];
                mapping.precision = match[1];
                mapping.formatString = formatString;
                break;
            }
        }
        if (mapping == null) {
            throw new Error('Unrecognised format string: ' + formatString);
        }

        var result = ko.computed({
            read: function () {
                return mapping.format(target(), mapping);
            },
            write: function (newValue) {
                var valueToWrite = mapping.parse(newValue, mapping);
                var current = target();
                //only write if it changed
                if (valueToWrite !== current) {
                    target(valueToWrite);
                } else {
                    //if the rounded value is the same, but a different value was written, force a notification for the current field
                    if (newValue !== current) {
                        target.notifySubscribers(valueToWrite);
                    }
                }
            }
        });

        //return the new computed observable
        return result;
    };

    ko.extenders.parScoreToString = function (target) {
        return ko.computed(function () {
            if (target() == null || target() == undefined) return '';
            if (target() === -1) return "-";
            if (target() === 1) return "+";
            if (target() === 0) return "0";
            return "";
        });
    };

    //KDB 2012-09-26 This extender creates two-way bindings for values in your viewmodel
    //based on a boolean to indicate whether it's a handicap index or not
    //
    //Example usage:
    //  data-bind="value: Handicap.extend({ handicapIndex: true })"
    //
    ko.extenders.handicapIndex = function (target, isIndex) {
        var result = ko.computed({
            read: function () {
                var val = (target() || target() == 0) ? target() : null;

                if (val == null) return null;

                if (isIndex) {
                    if (val < 0) {
                        return '+' + (val * -1).toFixed(1).replace(/\-/, '+');
                    }

                    return val.toFixed(1).replace(/\-/, '+'); ;
                } else {
                    if (val < 0) {
                        return '+' + parseInt(val * -1).toString().replace(/\-/, '+');
                    }

                    return val.toString().replace(/\-/, '+');
                }
            },
            write: function (newValue) {
                var valueToWrite, strVal;
                strVal = newValue.replace(/\+/, '-');

                if (isIndex) {
                    valueToWrite = parseFloat(parseFloat(strVal).toFixed(1)); // new Number(parseFloat(strVal).toFixed(1));
                } else {
                    valueToWrite = parseInt(strVal);
                }

                var current = target() ? parseFloat(parseFloat(target().toString().replace(/\+/, '-')).toFixed(1)) : null;
                //only write if it changed
                if (valueToWrite !== current) {
                    target(isNaN(valueToWrite) ? null : valueToWrite);
                }
            }
        });

        //return the new computed observable
        return result;
    };


    //DFH 2012-10-24 Knockout one-way binding to convert dates to strings.
    //This uses date.js internally to do the actual conversions.  Here is a list of
    //format specifiers:
    //
    //(See http://code.google.com/p/datejs/ for more details)
    //
    //  Format	Description	Example
    //  s	    The seconds of the minute between 0-59.	                        "0" to "59"
    //  ss	    The seconds of the minute with leading zero if required.	    "00" to "59"
    //  m	    The minute of the hour between 0-59.	                        "0" or "59"
    //  mm	    The minute of the hour with leading zero if required.	        "00" or "59"
    //  h	    The hour of the day between 1-12.	                            "1" to "12"
    //  hh	    The hour of the day with leading zero if required.	            "01" to "12"
    //  H	    The hour of the day between 0-23.	                            "0" to "23"
    //  HH	    The hour of the day with leading zero if required.	            "00" to "23"
    //  d	    The day of the month between 1 and 31.	                        "1" to "31"
    //  dd	    The day of the month with leading zero if required.	            "01" to "31"
    //  ddd	    Abbreviated day name. Date.CultureInfo.abbreviatedDayNames.	    "Mon" to "Sun"
    //  dddd	The full day name. Date.CultureInfo.dayNames.	                "Monday" to "Sunday"
    //  M	    The month of the year between 1-12.	                            "1" to "12"
    //  MM	    The month of the year with leading zero if required.	        "01" to "12"
    //  MMM	    Abbreviated month name. Date.CultureInfo.abbreviatedMonthNames.	"Jan" to "Dec"
    //  MMMM	The full month name. Date.CultureInfo.monthNames.	            "January" to "December"
    //  yy	    Displays the year as a two-digit number.	                    "99" or "07"
    //  yyyy	Displays the full four digit year.	                            "1999" or "2007"
    //  t	    Displays the first character of the A.M./P.M. designator. Date.CultureInfo.amDesignator or Date.CultureInfo.pmDesignator	 "A" or "P"
    //  tt	    Displays the A.M./P.M. designator. Date.CultureInfo.amDesignator or Date.CultureInfo.pmDesignator	 "AM" or "PM"
    //  S	    The ordinal suffix ("st, "nd", "rd" or "th") of the current day.	 "st, "nd", "rd" or "th"
    //
    //Example usage:
    //  data-bind="value: SlotDateTime.extend({ formatDateTime: 'd/MM/yyyy H:mm' })"
    //
    ko.extenders.formatDateTime = function (target, formatString) {

        if (typeof Date.today == 'undefined')
            throw new Error('You need date.js loaded to use the formatDateTime binding.');

        var format = function (val) {
            if (val == null) return null;

            return val.toString(formatString);
        };

        var result = ko.computed({
            read: function () {
                return format(target());
            }
        });

        //return the new computed observable
        return result;
    };

    // KDB 2012-10-26: Workaround for ko.observables to work better with Date objects
    //Ref from google group response:
    //    The same is with any object. Not only dates. And if you look into Knockout source this is by design,
    //    It only compares primitive types when you update observable.
    //    The problem is that there is no way to compare objects in general.
    //    For example, when you have object a = { foo: 'bar' };
    //    You may update observable either with new object b = {foo: 'boo'}. Or change initial object a.foo = 'boo' and update observable with it. So here is the problem what to do - compare references or contents of object,
    //    With dates it is the same - you may change intial date object property - and that would be another date but same date object.
        
    ko.observableDate = function(initialValue) {
        if (!(initialValue instanceof Date)) throw new Error('observableDate requires a JS Date object');
        var result = ko.observable(initialValue);

        ko.utils.extend(result, ko.observableDate['fn']);

        return result;
    };

    ko.observableDate['fn'] = {
        "equalityComparer": function (a, b) {
            if (!(b instanceof Date)) throw new Error('observableDate requires a JS Date object');
            return a.getTime() === b.getTime();
        }
    };
    
})();



// By: Hans Fjällemark and John Papa
// https://github.com/CodeSeven/KoLite
//
// Knockout.DirtyFlag
//
// John Papa 
//          http://johnpapa.net
//          http://twitter.com/@john_papa
//
// Depends on scripts:
//          Knockout 
//
//  Notes:
//          Special thanks to Steve Sanderson and Ryan Niemeyer for 
//          their influence and help.
//
//  Usage:      
//          To Setup Tracking, add this tracker property to your viewModel    
//              ===> viewModel.dirtyFlag = new ko.DirtyFlag(viewModel.model);
//
//          Hook these into your view ...
//              Did It Change?          
//              ===> viewModel.dirtyFlag().isDirty();
//
//          Hook this into your view model functions (ex: load, save) ...
//              Resync Changes
//              ===> viewModel.dirtyFlag().reset();
//
//          Optionally, you can pass your own hashFunction for state tracking.
//
////////////////////////////////////////////////////////////////////////////////////////
; (function (ko) {
    ko.DirtyFlag = function (objectToTrack, isInitiallyDirty, hashFunction) {

        hashFunction = hashFunction || ko.toJSON;

        var
            self = this,
            _objectToTrack = objectToTrack,
            _lastCleanState = ko.observable(hashFunction(_objectToTrack)),
            _isInitiallyDirty = ko.observable(isInitiallyDirty),

            result = function () {
                self.forceDirty = function () {
                    _isInitiallyDirty(true);
                };

                self.isDirty = ko.computed(function () {
                    return _isInitiallyDirty() || hashFunction(_objectToTrack) !== _lastCleanState();
                });

                self.reset = function () {
                    _lastCleanState(hashFunction(_objectToTrack));
                    _isInitiallyDirty(false);
                };
                return self;
            };

        return result;
    };
})(ko);





// Github repository: https://github.com/One-com/knockout-dragdrop
// License: standard 3-clause BSD license https://raw.github.com/One-com/knockout-dragdrop/master/LICENCE

/**
 * This binding selects the text in a input field or a textarea when the
 * field get focused.
 *
 * Usage:
 *     <input type="text" data-bind="selectOnFocus: true">
 *     Selects all text when the element is focused.
 *
 *     <input type="text" data-bind="selectOnFocus: /^[^\.]+/">
 *     Selects all text before the first period when the element is focused.
 *
 *     <input type="text" data-bind="selectOnFocus: { pattern: /^[^\.]+/, onlySelectOnFirstFocus: true }">
 *     Only select the pattern on the first focus.
 */
(function (factory) {
    if (typeof define === "function" && define.amd) {
        // AMD anonymous module with hard-coded dependency on "knockout"
        define(["knockout"], factory);
    } else {
        // <script> tag: use the global `ko` and `jQuery`
        factory(ko);
    }
})(function (ko) {
    function getOptions(valueAccessor) {
        var options = ko.utils.unwrapObservable(valueAccessor());
        if (options.pattern) {
            return options;
        } else {
            return { pattern: options };
        }
    }

    function selectText(field, start, end) {
        if (field.createTextRange) {
            var selRange = field.createTextRange();
            selRange.collapse(true);
            selRange.moveStart('character', start);
            selRange.moveEnd('character', end);
            selRange.select();
            field.focus();
        } else if (field.setSelectionRange) {
            field.focus();
            field.setSelectionRange(start, end);
        } else if (typeof field.selectionStart !== 'undefined') {
            field.selectionStart = start;
            field.selectionEnd = end;
            field.focus();
        }
    }

    ko.bindingHandlers.selectOnFocus = {
        init: function (element, valueAccessor) {
            var firstFocus = true;
            ko.utils.registerEventHandler(element, 'focus', function (e) {
                setTimeout(function () {
                    var options = getOptions(valueAccessor);
                    var pattern = ko.utils.unwrapObservable(options.pattern);
                    var onlySelectOnFirstFocus = ko.utils.unwrapObservable(options.onlySelectOnFirstFocus);

                    if (!onlySelectOnFirstFocus || firstFocus) {
                        if (Object.prototype.toString.call(pattern) === '[object RegExp]') {
                            var matchInfo = pattern.exec(element.value);
                            if (matchInfo) {
                                var startOffset = matchInfo.index,
                                    endOffset = matchInfo.index + matchInfo[0].length;
                                selectText(element, startOffset, endOffset);
                            }
                        } else {
                            element.select();
                        }
                        firstFocus = false;
                    }
                }, 1);
            });
        }
    };

    ko.bindingHandlers.filterDecimal = {
        init: function (element, valueAccessor) {
            if (valueAccessor())
                $(element).filter_input({ regex: '[0-9.]', live: true });
                $(element).val(Number($(element).val()).toFixed(2));
        }
    };

    ///calls jquery load on the bound element using a 'url' parameter
    //e.g. data-bind='load: { url: "/Controls/MyScoresWhs.html", data: $data }'
    ko.bindingHandlers.load = {
        init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {

            $(element).removeAttr("data-bind");
            ko.cleanNode(element);

            var url = valueAccessor().url
            if (url.indexOf('?') > -1) {
                url += "&rev=" + "263";
            } else {
                url += "?rev=" + "263";
            } 
            $(element).load(url, function () {
                ko.applyBindings(valueAccessor().data, $(element)[0]);
            });
            return { controlsDescendantBindings: true };
        },
    };
});