PageSourceSearch

https://my.actuaries.org.uk/Resources/Shared/Scripts//knockoutExtensions.js?cdv=1121

js actuaries.org.uk collected 2026-09-24 08:45:01 UTC 24,666 bytes, 630 lines download raw bytes

1//Author: Silverbear
2//Date: 18/04/2012
3//Summary: A collection of handy custom knockoutJS extensions.
4//jQuery widget needed for dialog
5var jQueryWidget = function (element, valueAccessor, name, constructor) {
6    var options = ko.utils.unwrapObservable(valueAccessor());
7    var $element = $(element);
8    setTimeout(function () { constructor($element, options) }, 0);
9    //$element.data(name, $widget);
10
11};
12///A computed observable that can be paused and resumed.
13ko.pauseableComputed = function (evaluatorFunction, evaluatorFunctionTarget) {
14    var _cachedValue = "";
15    var _isPaused = ko.observable(false);
16
17    //the computed observable that we will return
18    var result = ko.computed(function () {
19        if (!_isPaused()) {
20            //call the actual function that was passed in
21            return evaluatorFunction.call(evaluatorFunctionTarget);
22        }
23        return _cachedValue;
24    }, evaluatorFunctionTarget);
25    //keep track of our current value and set the pause flag to release our actual subscriptions
26    result.pause = function () {
27        _cachedValue = this();
28        _isPaused(true);
29    }.bind(result);
30    //clear the cached value and allow our computed observable to be re-evaluated
31    result.resume = function () {
32        _cachedValue = "";
33        _isPaused(false);
34    };
35    return result;
36};
37
38ko.extenders.withPrevious = function (target) {
39    // Define new properties for previous value and whether it's changed
40    target.previous = ko.observable();
41    target.changed = ko.computed(function () { return target() !== target.previous(); });
42
43    // Subscribe to observable to update previous, before change.
44    target.subscribe(function (v) {
45        target.previous(v);
46    }, null, 'beforeChange');
47
48    // Return modified observable
49    return target;
50};
51// a binding that can be used to scroll an element into view when an observable is set.
52ko.bindingHandlers.intoView = {
53    update: function (element, valueAccessor) {
54        if (valueAccessor()) {
55            // Happens on other thread to allow this thread to finish rendering html elements.
56            setTimeout(function () {
57                element.scrollIntoView();
58            }, 0);
59        };
60    }
61};
62//alternative dialog binding
63ko.bindingHandlers.dialogcmd = {
64    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
65        jQueryWidget(element, valueAccessor, 'dialog', function ($element, options) {
66            return $element.dialog(options);
67        });
68    }
69};
70ko.bindingHandlers.dialog = {
71    init: function (element, valueAccessor, allBindingsAccessor) {
72        var options = ko.utils.unwrapObservable(valueAccessor()) || {};
73        //do in a setTimeout, so the applyBindings doesn't bind twice from element being copied and moved to bottom
74        setTimeout(function () {
75            options.close = function () {
76                if (ko.isObservable(allBindingsAccessor().dialogVisible)) {
77                    allBindingsAccessor().dialogVisible(false);
78                } else {
79                    allBindingsAccessor().dialogVisible = false;
80                }
81            };
82            $(element).dialog(options);
83        }, 0);
84
85        //handle disposal (not strictly necessary in this scenario)
86        //        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
87        //            $(element).dialog("destroy");
88        //        });
89    },
90    update: function (element, valueAccessor, allBindingsAccessor) {
91        var shouldBeOpen = ko.utils.unwrapObservable(allBindingsAccessor().dialogVisible),
92            $el = $(element),
93            dialog = $el.data("uiDialog") || $el.data("dialog");
94
95        //don't call open/close before initilization
96        if (dialog) {
97            $el.dialog(shouldBeOpen ? "open" : "close");
98        }
99    }
100};
101//Custom controlsDescendantBindings for KnockoutJSRegulator JS
102ko.bindingHandlers.dateString = {
103    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
104        var value = valueAccessor(),
105            allBindings = allBindingsAccessor();
106        var valueUnwrapped = ko.utils.unwrapObservable(value);
107        var pattern = allBindings.datePattern || 'dd/MM/yyyy';
108        $(element).text(valueUnwrapped.toString(pattern));
109    }
110};
111ko.bindingHandlers.dateStringFromJsonString = {
112    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
113        var value = valueAccessor(),
114            allBindings = allBindingsAccessor();
115        var valueUnwrapped = ko.utils.unwrapObservable(value);
116        if (valueUnwrapped != null) {
117            var pattern = allBindings.datePattern || 'dd/MM/yyyy';
118            var date = new Date(parseInt(valueUnwrapped.substr(6)));
119            $(element).text(date.toString(pattern));
120        } else {
121            var nullText = allBindings.nullText || '';
122            $(element).text(nullText);
123        }
124    }
125};
126
127function FormatDate(date, pattern) {
128    var day = String(date.getUTCDate()).padStart(2, '0');
129    var month = String(date.getUTCMonth() + 1).padStart(2, '0'); // Adding 1 because month index starts from 0
130    var year = date.getUTCFullYear();
131
132    switch (pattern) {
133        case 'MM/dd/yyyy':
134            return month + '/' + day + '/' + year;
135        case 'yyyy/MM/dd':
136            return year + '/' + month + '/' + day;
137        case 'MM-dd-yyyy':
138            return month + '-' + day + '-' + year;
139        case 'yyyy-MM-dd':
140            return year + '-' + month + '-' + day;
141        // Add more cases for additional patterns as needed
142        default:
143            // Default to 'dd/MM/yyyy' if pattern is not matched - doubles up as a match to this pattern
144            return day + '/' + month + '/' + year;
145    }
146}
147
148
149ko.bindingHandlers.dateString = {
150    init: function (element, valueAccessor, allBindingsAccessor) {
151        // coerce date observabel value.
152        //handle date data coming via json from Microsoft
153        var observable = valueAccessor();
154        var value = ko.utils.unwrapObservable(observable);
155        if (String(value).indexOf('/Date(') == 0) {
156            var coercedDate = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
157            observable(coercedDate);
158        }
159    },
160    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
161        var value = valueAccessor(),
162            allBindings = allBindingsAccessor();
163        var valueUnwrapped = ko.utils.unwrapObservable(value);
164        if (valueUnwrapped != null) {
165            var pattern = allBindings.datePattern || 'dd/MM/yyyy';
166            var dateString = FormatDate(valueUnwrapped, pattern);
167            $(element).text(dateString);
168        } else {
169            var nullText = allBindings.nullText || '';
170            $(element).text(nullText);
171        }
172    }
173};
174function FormatAsShortDate(observableValue, emptyDateText) {
175    var dateValue = ko.utils.unwrapObservable(observableValue);
176    if (dateValue != null) {
177        var date = new Date(parseInt(dateValue.substr(6)));
178        var formattedDate = $.format.date(date, jQuery.format.date.defaultShortDateFormat);
179        return formattedDate;
180    } else {
181        return emptyDateText;
182    }
183}
184ko.observableArray.fn.sortByProperty = function (prop) {
185    this.sort(function (obj1, obj2) {
186        if (obj1[prop] == obj2[prop])
187            return 0;
188        else if (obj1[prop] < obj2[prop])
189            return -1;
190        else
191            return 1;
192    });
193};
194ko.observableArray.fn.pushAll = function (valuesToPush) {
195    var underlyingArray = this();
196    this.valueWillMutate();
197    ko.utils.arrayPushAll(underlyingArray, valuesToPush);
198    this.valueHasMutated();
199    return this;  //optional
200};
201ko.bindingHandlers.fadeVisible = {
202    update: function (element, valueAccessor, allBindingsAccessor) {
203        // First get the latest data that we're bound to
204        var value = valueAccessor(), allBindings = allBindingsAccessor();
205
206        // Next, whether or not the supplied model property is observable, get its current value
207        var valueUnwrapped = ko.utils.unwrapObservable(value);
208
209        // Grab some more data from another binding property
210        var duration = allBindings.fadeDuration || 400; // 400ms is default duration unless otherwise specified
211
212        // Now manipulate the DOM element
213        if (valueUnwrapped == true)
214            $(element).fadeIn(duration); // Make the element visible
215        else
216            $(element).fadeOut(duration);   // Make the element invisible
217    }
218};
219ko.bindingHandlers.fadeOnChange = {
220    update: function (element, valueAccessor, allBindingsAccessor) {
221        // First get the latest data that we're bound to
222        // var value = valueAccessor()
223        //        var allBindings = allBindingsAccessor();
224        //        // Grab some more data from another binding property
225        //        var duration = allBindings.fadeDuration || 400; // 400ms is default duration unless otherwise specified
226        //        // Now manipulate the DOM element
227        //        $(element).fadeIn(duration); // Make the element visible
228        var allBindings = allBindingsAccessor();
229        // Grab some more data from another binding property
230        var duration = allBindings.fadeDuration || 1000; // 400ms is default duration unless otherwise specified
231        // Now manipulate the DOM element
232        $(element).hide().fadeIn(duration); // Make the element visible
233    }
234};
235ko.bindingHandlers.slideVisible = {
236    update: function (element, valueAccessor, allBindingsAccessor) {
237        // First get the latest data that we're bound to
238        var value = valueAccessor(), allBindings = allBindingsAccessor();
239
240        // Next, whether or not the supplied model property is observable, get its current value
241        var valueUnwrapped = ko.utils.unwrapObservable(value);
242
243        // Grab some more data from another binding property
244        var duration = allBindings.slideDuration || 400; // 400ms is default duration unless otherwise specified
245
246        // Now manipulate the DOM element
247        if (valueUnwrapped == true)
248            $(element).slideDown(duration); // Make the element visible
249        else
250            $(element).slideUp(duration);   // Make the element invisible
251    }
252};
253ko.bindingHandlers.slideHorizontalVisible = {
254    update: function (element, valueAccessor, allBindingsAccessor) {
255        // First get the latest data that we're bound to
256        var value = valueAccessor(), allBindings = allBindingsAccessor();
257
258        // Next, whether or not the supplied model property is observable, get its current value
259        var valueUnwrapped = ko.utils.unwrapObservable(value);
260
261        // Grab some more data from another binding property
262        var duration = allBindings.slideDuration || 400; // 400ms is default duration unless otherwise specified
263
264        // Now manipulate the DOM element
265        if (valueUnwrapped == true)
266            $(element).slide(duration); // Make the element visible
267        else
268            $(element).slideUp(duration);   // Make the element invisible
269    }
270};
271ko.bindingHandlers.datepicker = {
272    init: function (element, valueAccessor, allBindingsAccessor) {
273        var timezoneOffset = new Date().getTimezoneOffset() * 60000;
274
275        //initialize datepicker with some optional options
276        var options = allBindingsAccessor().datepickerOptions || {};
277        if (!options.dateFormat) options.dateFormat = 'dd/mm/yy';
278
279        // Adds a 'clear' button to the datepicker.
280        options.closeText = "Clear";
281        options.showButtonPanel = true;
282        options.onClose = function (dateText, obj) {
283            if ($(window.event.srcElement).hasClass('ui-datepicker-close')) {
284                $(this).datepicker('setDate', null);
285                $(this).trigger('change');
286            }
287
288            $(this).blur();
289        };
290
291        options.beforeShow = function (input) {
292            setTimeout(function () {
293                    $(input).datepicker("widget").find(".ui-datepicker-current").hide();
294                },
295                1);
296        };
297
298        $(element).datepicker(options);
299
300        //handle the field changing
301        ko.utils.registerEventHandler(element, "change", function () {
302            var observable = valueAccessor();
303
304            //check that they haven't pasted rubbish into the box
305            try {
306                $.datepicker.parseDate(options.dateFormat, $(element).val());
307            } catch (e) {
308                $(element).val('');
309                observable(null);
310                return;
311            }
312
313            var selectedDate = $(element).datepicker("getDate");
314            if (selectedDate === undefined || selectedDate === null) {
315                observable(null);
316            } else {
317                var newEpoch = selectedDate.getTime();
318                // we want to get the timezone offset of the selected date so we account for BST properly
319                timezoneOffset = selectedDate.getTimezoneOffset() * 60000;
320                observable(new Date(newEpoch - timezoneOffset));
321            }
322        });
323
324        var observable = valueAccessor();
325        var value = ko.utils.unwrapObservable(observable);
326        if (String(value).indexOf('/Date(') == 0) {
327            var coercedDate = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
328            observable(coercedDate);
329        }
330
331        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
332            $(element).datepicker("destroy");
333        });
334
335    },
336    update: function (element, valueAccessor) {
337        var timezoneOffset = new Date().getTimezoneOffset() * 60000;
338        var newValue = ko.utils.unwrapObservable(valueAccessor());
339        var oldValue = $(element).datepicker("getDate");
340
341        var newEpoch;
342        var oldEpoch;
343
344        if (newValue === undefined || newValue === null) {
345            newEpoch = null;
346        }
347        else if (String(newValue).indexOf('/Date(') == 0) {
348            //handle date data coming via json from Microsoft
349            newEpoch = parseInt(newValue.replace(/\/Date\((.*?)\)\//gi, "$1"));
350        }
351        else if (newValue instanceof Date) {
352            newEpoch = newValue.getTime();
353        }
354
355        if (oldValue === undefined || oldValue === null) {
356            oldEpoch = null;
357        }
358        else if (String(oldValue).indexOf('/Date(') == 0) {
359            //handle date data coming via json from Microsoft
360            oldEpoch = parseInt(oldValue.replace(/\/Date\((.*?)\)\//gi, "$1"));
361        }
362        else if (oldValue instanceof Date) {
363            oldEpoch = oldValue.getTime();
364        }
365
366        if (newEpoch !== oldEpoch) {
367            if (newEpoch + timezoneOffset !== oldEpoch) {
368                var date = new Date(newEpoch + timezoneOffset);
369                $(element).datepicker("setDate", date);
370            }
371
372        } else if (newEpoch == null) {
373            $(element).datepicker("setDate", null);
374        }
375    }
376};
377ko.bindingHandlers.uniqueId = {
378    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
379        var value = valueAccessor();
380        var thisPrefix = value || ko.bindingHandlers.uniqueId.prefix;
381        //var options =  { prefix: thisPrefix };
382        var uniqueId = thisPrefix + (++ko.bindingHandlers.uniqueId.counter);
383        element.id = uniqueId;
384        viewModel.generatedUniqueId = uniqueId;
385    },
386    counter: 0,
387    prefix: "unique"
388};
389ko.bindingHandlers.uniqueFor = {
390    init: function (element, valueAccessor, allBindingsAccessor) {
391        var value = valueAccessor();
392        var thisPrefix = value || ko.bindingHandlers.uniqueId.prefix;
393        //var options = allBindingsAccessor().uniqueIdOptions || { prefix: thisPrefix };
394
395        var uniqueId = thisPrefix + (ko.bindingHandlers.uniqueId.counter + 1);
396        element.setAttribute("for", uniqueId);
397    }
398};
399ko.bindingHandlers.pickListValue = {
400    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
401        var value = valueAccessor(),
402            allBindings = allBindingsAccessor(),
403            dataModel = bindingContext.$parents[bindingContext.$parents.length - 2].DataModel;
404        var pickListItemId = ko.utils.unwrapObservable(value);
405        if (pickListItemId != null && dataModel.PickLists[allBindings.pickListName] && dataModel.PickLists[allBindings.pickListName][pickListItemId]) {
406            $(element).text(dataModel.PickLists[allBindings.pickListName][pickListItemId]());
407        } else {
408            var nullText = allBindings.nullText || '';
409            $(element).text(nullText);
410        }
411    }
412};
413ko.bindingHandlers.expand = {
414    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
415        if ($(element).hasClass('ui-expander')) {
416            var expander = element;
417            var head = $(expander).find('.ui-expander-head');
418            var content = $(expander).find('.ui-expander-content');
419
420            $(head).click(function () {
421                $(head).toggleClass('ui-expander-head-collapsed');
422                $(content).toggle();
423            });
424        }
425    }
426};
427ko.bindingHandlers.attrIf = {
428    update: function (element, valueAccessor, allBindingsAccessor) {
429        var h = ko.utils.unwrapObservable(valueAccessor());
430        var show = ko.utils.unwrapObservable(h._if);
431        if (show) {
432            ko.bindingHandlers.attr.update(element, valueAccessor, allBindingsAccessor);
433        } else {
434            for (var k in h) {
435                if (h.hasOwnProperty(k) && k.indexOf("_") !== 0) {
436                    $(element).removeAttr(k);
437                }
438            }
439        }
440    }
441};
442ko.bindingHandlers.singleClick = {
443    init: function (element, valueAccessor) {
444        var handler = valueAccessor(),
445            delay = 200,
446            clickTimeout = false;
447
448        $(element).click(function () {
449            if (clickTimeout !== false) {
450                clearTimeout(clickTimeout);
451                clickTimeout = false;
452            } else {
453                clickTimeout = setTimeout(function () {
454                    clickTimeout = false;
455                    handler();
456                }, delay);
457            }
458        });
459    }
460};
461ko.bindingHandlers.tooltip = {
462    init: function (element, valueAccessor) {
463        var local = ko.utils.unwrapObservable(valueAccessor()),
464            options = {};
465
466        ko.utils.extend(options, ko.bindingHandlers.tooltip.options);
467        ko.utils.extend(options, local);
468
469        $(element).tooltip(options);
470
471        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
472            $(element).tooltip("destroy");
473        });
474    },
475    options: {
476        placement: "right",
477        trigger: "click"
478    }
479};
480//Binding for http://www.bootstraptoggle.com/. JS and CSS file from their CDN but be included on the page separately. Must be used with checked binding.
481//Usage: data-bind="checked: observable, toggle: { on: 'Yes', off: 'No', onstyle: 'success', offstyle: 'danger', size: 'normal', style: 'cssclass' }"
482ko.bindingHandlers.toggle = {
483    init: function (element, valueAccessor, allBindings) {
484        if (!$.fn.bootstrapToggle) { return; }
485        var params = ko.unwrap(valueAccessor());
486        var on = ko.unwrap(params['on']) || 'On';
487        var off = ko.unwrap(params['off']) || 'Off';
488        var onstyle = ko.unwrap(params['onstyle']) || 'primary';
489        var offstyle = ko.unwrap(params['offstyle']) || 'default';
490        var size = ko.unwrap(params['size']) || 'normal';
491        var style = ko.unwrap(params['style']) || '';
492        $(element).bootstrapToggle({ on: on, off: off, onstyle: onstyle, offstyle: offstyle, size: size, style: style });
493        $(element).change(function () { $(element).triggerHandler('click'); }); //trigger update to observable
494    }
495};
496ko.bindingHandlers.autocomplete = {
497    init: function (element, valueAccessor, allBindings) {
498        var options = allBindings()['autocompleteoptions'];
499        var value = valueAccessor();
500        $(element).val(value());
501        $(element).autocomplete({
502            source: ko.unwrap(options),
503            select: function (event, ui) {
504                var value = valueAccessor();
505                if (ui.item) value(ui.item.value);
506            }
507        }).blur(function () {
508            var value = valueAccessor();
509            value($(element).val());
510        });
511    }
512};
513
514ko.bindingHandlers.slick = {
515    init: function (element, valueAccessor) {
516        var local = ko.utils.unwrapObservable(valueAccessor()),
517            options = {};
518
519        ko.utils.extend(options, ko.bindingHandlers.slick.options);
520        ko.utils.extend(options, local);
521
522        $(element).slick(options);
523    },
524    options: {
525        slidesToShow: 3,
526        slidesToScroll: 1,
527        responsive: [
528            {
529                breakpoint: 1024,
530                settings: {
531                    slidesToShow: 3,
532                    slidesToScroll: 3,
533                    infinite: true,
534                    dots: true
535                }
536            },
537            {
538                breakpoint: 600,
539                settings: {
540                    slidesToShow: 2,
541                    slidesToScroll: 2
542                }
543            },
544            {
545                breakpoint: 480,
546                settings: {
547                    slidesToShow: 1,
548                    slidesToScroll: 1
549                }
550            }
551        ]
552    }
553};
554if (!Function.prototype.bind) {
555    Function.prototype.bind = function (oThis) {
556        if (typeof this !== "function") {
557            // closest thing possible to the ECMAScript 5 internal IsCallable function
558            throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
559        }
560        var aArgs = Array.prototype.slice.call(arguments, 1),
561            fToBind = this,
562            fNOP = function () { },
563            fBound = function () {
564                return fToBind.apply(this instanceof fNOP && oThis
565                    ? this
566                    : oThis,
567                    aArgs.concat(Array.prototype.slice.call(arguments)));
568            };
569        fNOP.prototype = this.prototype;
570        fBound.prototype = new fNOP();
571        return fBound;
572    };
573}
574
575ko.bindingHandlers.comboTree = {
576    //On iniliatise we need to create our tree
577    init: function (element, valueAccessor, allBindingsAccessor) {
578
579        //initialize combotree with some optional options
580        var options = allBindingsAccessor().treeOptions;
581
582        if (options == undefined) {
583            options = {};
584        }
585
586        options.source = ko.toJS(ko.unwrap(valueAccessor()));
587
588        SubjectTree = $(element).comboTree(options);
589
590        ko.utils.registerEventHandler(element, "change", function (e) {
591            var observable = allBindingsAccessor().treeValue;
592
593            if (e.originalEvent.detail !== undefined) {
594                observable(e.originalEvent.detail);
595            }
596            else {
597                observable(null);
598            }
599
600        });
601
602    },
603    update: function (element, valueAccessor, allBindingsAccessor) {
604        var subjectId = ko.unwrap(allBindingsAccessor().treeValue());
605        if (subjectId !== undefined && subjectId != null) {
606
607            var tree = valueAccessor();
608            var flattenedTree = FlattenSubjects([], tree);
609
610            for (var i = 0; i < flattenedTree.length; i++) {
611                if (flattenedTree[i].id == subjectId) {
612                    element.value = flattenedTree[i].title;
613                    break;
614                }
615            }
616        } else {
617            element.value = "";
618        }
619    }
620};
621
622function FlattenSubjects(flattenedTree, tree) {
623    for (var i = 0; i < tree.length; i++) {
624        flattenedTree.push({ id: tree[i].id, title: tree[i].title });
625        if (tree[i].subs != null && tree[i].subs.length > 0) {
626            flattenedTree = FlattenSubjects(flattenedTree, tree[i].subs);
627        }
628    }
629    return flattenedTree;
630}

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.