PageSourceSearch

https://www.renfrewshiregolfunion.co.uk/shared/js/knockout.dotgolf.extensions.js?rev=441

js renfrewshiregolfunion.co.uk collected 2026-09-27 15:21:28 UTC 68,968 bytes, 1,590 lines download raw bytes

1
2
3(function () {
4    if (!ko) {
5        throw new Error('knockout.dotgolf.extensions.js requires Knockout to be loaded first.');
6        return;
7    }
8
9
10    //DFH 2012-07-06 adds our standard plural() function so that you can bind to observable strings, formatting them at the same time.
11    //Example usage:
12    //
13    //   <span data-bind="text: rowsAffected.plural('{0} row[s] affected, so {0} [person/people] may be unhappy.')"></span>
14    //
15    ko.observable.fn.plural = function (formatString, zeroText) {
16        return ko.computed(function () {
17            var val = this();
18
19            return formatString.plural(val, zeroText);
20        }, this);
21    };
22
23    ko.observableArray.fn.liveitems = function () {
24        return ko.utils.arrayFilter(this(), function (item) {
25            if (typeof item._destroy == 'function')
26                return !item._destroy();
27
28            return !item._destroy;
29        });
30    };
31
32    //DFH 2012-07-24 adds a groupby() function for knockout observable arrays.  Grabbed this from http://stackoverflow.com/questions/9877301/knockoutjs-observablearray-data-grouping.
33    //The cool thing is that this can groupby based on a property on the object.
34    //
35    //Example usage:
36    //
37    //  this.people = ko.observableArray([
38    //     new Person("Jimmy", "Friend"),
39    //     new Person("George", "Friend"),
40    //     new Person("Zippy", "Enemy")
41    //  ]).groupby('type');
42    //
43    ko.observableArray.fn.groupby = function (prop, excludeDestroyed) {
44        var target = this;
45
46        if (target.index && target.index[prop]) return target; //Group by already set up, bail out.
47
48        target.index = {};
49        target.index[prop] = ko.observable({});
50
51        ko.computed(function () {
52            //rebuild index
53            var propIndex = {};
54
55            ko.utils.arrayForEach(excludeDestroyed ? target.liveitems() : target(), function (item) {
56                var key = '' + ko.utils.unwrapObservable(item[prop]);
57                if (key) {
58                    propIndex[key] = propIndex[key] || [];
59                    propIndex[key].push(item);
60                }
61            });
62
63            target.index[prop](propIndex);
64        });
65
66        return target;
67    };
68
69    ko.observableArray.fn.distinct = function (prop, excludeDestroyed) {
70        var target = this.groupby(prop, excludeDestroyed);
71        var distinctArray = ko.observableArray();
72
73        ko.computed(function () {
74            distinctArray([]);
75            $.each(target.index[prop](), function (idx, item) {
76                distinctArray.push(item[0]);
77            });
78        });
79
80        return distinctArray;
81    };
82
83    //DFH 2015-09-15 Computes the sum of any property on an observable array.  You can invoke it two ways:
84    //
85    //  myArray.sum('Amount');  //Returns the sum of the Amount property across all items
86    //OR
87    //  myArray.sum(function() { return this.Amount(); });  //Uses an inline function to do the same - useful for conversions/parsing of properties.
88    //
89    ko.observableArray.fn.sum = function (propertyNameOrFunction) {
90        var target = this;
91
92        return ko.computed(function () {
93            var total = 0;
94
95            ko.utils.arrayForEach(target.liveitems(), function (item) {
96
97                var val = null;
98                if (typeof propertyNameOrFunction != 'function')
99                    val = +ko.utils.unwrapObservable(item[propertyNameOrFunction]);
100                else
101                    val = +propertyNameOrFunction.apply(item);
102
103                if (val)
104                    total += val;
105            });
106
107            return total;
108        });
109    };
110
111    //DFH 2012-07-10 adds a timepicker binding so you can have time pickers in your views.  I have pilfered
112    //some of this from the interwebz - http://stackoverflow.com/questions/6399078/knockoutjs-databind-with-jquery-ui-datepicker
113    //Example usage:
114    //
115    //  <input type="text" data-bind="timepicker: StartTime" />
116    //
117    ko.bindingHandlers.timepicker = {
118        init: function (element, valueAccessor, allBindingsAccessor) {
119            //initialize datepicker with some optional options
120            var options = allBindingsAccessor().timepickerOptions || {
121                ampm: false,
122                showAnim: 'fadeIn',
123                minute: 00,
124                hourGrid: 6,
125                minuteGrid: 15,
126                addSliderAccess: true,
127                sliderAccessArgs: { touchonly: false },
128                showOn: "both",
129                buttonImage: "/img/clock.png",
130                buttonImageOnly: true
131            };
132
133            if (!$(element).hasClass('hasDatepicker')) {
134                $(element).addClass('isTimePicker').timepicker(options);
135
136                //handle the field changing
137                ko.utils.registerEventHandler(element, "change", function () {
138                    var observable = valueAccessor();
139                    observable($(element).timepicker("getDate").val());
140                });
141
142                //handle disposal (if KO removes by the template binding)
143                ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
144                    $(element).timepicker("destroy");
145                });
146            }
147        },
148        update: function (element, valueAccessor) {
149            var value = ko.utils.unwrapObservable(valueAccessor());
150
151            $(element).val(value);
152
153            if ($(element).hasClass('hasDatepicker')) {
154                var current = $(element).timepicker("getDate");
155
156                if (value != current.val())
157                    $(element).timepicker("setDate", dgStringToDateTime(value));
158            }
159        }
160    };
161
162    //DFH 2012-07-10 adds a datepicker binding so you can have date pickers in your views.  Adapted from the timepicker binding above.
163    //Example usage:
164    //
165    //  <input type="text" data-bind="datepicker: ResignedDate" />
166    //
167    ko.bindingHandlers.datepicker = {
168        init: function (element, valueAccessor, allBindingsAccessor) {
169
170            //initialize datepicker with some optional options
171            var options = $.extend({
172                /*minDate: 0,*/
173                dateFormat: 'd/mm/yy',
174                showOn: 'both',
175                buttonImage: '/img/calendar_view_week.png',
176                buttonImageOnly: true,
177                showAnim: 'fadeIn'
178            }, allBindingsAccessor().datepickerOptions || {});
179
180            //DFH 2012-10-19 Special hacks for Internet Exploder to make the popup disappear once the user clicks.
181            var oldOnClose = options.onClose;
182            options.onClose = function (dateText, inst) {
183                if (typeof oldOnClose == 'function') oldOnClose(dateText, inst);
184                $(this).change();
185            };
186            var oldOnSelect = options.onSelect;
187            options.onSelect = function (dateText, inst) {
188                if (typeof oldOnSelect == 'function') oldOnSelect(dateText, inst);
189                $(this).blur();
190            };
191
192            if (!$(element).hasClass('hasDatepicker')) {
193
194                $(element).addClass('isDatePicker').before('<span class="date-picker-day-span"></span>').datepicker(options);
195
196                //handle the field changing
197                ko.utils.registerEventHandler(element, "change", function () {
198                    var observable = valueAccessor();
199                    observable($(element).datepicker("getDate"));
200                });
201
202                //handle disposal (if KO removes by the template binding)
203                ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
204                    $(element).datepicker("destroy");
205                });
206            }
207            
208            if (typeof allBindingsAccessor().disable == 'function') {
209                $(element).datepicker(allBindingsAccessor().disable() ? 'disable' : 'enable');
210                allBindingsAccessor().disable.subscribe(function () {
211                    $(element).datepicker(allBindingsAccessor().disable() ? 'disable' : 'enable');
212                });
213            }
214        },
215        update: function (element, valueAccessor) {
216            var value = ko.utils.unwrapObservable(valueAccessor());
217
218            //$(element).val(value+'');
219            if ($(element).hasClass('hasDatepicker')) {
220                var current = $(element).datepicker('getDate');
221
222                if (value != current) {
223                    $(element).datepicker('setDate', value);
224
225                    var theDate = new Date(value);
226                    var str = $.datepicker.formatDate('D', theDate);
227                    var full = $.datepicker.formatDate('DD, MM d, yy', theDate);
228                    $(element).attr('title', full).prev('.date-picker-day-span').text(str);
229                }
230            }
231        }
232    };
233
234    //DFH 2012-07-10 Automatically adds a LABEL element after your checkboxes, linking
235    //it with an ID (which is autogenerated if not specified on the input element).
236    //
237    //Example usage:
238    //
239    //  <input type="checkbox" data-bind="label: 'This text goes on the label'" />
240    //
241    ko.bindingHandlers.label = {
242        init: function (element, valueAccessor, allBindingsAccessor) {
243            ko.bindingHandlers.label.counter = ko.bindingHandlers.label.counter || 0;
244            element.id = element.id || 'ko_ux_id_' + (++ko.bindingHandlers.label.counter);
245            var lbl = $('<LABEL/>').text(valueAccessor()).attr('for', element.id);
246            $(element).after(lbl);
247        },
248        update: function (element, valueAccessor) {
249            var lbl = $(element).next('LABEL');
250            lbl.text(valueAccessor());
251        }
252    };
253
254    //DFH 2012-07-31 Like the visible binding, but rather fades the element(s) in question
255    //in or out when the property changes.  Loosely based on some article I found on the net.
256    ko.bindingHandlers.fadeVisible = {
257        init: function (element, valueAccessor) {
258            // Initially set the element to be instantly visible/hidden depending on the value
259            var value = valueAccessor();
260            $(element).toggle(ko.utils.unwrapObservable(value)); // Use "unwrapObservable" so we can handle values that may or may not be observable
261        },
262        update: function (element, valueAccessor) {
263            // Whenever the value subsequently changes, slowly fade the element in or out
264            var value = valueAccessor();
265            ko.utils.unwrapObservable(value) ? $(element).fadeIn() : $(element).fadeOut();
266        }
267    };
268
269    //KDB 2012-09-07 Like the visible binding, but rather slides the element(s) in question
270    //in or out when the property changes.  Exact copy from the koJS website.
271    //
272    //Example usage:
273    //
274    //  <div data-bind="slideVisible: trueOrfalse, slideDuration:600">This div will slide down or op</div>
275    //
276    ko.bindingHandlers.slideVisible = {
277        update: function (element, valueAccessor, allBindingsAccessor) {
278            // First get the latest data that we're bound to
279            var value = valueAccessor(), allBindings = allBindingsAccessor();
280
281            // Next, whether or not the supplied model property is observable, get its current value
282            var valueUnwrapped = ko.utils.unwrapObservable(value);
283
284            // Grab some more data from another binding property
285            var duration = allBindings.slideDuration || 400; // 400ms is default duration unless otherwise specified
286            var scrollTo = allBindings.scrollTo; // Scroll to object with params
287            var callback = function () { return; };
288            if (scrollTo) {
289                $.extend(scrollTo, { container: window, target: element, duration: 500 });
290                callback = function() {
291                    $(scrollTo.container).scrollTo(scrollTo.target, scrollTo.duration);
292                };
293            }
294
295            // Now manipulate the DOM element
296            if (valueUnwrapped == true)
297                $(element).slideDown(duration, callback); // Make the element visible
298            else
299                $(element).slideUp(duration);   // Make the element invisible
300        }
301    };
302
303    //KDB 2013-06-04 Alternates the given CSS Class
304    //
305    // Params:
306    // 
307    // trigger - A Boolean observable that will trigger the start
308    // speed - The speed in milliseconds to alternate at
309    // delay - Milliseconds to delay the start 
310    // duration - Time in milliseconds to toggle the class for, default = 0 = forever
311    //
312    //Example usage:
313    //
314    //  <div class="normal" data-bind="alternateCssClass: 'flash-on', trigger: trigger, speed: speed, delay: 3000"></div>
315    //
316
317    ko.bindingHandlers.alternateCssClass = {
318        init: function (element, valueAccessor, allBindingsAccessor) {
319            
320            // First get the latest data that we're bound to
321            var value = valueAccessor(), allBindings = allBindingsAccessor();
322
323            // Next, whether or not the supplied model property is observable, get its current value
324            var valueUnwrapped = ko.utils.unwrapObservable(value);
325
326            // Grab some more data from another binding property
327            var trigger = allBindings.trigger || false;
328            var triggerUnwrapped = ko.utils.unwrapObservable(trigger);
329            if (typeof ko.utils.unwrapObservable(trigger) !== 'boolean') {
330                console.error('alternateCssClass requires a trigger of type boolean to function correctly!');
331                return;
332            }
333            var delay = allBindings.delay || 0; // 0ms is default delay unless otherwise specified
334            var speed = allBindings.speed || 500; // 500ms is default speed unless otherwise specified
335            var duration = allBindings.duration || 0; // 0ms is default duration unless otherwise specified
336            
337            var alternateTimer = null;
338            var durationTimer = null;
339            var alternateTimerStart = function (delayStart) {
340                alternateTimer = setTimeout(
341                    function () {
342                        //console.log('class alternated');
343                        $(element).toggleClass(valueUnwrapped);
344                        alternateTimerStart();
345                        if (!durationTimer && ko.utils.unwrapObservable(duration) > 0) {
346                            durationTimer = setTimeout(function () {
347                                if (ko.isObservable(trigger)) {
348                                    trigger(false);
349                                } else {
350                                    alternateTimerStop();
351                                }
352                            }, ko.utils.unwrapObservable(duration));
353                        }
354                    },
355                    delayStart ? delayStart : ko.utils.unwrapObservable(speed));
356            };
357
358            var alternateTimerStop = function () {
359                $(element).removeClass(valueUnwrapped);
360                clearTimeout(alternateTimer);
361                alternateTimer = null;
362                clearTimeout(durationTimer);
363                durationTimer = null;
364            };
365
366            if (ko.isObservable(trigger)) {               
367                trigger.subscribe(function (triggerVal) {
368                    if (triggerVal && !alternateTimer) {
369                        //console.log('alternateCssClass Start');
370                        alternateTimerStart(ko.utils.unwrapObservable(delay));
371                    } else {
372                        //console.log('alternateCssClass Stop');
373                        alternateTimerStop();
374                    }
375                });
376                
377                //start the time if the default is true
378                if (triggerUnwrapped) {
379                    alternateTimerStart(ko.utils.unwrapObservable(delay));
380                }
381                
382            } else if (trigger) {
383                alternateTimerStart(ko.utils.unwrapObservable(delay));
384            }
385        }
386    };
387
388    //KDB 2012-09-10 Styles a checkbox or radio with the fancy styles
389    //
390    //Example usage:
391    //
392    //  <label for="myTextbox">
393    //      My Radio Label
394    //      <input type="radio" value="some-value-a" id="myTextbox" data-bind="fancyControl: <observable>" />
395    //  </label>
396    //
397    //  IMPORTANT NOTE: If you are binding to a set of radios, you will need to return
398    //  the corresponding VALUE from your observable, NOT true or false as with a checkbox.
399    //
400    if (ko.version >= '3.4') {
401        ko.bindingHandlers['fancyControl'] = {
402            'after': ['value', 'attr'],
403            'init': function (element, valueAccessor, allBindings) {
404                if ($(element).attr('type') !== 'radio' && $(element).attr('type') !== 'checkbox') return;
405
406                if ($(element).attr('type') == 'radio' && $(element).closest('.fancyControlGroup').length != 0) {
407                    var controlGroup = $(element).closest('.fancyControlGroup').attr('fancyControlGroupId');
408                    var controlId = $(element).attr('id');
409                    $(element).closest('label').attr('for', controlGroup + '_' + controlId);
410                    $(element).attr('id', controlGroup + '_' + controlId).attr('name', controlGroup);
411                }
412
413                var checkedValue = ko.pureComputed(function () {
414                    // Treat "value" like "checkedValue" when it is included with "checked" binding
415                    if (allBindings['has']('checkedValue')) {
416                        return ko.utils.unwrapObservable(allBindings.get('checkedValue'));
417                    } else if (allBindings['has']('value')) {
418                        return ko.utils.unwrapObservable(allBindings.get('value'));
419                    }
420
421                    return element.value;
422                });
423
424                function updateModel() {
425                    // This updates the model value from the view value.
426                    // It runs in response to DOM events (click) and changes in checkedValue.
427                    var isChecked = element.checked,
428                        elemValue = useCheckedValue ? checkedValue() : isChecked;
429
430                    // When we're first setting up this computed, don't change any model state.
431                    if (ko.computedContext.isInitial()) {
432                        return;
433                    }
434
435                    // We can ignore unchecked radio buttons, because some other radio
436                    // button will be getting checked, and that one can take care of updating state.
437                    if (isRadio && !isChecked) {
438                        return;
439                    }
440
441                    if (valueIsArray) {
442                        var writableValue = rawValueIsNonArrayObservable ? valueAccessor.peek() : valueAccessor;
443                        if (oldElemValue !== elemValue) {
444                            // When we're responding to the checkedValue changing, and the element is
445                            // currently checked, replace the old elem value with the new elem value
446                            // in the model array.
447                            if (isChecked) {
448                                ko.utils.addOrRemoveItem(writableValue, elemValue, true);
449                                ko.utils.addOrRemoveItem(writableValue, oldElemValue, false);
450                            }
451
452                            oldElemValue = elemValue;
453                        } else {
454                            // When we're responding to the user having checked/unchecked a checkbox,
455                            // add/remove the element value to the model array.
456                            ko.utils.addOrRemoveItem(writableValue, elemValue, isChecked);
457                        }
458                        if (rawValueIsNonArrayObservable && ko.isWriteableObservable(valueAccessor)) {
459                            valueAccessor(writableValue);
460                        }
461                    } else {
462                        if (ko.isWriteableObservable(valueAccessor()) && (valueAccessor().peek() !== elemValue)) {
463                            valueAccessor()(elemValue);
464                        }
465                    }
466
467                };
468
469                function updateView() {
470                    valueAccessor();
471                    if ($(element).attr('type') == 'radio' && $(element).closest('.fancyControlGroup').length != 0) {
472                        var controlGroup = $(element).closest('.fancyControlGroup').attr('fancyControlGroupId');
473                        var controlId = $(element).attr('id');
474                        $(element).closest('label').attr('for', controlGroup + '_' + controlId);
475                        $(element).attr('id', controlGroup + '_' + controlId).attr('name', controlGroup);
476                    }
477                    if ($(element).attr('type') == 'radio' || $(element).attr('type') == 'checkbox') {
478                        // This updates the view value from the model value.
479                        // It runs in response to changes in the bound (checked) value.
480                        var modelValue = ko.utils.unwrapObservable(valueAccessor());
481
482                        if (valueIsArray) {
483                            // When a checkbox is bound to an array, being checked represents its value being present in that array
484                            element.checked = ko.utils.arrayIndexOf(modelValue, checkedValue()) >= 0;
485                        } else if (isCheckbox) {
486                            // When a checkbox is bound to any other value (not an array), being checked represents the value being trueish
487                            element.checked = modelValue;
488                        } else {
489                            // For radio buttons, being checked means that the radio button's value corresponds to the model value
490                            element.checked = (checkedValue() == modelValue);
491                        }
492                        $(element).attr('type') == 'radio' ? dgSetFancyRadio($(element)) : dgSetFancyCheckBox($(element));
493                    }
494
495                };
496
497                var isCheckbox = element.type == "checkbox",
498                    isRadio = element.type == "radio";
499
500                // Only bind to check boxes and radio buttons
501                if (!isCheckbox && !isRadio) {
502                    return;
503                }
504
505                var rawValue = valueAccessor(),
506                    valueIsArray = isCheckbox && (ko.utils.unwrapObservable(rawValue) instanceof Array),
507                    rawValueIsNonArrayObservable = !(valueIsArray && rawValue.push && rawValue.splice),
508                    oldElemValue = valueIsArray ? checkedValue() : undefined,
509                    useCheckedValue = isRadio || valueIsArray;
510
511                // IE 6 won't allow radio buttons to be selected unless they have a name
512                if (isRadio && !element.name)
513                    ko.bindingHandlers['uniqueName']['init'](element, function () { return true });
514
515                // Set up two computeds to update the binding:
516
517                // The first responds to changes in the checkedValue value and to element clicks
518                ko.computed(updateModel, null, { disposeWhenNodeIsRemoved: element });
519                ko.utils.registerEventHandler(element, "click", updateModel);
520
521                // The second responds to changes in the model value (the one associated with the checked binding)
522                ko.computed(updateView, null, { disposeWhenNodeIsRemoved: element });
523
524                rawValue = undefined;
525
526                $(element).closest('label').addClass($(element).attr('type') == 'radio' ? 'ko fancy_radio' : 'ko fancy_check');
527
528                if (allBindings().disable !== undefined && !ko.isObservable(allBindings().disable))
529                    throw new Error('disable property on fancyControl binding MUST be an observable.');
530
531                if (ko.isObservable(allBindings().disable)) {
532                    $(element).attr('disabled', !!allBindings().disable());
533                    allBindings().disable.subscribe(function () {
534                        $(element).attr('disabled', !!allBindings().disable());
535                        $(element).attr('type') == 'radio' ? dgSetFancyRadio($(element)) : dgSetFancyCheckBox($(element));
536                    });
537                }
538            }
539        };
540    } else {
541        ko.bindingHandlers.fancyControl = {
542            init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
543                if ($(element).attr('type') == 'radio' || $(element).attr('type') == 'checkbox') {
544                    //KDB 2013-05-10 Check for radio button grouping
545                    if ($(element).attr('type') == 'radio' && $(element).closest('.fancyControlGroup').length != 0) {
546                        var controlGroup = $(element).closest('.fancyControlGroup').attr('fancyControlGroupId');
547                        var controlId = $(element).attr('id');
548                        $(element).closest('label').attr('for', controlGroup + '_' + controlId);
549                        $(element).attr('id', controlGroup + '_' + controlId).attr('name', controlGroup);
550                    }
551                    ko.bindingHandlers.checked.init(element, valueAccessor, allBindingsAccessor, viewModel);
552                    $(element).closest('label').addClass($(element).attr('type') == 'radio' ? 'ko fancy_radio' : 'ko fancy_check');
553
554                    if (allBindingsAccessor().disable !== undefined && !ko.isObservable(allBindingsAccessor().disable))
555                        throw new Error('disable property on fancyControl binding MUST be an observable.');
556
557                    if (ko.isObservable(allBindingsAccessor().disable)) {
558                        $(element).attr('disabled', allBindingsAccessor().disable());
559                        allBindingsAccessor().disable.subscribe(function () {
560                            $(element).attr('disabled', allBindingsAccessor().disable());
561                            $(element).attr('type') == 'radio' ? dgSetFancyRadio($(element)) : dgSetFancyCheckBox($(element));
562                        });
563                    }
564                }
565            },
566            update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
567                //KDB 2013-05-10 Handle radio button grouping changes
568                if ($(element).attr('type') == 'radio' && $(element).closest('.fancyControlGroup').length != 0) {
569                    var controlGroup = $(element).closest('.fancyControlGroup').attr('fancyControlGroupId');
570                    var controlId = $(element).attr('id');
571                    $(element).closest('label').attr('for', controlGroup + '_' + controlId);
572                    $(element).attr('id', controlGroup + '_' + controlId).attr('name', controlGroup);
573                }
574                if ($(element).attr('type') == 'radio' || $(element).attr('type') == 'checkbox') {
575                    ko.bindingHandlers.checked.update(element, valueAccessor, allBindingsAccessor, viewModel);
576                    $(element).attr('type') == 'radio' ? dgSetFancyRadio($(element)) : dgSetFancyCheckBox($(element));
577                }
578            }
579        };
580    }
581    
582    //KDB 2013-05-10 Dynamic grouping for fancy radio's
583    //
584    //Example usage:
585    //
586    //  <div data-bind="fancyControlGroup: <uniqueGroupId_observable>">
587    //      <fancyRadio>...
588    //      <fancyRadio>...
589    //      <fancyRadio>...
590    //  </div>
591    //
592    //
593    ko.bindingHandlers.fancyControlGroup = {
594        init: function (element, valueAccessor) {
595            $(element).addClass('fancyControlGroup').attr('fancyControlGroupId', 'FCG_'+(ko.isObservable(valueAccessor) ? valueAccessor() : valueAccessor()));
596        },
597        update: function (element, valueAccessor) {
598            $(element).attr('fancyControlGroupId', 'FCG_' + (ko.isObservable(valueAccessor) ? valueAccessor() : valueAccessor()));
599        }
600    };
601
602    //MSK 2020-10-13 Creates a country dropdown
603    //
604    //Example usage:
605    //  Basic (passing in a selectedCountryId observable only):
606    //   <div data-bind="countrySelect: <observable>"></div> )
607    //
608    //  Custom (passing in an options containing countrySelect properties to set):
609    //   <div data-bind="countrySelect: { selectedCountryId: <observable>, excludeDataSharingCountries: true }"></div>
610    //
611    ko.bindingHandlers.countrySelect = {
612        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
613            if (typeof $(document).countrySelect != 'function')
614                throw new Error('countrySelect binding error: The countrySelect binding requires a script reference to the "dotgolf.country-selector.js" file.');
615
616            var options = valueAccessor();
617            if (ko.isObservable(options))
618                options = { selectedCountryId: valueAccessor() };
619
620            $(element).countrySelect(options);
621            return { controlsDescendantBindings: true };
622        }
623    };
624
625    //MSK 2021-09-29 Creates a region dropdown
626    //
627    //Example usage:
628    //  Basic (passing in a selectedRegionId observable only):
629    //   <div data-bind="regionSelect: <observable>"></div> 
630    //  (regions will be from the current system county)
631    //
632    //  Custom (passing in an options containing regionSelect properties to set):
633    //   <div data-bind="regionSelect: { selectedRegionId: <observable>, selectedCountryId: <observable> }"></div>
634    //
635    ko.bindingHandlers.regionSelect = {
636        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
637            if (typeof $(document).regionSelect != 'function')
638                throw new Error('regionSelect binding error: The regionSelect binding requires a script reference to the "dotgolf.region-selector.js" file.');
639
640            var options = valueAccessor();
641            if (ko.isObservable(options))
642                options = { selectedRegionId: valueAccessor() };
643
644            $(element).regionSelect(options);
645            return { controlsDescendantBindings: true };
646        }
647    };
648
649    //DFH 2012-09-26 This extender creates a two-way binding from a string based DOM value to a boolean observable.
650    //Pretty useful if you have a SELECT with two values, 0 and 1, and a backing observable which is a bool.
651    //
652    //Example usage:
653    //
654    //  <SELECT data-bind="value: myBoolObservable.extend({ asBool:true })">
655    //    <OPTION value="0">Some Text (NO)</OPTION>
656    //    <OPTION value="1">Some Text (YES)</OPTION>
657    //  </SELECT>
658    //
659    ko.extenders.asBool = function (target, options) {
660        //options unused right now.
661
662        var result = ko.computed({
663            read: function () {
664                return (target()) == true;
665            },
666            write: function (newValue) {
667                if (newValue == 'true' || newValue == "1" || newValue == "yes" || newValue == "y" || newValue == "Y") {
668                    if (!target()) target(true);
669                } else {
670                    if (target()) target(false);
671                }
672            }
673        });
674
675        //return the new computed observable
676        return result;
677    };
678
679    //GTC 2018-05-16 This extender displays "Yes" for true and "No" for false.    
680    //
681    //Example usage:
682    //
683    //  <td data-bind="YesNo: HasPaid"></td>
684    //
685    ko.bindingHandlers.YesNo = {
686        update: function (element, valueAccessor) {
687            // defaults to false
688            var val = ko.utils.unwrapObservable(valueAccessor()) || false;
689
690            if (val)
691                $(element).text("Yes");
692            else
693                $(element).text("No");
694        }
695    }
696
697    //DFH 2013-01-09 This extender creates a two-way binding which inverts the underlying boolean observable value.
698    //This is useful when working with visible or disabled bindings where your model observable has the inverted meaning.
699    //
700    //Example:
701    //
702    //  <blah data-bind="visible: $root.hidden.extend({ invert: true})"></blah>
703    //
704    ko.extenders.invert = function (target, options) {
705        var result = ko.computed({
706            read: function() {
707                return !target();
708            },
709            write: function(newValue) {
710                target(!newValue);
711            }
712        });
713
714        //return the new computed observable
715        return result;
716    };
717
718
719    //KDB: qTip Validation Messages
720    ko.bindingHandlers.qtipValMessage = {
721        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
722            var observable = valueAccessor(), $element = $(element);
723            if (observable.isValid) {
724                observable.isValid.subscribe(function (valid) {
725                    $element.toggleClass('invalid', !valid);
726                    $element.toggleClass('valid', valid);
727                    if (!valid) {
728                        if (typeof $element.data('qtip') === 'object')
729                            $element.qtip('destroy');
730
731                        $element.qtip({
732                            overwrite: true,
733                            show: {
734                                when: !valid,
735                                ready: true
736                            },
737                            hide: false,
738                            content: ko.unwrap(observable.error),
739                            position: {
740                                corner: {
741                                    target: 'rightMiddle',
742                                    tooltip: 'leftMiddle'
743                                }
744
745                            },
746                            style: {
747                                name: 'red',
748                                tip: {
749                                    corner: 'leftMiddle', // We declare our corner within the object using the corner sub-option
750                                    size: {
751                                        x: 5, // Be careful that the x and y values refer to coordinates on screen, not height or width.
752                                        y: 10 // Depending on which corner your tooltip is at, x and y could mean either height or width!
753                                    }
754                                },
755                                border: {
756                                    width: 2
757                                }
758                            }
759                        });
760                    } else {
761                        if (typeof $element.data('qtip') === 'object') {
762                            $element.qtip('hide');
763                        }
764                    }
765                });
766            }
767        }
768    };
769
770    //KDB: qTip Validation Messages
771    ko.bindingHandlers.qtipValIcon = {
772        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
773            var observable = valueAccessor(), $element = $(element);
774            $element.addClass('qtip-validator');
775            if (observable.isValid) {
776                observable.isValid.subscribe(function (valid) {
777                    $element.toggleClass('invalid', !valid);
778                    $element.toggleClass('valid', valid);
779                    if (!valid) {
780                        $element.qtip({
781                            overwrite: true,
782                            show: {
783                                when: 'click'
784                            },
785                            hide: { when: 'inactive', delay: 3000 },
786                            content: ko.unwrap(observable.error),
787                            position: {
788                                corner: {
789                                    target: 'rightMiddle',
790                                    tooltip: 'leftMiddle'
791                                }
792
793                            },
794                            style: {
795                                name: 'red',
796                                tip: {
797                                    corner: 'leftMiddle', // We declare our corner within the object using the corner sub-option
798                                    size: {
799                                        x: 10, // Be careful that the x and y values refer to coordinates on screen, not height or width.
800                                        y: 10 // Depending on which corner your tooltip is at, x and y could mean either height or width!
801                                    }
802                                },
803                                border: {
804                                    width: 2
805                                }
806                            }
807                        });
808                    } else {
809                        if (typeof $element.data('qtip') === 'object') {
810                            $element.qtip('destroy');
811                        }
812                    }
813                });
814            }
815        }
816    };
817
818    //KDB: qTip Validation Messages
819    ko.bindingHandlers.qtipValGroup = {
820        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
821            var observable = valueAccessor(), $element = $(element);
822            if (observable.isValid) {
823                observable.isValid.subscribe(function (valid) {
824                    $element.toggleClass('invalid', !valid);
825                    $element.toggleClass('valid', valid);
826                    if (!valid) {
827                        $element.qtip({
828                            overwrite: true,
829                            show: {
830                                when: 'click'
831                            },
832                            hide: { when: 'inactive', delay: 3000 },
833                            content: observable.error,
834                            position: {
835                                corner: {
836                                    target: 'rightMiddle',
837                                    tooltip: 'leftMiddle'
838                                }
839
840                            },
841                            style: {
842                                name: 'red',
843                                tip: {
844                                    corner: 'leftMiddle', // We declare our corner within the object using the corner sub-option
845                                    size: {
846                                        x: 5, // Be careful that the x and y values refer to coordinates on screen, not height or width.
847                                        y: 10 // Depending on which corner your tooltip is at, x and y could mean either height or width!
848                                    }
849                                },
850                                border: {
851                                    width: 2
852                                }
853                            }
854                        });
855                    } else {
856                        if (typeof $element.data('qtip') === 'object') {
857                            $element.qtip('destroy');
858                        }
859                    }
860                });
861            }
862        }
863    };
864
865
866    //KDB: Adds an invalid or valid class to any element that fails or passes validation
867    ko.bindingHandlers.cssValGroup = {
868        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
869            var observable = valueAccessor(), $element = $(element);
870            if (observable.isValid) {
871                observable.isValid.subscribe(function (valid) {
872                    $element.toggleClass('invalid', !valid);
873                    $element.toggleClass('valid', valid);
874                });
875            }
876        }
877    };
878
879    //KDB: custom input filter based on regex
880    ko.bindingHandlers.filterRegEx = {
881        init: function (element, valueAccessor) {
882            $(element).filter_input({ regex: valueAccessor(), live: true });
883        }
884    };
885
886    //KDB: only numbers are allowed
887    ko.bindingHandlers.filterNumber = {
888        init: function (element, valueAccessor) {
889            if (valueAccessor())
890                $(element).filter_input({ regex: '[0-9]', live: true });
891        }
892    };
893
894    //KDB: only AlphaNumeric characters are allowed
895    ko.bindingHandlers.filterAlpha = {
896        init: function (element, valueAccessor) {
897            if (valueAccessor())
898                $(element).filter_input({ regex: '[a-zA-Z]', live: true });
899        }
900    };
901
902    //KDB: only AlphaNumeric characters are allowed
903    ko.bindingHandlers.filterAlphaNumeric = {
904        init: function (element, valueAccessor) {
905            if (valueAccessor())
906                $(element).filter_input({ regex: '[a-zA-Z0-9]', live: true });
907        }
908    };
909
910    //KDB: only AlphaNumeric characters are allowed
911    ko.bindingHandlers.filterHandicap = {
912        init: function (element, valueAccessor) {
913            if (valueAccessor())
914                $(element).filter_input({ regex: '[\+0-9\.-]', live: true });
915        }
916    };
917
918    //KDB 2012-10-10 Bind to the Course select control
919    //
920    //Example usage:
921    //
922    //      <div data-bind="courseSelect: [COURSE_ID]" /div>
923    //
924    ko.bindingHandlers.courseSelect = {
925        init: function (element, valueAccessor) {
926            var theValue = valueAccessor();
927            $(element).courseSelector({
928                disableSearch: ko.observable(true),
929                homeCoursesOnly: ko.observable(true),
930                selectedCourse: ko.computed({
931                    'read': function () {
932                        return { CourseId: theValue() };
933                    },
934                    'write': function (newObj) {
935                        theValue(newObj.CourseId);
936                    }
937                })
938            });
939            return { controlsDescendantBindings: true };
940        }
941    };
942
943
944    //KDB 2012-10-10 Bind to the Marker select control
945    //
946    //Example usage:
947    //
948    //      <div data-bind="markerSelect: [MARKER_ID]" /div>
949    //
950    ko.bindingHandlers.markerSelect = {
951        init: function (element, valueAccessor) {
952            var _courseName = ko.observable('');
953            var _marker = ko.observable(); 
954            $(element).courseMarkerSelector({
955                selectedMarker: ko.computed({
956                    'read': function () {
957                        _marker();
958                        return {
959                            //NZCRDataId
960                            MarkerId: valueAccessor().markerId(),
961                            PlayDate: valueAccessor().date(),
962                            onInvalidMarker: function () {
963                                valueAccessor().markerId(null);
964                            }
965                        };
966                    },
967                    'write': function (newObj) {
968                        valueAccessor().markerId(newObj ? newObj.MarkerId : null);
969                        _marker(newObj); 
970                    }
971                }),
972                playDate: valueAccessor().date,
973                filterGender: valueAccessor().gender,
974                filterNineHole: valueAccessor().nineHole,
975                markerSelectOnly: valueAccessor().allowCourseSelect ? false : true,
976                homeCoursesOnly: valueAccessor().homeCoursesOnly ? valueAccessor().homeCoursesOnly : ko.observable(true),
977                selectedCourse: ko.computed({
978                    'read': function () {
979                        return { CourseId: valueAccessor().courseId(), CourseName: _courseName() };
980                    },
981                    'write': function (newObj) {
982                        valueAccessor().courseId(newObj.CourseId);
983                        _courseName(newObj.Name);
984                    }
985                }),
986                onReady: valueAccessor().onReady,
987                disabled: valueAccessor().disabled,
988                clearOnInvalidMarker: valueAccessor().clearOnInvalidMarker,
989                markerDefaultData: valueAccessor().markerDefaultData,
990                templateFileName: valueAccessor().templateFileName,
991                filterByMarkerIDs: valueAccessor().filterByMarkerIDs,
992                onSelected: valueAccessor().onSelected,
993                includeClubs: valueAccessor().includeClubs
994            });
995
996            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
997                $(element).courseMarkerSelector("cancelLoad");
998            });
999            return { controlsDescendantBindings: true };
1000        }
1001    };
1002
1003    ko.bindingHandlers.playerDetailsControl = {
1004        init: function (element, valueAccessor) {
1005            $(element).playerDetails(valueAccessor());
1006            return { controlsDescendantBindings: true };
1007        }
1008    };
1009
1010    ko.bindingHandlers.feeSelectorControl = {
1011        init: function (element, valueAccessor) {
1012            var val = valueAccessor();
1013            $(element).feeSelector(val.props, val.options);
1014            return { controlsDescendantBindings: true };
1015        }
1016    };
1017
1018    ko.bindingHandlers.competitionSelectControl = {
1019        init: function (element, valueAccessor) {
1020            var val = valueAccessor();
1021            $(element).competitionSelect(val.props, val.options);
1022
1023            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
1024                $(element).competitionSelect("dispose");
1025            });
1026            return { controlsDescendantBindings: true };
1027        }
1028    };
1029    
1030    ko.bindingHandlers.feeSummaryControl = {
1031        init: function (element, valueAccessor) {
1032            $(element).feeSummary(valueAccessor());
1033            return { controlsDescendantBindings: true };
1034        }
1035    };
1036    
1037    ko.bindingHandlers.compositeMarkerSelectorControl = {
1038        init: function (element, valueAccessor) {
1039            $(element).compositeMarkerSelector(valueAccessor());
1040            return { controlsDescendantBindings: true };
1041        }
1042    };
1043
1044    //KDB 2012-10-26 Textbox watermark
1045    //
1046    //Example usage:
1047    //
1048    //      <input type="text" data-bind="value: [OBSERVABLE], watermark: 'Your first name'" />
1049    //
1050    ko.bindingHandlers.watermark = {
1051        init: function (element, valueAccessor, allBindingsAccessor) {
1052            var value = valueAccessor();
1053            var defaultWatermark = ko.utils.unwrapObservable(value);
1054            var $element = $(element);
1055
1056            setTimeout(function () {
1057                $element.val(defaultWatermark);
1058            }, 0);
1059
1060            $element.focus(
1061                function () {
1062                    if ($element.val() === defaultWatermark) {
1063                        $element.val("");
1064                    }
1065                }).blur(function () {
1066                    if ($element.val() === '') {
1067                        $element.val(defaultWatermark);
1068                    }
1069                });
1070        }
1071    };
1072    
1073    //
1074    // PEC - 2013-05-27
1075    // Example usage:
1076    // <input type="text" data-bind="value: [OBSERVABLE], selectText: true" />
1077    // This will set focus and select all the text in the textbox
1078    ko.bindingHandlers.selectText = {
1079        init: function (element, valueAccessor, allBindingsAccessor) {
1080            var value = valueAccessor();
1081            var selectAllValue = ko.utils.unwrapObservable(value);
1082            var $element = $(element);
1083
1084            if (selectAllValue) {
1085                setTimeout(function() {
1086                    $element.focus().select();
1087                }, 0);
1088            }
1089        }
1090    };
1091    
1092    //DFH 2013-03-20 This extender prevents binding of any child elements
1093    //Useful when binding on demand e.g. only bind on mouse over
1094    //
1095    //
1096    ko.bindingHandlers.stopBinding = {
1097        init: function (element, valueAccessor) {
1098
1099            var value = valueAccessor();
1100            if (ko.isObservable(value))
1101                value = ko.utils.unwrapObservable(valueAccessor());
1102
1103            if (!value)
1104                $(element).removeAttr('data-bind');
1105
1106            return { controlsDescendantBindings: value };
1107        }
1108    };
1109    ko.virtualElements.allowedBindings.stopBinding = true;
1110
1111
1112    //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.
1113    //This was heavily modified from someone else's code online, but I can't find the link.
1114    var allSorts = [];
1115    ko.bindingHandlers.sort = {
1116        init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
1117            element.style.cursor = 'pointer';
1118            $(element).addClass('canbesorted');
1119
1120
1121            element.onclick = function () {
1122                var value = valueAccessor();
1123                var prop = value.prop;
1124                var data = value.arr;
1125
1126                $('.sorted').not(element).removeClass('sorted').removeClass('up').removeClass('down');
1127                $(element).addClass('sorted');
1128
1129                var asc = false;
1130                if ($(element).hasClass('down') || $(element).hasClass('up'))
1131                    asc = $(element).hasClass('down');
1132                asc = !asc;
1133                if (asc)
1134                    $(element).removeClass('up').addClass('down');
1135                else
1136                    $(element).addClass('up').removeClass('down');
1137
1138                data.sort(function (left, right) {
1139                    var rec1 = left;
1140                    var rec2 = right;
1141
1142                    if (!asc) {
1143                        rec1 = right;
1144                        rec2 = left;
1145                    }
1146
1147                    var props = prop.split('.');
1148                    for (var i=0; i<props.length; i++) {
1149                        var propName = props[i];
1150                        var parenIndex = propName.indexOf('()');
1151                        if (parenIndex > 0) {
1152                            propName = propName.substring(0, parenIndex);
1153                            rec1 = rec1[propName]();
1154                            rec2 = rec2[propName]();
1155                        } else {
1156                            rec1 = rec1[propName];
1157                            rec2 = rec2[propName];
1158                        }
1159                    }
1160
1161                    //Check for numbers only sorts:
1162                    var re = new RegExp(/^[+-]?\d+([.]\d+){0,1}$/);
1163                    if (re.exec('' + rec1) && re.exec('' + rec2))
1164                        return +rec1 == +rec2 ? 0 : +rec1 < +rec2 ? -1 : 1;
1165
1166                    var r = ('' + rec1).toUpperCase() == ('' + rec2).toUpperCase() ? 0 : ('' + rec1).toUpperCase() < ('' + rec2).toUpperCase() ? -1 : 1;
1167                    if (r != 0) return r;
1168
1169                    return rec1 == rec2 ? 0 : rec1 < rec2 ? -1 : 1;
1170                });
1171            };
1172        }
1173    };
1174
1175
1176    //DFH 2012-08-10 This extender creates two-way bindings for values in your viewmodel
1177    //based on the .NET standard format strings.  I haven't implemented all of them yet, but
1178    //for now we have money and floats.
1179    //
1180    //Example usage:
1181    //  data-bind="value: AmountPercent.extend({ format: 'P0' })"
1182    //
1183    ko.extenders.format = function (target, formatString) {
1184        var formatDecimal = function (val, mapping) {
1185            if (val == null) return null;
1186            val = isNaN(val) ? 0 : ((mapping.multiplier || 1) * parseFloat(+val));
1187            return val.toFixed(mapping.precision || 0) + (mapping.suffix || '');
1188        };
1189        var parseDecimal = function (str, mapping) {
1190            if (str == null || str == '') return null;
1191            str = ('' + str).replace((mapping.suffix || ''), '');
1192            return (isNaN(str) ? 0 : parseFloat(+str)).toFixed(mapping.precision) / (mapping.multiplier || 1);
1193        };
1194        var formatMoney = function (val, mapping) {
1195            //DFH Adapted from dgNumberToMoney; this version supports different precisions.
1196            if (mapping.precision=='' || mapping.precision == null || mapping.precision == undefined) mapping.precision = 2;
1197            var i = isNaN(val) ? 0 : ((mapping.multiplier || 1) * parseFloat(+val));
1198            if (isNaN(i)) { i = 0.0; }
1199            var minus = '';
1200            if (i < 0) { minus = '-'; }
1201            i = Math.abs(i);
1202            i = parseInt((i + (.5 / Math.pow(10, mapping.precision))) * Math.pow(10, mapping.precision));
1203            i = i / Math.pow(10,mapping.precision);
1204
1205            var str = minus + new String(i) + (mapping.suffix || '');
1206            if (mapping.precision > 0) {
1207                if (str.indexOf('.') == -1) str += '.';
1208                while (str.indexOf('.') > str.length - mapping.precision -1)
1209                    str += '0';
1210            }
1211            return str;
1212        };
1213        var parseMoney = function (val, mapping) {
1214            if (mapping.precision == '') mapping.precision = 2;
1215            return parseDecimal(('' + val).replace('$', ''), {
1216                precision: mapping.precision
1217            });
1218        };
1219
1220        var mappings = [{
1221            regex: '^[pP]([0-9]?)$',
1222            format: formatDecimal,
1223            parse: parseDecimal,
1224            multiplier: 100.0,
1225            suffix: ''
1226        }, {
1227            regex: '^[fF]([0-9]?)$',
1228            format: formatDecimal,
1229            parse: parseDecimal,
1230            suffix: ''
1231        }, {
1232            regex: '^[C]([0-9]?)$',
1233            format: formatMoney,
1234            parse: parseMoney
1235        }];
1236
1237        var mapping = null;
1238        for (var i = 0; i < mappings.length; i++) {
1239            var match = new RegExp(mappings[i].regex).exec(formatString);
1240            if (match) {
1241                mapping = mappings[i];
1242                mapping.precision = match[1];
1243                mapping.formatString = formatString;
1244                break;
1245            }
1246        }
1247        if (mapping == null) {
1248            throw new Error('Unrecognised format string: ' + formatString);
1249        }
1250
1251        var result = ko.computed({
1252            read: function () {
1253                return mapping.format(target(), mapping);
1254            },
1255            write: function (newValue) {
1256                var valueToWrite = mapping.parse(newValue, mapping);
1257                var current = target();
1258                //only write if it changed
1259                if (valueToWrite !== current) {
1260                    target(valueToWrite);
1261                } else {
1262                    //if the rounded value is the same, but a different value was written, force a notification for the current field
1263                    if (newValue !== current) {
1264                        target.notifySubscribers(valueToWrite);
1265                    }
1266                }
1267            }
1268        });
1269
1270        //return the new computed observable
1271        return result;
1272    };
1273
1274    ko.extenders.parScoreToString = function (target) {
1275        return ko.computed(function () {
1276            if (target() == null || target() == undefined) return '';
1277            if (target() === -1) return "-";
1278            if (target() === 1) return "+";
1279            if (target() === 0) return "0";
1280            return "";
1281        });
1282    };
1283
1284    //KDB 2012-09-26 This extender creates two-way bindings for values in your viewmodel
1285    //based on a boolean to indicate whether it's a handicap index or not
1286    //
1287    //Example usage:
1288    //  data-bind="value: Handicap.extend({ handicapIndex: true })"
1289    //
1290    ko.extenders.handicapIndex = function (target, isIndex) {
1291        var result = ko.computed({
1292            read: function () {
1293                var val = (target() || target() == 0) ? target() : null;
1294
1295                if (val == null) return null;
1296
1297                if (isIndex) {
1298                    if (val < 0) {
1299                        return '+' + (val * -1).toFixed(1).replace(/\-/, '+');
1300                    }
1301
1302                    return val.toFixed(1).replace(/\-/, '+'); ;
1303                } else {
1304                    if (val < 0) {
1305                        return '+' + parseInt(val * -1).toString().replace(/\-/, '+');
1306                    }
1307
1308                    return val.toString().replace(/\-/, '+');
1309                }
1310            },
1311            write: function (newValue) {
1312                var valueToWrite, strVal;
1313                strVal = newValue.replace(/\+/, '-');
1314
1315                if (isIndex) {
1316                    valueToWrite = parseFloat(parseFloat(strVal).toFixed(1)); // new Number(parseFloat(strVal).toFixed(1));
1317                } else {
1318                    valueToWrite = parseInt(strVal);
1319                }
1320
1321                var current = target() ? parseFloat(parseFloat(target().toString().replace(/\+/, '-')).toFixed(1)) : null;
1322                //only write if it changed
1323                if (valueToWrite !== current) {
1324                    target(isNaN(valueToWrite) ? null : valueToWrite);
1325                }
1326            }
1327        });
1328
1329        //return the new computed observable
1330        return result;
1331    };
1332
1333
1334    //DFH 2012-10-24 Knockout one-way binding to convert dates to strings.
1335    //This uses date.js internally to do the actual conversions.  Here is a list of
1336    //format specifiers:
1337    //
1338    //(See http://code.google.com/p/datejs/ for more details)
1339    //
1340    //  Format	Description	Example
1341    //  s	    The seconds of the minute between 0-59.	                        "0" to "59"
1342    //  ss	    The seconds of the minute with leading zero if required.	    "00" to "59"
1343    //  m	    The minute of the hour between 0-59.	                        "0" or "59"
1344    //  mm	    The minute of the hour with leading zero if required.	        "00" or "59"
1345    //  h	    The hour of the day between 1-12.	                            "1" to "12"
1346    //  hh	    The hour of the day with leading zero if required.	            "01" to "12"
1347    //  H	    The hour of the day between 0-23.	                            "0" to "23"
1348    //  HH	    The hour of the day with leading zero if required.	            "00" to "23"
1349    //  d	    The day of the month between 1 and 31.	                        "1" to "31"
1350    //  dd	    The day of the month with leading zero if required.	            "01" to "31"
1351    //  ddd	    Abbreviated day name. Date.CultureInfo.abbreviatedDayNames.	    "Mon" to "Sun"
1352    //  dddd	The full day name. Date.CultureInfo.dayNames.	                "Monday" to "Sunday"
1353    //  M	    The month of the year between 1-12.	                            "1" to "12"
1354    //  MM	    The month of the year with leading zero if required.	        "01" to "12"
1355    //  MMM	    Abbreviated month name. Date.CultureInfo.abbreviatedMonthNames.	"Jan" to "Dec"
1356    //  MMMM	The full month name. Date.CultureInfo.monthNames.	            "January" to "December"
1357    //  yy	    Displays the year as a two-digit number.	                    "99" or "07"
1358    //  yyyy	Displays the full four digit year.	                            "1999" or "2007"
1359    //  t	    Displays the first character of the A.M./P.M. designator. Date.CultureInfo.amDesignator or Date.CultureInfo.pmDesignator	 "A" or "P"
1360    //  tt	    Displays the A.M./P.M. designator. Date.CultureInfo.amDesignator or Date.CultureInfo.pmDesignator	 "AM" or "PM"
1361    //  S	    The ordinal suffix ("st, "nd", "rd" or "th") of the current day.	 "st, "nd", "rd" or "th"
1362    //
1363    //Example usage:
1364    //  data-bind="value: SlotDateTime.extend({ formatDateTime: 'd/MM/yyyy H:mm' })"
1365    //
1366    ko.extenders.formatDateTime = function (target, formatString) {
1367
1368        if (typeof Date.today == 'undefined')
1369            throw new Error('You need date.js loaded to use the formatDateTime binding.');
1370
1371        var format = function (val) {
1372            if (val == null) return null;
1373
1374            return val.toString(formatString);
1375        };
1376
1377        var result = ko.computed({
1378            read: function () {
1379                return format(target());
1380            }
1381        });
1382
1383        //return the new computed observable
1384        return result;
1385    };
1386
1387    // KDB 2012-10-26: Workaround for ko.observables to work better with Date objects
1388    //Ref from google group response:
1389    //    The same is with any object. Not only dates. And if you look into Knockout source this is by design,
1390    //    It only compares primitive types when you update observable.
1391    //    The problem is that there is no way to compare objects in general.
1392    //    For example, when you have object a = { foo: 'bar' };
1393    //    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,
1394    //    With dates it is the same - you may change intial date object property - and that would be another date but same date object.
1395        
1396    ko.observableDate = function(initialValue) {
1397        if (!(initialValue instanceof Date)) throw new Error('observableDate requires a JS Date object');
1398        var result = ko.observable(initialValue);
1399
1400        ko.utils.extend(result, ko.observableDate['fn']);
1401
1402        return result;
1403    };
1404
1405    ko.observableDate['fn'] = {
1406        "equalityComparer": function (a, b) {
1407            if (!(b instanceof Date)) throw new Error('observableDate requires a JS Date object');
1408            return a.getTime() === b.getTime();
1409        }
1410    };
1411    
1412})();
1413
1414
1415
1416// By: Hans Fjällemark and John Papa
1417// https://github.com/CodeSeven/KoLite
1418//
1419// Knockout.DirtyFlag
1420//
1421// John Papa 
1422//          http://johnpapa.net
1423//          http://twitter.com/@john_papa
1424//
1425// Depends on scripts:
1426//          Knockout 
1427//
1428//  Notes:
1429//          Special thanks to Steve Sanderson and Ryan Niemeyer for 
1430//          their influence and help.
1431//
1432//  Usage:      
1433//          To Setup Tracking, add this tracker property to your viewModel    
1434//              ===> viewModel.dirtyFlag = new ko.DirtyFlag(viewModel.model);
1435//
1436//          Hook these into your view ...
1437//              Did It Change?          
1438//              ===> viewModel.dirtyFlag().isDirty();
1439//
1440//          Hook this into your view model functions (ex: load, save) ...
1441//              Resync Changes
1442//              ===> viewModel.dirtyFlag().reset();
1443//
1444//          Optionally, you can pass your own hashFunction for state tracking.
1445//
1446////////////////////////////////////////////////////////////////////////////////////////
1447; (function (ko) {
1448    ko.DirtyFlag = function (objectToTrack, isInitiallyDirty, hashFunction) {
1449
1450        hashFunction = hashFunction || ko.toJSON;
1451
1452        var
1453            self = this,
1454            _objectToTrack = objectToTrack,
1455            _lastCleanState = ko.observable(hashFunction(_objectToTrack)),
1456            _isInitiallyDirty = ko.observable(isInitiallyDirty),
1457
1458            result = function () {
1459                self.forceDirty = function () {
1460                    _isInitiallyDirty(true);
1461                };
1462
1463                self.isDirty = ko.computed(function () {
1464                    return _isInitiallyDirty() || hashFunction(_objectToTrack) !== _lastCleanState();
1465                });
1466
1467                self.reset = function () {
1468                    _lastCleanState(hashFunction(_objectToTrack));
1469                    _isInitiallyDirty(false);
1470                };
1471                return self;
1472            };
1473
1474        return result;
1475    };
1476})(ko);
1477
1478
1479
1480
1481
1482// Github repository: https://github.com/One-com/knockout-dragdrop
1483// License: standard 3-clause BSD license https://raw.github.com/One-com/knockout-dragdrop/master/LICENCE
1484
1485/**
1486 * This binding selects the text in a input field or a textarea when the
1487 * field get focused.
1488 *
1489 * Usage:
1490 *     <input type="text" data-bind="selectOnFocus: true">
1491 *     Selects all text when the element is focused.
1492 *
1493 *     <input type="text" data-bind="selectOnFocus: /^[^\.]+/">
1494 *     Selects all text before the first period when the element is focused.
1495 *
1496 *     <input type="text" data-bind="selectOnFocus: { pattern: /^[^\.]+/, onlySelectOnFirstFocus: true }">
1497 *     Only select the pattern on the first focus.
1498 */
1499(function (factory) {
1500    if (typeof define === "function" && define.amd) {
1501        // AMD anonymous module with hard-coded dependency on "knockout"
1502        define(["knockout"], factory);
1503    } else {
1504        // <script> tag: use the global `ko` and `jQuery`
1505        factory(ko);
1506    }
1507})(function (ko) {
1508    function getOptions(valueAccessor) {
1509        var options = ko.utils.unwrapObservable(valueAccessor());
1510        if (options.pattern) {
1511            return options;
1512        } else {
1513            return { pattern: options };
1514        }
1515    }
1516
1517    function selectText(field, start, end) {
1518        if (field.createTextRange) {
1519            var selRange = field.createTextRange();
1520            selRange.collapse(true);
1521            selRange.moveStart('character', start);
1522            selRange.moveEnd('character', end);
1523            selRange.select();
1524            field.focus();
1525        } else if (field.setSelectionRange) {
1526            field.focus();
1527            field.setSelectionRange(start, end);
1528        } else if (typeof field.selectionStart !== 'undefined') {
1529            field.selectionStart = start;
1530            field.selectionEnd = end;
1531            field.focus();
1532        }
1533    }
1534
1535    ko.bindingHandlers.selectOnFocus = {
1536        init: function (element, valueAccessor) {
1537            var firstFocus = true;
1538            ko.utils.registerEventHandler(element, 'focus', function (e) {
1539                setTimeout(function () {
1540                    var options = getOptions(valueAccessor);
1541                    var pattern = ko.utils.unwrapObservable(options.pattern);
1542                    var onlySelectOnFirstFocus = ko.utils.unwrapObservable(options.onlySelectOnFirstFocus);
1543
1544                    if (!onlySelectOnFirstFocus || firstFocus) {
1545                        if (Object.prototype.toString.call(pattern) === '[object RegExp]') {
1546                            var matchInfo = pattern.exec(element.value);
1547                            if (matchInfo) {
1548                                var startOffset = matchInfo.index,
1549                                    endOffset = matchInfo.index + matchInfo[0].length;
1550                                selectText(element, startOffset, endOffset);
1551                            }
1552                        } else {
1553                            element.select();
1554                        }
1555                        firstFocus = false;
1556                    }
1557                }, 1);
1558            });
1559        }
1560    };
1561
1562    ko.bindingHandlers.filterDecimal = {
1563        init: function (element, valueAccessor) {
1564            if (valueAccessor())
1565                $(element).filter_input({ regex: '[0-9.]', live: true });
1566                $(element).val(Number($(element).val()).toFixed(2));
1567        }
1568    };
1569
1570    ///calls jquery load on the bound element using a 'url' parameter
1571    //e.g. data-bind='load: { url: "/Controls/MyScoresWhs.html", data: $data }'
1572    ko.bindingHandlers.load = {
1573        init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
1574
1575            $(element).removeAttr("data-bind");
1576            ko.cleanNode(element);
1577
1578            var url = valueAccessor().url
1579            if (url.indexOf('?') > -1) {
1580                url += "&rev=" + "441";
1581            } else {
1582                url += "?rev=" + "441";
1583            } 
1584            $(element).load(url, function () {
1585                ko.applyBindings(valueAccessor().data, $(element)[0]);
1586            });
1587            return { controlsDescendantBindings: true };
1588        },
1589    };
1590});

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.