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.