1 2 3var resources = { 4 ok: { 5 default: 'OK' 6 }, 7 silhouette: { 8 m: "/Content/Parliamentarians/images/OfficialMpPhotos/silhouette_m.jpg", 9 f: "/Content/Parliamentarians/images/OfficialMpPhotos/silhouette.jpg" 10 }, 11 current_member_speaking: { 12 en: 'Current Member Speaking', 13 fr: 'Député ayant présentement la parole' 14 }, 15 current_member_speakingFe: { 16 en: 'Current Member Speaking', 17 fr: 'Députée ayant présentement la parole' 18 }, 19 current_member_speakingOther: { 20 en: 'Current Member Speaking', 21 fr: 'Député·e ayant présentement la parole' 22 }, 23 previous_member_speaking: { 24 en: 'Previous Members Speaking', 25 fr: 'Député(e)s ayant pris la parole', 26 }, 27 view_live: { 28 en: 'Watch on ParlVU', 29 fr: 'Regardez sur ParlVU', 30 }, 31 house_is_sitting: { 32 en: 'The House is currently sitting.', 33 fr: 'Une séance de la Chambre est en cours.' 34 }, 35 house_is_adjourned:{ 36 en: 'Adjourned', 37 fr: 'Ajournement' 38 }, 39 extended_break_message: { 40 en: 'The House is adjourned.', 41 fr: 'La Chambre est ajournée.' 42 }, 43 parliament_dissolved_message: { 44 en: 'Parliament is dissolved.', 45 fr: 'Le Parlement est dissout.' 46 }, 47 session_prorogued_message: { 48 en: 'Parliament is prorogued.', 49 fr: 'Le Parlement est prorogé.' 50 }, 51 decision: { 52 en: 'Decision:', 53 fr: 'Décision :' 54 }, 55 speaker_decision: { 56 en: 'Speaker\'s Casting Vote:', 57 fr: 'Voix prépondérante du Président :' 58 }, 59 yes: { 60 en: 'Yeas:', 61 fr: 'Pour :' 62 }, 63 no: { 64 en: 'Nays:', 65 fr: 'Contre :' 66 }, 67 subsequent_divisions_title: { 68 en: 'Next Votes', 69 fr: 'Votes à venir' 70 }, 71 the_speaker: { 72 en: 'The Speaker', 73 fr: 'Le Président' 74 }, 75 the_speakerFe: { 76 en: 'The Speaker', 77 fr: 'La Présidente' 78 }, 79 the_speakerOther: { 80 en: 'The Speaker', 81 fr: 'Le Président·e' 82 }, 83 deputy_chair: { 84 en: 'The Assistant Deputy Speaker ', 85 fr: 'Le Vice-président adjoint' 86 }, 87 deputy_chairFe: { 88 en: 'The Assistant Deputy Speaker ', 89 fr: 'La Vice-présidente adjointe' 90 }, 91 deputy_chairOther: { 92 en: 'The Assistant Deputy Speaker ', 93 fr: 'Le Vice-président·e adjoint·e' 94 }, 95 assistant_deputy_chair: { 96 en: 'The Assistant Deputy Speaker', 97 fr: 'Le Vice-président adjoint' 98 }, 99 assistant_deputy_chairFe: { 100 en: 'The Assistant Deputy Speaker', 101 fr: 'La Vice-présidente adjointe' 102 }, 103 assistant_deputy_chairOther: { 104 en: 'The Assistant Deputy Speaker', 105 fr: 'Le Vice-président·e adjoint·e' 106 }, 107 deputy_speaker: { 108 en: 'The Deputy Speaker', 109 fr: 'Le Vice-président', 110 }, 111 deputy_speakerFe: { 112 en: 'The Deputy Speaker', 113 fr: 'La Vice-présidente' 114 }, 115 deputy_speakerOther: { 116 en: 'The Deputy Speaker', 117 fr: 'Le Vice-président·e' 118 }, 119 error_event_capture_offline: { 120 en: ' Connection lost. Attempting to reconnect.', 121 fr: ' Connexion perdue. Nous tentons de la rétablir.' 122 }, 123 error_signalR_connection: { 124 en: ' Connection lost. Attempting to reconnect.', 125 fr: ' Connexion perdue. Nous tentons de la rétablir.' 126 }, 127 error_signalR_slow: { 128 en: ' Slow connection. Site may not update.', 129 fr: ' Connexion lente. Il est possible que le site ne soit pas à jour.' 130 }, 131 bells_are_ringing: { 132 en: 'Bells are Ringing', 133 fr: 'Le timbre dâappel sonne' 134 }, 135 short_language: { 136 en: 'en', 137 fr: 'fr' 138 }, 139 bells_are_ringing_message: { 140 en: 'Members are called to the House', 141 fr: 'Les députés sont convoqués à la Chambre' 142 }, 143 show_more: { 144 en: 'Show more', 145 fr: 'Afficher plus' 146 }, 147 show_less: { 148 en: 'Show less', 149 fr: 'Afficher moins' 150 }, 151 Tooltips: [ 152 { 153 en: "Approval, by a representative of the Crown, of a bill passed by the House and the Senate, making it into an Act of Parliament.", 154 fr: "La sanction royale est accordée par un représentant de la Couronne à un projet de loi adopté par la Chambre et le Sénat; le projet de loi devient alors une loi du Parlement.", 155 titleEn: "Royal Assent", 156 titleFr: "Sanction royale", 157 sectionId : "102" 158 }, 159 { 160 en: "A decision on the procedural acceptability of some matter before the House.", 161 fr: "Jugement sur la recevabilité d'une question de procédure dont la Chambre est saisie.", 162 titleEn: "Speaker's Ruling", 163 titleFr: "Décision de la présidence", 164 sectionId : "134" 165 }, 166 { 167 en: "At the opening of the Wednesday sitting, a designated Member leads the House in singing <em>O Canada</em>.", 168 fr: "à l'ouverture de la séance du mercredi, un député désigné entonne l'<em>à Canada</em>.", 169 titleEn: "National Anthem", 170 titleFr: "Hymne national", 171 sectionId : "136" 172 }, 173 { 174 en: "Delivered at the opening of a session of Parliament, the Speech from the Throne outlines the government's plans for the session.", 175 fr: "Prononcé à l'ouverture d'une session du Parlement, le discours du Trône donne un aperçu du programme du gouvernement pour la session.", 176 titleEn: "Address in Reply to the Speech from the Throne", 177 titleFr: "Adresse en réponse au discours du Trône", 178 sectionId : "168" 179 }, 180 { 181 en: "A 30-minute period prior to the daily adjournment, during which Members may raise matters they believe have not been dealt with satisfactorily. ", 182 fr: "Période de 30 minutes avant l'ajournement quotidien, au cours de laquelle les députés peuvent soulever des questions auxquelles, à leur avis, on n'a pas répondu d'une façon satisfaisante.", 183 titleEn: "Adjournment Proceedings", 184 titleFr: "Débat d'ajournement", 185 sectionId : "173" 186 }, 187 { 188 en: "A debate held on a motion to adjourn, devoted to the discussion of a specific and important matter requiring urgent consideration.", 189 fr: "Débat sur une motion d'ajournement consacré à la discussion d'une affaire précise et importante dont l'étude s'impose d'urgence.", 190 titleEn: "Emergency Debate", 191 titleFr: "Débat d'urgence", 192 sectionId : "181" 193 }, 194 { 195 en: "A daily 15-minute period when Members may make brief statements on matters of national, regional or local importance.", 196 fr: "Période quotidienne de 15 minutes au cours de laquelle des députés peuvent faire de brèves déclarations sur des questions d'intérêt national, régional ou local.", 197 titleEn: "Statements by Members", 198 titleFr: "Déclarations de députés", 199 sectionId : "2" 200 }, 201 { 202 en: "When a Member requests an emergency debate, the Speaker decides whether or not the request will be granted, weighing certain criteria to determine if the matter relates to a genuine emergency and if it can be dealt with by the House in another way.", 203 fr: "Quand un député demande un débat d'urgence, le Président décide s'il accepte ou non la demande en tenant compte de certains critères, afin de déterminer si l'affaire est vraiment urgente et si la Chambre peut en être saisie d'une autre manière.", 204 titleEn: "Requests for Emergency Debates", 205 titleFr: "Demandes de débats d'urgence", 206 sectionId : "203" 207 }, 208 { 209 en: "Under Routine Proceedings, a Minister may make a short factual announcement or statement of government policy. Spokespersons of recognized opposition parties are given an opportunity to comment.",
210 fr: "Pendant les Affaires courantes, un ministre peut prononcer un court exposé de faits ou de politique gouvernementale. Un porte-parole de chaque parti de l'opposition officiellement reconnu peut ensuite commenter l'exposé.", 211 titleEn: "Statements by Ministers", 212 titleFr: "Déclarations de députés", 213 sectionId : "208" 214 }, 215 { 216 en: "Business of a basic nature for which a daily period is set aside in the House.", 217 fr: "Affaires usuelles pour lesquelles la Chambre réserve une période quotidienne.", 218 titleEn: "Routine Proceedings", 219 titleFr: "Affaires courantes", 220 sectionId : "3" 221 }, 222 { 223 en: "The process by which the government submits its projected annual expenditures for parliamentary approval.", 224 fr: "Processus par lequel le gouvernement soumet ses prévisions annuelles de dépenses à l'approbation du Parlement.", 225 titleEn: "Business of Supply", 226 titleFr: "Travaux des subsides", 227 sectionId : "60003" 228 }, 229 { 230 en: "Bills and motions sponsored by private Members, for which a period is devoted each sitting day.", 231 fr: "Chaque jour de séance, une période de temps est réservée pour lâétude des projets de loi et motions parrainés par de simples députés.", 232 titleEn: "Private Members' Business", 233 titleFr: "Affaires émanant des députés", 234 sectionId : "60004" 235 }, 236 { 237 en: "The process by which the government obtains the necessary resources to meet its expenses.", 238 fr: "Processus par lequel le gouvernement obtient les fonds nécessaires en prévision de ses dépenses.", 239 titleEn: "Ways and Means", 240 titleFr: "Voies et moyens", 241 sectionId : "60007" 242 }, 243 { 244 en: "The allocation of a specific period of time for the consideration of one or more stages of a public bill.", 245 fr: "Désignation d'une période de temps déterminée pour les délibérations sur une ou plusieurs étapes d'un projet de loi d'intérêt public", 246 titleEn: "Time Allocation", 247 titleFr: "Attribution de temps", 248 sectionId : "60014" 249 }, 250 { 251 en: "A procedure preventing further adjournment of debate on any motion and requiring that the motion come to a vote at the end of the sitting in which it is invoked.", 252 fr: "Procédure qui a pour effet d'empêcher qu'un débat sur une motion soit de nouveau ajourné et de provoquer un vote à la fin de la séance en cours.", 253 titleEn: "Closure", 254 titleFr: "Clôture", 255 sectionId : "60016" 256 }, 257 { 258 en: "A statement normally made at the end of Question Period on Thursday, outlining the government business to be considered in the following week.", 259 fr: "Déclaration ayant lieu normalement à la fin de la période des questions orales du jeudi, au cours de laquelle on donne un aperçu des travaux qui seront abordés la semaine suivante.", 260 titleEn: "Business of the House - Weekly Statement", 261 titleFr: "Travaux de la Chambre - Déclaration hebdomadaire", 262 sectionId : "60100" 263 }, 264 { 265 en: "Delivered at the opening of a session of Parliament, the Speech from the Throne outlines the government's plans for the session.", 266 fr: "Prononcé à l'ouverture d'une session du Parlement, le discours du Trône donne un aperçu du programme du gouvernement pour la session.", 267 titleEn: "Consideration of Her Excellency's Speech from the Throne", 268 titleFr: "Prise en considération du discours du Trône de Son Excellence", 269 sectionId : "60101" 270 }, 271 { 272 en: "Bills concerned with public policy introduced by a Minister in the House. ", 273 fr: "Projet de loi dâintérêt public présenté à la Chambre par un ministre.", 274 titleEn: "Government Bills (Commons)", 275 titleFr: "Projets de loi émanant du gouvernement (Communes)", 276 sectionId : "60102" 277 }, 278 { 279 en: "Bills concerned with public policy sponsored by a Minister in the House, but first introduced in the Senate.", 280 fr: "Projet de loi dâintérêt public parrainé à la Chambre par un ministre, mais dâabord présenté au Sénat.", 281 titleEn: "Government Bills (Senate)", 282 titleFr: "Projets de loi émanant du gouvernement (Sénat)", 283 sectionId : "60103" 284 }, 285 { 286 en: "Motions initiated by a Minister or Parliamentary Secretary.", 287 fr: "Motions initiées par un ministre ou un secrétaire parlementaire.", 288 titleEn: "Government Business", 289 titleFr: "Affaires émanant du gouvernement", 290 sectionId : "600104" 291 }, 292 { 293 en: "Funds approved by Parliament to cover government expenditures for the peri
293od from April 1 to June 23 of each fiscal year, pending approval of the main estimates in June.", 294 fr: "Crédits que le Parlement accorde chaque année au gouvernement pour la période du 1er avril au 23 juin afin de couvrir ses dépenses au cours des mois précédant l'adoption du budget principal des dépenses en juin.", 295 titleEn: "Interim Supply", 296 titleFr: "Crédits provisoires", 297 sectionId : "60105" 298 }, 299 { 300 en: "Consideration of the governmentâs planned spending for the coming fiscal year.", 301 fr: "Ãtude des dépenses prévues du gouvernement pour le prochain exercice financier.", 302 titleEn: "Main Estimates", 303 titleFr: "Budget principal des dépenses", 304 sectionId : "60106" 305 }, 306 { 307 en: "Motion moved by a Member of the opposition on an allotted day under business of supply. Such a motion may relate to any matter within Parliamentâs jurisdiction.", 308 fr: "Motion proposée par un député de lâopposition lors dâun jour désigné en vertu des travaux des subsides. Une telle motion peut traiter de toute question relevant de la compétence du Parlement.", 309 titleEn: "Opposition Motion", 310 titleFr: "Motion de l'opposition", 311 sectionId : "60107" 312 }, 313 { 314 en: "The Standing Orders provide for a one-day special debate on the Standing Orders and procedures of the House and its committees early in each Parliament.", 315 fr: "Le Règlement exige un débat spécial dâun jour sur le Règlement et la procédure de la Chambre et de ses comités au début de chaque législature.", 316 titleEn: "Standing Orders and Procedure", 317 titleFr: "Règlement et procédure", 318 sectionId : "60108" 319 }, 320 { 321 en: "An expenditure plan introduced to provide funds to the government to meet new or increased expenses.", 322 fr: "Plan de dépenses visant à obtenir des crédits supplémentaires pour faire face à des imprévus ou à une augmentation des dépenses du gouvernement.", 323 titleEn: "Supplementary Estimates", 324 titleFr: "Budget supplémentaire des dépenses", 325 sectionId : "60109" 326 }, 327 { 328 en: "Vote on the Designation of an Item", 329 fr: "Vote sur la désignation d'une affaire", 330 titleEn: "Vote on the Designation of an Item", 331 titleFr: "Vote sur la désignation d'une affaire", 332 sectionId : "60110" 333 }, 334 { 335 en: "The process by which the government submits its projected annual expenditures for parliamentary approval.", 336 fr: "Processus par lequel le gouvernement soumet ses prévisions annuelles de dépenses à l'approbation du Parlement.", 337 titleEn: "Business of Supply", 338 titleFr: "Travaux des subsides", 339 sectionId : "60124" 340 }, 341 { 342 en: "Funds approved by Parliament to cover government expenditures for the period from April 1 to June 23 of each fiscal year, pending approval of the main estimates in June.", 343 fr: "Crédits que le Parlement accorde chaque année au gouvernement pour la période du 1er avril au 23 juin afin de couvrir ses dépenses au cours des mois précédant l'adoption du budget principal des dépenses en juin.", 344 titleEn: "Interim Supply", 345 titleFr: "Crédits provisoires", 346 sectionId : "60130" 347 }, 348 { 349 en: "A question raised by a Member and decided by the Speaker, with respect to any departure from the Standing Orders or customary procedures.", 350 fr: "Question soulevée par un député et tranchée par le Président, à la suite de toute dérogation au Règlement ou à la procédure courante.", 351 titleEn: "Point of Order", 352 titleFr: "Rappel au Règlement", 353 sectionId : "96" 354 } 355 ] 356 357}; 358 359 360 361 362/*! 363 * Knockout JavaScript library v3.5.1 364 * (c) The Knockout.js team - http://knockoutjs.com/ 365 * License: MIT (http://www.opensource.org/licenses/mit-license.php) 366 */ 367 368(function () { 369 var DEBUG = true; 370 (function (undefined) { 371 // (0, eval)('this') is a robust way of getting a reference to the global object 372 // For details, see http://stackoverflow.com/questions/14119988/return-this-0-evalthis/14120023#14120023 373 var window = this || (0, eval)('this'), 374 document = window['document'], 375 navigator = window['navigator'], 376 jQueryInstance = window["jQuery"], 377 JSON = window["JSON"]; 378 379 if (!jQueryInstance && typeof jQuery !== "undefined") { 380 jQueryInstance = jQuery; 381 } 382 (function (factory) {
vendor: 386,997 bytes, lines 383-6943
383 // Support three module loading scenarios 384 if (typeof define === 'function' && define['amd']) { 385 // [1] AMD anonymous module 386 define(['exports', 'require'], factory); 387 } else if (typeof exports === 'object' && typeof module === 'object') { 388 // [2] CommonJS/Node.js 389 factory(module['exports'] || exports); // module.exports is for Node.js 390 } else { 391 // [3] No module loader (plain <script> tag) - put directly in global namespace 392 factory(window['ko'] = {}); 393 } 394 }(function (koExports, amdRequire) { 395 // Internally, all KO objects are attached to koExports (even the non-exported ones whose names will be minified by the closure compiler). 396 // In the future, the following "ko" variable may be made distinct from "koExports" so that private objects are not externally reachable. 397 var ko = typeof koExports !== 'undefined' ? koExports : {}; 398 // Google Closure Compiler helpers (used only to make the minified file smaller) 399 ko.exportSymbol = function (koPath, object) { 400 var tokens = koPath.split("."); 401 402 // In the future, "ko" may become distinct from "koExports" (so that non-exported objects are not reachable) 403 // At that point, "target" would be set to: (typeof koExports !== "undefined" ? koExports : ko) 404 var target = ko; 405 406 for (var i = 0; i < tokens.length - 1; i++) 407 target = target[tokens[i]]; 408 target[tokens[tokens.length - 1]] = object; 409 }; 410 ko.exportProperty = function (owner, publicName, object) { 411 owner[publicName] = object; 412 }; 413 ko.version = "3.5.1"; 414 415 ko.exportSymbol('version', ko.version); 416 // For any options that may affect various areas of Knockout and aren't directly associated with data binding. 417 ko.options = { 418 'deferUpdates': false, 419 'useOnlyNativeEvents': false, 420 'foreachHidesDestroyed': false 421 }; 422 423 //ko.exportSymbol('options', ko.options); // 'options' isn't minified 424 ko.utils = (function () { 425 var hasOwnProperty = Object.prototype.hasOwnProperty; 426 427 function objectForEach(obj, action) { 428 for (var prop in obj) { 429 if (hasOwnProperty.call(obj, prop)) { 430 action(prop, obj[prop]); 431 } 432 } 433 } 434 435 function extend(target, source) { 436 if (source) { 437 for (var prop in source) { 438 if (hasOwnProperty.call(source, prop)) { 439 target[prop] = source[prop]; 440 } 441 } 442 } 443 return target; 444 } 445 446 function setPrototypeOf(obj, proto) { 447 obj.__proto__ = proto; 448 return obj; 449 } 450 451 var canSetPrototype = ({ __proto__: [] } instanceof Array); 452 var canUseSymbols = !DEBUG && typeof Symbol === 'function'; 453 454 // Represent the known event types in a compact way, then at runtime transform it into a hash with event name as key (for fast lookup) 455 var knownEvents = {}, knownEventTypesByEventName = {}; 456 var keyEventTypeName = (navigator && /Firefox\/2/i.test(navigator.userAgent)) ? 'KeyboardEvent' : 'UIEvents'; 457 knownEvents[keyEventTypeName] = ['keyup', 'keydown', 'keypress']; 458 knownEvents['MouseEvents'] = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseover', 'mouseout', 'mouseenter', 'mouseleave']; 459 objectForEach(knownEvents, function (eventType, knownEventsForType) { 460 if (knownEventsForType.length) { 461 for (var i = 0, j = knownEventsForType.length; i < j; i++) 462 knownEventTypesByEventName[knownEventsForType[i]] = eventType; 463 } 464 }); 465 var eventsThatMustBeRegisteredUsingAttachEvent = { 'propertychange': true }; // Workaround for an IE9 issue - https://github.com/SteveSanderson/knockout/issues/406 466 467 // Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness) 468 // Note that, since IE 10 does not support conditional comments, the following logic only detects IE < 10. 469 // Currently this is by design, since IE 10+ behaves correctly when treated as a standard browser. 470 // If there is a future need to detect specific versions of IE10+, we will amend this. 471 var ieVersion = document && (function () { 472 var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i'); 473 474 // Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment 475 while ( 476 div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->', 477 iElems[0] 478 ) { } 479 return version > 4 ? version : undefined; 480 }()); 481 var isIe6 = ieVersion === 6, 482 isIe7 = ieVersion === 7; 483 484 function isClickOnCheckableElement(element, eventType) { 485 if ((ko.utils.tagNameLower(element) !== "input") || !element.type) return false; 486 if (eventType.toLowerCase() != "click") return false; 487 var inputType = element.type; 488 return (inputType == "checkbox") || (inputType == "radio"); 489 } 490 491 // For details on the pattern for changing node classes 492 // see: https://github.com/knockout/knockout/issues/1597 493 var cssClassNameRegex = /\S+/g; 494 495 var jQueryEventAttachName; 496 497 function toggleDomNodeCssClass(node, classNames, shouldHaveClass) { 498 var addOrRemoveFn; 499 if (classNames) { 500 if (typeof node.classList === 'object') { 501 addOrRemoveFn = node.classList[shouldHaveClass ? 'add' : 'remove']; 502 ko.utils.arrayForEach(classNames.match(cssClassNameRegex), function (className) { 503 addOrRemoveFn.call(node.classList, className); 504 }); 505 } else if (typeof node.className['baseVal'] === 'string') { 506 // SVG tag .classNames is an SVGAnimatedString instance 507 toggleObjectClassPropertyString(node.className, 'baseVal', classNames, shouldHaveClass); 508 } else { 509 // node.className ought to be a string. 510 toggleObjectClassPropertyString(node, 'className', classNames, shouldHaveClass); 511 } 512 } 513 } 514 515 function toggleObjectClassPropertyString(obj, prop, classNames, shouldHaveClass) { 516 // obj/prop is either a node/'className' or a SVGAnimatedString/'baseVal'. 517 var currentClassNames = obj[prop].match(cssClassNameRegex) || []; 518 ko.utils.arrayForEach(classNames.match(cssClassNameRegex), function (className) { 519 ko.utils.addOrRemoveItem(currentClassNames, className, shouldHaveClass); 520 }); 521 obj[prop] = currentClassNames.join(" "); 522 } 523 524 return { 525 fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/], 526 527 arrayForEach: function (array, action, actionOwner) { 528 for (var i = 0, j = array.length; i < j; i++) { 529 action.call(actionOwner, array[i], i, array); 530 } 531 }, 532 533 arrayIndexOf: typeof Array.prototype.indexOf == "function" 534 ? function (array, item) { 535 return Array.prototype.indexOf.call(array, item); 536 } 537 : function (array, item) { 538 for (var i = 0, j = array.length; i < j; i++) { 539 if (array[i] === item) 540 return i; 541 } 542 return -1; 543 }, 544 545 arrayFirst: function (array, predicate, predicateOwner) { 546 for (var i = 0, j = array.length; i < j; i++) { 547 if (predicate.call(predicateOwner, array[i], i, array)) 548 return array[i]; 549 } 550 return undefined; 551 }, 552 553 arrayRemoveItem: function (array, itemToRemove) { 554 var index = ko.utils.arrayIndexOf(array, itemToRemove); 555 if (index > 0) { 556 array.splice(index, 1); 557 } 558 else if (index === 0) { 559 array.shift(); 560 } 561 }, 562 563 arrayGetDistinctValues: function (array) { 564 var result = []; 565 if (array) { 566 ko.utils.arrayForEach(array, function (item) { 567 if (ko.utils.arrayIndexOf(result, item) < 0) 568 result.push(item); 569 }); 570 } 571 return result; 572 }, 573 574 arrayMap: function (array, mapping, mappingOwner) { 575 var result = []; 576 if (array) { 577 for (var i = 0, j = array.length; i < j; i++) 578 result.push(mapping.call(mappingOwner, array[i], i)); 579 } 580 return result; 581 }, 582 583 arrayFilter: function (array, predicate, predicateOwner) { 584 var result = []; 585 if (array) { 586 for (var i = 0, j = array.length; i < j; i++) 587 if (predicate.call(predicateOwner, array[i], i)) 588 result.push(array[i]); 589 } 590 return result; 591 }, 592 593 arrayPushAll: function (array, valuesToPush) { 594 if (valuesToPush instanceof Array) 595 array.push.apply(array, valuesToPush); 596 else 597 for (var i = 0, j = valuesToPush.length; i < j; i++) 598 array.push(valuesToPush[i]); 599 return array; 600 }, 601 602 addOrRemoveItem: function (array, value, included) { 603 var existingEntryIndex = ko.utils.arrayIndexOf(ko.utils.peekObservable(array), value); 604 if (existingEntryIndex < 0) { 605 if (included) 606 array.push(value); 607 } else { 608 if (!included) 609 array.splice(existingEntryIndex, 1); 610 } 611 }, 612 613 canSetPrototype: canSetPrototype, 614 615 extend: extend, 616 617 setPrototypeOf: setPrototypeOf, 618 619 setPrototypeOfOrExtend: canSetPrototype ? setPrototypeOf : extend, 620 621 objectForEach: objectForEach, 622 623 objectMap: function (source, mapping, mappingOwner) { 624 if (!source) 625 return source; 626 var target = {}; 627 for (var prop in source) { 628 if (hasOwnProperty.call(source, prop)) { 629 target[prop] = mapping.call(mappingOwner, source[prop], prop, source); 630 } 631 } 632 return target; 633 }, 634 635 emptyDomNode: function (domNode) { 636 while (domNode.firstChild) { 637 ko.removeNode(domNode.firstChild); 638 } 639 }, 640 641 moveCleanedNodesToContainerElement: function (nodes) { 642 // Ensure it's a real array, as we're about to reparent the nodes and 643 // we don't want the underlying collection to change while we're doing that. 644 var nodesArray = ko.utils.makeArray(nodes); 645 var templateDocument = (nodesArray[0] && nodesArray[0].ownerDocument) || document; 646 647 var container = templateDocument.createElement('div'); 648 for (var i = 0, j = nodesArray.length; i < j; i++) { 649 container.appendChild(ko.cleanNode(nodesArray[i])); 650 } 651 return container; 652 }, 653 654 cloneNodes: function (nodesArray, shouldCleanNodes) { 655 for (var i = 0, j = nodesArray.length, newNodesArray = []; i < j; i++) { 656 var clonedNode = nodesArray[i].cloneNode(true); 657 newNodesArray.push(shouldCleanNodes ? ko.cleanNode(clonedNode) : clonedNode); 658 } 659 return newNodesArray; 660 }, 661 662 setDomNodeChildren: function (domNode, childNodes) { 663 ko.utils.emptyDomNode(domNode); 664 if (childNodes) { 665 for (var i = 0, j = childNodes.length; i < j; i++) 666 domNode.appendChild(childNodes[i]); 667 } 668 }, 669 670 replaceDomNodes: function (nodeToReplaceOrNodeArray, newNodesArray) { 671 var nodesToReplaceArray = nodeToReplaceOrNodeArray.nodeType ? [nodeToReplaceOrNodeArray] : nodeToReplaceOrNodeArray; 672 if (nodesToReplaceArray.length > 0) { 673 var insertionPoint = nodesToReplaceArray[0]; 674 var parent = insertionPoint.parentNode; 675 for (var i = 0, j = newNodesArray.length; i < j; i++) 676 parent.insertBefore(newNodesArray[i], insertionPoint); 677 for (var i = 0, j = nodesToReplaceArray.length; i < j; i++) { 678 ko.removeNode(nodesToReplaceArray[i]); 679 } 680 } 681 }, 682 683 fixUpContinuousNodeArray: function (continuousNodeArray, parentNode) { 684 // Before acting on a set of nodes that were previously outputted by a template function, we have to reconcile 685 // them against what is in the DOM right now. It may be that some of the nodes have already been removed, or that 686 // new nodes might have been inserted in the middle, for example by a binding. Also, there may previously have been 687 // leading comment nodes (created by rewritten string-based templates) that have since been removed during binding. 688 // So, this function translates the old "map" output array into its best guess of the set of current DOM nodes. 689 // 690 // Rules: 691 // [A] Any leading nodes that have been removed should be ignored 692 // These most likely correspond to memoization nodes that were already removed during binding 693 // See https://github.com/knockout/knockout/pull/440 694 // [B] Any trailing nodes that have been remove should be ignored 695 // This prevents the code here from adding unrelated nodes to the array while processing rule [C] 696 // See https://github.com/knockout/knockout/pull/1903 697 // [C] We want to output a continuous series of nodes. So, ignore any nodes that have already been removed, 698 // and include any nodes that have been inserted among the previous collection 699 700 if (continuousNodeArray.length) { 701 // The parent node can be a virtual element; so get the real parent node 702 parentNode = (parentNode.nodeType === 8 && parentNode.parentNode) || parentNode; 703 704 // Rule [A] 705 while (continuousNodeArray.length && continuousNodeArray[0].parentNode !== parentNode) 706 continuousNodeArray.splice(0, 1); 707 708 // Rule [B] 709 while (continuousNodeArray.length > 1 && continuousNodeArray[continuousNodeArray.length - 1].parentNode !== parentNode) 710 continuousNodeArray.length--; 711 712 // Rule [C] 713 if (continuousNodeArray.length > 1) { 714 var current = continuousNodeArray[0], last = continuousNodeArray[continuousNodeArray.length - 1]; 715 // Replace with the actual new continuous node set 716 continuousNodeArray.length = 0; 717 while (current !== last) { 718 continuousNodeArray.push(current); 719 current = current.nextSibling; 720 } 721 continuousNodeArray.push(last); 722 } 723 } 724 return continuousNodeArray; 725 }, 726 727 setOptionNodeSelectionState: function (optionNode, isSelected) { 728 // IE6 sometimes throws "unknown error" if you try to write to .selected directly, whereas Firefox struggles with setAttribute. Pick one based on browser. 729 if (ieVersion < 7) 730 optionNode.setAttribute("selected", isSelected); 731 else 732 optionNode.selected = isSelected; 733 }, 734 735 stringTrim: function (string) { 736 return string === null || string === undefined ? '' : 737 string.trim ? 738 string.trim() : 739 string.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); 740 }, 741 742 stringStartsWith: function (string, startsWith) { 743 string = string || ""; 744 if (startsWith.length > string.length) 745 return false; 746 return string.substring(0, startsWith.length) === startsWith; 747 }, 748 749 domNodeIsContainedBy: function (node, containedByNode) { 750 if (node === containedByNode) 751 return true; 752 if (node.nodeType === 11) 753 return false; // Fixes issue #1162 - can't use node.contains for document fragments on IE8 754 if (containedByNode.contains) 755 return containedByNode.contains(node.nodeType !== 1 ? node.parentNode : node); 756 if (containedByNode.compareDocumentPosition) 757 return (containedByNode.compareDocumentPosition(node) & 16) == 16; 758 while (node && node != containedByNode) { 759 node = node.parentNode; 760 } 761 return !!node; 762 }, 763 764 domNodeIsAttachedToDocument: function (node) { 765 return ko.utils.domNodeIsContainedBy(node, node.ownerDocument.documentElement); 766 }, 767 768 anyDomNodeIsAttachedToDocument: function (nodes) { 769 return !!ko.utils.arrayFirst(nodes, ko.utils.domNodeIsAttachedToDocument); 770 }, 771 772 tagNameLower: function (element) { 773 // For HTML elements, tagName will always be upper case; for XHTML elements, it'll be lower case. 774 // Possible future optimization: If we know it's an element from an XHTML document (not HTML), 775 // we don't need to do the .toLowerCase() as it will always be lower case anyway. 776 return element && element.tagName && element.tagName.toLowerCase(); 777 }, 778 779 catchFunctionErrors: function (delegate) { 780 return ko['onError'] ? function () { 781 try { 782 return delegate.apply(this, arguments); 783 } catch (e) { 784 ko['onError'] && ko['onError'](e); 785 throw e; 786 } 787 } : delegate; 788 }, 789 790 setTimeout: function (handler, timeout) { 791 return setTimeout(ko.utils.catchFunctionErrors(handler), timeout); 792 }, 793 794 deferError: function (error) { 795 setTimeout(function () { 796 ko['onError'] && ko['onError'](error); 797 throw error; 798 }, 0); 799 }, 800 801 registerEventHandler: function (element, eventType, handler) { 802 var wrappedHandler = ko.utils.catchFunctionErrors(handler); 803 804 var mustUseAttachEvent = eventsThatMustBeRegisteredUsingAttachEvent[eventType]; 805 if (!ko.options['useOnlyNativeEvents'] && !mustUseAttachEvent && jQueryInstance) { 806 if (!jQueryEventAttachName) { 807 jQueryEventAttachName = (typeof jQueryInstance(element)['on'] == 'function') ? 'on' : 'bind'; 808 } 809 jQueryInstance(element)[jQueryEventAttachName](eventType, wrappedHandler); 810 } else if (!mustUseAttachEvent && typeof element.addEventListener == "function") 811 element.addEventListener(eventType, wrappedHandler, false); 812 else if (typeof element.attachEvent != "undefined") { 813 var attachEventHandler = function (event) { wrappedHandler.call(element, event); }, 814 attachEventName = "on" + eventType; 815 element.attachEvent(attachEventName, attachEventHandler); 816 817 // IE does not dispose attachEvent handlers automatically (unlike with addEventListener) 818 // so to avoid leaks, we have to remove them manually. See bug #856 819 ko.utils.domNodeDisposal.addDisposeCallback(element, function () { 820 element.detachEvent(attachEventName, attachEventHandler); 821 }); 822 } else 823 throw new Error("Browser doesn't support addEventListener or attachEvent"); 824 }, 825 826 triggerEvent: function (element, eventType) { 827 if (!(element && element.nodeType)) 828 throw new Error("element must be a DOM node when calling triggerEvent"); 829 830 // For click events on checkboxes and radio buttons, jQuery toggles the element checked state *after* the 831 // event handler runs instead of *before*. (This was fixed in 1.9 for checkboxes but not for radio buttons.) 832 // IE doesn't change the checked state when you trigger the click event using "fireEvent". 833 // In both cases, we'll use the click method instead. 834 var useClickWorkaround = isClickOnCheckableElement(element, eventType); 835 836 if (!ko.options['useOnlyNativeEvents'] && jQueryInstance && !useClickWorkaround) { 837 jQueryInstance(element)['trigger'](eventType); 838 } else if (typeof document.createEvent == "function") { 839 if (typeof element.dispatchEvent == "function") { 840 var eventCategory = knownEventTypesByEventName[eventType] || "HTMLEvents"; 841 var event = document.createEvent(eventCategory); 842 event.initEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element); 843 element.dispatchEvent(event); 844 } 845 else 846 throw new Error("The supplied element doesn't support dispatchEvent"); 847 } else if (useClickWorkaround && element.click) { 848 element.click(); 849 } else if (typeof element.fireEvent != "undefined") { 850 element.fireEvent("on" + eventType); 851 } else { 852 throw new Error("Browser doesn't support triggering events"); 853 } 854 }, 855 856 unwrapObservable: function (value) { 857 return ko.isObservable(value) ? value() : value; 858 }, 859 860 peekObservable: function (value) { 861 return ko.isObservable(value) ? value.peek() : value; 862 }, 863 864 toggleDomNodeCssClass: toggleDomNodeCssClass, 865 866 setTextContent: function (element, textContent) { 867 var value = ko.utils.unwrapObservable(textContent); 868 if ((value === null) || (value === undefined)) 869 value = ""; 870 871 // We need there to be exactly one child: a text node. 872 // If there are no children, more than one, or if it's not a text node, 873 // we'll clear everything and create a single text node. 874 var innerTextNode = ko.virtualElements.firstChild(element); 875 if (!innerTextNode || innerTextNode.nodeType != 3 || ko.virtualElements.nextSibling(innerTextNode)) { 876 ko.virtualElements.setDomNodeChildren(element, [element.ownerDocument.createTextNode(value)]); 877 } else { 878 innerTextNode.data = value; 879 } 880 881 ko.utils.forceRefresh(element); 882 }, 883 884 setElementName: function (element, name) { 885 element.name = name; 886 887 // Workaround IE 6/7 issue 888 // - https://github.com/SteveSanderson/knockout/issues/197 889 // - http://www.matts411.com/post/setting_the_name_attribute_in_ie_dom/ 890 if (ieVersion <= 7) { 891 try { 892 var escapedName = element.name.replace(/[&<>'"]/g, function (r) { return "&#" + r.charCodeAt(0) + ";"; }); 893 element.mergeAttributes(document.createElement("<input name='" + escapedName + "'/>"), false); 894 } 895 catch (e) { } // For IE9 with doc mode "IE9 Standards" and browser mode "IE9 Compatibility View" 896 } 897 }, 898 899 forceRefresh: function (node) { 900 // Workaround for an IE9 rendering bug - https://github.com/SteveSanderson/knockout/issues/209 901 if (ieVersion >= 9) { 902 // For text nodes and comment nodes (most likely virtual elements), we will have to refresh the container 903 var elem = node.nodeType == 1 ? node : node.parentNode; 904 if (elem.style) 905 elem.style.zoom = elem.style.zoom; 906 } 907 }, 908 909 ensureSelectElementIsRenderedCorrectly: function (selectElement) { 910 // Workaround for IE9 rendering bug - it doesn't reliably display all the text in dynamically-added select boxes unless you force it to re-render by updating the width. 911 // (See https://github.com/SteveSanderson/knockout/issues/312, http://stackoverflow.com/questions/5908494/select-only-shows-first-char-of-selected-option) 912 // Also fixes IE7 and IE8 bug that causes selects to be zero width if enclosed by 'if' or 'with'. (See issue #839) 913 if (ieVersion) { 914 var originalWidth = selectElement.style.width; 915 selectElement.style.width = 0; 916 selectElement.style.width = originalWidth; 917 } 918 }, 919 920 range: function (min, max) { 921 min = ko.utils.unwrapObservable(min); 922 max = ko.utils.unwrapObservable(max); 923 var result = []; 924 for (var i = min; i <= max; i++) 925 result.push(i); 926 return result; 927 }, 928 929 makeArray: function (arrayLikeObject) { 930 var result = []; 931 for (var i = 0, j = arrayLikeObject.length; i < j; i++) { 932 result.push(arrayLikeObject[i]); 933 }; 934 return result; 935 }, 936 937 createSymbolOrString: function (identifier) { 938 return canUseSymbols ? Symbol(identifier) : identifier; 939 }, 940 941 isIe6: isIe6, 942 isIe7: isIe7, 943 ieVersion: ieVersion, 944 945 getFormFields: function (form, fieldName) { 946 var fields = ko.utils.makeArray(form.getElementsByTagName("input")).concat(ko.utils.makeArray(form.getElementsByTagName("textarea"))); 947 var isMatchingField = (typeof fieldName == 'string') 948 ? function (field) { return field.name === fieldName } 949 : function (field) { return fieldName.test(field.name) }; // Treat fieldName as regex or object containing predicate 950 var matches = []; 951 for (var i = fields.length - 1; i >= 0; i--) { 952 if (isMatchingField(fields[i])) 953 matches.push(fields[i]); 954 }; 955 return matches; 956 }, 957 958 parseJson: function (jsonString) { 959 if (typeof jsonString == "string") { 960 jsonString = ko.utils.stringTrim(jsonString); 961 if (jsonString) { 962 if (JSON && JSON.parse) // Use native parsing where available 963 return JSON.parse(jsonString); 964 return (new Function("return " + jsonString))(); // Fallback on less safe parsing for older browsers 965 } 966 } 967 return null; 968 }, 969 970 stringifyJson: function (data, replacer, space) { // replacer and space are optional 971 if (!JSON || !JSON.stringify) 972 throw new Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js"); 973 return JSON.stringify(ko.utils.unwrapObservable(data), replacer, space); 974 }, 975 976 postJson: function (urlOrForm, data, options) { 977 options = options || {}; 978 var params = options['params'] || {}; 979 var includeFields = options['includeFields'] || this.fieldsIncludedWithJsonPost; 980 var url = urlOrForm; 981 982 // If we were given a form, use its 'action' URL and pick out any requested field values 983 if ((typeof urlOrForm == 'object') && (ko.utils.tagNameLower(urlOrForm) === "form")) { 984 var originalForm = urlOrForm; 985 url = originalForm.action; 986 for (var i = includeFields.length - 1; i >= 0; i--) { 987 var fields = ko.utils.getFormFields(originalForm, includeFields[i]); 988 for (var j = fields.length - 1; j >= 0; j--) 989 params[fields[j].name] = fields[j].value; 990 } 991 } 992 993 data = ko.utils.unwrapObservable(data); 994 var form = document.createElement("form"); 995 form.style.display = "none"; 996 form.action = url; 997 form.method = "post"; 998 for (var key in data) { 999 // Since 'data' this is a model object, we include all properties including those inherited from its prototype 1000 var input = document.createElement("input"); 1001 input.type = "hidden"; 1002 input.name = key; 1003 input.value = ko.utils.stringifyJson(ko.utils.unwrapObservable(data[key])); 1004 form.appendChild(input); 1005 } 1006 objectForEach(params, function (key, value) { 1007 var input = document.createElement("input"); 1008 input.type = "hidden"; 1009 input.name = key; 1010 input.value = value; 1011 form.appendChild(input); 1012 }); 1013 document.body.appendChild(form); 1014 options['submitter'] ? options['submitter'](form) : form.submit(); 1015 setTimeout(function () { form.parentNode.removeChild(form); }, 0); 1016 } 1017 } 1018 }()); 1019 1020 ko.exportSymbol('utils', ko.utils); 1021 ko.exportSymbol('utils.arrayForEach', ko.utils.arrayForEach); 1022 ko.exportSymbol('utils.arrayFirst', ko.utils.arrayFirst); 1023 ko.exportSymbol('utils.arrayFilter', ko.utils.arrayFilter); 1024 ko.exportSymbol('utils.arrayGetDistinctValues', ko.utils.arrayGetDistinctValues); 1025 ko.exportSymbol('utils.arrayIndexOf', ko.utils.arrayIndexOf); 1026 ko.exportSymbol('utils.arrayMap', ko.utils.arrayMap); 1027 ko.exportSymbol('utils.arrayPushAll', ko.utils.arrayPushAll); 1028 ko.exportSymbol('utils.arrayRemoveItem', ko.utils.arrayRemoveItem); 1029 ko.exportSymbol('utils.cloneNodes', ko.utils.cloneNodes); 1030 ko.exportSymbol('utils.createSymbolOrString', ko.utils.createSymbolOrString); 1031 ko.exportSymbol('utils.extend', ko.utils.extend); 1032 ko.exportSymbol('utils.fieldsIncludedWithJsonPost', ko.utils.fieldsIncludedWithJsonPost); 1033 ko.exportSymbol('utils.getFormFields', ko.utils.getFormFields); 1034 ko.exportSymbol('utils.objectMap', ko.utils.objectMap); 1035 ko.exportSymbol('utils.peekObservable', ko.utils.peekObservable); 1036 ko.exportSymbol('utils.postJson', ko.utils.postJson); 1037 ko.exportSymbol('utils.parseJson', ko.utils.parseJson); 1038 ko.exportSymbol('utils.registerEventHandler', ko.utils.registerEventHandler); 1039 ko.exportSymbol('utils.stringifyJson', ko.utils.stringifyJson); 1040 ko.exportSymbol('utils.range', ko.utils.range); 1041 ko.exportSymbol('utils.toggleDomNodeCssClass', ko.utils.toggleDomNodeCssClass); 1042 ko.exportSymbol('utils.triggerEvent', ko.utils.triggerEvent); 1043 ko.exportSymbol('utils.unwrapObservable', ko.utils.unwrapObservable); 1044 ko.exportSymbol('utils.objectForEach', ko.utils.objectForEach); 1045 ko.exportSymbol('utils.addOrRemoveItem', ko.utils.addOrRemoveItem); 1046 ko.exportSymbol('utils.setTextContent', ko.utils.setTextContent); 1047 ko.exportSymbol('unwrap', ko.utils.unwrapObservable); // Convenient shorthand, because this is used so commonly 1048 1049 if (!Function.prototype['bind']) { 1050 // Function.prototype.bind is a standard part of ECMAScript 5th Edition (December 2009, http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf) 1051 // In case the browser doesn't implement it natively, provide a JavaScript implementation. This implementation is based on the one in prototype.js 1052 Function.prototype['bind'] = function (object) { 1053 var originalFunction = this; 1054 if (arguments.length === 1) { 1055 return function () { 1056 return originalFunction.apply(object, arguments); 1057 }; 1058 } else { 1059 var partialArgs = Array.prototype.slice.call(arguments, 1); 1060 return function () { 1061 var args = partialArgs.slice(0); 1062 args.push.apply(args, arguments); 1063 return originalFunction.apply(object, args); 1064 }; 1065 } 1066 }; 1067 } 1068 1069 ko.utils.domData = new (function () { 1070 var uniqueId = 0; 1071 var dataStoreKeyExpandoPropertyName = "__ko__" + (new Date).getTime(); 1072 var dataStore = {}; 1073 1074 var getDataForNode, clear; 1075 if (!ko.utils.ieVersion) { 1076 // We considered using WeakMap, but it has a problem in IE 11 and Edge that prevents using 1077 // it cross-window, so instead we just store the data directly on the node. 1078 // See https://github.com/knockout/knockout/issues/2141 1079 getDataForNode = function (node, createIfNotFound) { 1080 var dataForNode = node[dataStoreKeyExpandoPropertyName]; 1081 if (!dataForNode && createIfNotFound) { 1082 dataForNode = node[dataStoreKeyExpandoPropertyName] = {}; 1083 } 1084 return dataForNode; 1085 }; 1086 clear = function (node) { 1087 if (node[dataStoreKeyExpandoPropertyName]) { 1088 delete node[dataStoreKeyExpandoPropertyName]; 1089 return true; // Exposing "did clean" flag purely so specs can infer whether things have been cleaned up as intended 1090 } 1091 return false; 1092 }; 1093 } else { 1094 // Old IE versions have memory issues if you store objects on the node, so we use a 1095 // separate data storage and link to it from the node using a string key. 1096 getDataForNode = function (node, createIfNotFound) { 1097 var dataStoreKey = node[dataStoreKeyExpandoPropertyName]; 1098 var hasExistingDataStore = dataStoreKey && (dataStoreKey !== "null") && dataStore[dataStoreKey]; 1099 if (!hasExistingDataStore) { 1100 if (!createIfNotFound) 1101 return undefined; 1102 dataStoreKey = node[dataStoreKeyExpandoPropertyName] = "ko" + uniqueId++; 1103 dataStore[dataStoreKey] = {}; 1104 } 1105 return dataStore[dataStoreKey]; 1106 }; 1107 clear = function (node) { 1108 var dataStoreKey = node[dataStoreKeyExpandoPropertyName]; 1109 if (dataStoreKey) { 1110 delete dataStore[dataStoreKey]; 1111 node[dataStoreKeyExpandoPropertyName] = null; 1112 return true; // Exposing "did clean" flag purely so specs can infer whether things have been cleaned up as intended 1113 } 1114 return false; 1115 }; 1116 } 1117 1118 return { 1119 get: function (node, key) { 1120 var dataForNode = getDataForNode(node, false); 1121 return dataForNode && dataForNode[key]; 1122 }, 1123 set: function (node, key, value) { 1124 // Make sure we don't actually create a new domData key if we are actually deleting a value 1125 var dataForNode = getDataForNode(node, value !== undefined /* createIfNotFound */); 1126 dataForNode && (dataForNode[key] = value); 1127 }, 1128 getOrSet: function (node, key, value) { 1129 var dataForNode = getDataForNode(node, true /* createIfNotFound */); 1130 return dataForNode[key] || (dataForNode[key] = value); 1131 }, 1132 clear: clear, 1133 1134 nextKey: function () { 1135 return (uniqueId++) + dataStoreKeyExpandoPropertyName; 1136 } 1137 }; 1138 })(); 1139 1140 ko.exportSymbol('utils.domData', ko.utils.domData); 1141 ko.exportSymbol('utils.domData.clear', ko.utils.domData.clear); // Exporting only so specs can clear up after themselves fully 1142 1143 ko.utils.domNodeDisposal = new (function () { 1144 var domDataKey = ko.utils.domData.nextKey(); 1145 var cleanableNodeTypes = { 1: true, 8: true, 9: true }; // Element, Comment, Document 1146 var cleanableNodeTypesWithDescendants = { 1: true, 9: true }; // Element, Document 1147 1148 function getDisposeCallbacksCollection(node, createIfNotFound) { 1149 var allDisposeCallbacks = ko.utils.domData.get(node, domDataKey); 1150 if ((allDisposeCallbacks === undefined) && createIfNotFound) { 1151 allDisposeCallbacks = []; 1152 ko.utils.domData.set(node, domDataKey, allDisposeCallbacks); 1153 } 1154 return allDisposeCallbacks; 1155 } 1156 function destroyCallbacksCollection(node) { 1157 ko.utils.domData.set(node, domDataKey, undefined); 1158 } 1159 1160 function cleanSingleNode(node) { 1161 // Run all the dispose callbacks 1162 var callbacks = getDisposeCallbacksCollection(node, false); 1163 if (callbacks) { 1164 callbacks = callbacks.slice(0); // Clone, as the array may be modified during iteration (typically, callbacks will remove themselves) 1165 for (var i = 0; i < callbacks.length; i++) 1166 callbacks[i](node); 1167 } 1168 1169 // Erase the DOM data 1170 ko.utils.domData.clear(node); 1171 1172 // Perform cleanup needed by external libraries (currently only jQuery, but can be extended) 1173 ko.utils.domNodeDisposal["cleanExternalData"](node); 1174 1175 // Clear any immediate-child comment nodes, as these wouldn't have been found by 1176 // node.getElementsByTagName("*") in cleanNode() (comment nodes aren't elements) 1177 if (cleanableNodeTypesWithDescendants[node.nodeType]) { 1178 cleanNodesInList(node.childNodes, true/*onlyComments*/); 1179 } 1180 } 1181 1182 function cleanNodesInList(nodeList, onlyComments) { 1183 var cleanedNodes = [], lastCleanedNode; 1184 for (var i = 0; i < nodeList.length; i++) { 1185 if (!onlyComments || nodeList[i].nodeType === 8) { 1186 cleanSingleNode(cleanedNodes[cleanedNodes.length] = lastCleanedNode = nodeList[i]); 1187 if (nodeList[i] !== lastCleanedNode) { 1188 while (i-- && ko.utils.arrayIndexOf(cleanedNodes, nodeList[i]) == -1) { } 1189 } 1190 } 1191 } 1192 } 1193 1194 return { 1195 addDisposeCallback: function (node, callback) { 1196 if (typeof callback != "function") 1197 throw new Error("Callback must be a function"); 1198 getDisposeCallbacksCollection(node, true).push(callback); 1199 }, 1200 1201 removeDisposeCallback: function (node, callback) { 1202 var callbacksCollection = getDisposeCallbacksCollection(node, false); 1203 if (callbacksCollection) { 1204 ko.utils.arrayRemoveItem(callbacksCollection, callback); 1205 if (callbacksCollection.length == 0) 1206 destroyCallbacksCollection(node); 1207 } 1208 }, 1209 1210 cleanNode: function (node) { 1211 ko.dependencyDetection.ignore(function () { 1212 // First clean this node, where applicable 1213 if (cleanableNodeTypes[node.nodeType]) { 1214 cleanSingleNode(node); 1215 1216 // ... then its descendants, where applicable 1217 if (cleanableNodeTypesWithDescendants[node.nodeType]) { 1218 cleanNodesInList(node.getElementsByTagName("*")); 1219 } 1220 } 1221 }); 1222 1223 return node; 1224 }, 1225 1226 removeNode: function (node) { 1227 ko.cleanNode(node); 1228 if (node.parentNode) 1229 node.parentNode.removeChild(node); 1230 }, 1231 1232 "cleanExternalData": function (node) { 1233 // Special support for jQuery here because it's so commonly used. 1234 // Many jQuery plugins (including jquery.tmpl) store data using jQuery's equivalent of domData 1235 // so notify it to tear down any resources associated with the node & descendants here. 1236 if (jQueryInstance && (typeof jQueryInstance['cleanData'] == "function")) 1237 jQueryInstance['cleanData']([node]); 1238 } 1239 }; 1240 })(); 1241 ko.cleanNode = ko.utils.domNodeDisposal.cleanNode; // Shorthand name for convenience 1242 ko.removeNode = ko.utils.domNodeDisposal.removeNode; // Shorthand name for convenience 1243 ko.exportSymbol('cleanNode', ko.cleanNode); 1244 ko.exportSymbol('removeNode', ko.removeNode); 1245 ko.exportSymbol('utils.domNodeDisposal', ko.utils.domNodeDisposal); 1246 ko.exportSymbol('utils.domNodeDisposal.addDisposeCallback', ko.utils.domNodeDisposal.addDisposeCallback); 1247 ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeDisposal.removeDisposeCallback); 1248 (function () { 1249 var none = [0, "", ""], 1250 table = [1, "<table>", "</table>"], 1251 tbody = [2, "<table><tbody>", "</tbody></table>"], 1252 tr = [3, "<table><tbody><tr>", "</tr></tbody></table>"], 1253 select = [1, "<select multiple='multiple'>", "</select>"], 1254 lookup = { 1255 'thead': table, 1256 'tbody': table, 1257 'tfoot': table, 1258 'tr': tbody, 1259 'td': tr, 1260 'th': tr, 1261 'option': select, 1262 'optgroup': select 1263 }, 1264 1265 // This is needed for old IE if you're *not* using either jQuery or innerShiv. Doesn't affect other cases. 1266 mayRequireCreateElementHack = ko.utils.ieVersion <= 8; 1267 1268 function getWrap(tags) { 1269 var m = tags.match(/^(?:<!--.*?-->\s*?)*?<([a-z]+)[\s>]/); 1270 return (m && lookup[m[1]]) || none; 1271 } 1272 1273 function simpleHtmlParse(html, documentContext) { 1274 documentContext || (documentContext = document); 1275 var windowContext = documentContext['parentWindow'] || documentContext['defaultView'] || window; 1276 1277 // Based on jQuery's "clean" function, but only accounting for table-related elements. 1278 // If you have referenced jQuery, this won't be used anyway - KO will use jQuery's "clean" function directly 1279 1280 // Note that there's still an issue in IE < 9 whereby it will discard comment nodes that are the first child of 1281 // a descendant node. For example: "<div><!-- mycomment -->abc</div>" will get parsed as "<div>abc</div>" 1282 // This won't affect anyone who has referenced jQuery, and there's always the workaround of inserting a dummy node 1283 // (possibly a text node) in front of the comment. So, KO does not attempt to workaround this IE issue automatically at present. 1284 1285 // Trim whitespace, otherwise indexOf won't work as expected 1286 var tags = ko.utils.stringTrim(html).toLowerCase(), div = documentContext.createElement("div"), 1287 wrap = getWrap(tags), 1288 depth = wrap[0]; 1289 1290 // Go to html and back, then peel off extra wrappers 1291 // Note that we always prefix with some dummy text, because otherwise, IE<9 will strip out leading comment nodes in descendants. Total madness. 1292 var markup = "ignored<div>" + wrap[1] + html + wrap[2] + "</div>"; 1293 if (typeof windowContext['innerShiv'] == "function") { 1294 // Note that innerShiv is deprecated in favour of html5shiv. We should consider adding 1295 // support for html5shiv (except if no explicit support is needed, e.g., if html5shiv 1296 // somehow shims the native APIs so it just works anyway) 1297 div.appendChild(windowContext['innerShiv'](markup)); 1298 } else { 1299 if (mayRequireCreateElementHack) { 1300 // The document.createElement('my-element') trick to enable custom elements in IE6-8 1301 // only works if we assign innerHTML on an element associated with that document. 1302 documentContext.body.appendChild(div); 1303 } 1304 1305 div.innerHTML = markup; 1306 1307 if (mayRequireCreateElementHack) { 1308 div.parentNode.removeChild(div); 1309 } 1310 } 1311 1312 // Move to the right depth 1313 while (depth--) 1314 div = div.lastChild; 1315 1316 return ko.utils.makeArray(div.lastChild.childNodes); 1317 } 1318 1319 function jQueryHtmlParse(html, documentContext) { 1320 // jQuery's "parseHTML" function was introduced in jQuery 1.8.0 and is a documented public API. 1321 if (jQueryInstance['parseHTML']) { 1322 return jQueryInstance['parseHTML'](html, documentContext) || []; // Ensure we always return an array and never null 1323 } else { 1324 // For jQuery < 1.8.0, we fall back on the undocumented internal "clean" function. 1325 var elems = jQueryInstance['clean']([html], documentContext); 1326 1327 // As of jQuery 1.7.1, jQuery parses the HTML by appending it to some dummy parent nodes held in an in-memory document fragment. 1328 // Unfortunately, it never clears the dummy parent nodes from the document fragment, so it leaks memory over time. 1329 // Fix this by finding the top-most dummy parent element, and detaching it from its owner fragment. 1330 if (elems && elems[0]) { 1331 // Find the top-most parent element that's a direct child of a document fragment 1332 var elem = elems[0]; 1333 while (elem.parentNode && elem.parentNode.nodeType !== 11 /* i.e., DocumentFragment */) 1334 elem = elem.parentNode; 1335 // ... then detach it 1336 if (elem.parentNode) 1337 elem.parentNode.removeChild(elem); 1338 } 1339 1340 return elems; 1341 } 1342 } 1343 1344 ko.utils.parseHtmlFragment = function (html, documentContext) { 1345 return jQueryInstance ? 1346 jQueryHtmlParse(html, documentContext) : // As below, benefit from jQuery's optimisations where possible 1347 simpleHtmlParse(html, documentContext); // ... otherwise, this simple logic will do in most common cases. 1348 }; 1349 1350 ko.utils.parseHtmlForTemplateNodes = function (html, documentContext) { 1351 var nodes = ko.utils.parseHtmlFragment(html, documentContext); 1352 return (nodes.length && nodes[0].parentElement) || ko.utils.moveCleanedNodesToContainerElement(nodes); 1353 }; 1354 1355 ko.utils.setHtml = function (node, html) { 1356 ko.utils.emptyDomNode(node); 1357 1358 // There's no legitimate reason to display a stringified observable without unwrapping it, so we'll unwrap it 1359 html = ko.utils.unwrapObservable(html); 1360 1361 if ((html !== null) && (html !== undefined)) { 1362 if (typeof html != 'string') 1363 html = html.toString(); 1364 1365 // jQuery contains a lot of sophisticated code to parse arbitrary HTML fragments, 1366 // for example <tr> elements which are not normally allowed to exist on their own. 1367 // If you've referenced jQuery we'll use that rather than duplicating its code. 1368 if (jQueryInstance) { 1369 jQueryInstance(node)['html'](html); 1370 } else { 1371 // ... otherwise, use KO's own parsing logic. 1372 var parsedNodes = ko.utils.parseHtmlFragment(html, node.ownerDocument); 1373 for (var i = 0; i < parsedNodes.length; i++) 1374 node.appendChild(parsedNodes[i]); 1375 } 1376 } 1377 }; 1378 })(); 1379 1380 ko.exportSymbol('utils.parseHtmlFragment', ko.utils.parseHtmlFragment); 1381 ko.exportSymbol('utils.setHtml', ko.utils.setHtml); 1382 1383 ko.memoization = (function () { 1384 var memos = {}; 1385 1386 function randomMax8HexChars() { 1387 return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1); 1388 } 1389 function generateRandomId() { 1390 return randomMax8HexChars() + randomMax8HexChars(); 1391 } 1392 function findMemoNodes(rootNode, appendToArray) { 1393 if (!rootNode) 1394 return; 1395 if (rootNode.nodeType == 8) { 1396 var memoId = ko.memoization.parseMemoText(rootNode.nodeValue); 1397 if (memoId != null) 1398 appendToArray.push({ domNode: rootNode, memoId: memoId }); 1399 } else if (rootNode.nodeType == 1) { 1400 for (var i = 0, childNodes = rootNode.childNodes, j = childNodes.length; i < j; i++) 1401 findMemoNodes(childNodes[i], appendToArray); 1402 } 1403 } 1404 1405 return { 1406 memoize: function (callback) { 1407 if (typeof callback != "function") 1408 throw new Error("You can only pass a function to ko.memoization.memoize()"); 1409 var memoId = generateRandomId(); 1410 memos[memoId] = callback; 1411 return "<!--[ko_memo:" + memoId + "]-->"; 1412 }, 1413 1414 unmemoize: function (memoId, callbackParams) { 1415 var callback = memos[memoId]; 1416 if (callback === undefined) 1417 throw new Error("Couldn't find any memo with ID " + memoId + ". Perhaps it's already been unmemoized."); 1418 try { 1419 callback.apply(null, callbackParams || []); 1420 return true; 1421 } 1422 finally { delete memos[memoId]; } 1423 }, 1424 1425 unmemoizeDomNodeAndDescendants: function (domNode, extraCallbackParamsArray) { 1426 var memos = []; 1427 findMemoNodes(domNode, memos); 1428 for (var i = 0, j = memos.length; i < j; i++) { 1429 var node = memos[i].domNode; 1430 var combinedParams = [node]; 1431 if (extraCallbackParamsArray) 1432 ko.utils.arrayPushAll(combinedParams, extraCallbackParamsArray); 1433 ko.memoization.unmemoize(memos[i].memoId, combinedParams); 1434 node.nodeValue = ""; // Neuter this node so we don't try to unmemoize it again 1435 if (node.parentNode) 1436 node.parentNode.removeChild(node); // If possible, erase it totally (not always possible - someone else might just hold a reference to it then call unmemoizeDomNodeAndDescendants again) 1437 } 1438 }, 1439 1440 parseMemoText: function (memoText) { 1441 var match = memoText.match(/^\[ko_memo\:(.*?)\]$/); 1442 return match ? match[1] : null; 1443 } 1444 }; 1445 })(); 1446 1447 ko.exportSymbol('memoization', ko.memoization); 1448 ko.exportSymbol('memoization.memoize', ko.memoization.memoize); 1449 ko.exportSymbol('memoization.unmemoize', ko.memoization.unmemoize); 1450 ko.exportSymbol('memoization.parseMemoText', ko.memoization.parseMemoText); 1451 ko.exportSymbol('memoization.unmemoizeDomNodeAndDescendants', ko.memoization.unmemoizeDomNodeAndDescendants); 1452 ko.tasks = (function () { 1453 var scheduler, 1454 taskQueue = [], 1455 taskQueueLength = 0, 1456 nextHandle = 1, 1457 nextIndexToProcess = 0; 1458 1459 if (window['MutationObserver']) { 1460 // Chrome 27+, Firefox 14+, IE 11+, Opera 15+, Safari 6.1+ 1461 // From https://github.com/petkaantonov/bluebird * Copyright (c) 2014 Petka Antonov * License: MIT 1462 scheduler = (function (callback) { 1463 var div = document.createElement("div"); 1464 new MutationObserver(callback).observe(div, { attributes: true }); 1465 return function () { div.classList.toggle("foo"); }; 1466 })(scheduledProcess); 1467 } else if (document && "onreadystatechange" in document.createElement("script")) { 1468 // IE 6-10 1469 // From https://github.com/YuzuJS/setImmediate * Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, and Domenic Denicola * License: MIT 1470 scheduler = function (callback) { 1471 var script = document.createElement("script"); 1472 script.onreadystatechange = function () { 1473 script.onreadystatechange = null; 1474 document.documentElement.removeChild(script); 1475 script = null; 1476 callback(); 1477 }; 1478 document.documentElement.appendChild(script); 1479 }; 1480 } else { 1481 scheduler = function (callback) { 1482 setTimeout(callback, 0); 1483 }; 1484 } 1485 1486 function processTasks() { 1487 if (taskQueueLength) { 1488 // Each mark represents the end of a logical group of tasks and the number of these groups is 1489 // limited to prevent unchecked recursion. 1490 var mark = taskQueueLength, countMarks = 0; 1491 1492 // nextIndexToProcess keeps track of where we are in the queue; processTasks can be called recursively without issue 1493 for (var task; nextIndexToProcess < taskQueueLength;) { 1494 if (task = taskQueue[nextIndexToProcess++]) { 1495 if (nextIndexToProcess > mark) { 1496 if (++countMarks >= 5000) { 1497 nextIndexToProcess = taskQueueLength; // skip all tasks remaining in the queue since any of them could be causing the recursion 1498 ko.utils.deferError(Error("'Too much recursion' after processing " + countMarks + " task groups.")); 1499 break; 1500 } 1501 mark = taskQueueLength; 1502 } 1503 try { 1504 task(); 1505 } catch (ex) { 1506 ko.utils.deferError(ex); 1507 } 1508 } 1509 } 1510 } 1511 } 1512 1513 function scheduledProcess() { 1514 processTasks(); 1515 1516 // Reset the queue 1517 nextIndexToProcess = taskQueueLength = taskQueue.length = 0; 1518 } 1519 1520 function scheduleTaskProcessing() { 1521 ko.tasks['scheduler'](scheduledProcess); 1522 } 1523 1524 var tasks = { 1525 'scheduler': scheduler, // Allow overriding the scheduler 1526 1527 schedule: function (func) { 1528 if (!taskQueueLength) { 1529 scheduleTaskProcessing(); 1530 } 1531 1532 taskQueue[taskQueueLength++] = func; 1533 return nextHandle++; 1534 }, 1535 1536 cancel: function (handle) { 1537 var index = handle - (nextHandle - taskQueueLength); 1538 if (index >= nextIndexToProcess && index < taskQueueLength) { 1539 taskQueue[index] = null; 1540 } 1541 }, 1542 1543 // For testing only: reset the queue and return the previous queue length 1544 'resetForTesting': function () { 1545 var length = taskQueueLength - nextIndexToProcess; 1546 nextIndexToProcess = taskQueueLength = taskQueue.length = 0; 1547 return length; 1548 }, 1549 1550 runEarly: processTasks 1551 }; 1552 1553 return tasks; 1554 })(); 1555 1556 ko.exportSymbol('tasks', ko.tasks); 1557 ko.exportSymbol('tasks.schedule', ko.tasks.schedule); 1558 //ko.exportSymbol('tasks.cancel', ko.tasks.cancel); "cancel" isn't minified 1559 ko.exportSymbol('tasks.runEarly', ko.tasks.runEarly); 1560 ko.extenders = { 1561 'throttle': function (target, timeout) { 1562 // Throttling means two things: 1563 1564 // (1) For dependent observables, we throttle *evaluations* so that, no matter how fast its dependencies 1565 // notify updates, the target doesn't re-evaluate (and hence doesn't notify) faster than a certain rate 1566 target['throttleEvaluation'] = timeout; 1567 1568 // (2) For writable targets (observables, or writable dependent observables), we throttle *writes* 1569 // so the target cannot change value synchronously or faster than a certain rate 1570 var writeTimeoutInstance = null; 1571 return ko.dependentObservable({ 1572 'read': target, 1573 'write': function (value) { 1574 clearTimeout(writeTimeoutInstance); 1575 writeTimeoutInstance = ko.utils.setTimeout(function () { 1576 target(value); 1577 }, timeout); 1578 } 1579 }); 1580 }, 1581 1582 'rateLimit': function (target, options) { 1583 var timeout, method, limitFunction; 1584 1585 if (typeof options == 'number') { 1586 timeout = options; 1587 } else { 1588 timeout = options['timeout']; 1589 method = options['method']; 1590 } 1591 1592 // rateLimit supersedes deferred updates 1593 target._deferUpdates = false; 1594 1595 limitFunction = typeof method == 'function' ? method : method == 'notifyWhenChangesStop' ? debounce : throttle; 1596 target.limit(function (callback) { 1597 return limitFunction(callback, timeout, options); 1598 }); 1599 }, 1600 1601 'deferred': function (target, options) { 1602 if (options !== true) { 1603 throw new Error('The \'deferred\' extender only accepts the value \'true\', because it is not supported to turn deferral off once enabled.') 1604 } 1605 1606 if (!target._deferUpdates) { 1607 target._deferUpdates = true; 1608 target.limit(function (callback) { 1609 var handle, 1610 ignoreUpdates = false; 1611 return function () { 1612 if (!ignoreUpdates) { 1613 ko.tasks.cancel(handle); 1614 handle = ko.tasks.schedule(callback); 1615 1616 try { 1617 ignoreUpdates = true; 1618 target['notifySubscribers'](undefined, 'dirty'); 1619 } finally { 1620 ignoreUpdates = false; 1621 } 1622 } 1623 }; 1624 }); 1625 } 1626 }, 1627 1628 'notify': function (target, notifyWhen) { 1629 target["equalityComparer"] = notifyWhen == "always" ? 1630 null : // null equalityComparer means to always notify 1631 valuesArePrimitiveAndEqual; 1632 } 1633 }; 1634 1635 var primitiveTypes = { 'undefined': 1, 'boolean': 1, 'number': 1, 'string': 1 }; 1636 function valuesArePrimitiveAndEqual(a, b) { 1637 var oldValueIsPrimitive = (a === null) || (typeof (a) in primitiveTypes); 1638 return oldValueIsPrimitive ? (a === b) : false; 1639 } 1640 1641 function throttle(callback, timeout) { 1642 var timeoutInstance; 1643 return function () { 1644 if (!timeoutInstance) { 1645 timeoutInstance = ko.utils.setTimeout(function () { 1646 timeoutInstance = undefined; 1647 callback(); 1648 }, timeout); 1649 } 1650 }; 1651 } 1652 1653 function debounce(callback, timeout) { 1654 var timeoutInstance; 1655 return function () { 1656 clearTimeout(timeoutInstance); 1657 timeoutInstance = ko.utils.setTimeout(callback, timeout); 1658 }; 1659 } 1660 1661 function applyExtenders(requestedExtenders) { 1662 var target = this; 1663 if (requestedExtenders) { 1664 ko.utils.objectForEach(requestedExtenders, function (key, value) { 1665 var extenderHandler = ko.extenders[key]; 1666 if (typeof extenderHandler == 'function') { 1667 target = extenderHandler(target, value) || target; 1668 } 1669 }); 1670 } 1671 return target; 1672 } 1673 1674 ko.exportSymbol('extenders', ko.extenders); 1675 1676 ko.subscription = function (target, callback, disposeCallback) { 1677 this._target = target; 1678 this._callback = callback; 1679 this._disposeCallback = disposeCallback; 1680 this._isDisposed = false; 1681 this._node = null; 1682 this._domNodeDisposalCallback = null; 1683 ko.exportProperty(this, 'dispose', this.dispose); 1684 ko.exportProperty(this, 'disposeWhenNodeIsRemoved', this.disposeWhenNodeIsRemoved); 1685 }; 1686 ko.subscription.prototype.dispose = function () { 1687 var self = this; 1688 if (!self._isDisposed) { 1689 if (self._domNodeDisposalCallback) { 1690 ko.utils.domNodeDisposal.removeDisposeCallback(self._node, self._domNodeDisposalCallback); 1691 } 1692 self._isDisposed = true; 1693 self._disposeCallback(); 1694 1695 self._target = self._callback = self._disposeCallback = self._node = self._domNodeDisposalCallback = null; 1696 } 1697 }; 1698 ko.subscription.prototype.disposeWhenNodeIsRemoved = function (node) { 1699 this._node = node; 1700 ko.utils.domNodeDisposal.addDisposeCallback(node, this._domNodeDisposalCallback = this.dispose.bind(this)); 1701 }; 1702 1703 ko.subscribable = function () { 1704 ko.utils.setPrototypeOfOrExtend(this, ko_subscribable_fn); 1705 ko_subscribable_fn.init(this); 1706 } 1707 1708 var defaultEvent = "change"; 1709 1710 // Moved out of "limit" to avoid the extra closure 1711 function limitNotifySubscribers(value, event) { 1712 if (!event || event === defaultEvent) { 1713 this._limitChange(value); 1714 } else if (event === 'beforeChange') { 1715 this._limitBeforeChange(value); 1716 } else { 1717 this._origNotifySubscribers(value, event); 1718 } 1719 } 1720 1721 var ko_subscribable_fn = { 1722 init: function (instance) { 1723 instance._subscriptions = { "change": [] }; 1724 instance._versionNumber = 1; 1725 }, 1726 1727 subscribe: function (callback, callbackTarget, event) { 1728 var self = this; 1729 1730 event = event || defaultEvent; 1731 var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback; 1732 1733 var subscription = new ko.subscription(self, boundCallback, function () { 1734 ko.utils.arrayRemoveItem(self._subscriptions[event], subscription); 1735 if (self.afterSubscriptionRemove) 1736 self.afterSubscriptionRemove(event); 1737 }); 1738 1739 if (self.beforeSubscriptionAdd) 1740 self.beforeSubscriptionAdd(event); 1741 1742 if (!self._subscriptions[event]) 1743 self._subscriptions[event] = []; 1744 self._subscriptions[event].push(subscription); 1745 1746 return subscription; 1747 }, 1748 1749 "notifySubscribers": function (valueToNotify, event) { 1750 event = event || defaultEvent; 1751 if (event === defaultEvent) { 1752 this.updateVersion(); 1753 } 1754 if (this.hasSubscriptionsForEvent(event)) { 1755 var subs = event === defaultEvent && this._changeSubscriptions || this._subscriptions[event].slice(0); 1756 try { 1757 ko.dependencyDetection.begin(); // Begin suppressing dependency detection (by setting the top frame to undefined) 1758 for (var i = 0, subscription; subscription = subs[i]; ++i) { 1759 // In case a subscription was disposed during the arrayForEach cycle, check 1760 // for isDisposed on each subscription before invoking its callback 1761 if (!subscription._isDisposed) 1762 subscription._callback(valueToNotify); 1763 } 1764 } finally { 1765 ko.dependencyDetection.end(); // End suppressing dependency detection 1766 } 1767 } 1768 }, 1769 1770 getVersion: function () { 1771 return this._versionNumber; 1772 }, 1773 1774 hasChanged: function (versionToCheck) { 1775 return this.getVersion() !== versionToCheck; 1776 }, 1777 1778 updateVersion: function () { 1779 ++this._versionNumber; 1780 }, 1781 1782 limit: function (limitFunction) { 1783 var self = this, selfIsObservable = ko.isObservable(self), 1784 ignoreBeforeChange, notifyNextChange, previousValue, pendingValue, didUpdate, 1785 beforeChange = 'beforeChange'; 1786 1787 if (!self._origNotifySubscribers) { 1788 self._origNotifySubscribers = self["notifySubscribers"]; 1789 self["notifySubscribers"] = limitNotifySubscribers; 1790 } 1791 1792 var finish = limitFunction(function () { 1793 self._notificationIsPending = false; 1794 1795 // If an observable provided a reference to itself, access it to get the latest value. 1796 // This allows computed observables to delay calculating their value until needed. 1797 if (selfIsObservable && pendingValue === self) { 1798 pendingValue = self._evalIfChanged ? self._evalIfChanged() : self(); 1799 } 1800 var shouldNotify = notifyNextChange || (didUpdate && self.isDifferent(previousValue, pendingValue)); 1801 1802 didUpdate = notifyNextChange = ignoreBeforeChange = false; 1803 1804 if (shouldNotify) { 1805 self._origNotifySubscribers(previousValue = pendingValue); 1806 } 1807 }); 1808 1809 self._limitChange = function (value, isDirty) { 1810 if (!isDirty || !self._notificationIsPending) { 1811 didUpdate = !isDirty; 1812 } 1813 self._changeSubscriptions = self._subscriptions[defaultEvent].slice(0); 1814 self._notificationIsPending = ignoreBeforeChange = true; 1815 pendingValue = value; 1816 finish(); 1817 }; 1818 self._limitBeforeChange = function (value) { 1819 if (!ignoreBeforeChange) { 1820 previousValue = value; 1821 self._origNotifySubscribers(value, beforeChange); 1822 } 1823 }; 1824 self._recordUpdate = function () { 1825 didUpdate = true; 1826 }; 1827 self._notifyNextChangeIfValueIsDifferent = function () { 1828 if (self.isDifferent(previousValue, self.peek(true /*evaluate*/))) { 1829 notifyNextChange = true; 1830 } 1831 }; 1832 }, 1833 1834 hasSubscriptionsForEvent: function (event) { 1835 return this._subscriptions[event] && this._subscriptions[event].length; 1836 }, 1837 1838 getSubscriptionsCount: function (event) { 1839 if (event) { 1840 return this._subscriptions[event] && this._subscriptions[event].length || 0; 1841 } else { 1842 var total = 0; 1843 ko.utils.objectForEach(this._subscriptions, function (eventName, subscriptions) { 1844 if (eventName !== 'dirty') 1845 total += subscriptions.length; 1846 }); 1847 return total; 1848 } 1849 }, 1850 1851 isDifferent: function (oldValue, newValue) { 1852 return !this['equalityComparer'] || !this['equalityComparer'](oldValue, newValue); 1853 }, 1854 1855 toString: function () { 1856 return '[object Object]' 1857 }, 1858 1859 extend: applyExtenders 1860 }; 1861 1862 ko.exportProperty(ko_subscribable_fn, 'init', ko_subscribable_fn.init); 1863 ko.exportProperty(ko_subscribable_fn, 'subscribe', ko_subscribable_fn.subscribe); 1864 ko.exportProperty(ko_subscribable_fn, 'extend', ko_subscribable_fn.extend); 1865 ko.exportProperty(ko_subscribable_fn, 'getSubscriptionsCount', ko_subscribable_fn.getSubscriptionsCount); 1866 1867 // For browsers that support proto assignment, we overwrite the prototype of each 1868 // observable instance. Since observables are functions, we need Function.prototype 1869 // to still be in the prototype chain. 1870 if (ko.utils.canSetPrototype) { 1871 ko.utils.setPrototypeOf(ko_subscribable_fn, Function.prototype); 1872 } 1873 1874 ko.subscribable['fn'] = ko_subscribable_fn; 1875 1876 1877 ko.isSubscribable = function (instance) { 1878 return instance != null && typeof instance.subscribe == "function" && typeof instance["notifySubscribers"] == "function"; 1879 }; 1880 1881 ko.exportSymbol('subscribable', ko.subscribable); 1882 ko.exportSymbol('isSubscribable', ko.isSubscribable); 1883 1884 ko.computedContext = ko.dependencyDetection = (function () { 1885 var outerFrames = [], 1886 currentFrame, 1887 lastId = 0; 1888 1889 // Return a unique ID that can be assigned to an observable for dependency tracking. 1890 // Theoretically, you could eventually overflow the number storage size, resulting 1891 // in duplicate IDs. But in JavaScript, the largest exact integral value is 2^53 1892 // or 9,007,199,254,740,992. If you created 1,000,000 IDs per second, it would 1893 // take over 285 years to reach that number. 1894 // Reference http://blog.vjeux.com/2010/javascript/javascript-max_int-number-limits.html 1895 function getId() { 1896 return ++lastId; 1897 } 1898 1899 function begin(options) { 1900 outerFrames.push(currentFrame); 1901 currentFrame = options; 1902 } 1903 1904 function end() { 1905 currentFrame = outerFrames.pop(); 1906 } 1907 1908 return { 1909 begin: begin, 1910 1911 end: end, 1912 1913 registerDependency: function (subscribable) { 1914 if (currentFrame) { 1915 if (!ko.isSubscribable(subscribable)) 1916 throw new Error("Only subscribable things can act as dependencies"); 1917 currentFrame.callback.call(currentFrame.callbackTarget, subscribable, subscribable._id || (subscribable._id = getId())); 1918 } 1919 }, 1920 1921 ignore: function (callback, callbackTarget, callbackArgs) { 1922 try { 1923 begin(); 1924 return callback.apply(callbackTarget, callbackArgs || []); 1925 } finally { 1926 end(); 1927 } 1928 }, 1929 1930 getDependenciesCount: function () { 1931 if (currentFrame) 1932 return currentFrame.computed.getDependenciesCount(); 1933 }, 1934 1935 getDependencies: function () { 1936 if (currentFrame) 1937 return currentFrame.computed.getDependencies(); 1938 }, 1939 1940 isInitial: function () { 1941 if (currentFrame) 1942 return currentFrame.isInitial; 1943 }, 1944 1945 computed: function () { 1946 if (currentFrame) 1947 return currentFrame.computed; 1948 } 1949 }; 1950 })(); 1951 1952 ko.exportSymbol('computedContext', ko.computedContext); 1953 ko.exportSymbol('computedContext.getDependenciesCount', ko.computedContext.getDependenciesCount); 1954 ko.exportSymbol('computedContext.getDependencies', ko.computedContext.getDependencies); 1955 ko.exportSymbol('computedContext.isInitial', ko.computedContext.isInitial); 1956 ko.exportSymbol('computedContext.registerDependency', ko.computedContext.registerDependency); 1957 1958 ko.exportSymbol('ignoreDependencies', ko.ignoreDependencies = ko.dependencyDetection.ignore); 1959 var observableLatestValue = ko.utils.createSymbolOrString('_latestValue'); 1960 1961 ko.observable = function (initialValue) { 1962 function observable() { 1963 if (arguments.length > 0) { 1964 // Write 1965 1966 // Ignore writes if the value hasn't changed 1967 if (observable.isDifferent(observable[observableLatestValue], arguments[0])) { 1968 observable.valueWillMutate(); 1969 observable[observableLatestValue] = arguments[0]; 1970 observable.valueHasMutated(); 1971 } 1972 return this; // Permits chained assignments 1973 } 1974 else { 1975 // Read 1976 ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation 1977 return observable[observableLatestValue]; 1978 } 1979 } 1980 1981 observable[observableLatestValue] = initialValue; 1982 1983 // Inherit from 'subscribable' 1984 if (!ko.utils.canSetPrototype) { 1985 // 'subscribable' won't be on the prototype chain unless we put it there directly 1986 ko.utils.extend(observable, ko.subscribable['fn']); 1987 } 1988 ko.subscribable['fn'].init(observable); 1989 1990 // Inherit from 'observable' 1991 ko.utils.setPrototypeOfOrExtend(observable, observableFn); 1992 1993 if (ko.options['deferUpdates']) { 1994 ko.extenders['deferred'](observable, true); 1995 } 1996 1997 return observable; 1998 } 1999 2000 // Define prototype for observables 2001 var observableFn = { 2002 'equalityComparer': valuesArePrimitiveAndEqual, 2003 peek: function () { return this[observableLatestValue]; }, 2004 valueHasMutated: function () { 2005 this['notifySubscribers'](this[observableLatestValue], 'spectate'); 2006 this['notifySubscribers'](this[observableLatestValue]); 2007 }, 2008 valueWillMutate: function () { this['notifySubscribers'](this[observableLatestValue], 'beforeChange'); } 2009 }; 2010 2011 // Note that for browsers that don't support proto assignment, the 2012 // inheritance chain is created manually in the ko.observable constructor 2013 if (ko.utils.canSetPrototype) { 2014 ko.utils.setPrototypeOf(observableFn, ko.subscribable['fn']); 2015 } 2016 2017 var protoProperty = ko.observable.protoProperty = '__ko_proto__'; 2018 observableFn[protoProperty] = ko.observable; 2019 2020 ko.isObservable = function (instance) { 2021 var proto = typeof instance == 'function' && instance[protoProperty]; 2022 if (proto && proto !== observableFn[protoProperty] && proto !== ko.computed['fn'][protoProperty]) { 2023 throw Error("Invalid object that looks like an observable; possibly from another Knockout instance"); 2024 } 2025 return !!proto; 2026 }; 2027 2028 ko.isWriteableObservable = function (instance) { 2029 return (typeof instance == 'function' && ( 2030 (instance[protoProperty] === observableFn[protoProperty]) || // Observable 2031 (instance[protoProperty] === ko.computed['fn'][protoProperty] && instance.hasWriteFunction))); // Writable computed observable 2032 }; 2033 2034 ko.exportSymbol('observable', ko.observable); 2035 ko.exportSymbol('isObservable', ko.isObservable); 2036 ko.exportSymbol('isWriteableObservable', ko.isWriteableObservable); 2037 ko.exportSymbol('isWritableObservable', ko.isWriteableObservable); 2038 ko.exportSymbol('observable.fn', observableFn); 2039 ko.exportProperty(observableFn, 'peek', observableFn.peek); 2040 ko.exportProperty(observableFn, 'valueHasMutated', observableFn.valueHasMutated); 2041 ko.exportProperty(observableFn, 'valueWillMutate', observableFn.valueWillMutate); 2042 ko.observableArray = function (initialValues) { 2043 initialValues = initialValues || []; 2044 2045 if (typeof initialValues != 'object' || !('length' in initialValues)) 2046 throw new Error("The argument passed when initializing an observable array must be an array, or null, or undefined."); 2047 2048 var result = ko.observable(initialValues); 2049 ko.utils.setPrototypeOfOrExtend(result, ko.observableArray['fn']); 2050 return result.extend({ 'trackArrayChanges': true }); 2051 }; 2052 2053 ko.observableArray['fn'] = { 2054 'remove': function (valueOrPredicate) { 2055 var underlyingArray = this.peek(); 2056 var removedValues = []; 2057 var predicate = typeof valueOrPredicate == "function" && !ko.isObservable(valueOrPredicate) ? valueOrPredicate : function (value) { return value === valueOrPredicate; }; 2058 for (var i = 0; i < underlyingArray.length; i++) { 2059 var value = underlyingArray[i]; 2060 if (predicate(value)) { 2061 if (removedValues.length === 0) { 2062 this.valueWillMutate(); 2063 } 2064 if (underlyingArray[i] !== value) { 2065 throw Error("Array modified during remove; cannot remove item"); 2066 } 2067 removedValues.push(value); 2068 underlyingArray.splice(i, 1); 2069 i--; 2070 } 2071 } 2072 if (removedValues.length) { 2073 this.valueHasMutated(); 2074 } 2075 return removedValues; 2076 }, 2077 2078 'removeAll': function (arrayOfValues) { 2079 // If you passed zero args, we remove everything 2080 if (arrayOfValues === undefined) { 2081 var underlyingArray = this.peek(); 2082 var allValues = underlyingArray.slice(0); 2083 this.valueWillMutate(); 2084 underlyingArray.splice(0, underlyingArray.length); 2085 this.valueHasMutated(); 2086 return allValues; 2087 } 2088 // If you passed an arg, we interpret it as an array of entries to remove 2089 if (!arrayOfValues) 2090 return []; 2091 return this['remove'](function (value) { 2092 return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0; 2093 }); 2094 }, 2095 2096 'destroy': function (valueOrPredicate) { 2097 var underlyingArray = this.peek(); 2098 var predicate = typeof valueOrPredicate == "function" && !ko.isObservable(valueOrPredicate) ? valueOrPredicate : function (value) { return value === valueOrPredicate; }; 2099 this.valueWillMutate(); 2100 for (var i = underlyingArray.length - 1; i >= 0; i--) { 2101 var value = underlyingArray[i]; 2102 if (predicate(value)) 2103 value["_destroy"] = true; 2104 } 2105 this.valueHasMutated(); 2106 }, 2107 2108 'destroyAll': function (arrayOfValues) { 2109 // If you passed zero args, we destroy everything 2110 if (arrayOfValues === undefined) 2111 return this['destroy'](function () { return true }); 2112 2113 // If you passed an arg, we interpret it as an array of entries to destroy 2114 if (!arrayOfValues) 2115 return []; 2116 return this['destroy'](function (value) { 2117 return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0; 2118 }); 2119 }, 2120 2121 'indexOf': function (item) { 2122 var underlyingArray = this(); 2123 return ko.utils.arrayIndexOf(underlyingArray, item); 2124 }, 2125 2126 'replace': function (oldItem, newItem) { 2127 var index = this['indexOf'](oldItem); 2128 if (index >= 0) { 2129 this.valueWillMutate(); 2130 this.peek()[index] = newItem; 2131 this.valueHasMutated(); 2132 } 2133 }, 2134 2135 'sorted': function (compareFunction) { 2136 var arrayCopy = this().slice(0); 2137 return compareFunction ? arrayCopy.sort(compareFunction) : arrayCopy.sort(); 2138 }, 2139 2140 'reversed': function () { 2141 return this().slice(0).reverse(); 2142 } 2143 }; 2144 2145 // Note that for browsers that don't support proto assignment, the 2146 // inheritance chain is created manually in the ko.observableArray constructor 2147 if (ko.utils.canSetPrototype) { 2148 ko.utils.setPrototypeOf(ko.observableArray['fn'], ko.observable['fn']); 2149 } 2150 2151 // Populate ko.observableArray.fn with read/write functions from native arrays 2152 // Important: Do not add any additional functions here that may reasonably be used to *read* data from the array 2153 // because we'll eval them without causing subscriptions, so ko.computed output could end up getting stale 2154 ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function (methodName) { 2155 ko.observableArray['fn'][methodName] = function () { 2156 // Use "peek" to avoid creating a subscription in any computed that we're executing in the context of 2157 // (for consistency with mutating regular observables) 2158 var underlyingArray = this.peek(); 2159 this.valueWillMutate(); 2160 this.cacheDiffForKnownOperation(underlyingArray, methodName, arguments); 2161 var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments); 2162 this.valueHasMutated(); 2163 // The native sort and reverse methods return a reference to the array, but it makes more sense to return the observable array instead. 2164 return methodCallResult === underlyingArray ? this : methodCallResult; 2165 }; 2166 }); 2167 2168 // Populate ko.observableArray.fn with read-only functions from native arrays 2169 ko.utils.arrayForEach(["slice"], function (methodName) { 2170 ko.observableArray['fn'][methodName] = function () { 2171 var underlyingArray = this(); 2172 return underlyingArray[methodName].apply(underlyingArray, arguments); 2173 }; 2174 }); 2175 2176 ko.isObservableArray = function (instance) { 2177 return ko.isObservable(instance) 2178 && typeof instance["remove"] == "function" 2179 && typeof instance["push"] == "function"; 2180 }; 2181 2182 ko.exportSymbol('observableArray', ko.observableArray); 2183 ko.exportSymbol('isObservableArray', ko.isObservableArray); 2184 var arrayChangeEventName = 'arrayChange'; 2185 ko.extenders['trackArrayChanges'] = function (target, options) { 2186 // Use the provided options--each call to trackArrayChanges overwrites the previously set options 2187 target.compareArrayOptions = {}; 2188 if (options && typeof options == "object") { 2189 ko.utils.extend(target.compareArrayOptions, options); 2190 } 2191 target.compareArrayOptions['sparse'] = true; 2192 2193 // Only modify the target observable once 2194 if (target.cacheDiffForKnownOperation) { 2195 return; 2196 } 2197 var trackingChanges = false, 2198 cachedDiff = null, 2199 changeSubscription, 2200 spectateSubscription, 2201 pendingChanges = 0, 2202 previousContents, 2203 underlyingBeforeSubscriptionAddFunction = target.beforeSubscriptionAdd, 2204 underlyingAfterSubscriptionRemoveFunction = target.afterSubscriptionRemove; 2205 2206 // Watch "subscribe" calls, and for array change events, ensure change tracking is enabled 2207 target.beforeSubscriptionAdd = function (event) { 2208 if (underlyingBeforeSubscriptionAddFunction) { 2209 underlyingBeforeSubscriptionAddFunction.call(target, event); 2210 } 2211 if (event === arrayChangeEventName) { 2212 trackChanges(); 2213 } 2214 }; 2215 // Watch "dispose" calls, and for array change events, ensure change tracking is disabled when all are disposed 2216 target.afterSubscriptionRemove = function (event) { 2217 if (underlyingAfterSubscriptionRemoveFunction) { 2218 underlyingAfterSubscriptionRemoveFunction.call(target, event); 2219 } 2220 if (event === arrayChangeEventName && !target.hasSubscriptionsForEvent(arrayChangeEventName)) { 2221 if (changeSubscription) { 2222 changeSubscription.dispose(); 2223 } 2224 if (spectateSubscription) { 2225 spectateSubscription.dispose(); 2226 } 2227 spectateSubscription = changeSubscription = null; 2228 trackingChanges = false; 2229 previousContents = undefined; 2230 } 2231 }; 2232 2233 function trackChanges() { 2234 if (trackingChanges) { 2235 // Whenever there's a new subscription and there are pending notifications, make sure all previous 2236 // subscriptions are notified of the change so that all subscriptions are in sync. 2237 notifyChanges(); 2238 return; 2239 } 2240 2241 trackingChanges = true; 2242 2243 // Track how many times the array actually changed value 2244 spectateSubscription = target.subscribe(function () { 2245 ++pendingChanges; 2246 }, null, "spectate"); 2247 2248 // Each time the array changes value, capture a clone so that on the next 2249 // change it's possible to produce a diff 2250 previousContents = [].concat(target.peek() || []); 2251 cachedDiff = null; 2252 changeSubscription = target.subscribe(notifyChanges); 2253 2254 function notifyChanges() { 2255 if (pendingChanges) { 2256 // Make a copy of the current contents and ensure it's an array 2257 var currentContents = [].concat(target.peek() || []), changes; 2258 2259 // Compute the diff and issue notifications, but only if someone is listening 2260 if (target.hasSubscriptionsForEvent(arrayChangeEventName)) { 2261 changes = getChanges(previousContents, currentContents); 2262 } 2263 2264 // Eliminate references to the old, removed items, so they can be GCed 2265 previousContents = currentContents; 2266 cachedDiff = null; 2267 pendingChanges = 0; 2268 2269 if (changes && changes.length) { 2270 target['notifySubscribers'](changes, arrayChangeEventName); 2271 } 2272 } 2273 } 2274 } 2275 2276 function getChanges(previousContents, currentContents) { 2277 // We try to re-use cached diffs. 2278 // The scenarios where pendingChanges > 1 are when using rate limiting or deferred updates, 2279 // which without this check would not be compatible with arrayChange notifications. Normally, 2280 // notifications are issued immediately so we wouldn't be queueing up more than one. 2281 if (!cachedDiff || pendingChanges > 1) { 2282 cachedDiff = ko.utils.compareArrays(previousContents, currentContents, target.compareArrayOptions); 2283 } 2284 2285 return cachedDiff; 2286 } 2287 2288 target.cacheDiffForKnownOperation = function (rawArray, operationName, args) { 2289 // Only run if we're currently tracking changes for this observable array 2290 // and there aren't any pending deferred notifications. 2291 if (!trackingChanges || pendingChanges) { 2292 return; 2293 } 2294 var diff = [], 2295 arrayLength = rawArray.length, 2296 argsLength = args.length, 2297 offset = 0; 2298 2299 function pushDiff(status, value, index) { 2300 return diff[diff.length] = { 'status': status, 'value': value, 'index': index }; 2301 } 2302 switch (operationName) { 2303 case 'push': 2304 offset = arrayLength; 2305 case 'unshift': 2306 for (var index = 0; index < argsLength; index++) { 2307 pushDiff('added', args[index], offset + index); 2308 } 2309 break; 2310 2311 case 'pop': 2312 offset = arrayLength - 1; 2313 case 'shift': 2314 if (arrayLength) { 2315 pushDiff('deleted', rawArray[offset], offset); 2316 } 2317 break; 2318 2319 case 'splice': 2320 // Negative start index means 'from end of array'. After that we clamp to [0...arrayLength]. 2321 // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice 2322 var startIndex = Math.min(Math.max(0, args[0] < 0 ? arrayLength + args[0] : args[0]), arrayLength), 2323 endDeleteIndex = argsLength === 1 ? arrayLength : Math.min(startIndex + (args[1] || 0), arrayLength), 2324 endAddIndex = startIndex + argsLength - 2, 2325 endIndex = Math.max(endDeleteIndex, endAddIndex), 2326 additions = [], deletions = []; 2327 for (var index = startIndex, argsIndex = 2; index < endIndex; ++index, ++argsIndex) { 2328 if (index < endDeleteIndex) 2329 deletions.push(pushDiff('deleted', rawArray[index], index)); 2330 if (index < endAddIndex) 2331 additions.push(pushDiff('added', args[argsIndex], index)); 2332 } 2333 ko.utils.findMovesInArrayComparison(deletions, additions); 2334 break; 2335 2336 default: 2337 return; 2338 } 2339 cachedDiff = diff; 2340 }; 2341 }; 2342 var computedState = ko.utils.createSymbolOrString('_state'); 2343 2344 ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) { 2345 if (typeof evaluatorFunctionOrOptions === "object") { 2346 // Single-parameter syntax - everything is on this "options" param 2347 options = evaluatorFunctionOrOptions; 2348 } else { 2349 // Multi-parameter syntax - construct the options according to the params passed 2350 options = options || {}; 2351 if (evaluatorFunctionOrOptions) { 2352 options["read"] = evaluatorFunctionOrOptions; 2353 } 2354 } 2355 if (typeof options["read"] != "function") 2356 throw Error("Pass a function that returns the value of the ko.computed"); 2357 2358 var writeFunction = options["write"]; 2359 var state = { 2360 latestValue: undefined, 2361 isStale: true, 2362 isDirty: true, 2363 isBeingEvaluated: false, 2364 suppressDisposalUntilDisposeWhenReturnsFalse: false, 2365 isDisposed: false, 2366 pure: false, 2367 isSleeping: false, 2368 readFunction: options["read"], 2369 evaluatorFunctionTarget: evaluatorFunctionTarget || options["owner"], 2370 disposeWhenNodeIsRemoved: options["disposeWhenNodeIsRemoved"] || options.disposeWhenNodeIsRemoved || null, 2371 disposeWhen: options["disposeWhen"] || options.disposeWhen, 2372 domNodeDisposalCallback: null, 2373 dependencyTracking: {}, 2374 dependenciesCount: 0, 2375 evaluationTimeoutInstance: null 2376 }; 2377 2378 function computedObservable() { 2379 if (arguments.length > 0) { 2380 if (typeof writeFunction === "function") { 2381 // Writing a value 2382 writeFunction.apply(state.evaluatorFunctionTarget, arguments); 2383 } else { 2384 throw new Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters."); 2385 } 2386 return this; // Permits chained assignments 2387 } else { 2388 // Reading the value 2389 if (!state.isDisposed) { 2390 ko.dependencyDetection.registerDependency(computedObservable); 2391 } 2392 if (state.isDirty || (state.isSleeping && computedObservable.haveDependenciesChanged())) { 2393 computedObservable.evaluateImmediate(); 2394 } 2395 return state.latestValue; 2396 } 2397 } 2398 2399 computedObservable[computedState] = state; 2400 computedObservable.hasWriteFunction = typeof writeFunction === "function"; 2401 2402 // Inherit from 'subscribable' 2403 if (!ko.utils.canSetPrototype) { 2404 // 'subscribable' won't be on the prototype chain unless we put it there directly 2405 ko.utils.extend(computedObservable, ko.subscribable['fn']); 2406 } 2407 ko.subscribable['fn'].init(computedObservable); 2408 2409 // Inherit from 'computed' 2410 ko.utils.setPrototypeOfOrExtend(computedObservable, computedFn); 2411 2412 if (options['pure']) { 2413 state.pure = true; 2414 state.isSleeping = true; // Starts off sleeping; will awake on the first subscription 2415 ko.utils.extend(computedObservable, pureComputedOverrides); 2416 } else if (options['deferEvaluation']) { 2417 ko.utils.extend(computedObservable, deferEvaluationOverrides); 2418 } 2419 2420 if (ko.options['deferUpdates']) { 2421 ko.extenders['deferred'](computedObservable, true); 2422 } 2423 2424 if (DEBUG) { 2425 // #1731 - Aid debugging by exposing the computed's options 2426 computedObservable["_options"] = options; 2427 } 2428 2429 if (state.disposeWhenNodeIsRemoved) { 2430 // Since this computed is associated with a DOM node, and we don't want to dispose the computed 2431 // until the DOM node is *removed* from the document (as opposed to never having been in the document), 2432 // we'll prevent disposal until "disposeWhen" first returns false. 2433 state.suppressDisposalUntilDisposeWhenReturnsFalse = true; 2434 2435 // disposeWhenNodeIsRemoved: true can be used to opt into the "only dispose after first false result" 2436 // behaviour even if there's no specific node to watch. In that case, clear the option so we don't try 2437 // to watch for a non-node's disposal. This technique is intended for KO's internal use only and shouldn't 2438 // be documented or used by application code, as it's likely to change in a future version of KO. 2439 if (!state.disposeWhenNodeIsRemoved.nodeType) { 2440 state.disposeWhenNodeIsRemoved = null; 2441 } 2442 } 2443 2444 // Evaluate, unless sleeping or deferEvaluation is true 2445 if (!state.isSleeping && !options['deferEvaluation']) { 2446 computedObservable.evaluateImmediate(); 2447 } 2448 2449 // Attach a DOM node disposal callback so that the computed will be proactively disposed as soon as the node is 2450 // removed using ko.removeNode. But skip if isActive is false (there will never be any dependencies to dispose). 2451 if (state.disposeWhenNodeIsRemoved && computedObservable.isActive()) { 2452 ko.utils.domNodeDisposal.addDisposeCallback(state.disposeWhenNodeIsRemoved, state.domNodeDisposalCallback = function () { 2453 computedObservable.dispose(); 2454 }); 2455 } 2456 2457 return computedObservable; 2458 }; 2459 2460 // Utility function that disposes a given dependencyTracking entry 2461 function computedDisposeDependencyCallback(id, entryToDispose) { 2462 if (entryToDispose !== null && entryToDispose.dispose) { 2463 entryToDispose.dispose(); 2464 } 2465 } 2466 2467 // This function gets called each time a dependency is detected while evaluating a computed. 2468 // It's factored out as a shared function to avoid creating unnecessary function instances during evaluation. 2469 function computedBeginDependencyDetectionCallback(subscribable, id) { 2470 var computedObservable = this.computedObservable, 2471 state = computedObservable[computedState]; 2472 if (!state.isDisposed) { 2473 if (this.disposalCount && this.disposalCandidates[id]) { 2474 // Don't want to dispose this subscription, as it's still being used 2475 computedObservable.addDependencyTracking(id, subscribable, this.disposalCandidates[id]); 2476 this.disposalCandidates[id] = null; // No need to actually delete the property - disposalCandidates is a transient object anyway 2477 --this.disposalCount; 2478 } else if (!state.dependencyTracking[id]) { 2479 // Brand new subscription - add it 2480 computedObservable.addDependencyTracking(id, subscribable, state.isSleeping ? { _target: subscribable } : computedObservable.subscribeToDependency(subscribable)); 2481 } 2482 // If the observable we've accessed has a pending notification, ensure we get notified of the actual final value (bypass equality checks) 2483 if (subscribable._notificationIsPending) { 2484 subscribable._notifyNextChangeIfValueIsDifferent(); 2485 } 2486 } 2487 } 2488 2489 var computedFn = { 2490 "equalityComparer": valuesArePrimitiveAndEqual, 2491 getDependenciesCount: function () { 2492 return this[computedState].dependenciesCount; 2493 }, 2494 getDependencies: function () { 2495 var dependencyTracking = this[computedState].dependencyTracking, dependentObservables = []; 2496 2497 ko.utils.objectForEach(dependencyTracking, function (id, dependency) { 2498 dependentObservables[dependency._order] = dependency._target; 2499 }); 2500 2501 return dependentObservables; 2502 }, 2503 hasAncestorDependency: function (obs) { 2504 if (!this[computedState].dependenciesCount) { 2505 return false; 2506 } 2507 var dependencies = this.getDependencies(); 2508 if (ko.utils.arrayIndexOf(dependencies, obs) !== -1) { 2509 return true; 2510 } 2511 return !!ko.utils.arrayFirst(dependencies, function (dep) { 2512 return dep.hasAncestorDependency && dep.hasAncestorDependency(obs); 2513 }); 2514 }, 2515 addDependencyTracking: function (id, target, trackingObj) { 2516 if (this[computedState].pure && target === this) { 2517 throw Error("A 'pure' computed must not be called recursively"); 2518 } 2519 2520 this[computedState].dependencyTracking[id] = trackingObj; 2521 trackingObj._order = this[computedState].dependenciesCount++; 2522 trackingObj._version = target.getVersion(); 2523 }, 2524 haveDependenciesChanged: function () { 2525 var id, dependency, dependencyTracking = this[computedState].dependencyTracking; 2526 for (id in dependencyTracking) { 2527 if (Object.prototype.hasOwnProperty.call(dependencyTracking, id)) { 2528 dependency = dependencyTracking[id]; 2529 if ((this._evalDelayed && dependency._target._notificationIsPending) || dependency._target.hasChanged(dependency._version)) { 2530 return true; 2531 } 2532 } 2533 } 2534 }, 2535 markDirty: function () { 2536 // Process "dirty" events if we can handle delayed notifications 2537 if (this._evalDelayed && !this[computedState].isBeingEvaluated) { 2538 this._evalDelayed(false /*isChange*/); 2539 } 2540 }, 2541 isActive: function () { 2542 var state = this[computedState]; 2543 return state.isDirty || state.dependenciesCount > 0; 2544 }, 2545 respondToChange: function () { 2546 // Ignore "change" events if we've already scheduled a delayed notification 2547 if (!this._notificationIsPending) { 2548 this.evaluatePossiblyAsync(); 2549 } else if (this[computedState].isDirty) { 2550 this[computedState].isStale = true; 2551 } 2552 }, 2553 subscribeToDependency: function (target) { 2554 if (target._deferUpdates) { 2555 var dirtySub = target.subscribe(this.markDirty, this, 'dirty'), 2556 changeSub = target.subscribe(this.respondToChange, this); 2557 return { 2558 _target: target, 2559 dispose: function () { 2560 dirtySub.dispose(); 2561 changeSub.dispose(); 2562 } 2563 }; 2564 } else { 2565 return target.subscribe(this.evaluatePossiblyAsync, this); 2566 } 2567 }, 2568 evaluatePossiblyAsync: function () { 2569 var computedObservable = this, 2570 throttleEvaluationTimeout = computedObservable['throttleEvaluation']; 2571 if (throttleEvaluationTimeout && throttleEvaluationTimeout >= 0) { 2572 clearTimeout(this[computedState].evaluationTimeoutInstance); 2573 this[computedState].evaluationTimeoutInstance = ko.utils.setTimeout(function () { 2574 computedObservable.evaluateImmediate(true /*notifyChange*/); 2575 }, throttleEvaluationTimeout); 2576 } else if (computedObservable._evalDelayed) { 2577 computedObservable._evalDelayed(true /*isChange*/); 2578 } else { 2579 computedObservable.evaluateImmediate(true /*notifyChange*/); 2580 } 2581 }, 2582 evaluateImmediate: function (notifyChange) { 2583 var computedObservable = this, 2584 state = computedObservable[computedState], 2585 disposeWhen = state.disposeWhen, 2586 changed = false; 2587 2588 if (state.isBeingEvaluated) { 2589 // If the evaluation of a ko.computed causes side effects, it's possible that it will trigger its own re-evaluation. 2590 // This is not desirable (it's hard for a developer to realise a chain of dependencies might cause this, and they almost 2591 // certainly didn't intend infinite re-evaluations). So, for predictability, we simply prevent ko.computeds from causing 2592 // their own re-evaluation. Further discussion at https://github.com/SteveSanderson/knockout/pull/387 2593 return; 2594 } 2595 2596 // Do not evaluate (and possibly capture new dependencies) if disposed 2597 if (state.isDisposed) { 2598 return; 2599 } 2600 2601 if (state.disposeWhenNodeIsRemoved && !ko.utils.domNodeIsAttachedToDocument(state.disposeWhenNodeIsRemoved) || disposeWhen && disposeWhen()) { 2602 // See comment above about suppressDisposalUntilDisposeWhenReturnsFalse 2603 if (!state.suppressDisposalUntilDisposeWhenReturnsFalse) { 2604 computedObservable.dispose(); 2605 return; 2606 } 2607 } else { 2608 // It just did return false, so we can stop suppressing now 2609 state.suppressDisposalUntilDisposeWhenReturnsFalse = false; 2610 } 2611 2612 state.isBeingEvaluated = true; 2613 try { 2614 changed = this.evaluateImmediate_CallReadWithDependencyDetection(notifyChange); 2615 } finally { 2616 state.isBeingEvaluated = false; 2617 } 2618 2619 return changed; 2620 }, 2621 evaluateImmediate_CallReadWithDependencyDetection: function (notifyChange) { 2622 // This function is really just part of the evaluateImmediate logic. You would never call it from anywhere else. 2623 // Factoring it out into a separate function means it can be independent of the try/catch block in evaluateImmediate, 2624 // which contributes to saving about 40% off the CPU overhead of computed evaluation (on V8 at least). 2625 2626 var computedObservable = this, 2627 state = computedObservable[computedState], 2628 changed = false; 2629 2630 // Initially, we assume that none of the subscriptions are still being used (i.e., all are candidates for disposal). 2631 // Then, during evaluation, we cross off any that are in fact still being used. 2632 var isInitial = state.pure ? undefined : !state.dependenciesCount, // If we're evaluating when there are no previous dependencies, it must be the first time 2633 dependencyDetectionContext = { 2634 computedObservable: computedObservable, 2635 disposalCandidates: state.dependencyTracking, 2636 disposalCount: state.dependenciesCount 2637 }; 2638 2639 ko.dependencyDetection.begin({ 2640 callbackTarget: dependencyDetectionContext, 2641 callback: computedBeginDependencyDetectionCallback, 2642 computed: computedObservable, 2643 isInitial: isInitial 2644 }); 2645 2646 state.dependencyTracking = {}; 2647 state.dependenciesCount = 0; 2648 2649 var newValue = this.evaluateImmediate_CallReadThenEndDependencyDetection(state, dependencyDetectionContext); 2650 2651 if (!state.dependenciesCount) { 2652 computedObservable.dispose(); 2653 changed = true; // When evaluation causes a disposal, make sure all dependent computeds get notified so they'll see the new state 2654 } else { 2655 changed = computedObservable.isDifferent(state.latestValue, newValue); 2656 } 2657 2658 if (changed) { 2659 if (!state.isSleeping) { 2660 computedObservable["notifySubscribers"](state.latestValue, "beforeChange"); 2661 } else { 2662 computedObservable.updateVersion(); 2663 } 2664 2665 state.latestValue = newValue; 2666 if (DEBUG) computedObservable._latestValue = newValue; 2667 2668 computedObservable["notifySubscribers"](state.latestValue, "spectate"); 2669 2670 if (!state.isSleeping && notifyChange) { 2671 computedObservable["notifySubscribers"](state.latestValue); 2672 } 2673 if (computedObservable._recordUpdate) { 2674 computedObservable._recordUpdate(); 2675 } 2676 } 2677 2678 if (isInitial) { 2679 computedObservable["notifySubscribers"](state.latestValue, "awake"); 2680 } 2681 2682 return changed; 2683 }, 2684 evaluateImmediate_CallReadThenEndDependencyDetection: function (state, dependencyDetectionContext) { 2685 // This function is really part of the evaluateImmediate_CallReadWithDependencyDetection logic. 2686 // You'd never call it from anywhere else. Factoring it out means that evaluateImmediate_CallReadWithDependencyDetection 2687 // can be independent of try/finally blocks, which contributes to saving about 40% off the CPU 2688 // overhead of computed evaluation (on V8 at least). 2689 2690 try { 2691 var readFunction = state.readFunction; 2692 return state.evaluatorFunctionTarget ? readFunction.call(state.evaluatorFunctionTarget) : readFunction(); 2693 } finally { 2694 ko.dependencyDetection.end(); 2695 2696 // For each subscription no longer being used, remove it from the active subscriptions list and dispose it 2697 if (dependencyDetectionContext.disposalCount && !state.isSleeping) { 2698 ko.utils.objectForEach(dependencyDetectionContext.disposalCandidates, computedDisposeDependencyCallback); 2699 } 2700 2701 state.isStale = state.isDirty = false; 2702 } 2703 }, 2704 peek: function (evaluate) { 2705 // By default, peek won't re-evaluate, except while the computed is sleeping or to get the initial value when "deferEvaluation" is set. 2706 // Pass in true to evaluate if needed. 2707 var state = this[computedState]; 2708 if ((state.isDirty && (evaluate || !state.dependenciesCount)) || (state.isSleeping && this.haveDependenciesChanged())) { 2709 this.evaluateImmediate(); 2710 } 2711 return state.latestValue; 2712 }, 2713 limit: function (limitFunction) { 2714 // Override the limit function with one that delays evaluation as well 2715 ko.subscribable['fn'].limit.call(this, limitFunction); 2716 this._evalIfChanged = function () { 2717 if (!this[computedState].isSleeping) { 2718 if (this[computedState].isStale) { 2719 this.evaluateImmediate(); 2720 } else { 2721 this[computedState].isDirty = false; 2722 } 2723 } 2724 return this[computedState].latestValue; 2725 }; 2726 this._evalDelayed = function (isChange) { 2727 this._limitBeforeChange(this[computedState].latestValue); 2728 2729 // Mark as dirty 2730 this[computedState].isDirty = true; 2731 if (isChange) { 2732 this[computedState].isStale = true; 2733 } 2734 2735 // Pass the observable to the "limit" code, which will evaluate it when 2736 // it's time to do the notification. 2737 this._limitChange(this, !isChange /* isDirty */); 2738 }; 2739 }, 2740 dispose: function () { 2741 var state = this[computedState]; 2742 if (!state.isSleeping && state.dependencyTracking) { 2743 ko.utils.objectForEach(state.dependencyTracking, function (id, dependency) { 2744 if (dependency.dispose) 2745 dependency.dispose(); 2746 }); 2747 } 2748 if (state.disposeWhenNodeIsRemoved && state.domNodeDisposalCallback) { 2749 ko.utils.domNodeDisposal.removeDisposeCallback(state.disposeWhenNodeIsRemoved, state.domNodeDisposalCallback); 2750 } 2751 state.dependencyTracking = undefined; 2752 state.dependenciesCount = 0; 2753 state.isDisposed = true; 2754 state.isStale = false; 2755 state.isDirty = false; 2756 state.isSleeping = false; 2757 state.disposeWhenNodeIsRemoved = undefined; 2758 state.disposeWhen = undefined; 2759 state.readFunction = undefined; 2760 if (!this.hasWriteFunction) { 2761 state.evaluatorFunctionTarget = undefined; 2762 } 2763 } 2764 }; 2765 2766 var pureComputedOverrides = { 2767 beforeSubscriptionAdd: function (event) { 2768 // If asleep, wake up the computed by subscribing to any dependencies. 2769 var computedObservable = this, 2770 state = computedObservable[computedState]; 2771 if (!state.isDisposed && state.isSleeping && event == 'change') { 2772 state.isSleeping = false; 2773 if (state.isStale || computedObservable.haveDependenciesChanged()) { 2774 state.dependencyTracking = null; 2775 state.dependenciesCount = 0; 2776 if (computedObservable.evaluateImmediate()) { 2777 computedObservable.updateVersion(); 2778 } 2779 } else { 2780 // First put the dependencies in order 2781 var dependenciesOrder = []; 2782 ko.utils.objectForEach(state.dependencyTracking, function (id, dependency) { 2783 dependenciesOrder[dependency._order] = id; 2784 }); 2785 // Next, subscribe to each one 2786 ko.utils.arrayForEach(dependenciesOrder, function (id, order) { 2787 var dependency = state.dependencyTracking[id], 2788 subscription = computedObservable.subscribeToDependency(dependency._target); 2789 subscription._order = order; 2790 subscription._version = dependency._version; 2791 state.dependencyTracking[id] = subscription; 2792 }); 2793 // Waking dependencies may have triggered effects 2794 if (computedObservable.haveDependenciesChanged()) { 2795 if (computedObservable.evaluateImmediate()) { 2796 computedObservable.updateVersion(); 2797 } 2798 } 2799 } 2800 2801 if (!state.isDisposed) { // test since evaluating could trigger disposal 2802 computedObservable["notifySubscribers"](state.latestValue, "awake"); 2803 } 2804 } 2805 }, 2806 afterSubscriptionRemove: function (event) { 2807 var state = this[computedState]; 2808 if (!state.isDisposed && event == 'change' && !this.hasSubscriptionsForEvent('change')) { 2809 ko.utils.objectForEach(state.dependencyTracking, function (id, dependency) { 2810 if (dependency.dispose) { 2811 state.dependencyTracking[id] = { 2812 _target: dependency._target, 2813 _order: dependency._order, 2814 _version: dependency._version 2815 }; 2816 dependency.dispose(); 2817 } 2818 }); 2819 state.isSleeping = true; 2820 this["notifySubscribers"](undefined, "asleep"); 2821 } 2822 }, 2823 getVersion: function () { 2824 // Because a pure computed is not automatically updated while it is sleeping, we can't 2825 // simply return the version number. Instead, we check if any of the dependencies have 2826 // changed and conditionally re-evaluate the computed observable. 2827 var state = this[computedState]; 2828 if (state.isSleeping && (state.isStale || this.haveDependenciesChanged())) { 2829 this.evaluateImmediate(); 2830 } 2831 return ko.subscribable['fn'].getVersion.call(this); 2832 } 2833 }; 2834 2835 var deferEvaluationOverrides = { 2836 beforeSubscriptionAdd: function (event) { 2837 // This will force a computed with deferEvaluation to evaluate when the first subscription is registered. 2838 if (event == 'change' || event == 'beforeChange') { 2839 this.peek(); 2840 } 2841 } 2842 }; 2843 2844 // Note that for browsers that don't support proto assignment, the 2845 // inheritance chain is created manually in the ko.computed constructor 2846 if (ko.utils.canSetPrototype) { 2847 ko.utils.setPrototypeOf(computedFn, ko.subscribable['fn']); 2848 } 2849 2850 // Set the proto values for ko.computed 2851 var protoProp = ko.observable.protoProperty; // == "__ko_proto__" 2852 computedFn[protoProp] = ko.computed; 2853 2854 ko.isComputed = function (instance) { 2855 return (typeof instance == 'function' && instance[protoProp] === computedFn[protoProp]); 2856 }; 2857 2858 ko.isPureComputed = function (instance) { 2859 return ko.isComputed(instance) && instance[computedState] && instance[computedState].pure; 2860 }; 2861 2862 ko.exportSymbol('computed', ko.computed); 2863 ko.exportSymbol('dependentObservable', ko.computed); // export ko.dependentObservable for backwards compatibility (1.x) 2864 ko.exportSymbol('isComputed', ko.isComputed); 2865 ko.exportSymbol('isPureComputed', ko.isPureComputed); 2866 ko.exportSymbol('computed.fn', computedFn); 2867 ko.exportProperty(computedFn, 'peek', computedFn.peek); 2868 ko.exportProperty(computedFn, 'dispose', computedFn.dispose); 2869 ko.exportProperty(computedFn, 'isActive', computedFn.isActive); 2870 ko.exportProperty(computedFn, 'getDependenciesCount', computedFn.getDependenciesCount); 2871 ko.exportProperty(computedFn, 'getDependencies', computedFn.getDependencies); 2872 2873 ko.pureComputed = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget) { 2874 if (typeof evaluatorFunctionOrOptions === 'function') { 2875 return ko.computed(evaluatorFunctionOrOptions, evaluatorFunctionTarget, { 'pure': true }); 2876 } else { 2877 evaluatorFunctionOrOptions = ko.utils.extend({}, evaluatorFunctionOrOptions); // make a copy of the parameter object 2878 evaluatorFunctionOrOptions['pure'] = true; 2879 return ko.computed(evaluatorFunctionOrOptions, evaluatorFunctionTarget); 2880 } 2881 } 2882 ko.exportSymbol('pureComputed', ko.pureComputed); 2883 2884 (function () { 2885 var maxNestedObservableDepth = 10; // Escape the (unlikely) pathological case where an observable's current value is itself (or similar reference cycle) 2886 2887 ko.toJS = function (rootObject) { 2888 if (arguments.length == 0) 2889 throw new Error("When calling ko.toJS, pass the object you want to convert."); 2890 2891 // We just unwrap everything at every level in the object graph 2892 return mapJsObjectGraph(rootObject, function (valueToMap) { 2893 // Loop because an observable's value might in turn be another observable wrapper 2894 for (var i = 0; ko.isObservable(valueToMap) && (i < maxNestedObservableDepth); i++) 2895 valueToMap = valueToMap(); 2896 return valueToMap; 2897 }); 2898 }; 2899 2900 ko.toJSON = function (rootObject, replacer, space) { // replacer and space are optional 2901 var plainJavaScriptObject = ko.toJS(rootObject); 2902 return ko.utils.stringifyJson(plainJavaScriptObject, replacer, space); 2903 }; 2904 2905 function mapJsObjectGraph(rootObject, mapInputCallback, visitedObjects) { 2906 visitedObjects = visitedObjects || new objectLookup(); 2907 2908 rootObject = mapInputCallback(rootObject); 2909 var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined) && (!(rootObject instanceof RegExp)) && (!(rootObject instanceof Date)) && (!(rootObject instanceof String)) && (!(rootObject instanceof Number)) && (!(rootObject instanceof Boolean)); 2910 if (!canHaveProperties) 2911 return rootObject; 2912 2913 var outputProperties = rootObject instanceof Array ? [] : {}; 2914 visitedObjects.save(rootObject, outputProperties); 2915 2916 visitPropertiesOrArrayEntries(rootObject, function (indexer) { 2917 var propertyValue = mapInputCallback(rootObject[indexer]); 2918 2919 switch (typeof propertyValue) { 2920 case "boolean": 2921 case "number": 2922 case "string": 2923 case "function": 2924 outputProperties[indexer] = propertyValue; 2925 break; 2926 case "object": 2927 case "undefined": 2928 var previouslyMappedValue = visitedObjects.get(propertyValue); 2929 outputProperties[indexer] = (previouslyMappedValue !== undefined) 2930 ? previouslyMappedValue 2931 : mapJsObjectGraph(propertyValue, mapInputCallback, visitedObjects); 2932 break; 2933 } 2934 }); 2935 2936 return outputProperties; 2937 } 2938 2939 function visitPropertiesOrArrayEntries(rootObject, visitorCallback) { 2940 if (rootObject instanceof Array) { 2941 for (var i = 0; i < rootObject.length; i++) 2942 visitorCallback(i); 2943 2944 // For arrays, also respect toJSON property for custom mappings (fixes #278) 2945 if (typeof rootObject['toJSON'] == 'function') 2946 visitorCallback('toJSON'); 2947 } else { 2948 for (var propertyName in rootObject) { 2949 visitorCallback(propertyName); 2950 } 2951 } 2952 }; 2953 2954 function objectLookup() { 2955 this.keys = []; 2956 this.values = []; 2957 }; 2958 2959 objectLookup.prototype = { 2960 constructor: objectLookup, 2961 save: function (key, value) { 2962 var existingIndex = ko.utils.arrayIndexOf(this.keys, key); 2963 if (existingIndex >= 0) 2964 this.values[existingIndex] = value; 2965 else { 2966 this.keys.push(key); 2967 this.values.push(value); 2968 } 2969 }, 2970 get: function (key) { 2971 var existingIndex = ko.utils.arrayIndexOf(this.keys, key); 2972 return (existingIndex >= 0) ? this.values[existingIndex] : undefined; 2973 } 2974 }; 2975 })(); 2976 2977 ko.exportSymbol('toJS', ko.toJS); 2978 ko.exportSymbol('toJSON', ko.toJSON); 2979 ko.when = function (predicate, callback, context) { 2980 function kowhen(resolve) { 2981 var observable = ko.pureComputed(predicate, context).extend({ notify: 'always' }); 2982 var subscription = observable.subscribe(function (value) { 2983 if (value) { 2984 subscription.dispose(); 2985 resolve(value); 2986 } 2987 }); 2988 // In case the initial value is true, process it right away 2989 observable['notifySubscribers'](observable.peek()); 2990 2991 return subscription; 2992 } 2993 if (typeof Promise === "function" && !callback) { 2994 return new Promise(kowhen); 2995 } else { 2996 return kowhen(callback.bind(context)); 2997 } 2998 }; 2999 3000 ko.exportSymbol('when', ko.when); 3001 (function () { 3002 var hasDomDataExpandoProperty = '__ko__hasDomDataOptionValue__'; 3003 3004 // Normally, SELECT elements and their OPTIONs can only take value of type 'string' (because the values 3005 // are stored on DOM attributes). ko.selectExtensions provides a way for SELECTs/OPTIONs to have values 3006 // that are arbitrary objects. This is very convenient when implementing things like cascading dropdowns. 3007 ko.selectExtensions = { 3008 readValue: function (element) { 3009 switch (ko.utils.tagNameLower(element)) { 3010 case 'option': 3011 if (element[hasDomDataExpandoProperty] === true) 3012 return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey); 3013 return ko.utils.ieVersion <= 7 3014 ? (element.getAttributeNode('value') && element.getAttributeNode('value').specified ? element.value : element.text) 3015 : element.value; 3016 case 'select': 3017 return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined; 3018 default: 3019 return element.value; 3020 } 3021 }, 3022 3023 writeValue: function (element, value, allowUnset) { 3024 switch (ko.utils.tagNameLower(element)) { 3025 case 'option': 3026 if (typeof value === "string") { 3027 ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined); 3028 if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node 3029 delete element[hasDomDataExpandoProperty]; 3030 } 3031 element.value = value; 3032 } 3033 else { 3034 // Store arbitrary object using DomData 3035 ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value); 3036 element[hasDomDataExpandoProperty] = true; 3037 3038 // Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value. 3039 element.value = typeof value === "number" ? value : ""; 3040 } 3041 break; 3042 case 'select': 3043 if (value === "" || value === null) // A blank string or null value will select the caption 3044 value = undefined; 3045 var selection = -1; 3046 for (var i = 0, n = element.options.length, optionValue; i < n; ++i) { 3047 optionValue = ko.selectExtensions.readValue(element.options[i]); 3048 // Include special check to handle selecting a caption with a blank string value 3049 if (optionValue == value || (optionValue === "" && value === undefined)) { 3050 selection = i; 3051 break; 3052 } 3053 } 3054 if (allowUnset || selection >= 0 || (value === undefined && element.size > 1)) { 3055 element.selectedIndex = selection; 3056 if (ko.utils.ieVersion === 6) { 3057 // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread 3058 // right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread 3059 // to apply the value as well. 3060 ko.utils.setTimeout(function () { 3061 element.selectedIndex = selection; 3062 }, 0); 3063 } 3064 } 3065 break; 3066 default: 3067 if ((value === null) || (value === undefined)) 3068 value = ""; 3069 element.value = value; 3070 break; 3071 } 3072 } 3073 }; 3074 })(); 3075 3076 ko.exportSymbol('selectExtensions', ko.selectExtensions); 3077 ko.exportSymbol('selectExtensions.readValue', ko.selectExtensions.readValue); 3078 ko.exportSymbol('selectExtensions.writeValue', ko.selectExtensions.writeValue); 3079 ko.expressionRewriting = (function () { 3080 var javaScriptReservedWords = ["true", "false", "null", "undefined"]; 3081 3082 // Matches something that can be assigned to--either an isolated identifier or something ending with a property accessor 3083 // This is designed to be simple and avoid false negatives, but could produce false positives (e.g., a+b.c). 3084 // This also will not properly handle nested brackets (e.g., obj1[obj2['prop']]; see #911). 3085 var javaScriptAssignmentTarget = /^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i; 3086 3087 function getWriteableValue(expression) { 3088 if (ko.utils.arrayIndexOf(javaScriptReservedWords, expression) >= 0) 3089 return false; 3090 var match = expression.match(javaScriptAssignmentTarget); 3091 return match === null ? false : match[1] ? ('Object(' + match[1] + ')' + match[2]) : expression; 3092 } 3093 3094 // The following regular expressions will be used to split an object-literal string into tokens 3095 3096 var specials = ',"\'`{}()/:[\\]', // These characters have special meaning to the parser and must not appear in the middle of a token, except as part of a string. 3097 // Create the actual regular expression by or-ing the following regex strings. The order is important. 3098 bindingToken = RegExp([ 3099 // These match strings, either with double quotes, single quotes, or backticks 3100 '"(?:\\\\.|[^"])*"', 3101 "'(?:\\\\.|[^'])*'", 3102 "`(?:\\\\.|[^`])*`", 3103 // Match C style comments 3104 "/\\*(?:[^*]|\\*+[^*/])*\\*+/", 3105 // Match C++ style comments 3106 "//.*\n", 3107 // Match a regular expression (text enclosed by slashes), but will also match sets of divisions 3108 // as a regular expression (this is handled by the parsing loop below). 3109 '/(?:\\\\.|[^/])+/\w*', 3110 // Match text (at least two characters) that does not contain any of the above special characters, 3111 // although some of the special characters are allowed to start it (all but the colon and comma). 3112 // The text can contain spaces, but leading or trailing spaces are skipped. 3113 '[^\\s:,/][^' + specials + ']*[^\\s' + specials + ']', 3114 // Match any non-space character not matched already. This will match colons and commas, since they're 3115 // not matched by "everyThingElse", but will also match any other single character that wasn't already 3116 // matched (for example: in "a: 1, b: 2", each of the non-space characters will be matched by oneNotSpace). 3117 '[^\\s]' 3118 ].join('|'), 'g'), 3119 3120 // Match end of previous token to determine whether a slash is a division or regex. 3121 divisionLookBehind = /[\])"'A-Za-z0-9_$]+$/, 3122 keywordRegexLookBehind = { 'in': 1, 'return': 1, 'typeof': 1 }; 3123 3124 function parseObjectLiteral(objectLiteralString) { 3125 // Trim leading and trailing spaces from the string 3126 var str = ko.utils.stringTrim(objectLiteralString); 3127 3128 // Trim braces '{' surrounding the whole object literal 3129 if (str.charCodeAt(0) === 123) str = str.slice(1, -1); 3130 3131 // Add a newline to correctly match a C++ style comment at the end of the string and 3132 // add a comma so that we don't need a separate code block to deal with the last item 3133 str += "\n,"; 3134 3135 // Split into tokens 3136 var result = [], toks = str.match(bindingToken), key, values = [], depth = 0; 3137 3138 if (toks.length > 1) { 3139 for (var i = 0, tok; tok = toks[i]; ++i) { 3140 var c = tok.charCodeAt(0); 3141 // A comma signals the end of a key/value pair if depth is zero 3142 if (c === 44) { // "," 3143 if (depth <= 0) { 3144 result.push((key && values.length) ? { key: key, value: values.join('') } : { 'unknown': key || values.join('') }); 3145 key = depth = 0; 3146 values = []; 3147 continue; 3148 } 3149 // Simply skip the colon that separates the name and value 3150 } else if (c === 58) { // ":" 3151 if (!depth && !key && values.length === 1) { 3152 key = values.pop(); 3153 continue; 3154 } 3155 // Comments: skip them 3156 } else if (c === 47 && tok.length > 1 && (tok.charCodeAt(1) === 47 || tok.charCodeAt(1) === 42)) { // "//" or "/*" 3157 continue; 3158 // A set of slashes is initially matched as a regular expression, but could be division 3159 } else if (c === 47 && i && tok.length > 1) { // "/" 3160 // Look at the end of the previous token to determine if the slash is actually division 3161 var match = toks[i - 1].match(divisionLookBehind); 3162 if (match && !keywordRegexLookBehind[match[0]]) { 3163 // The slash is actually a division punctuator; re-parse the remainder of the string (not including the slash) 3164 str = str.substr(str.indexOf(tok) + 1); 3165 toks = str.match(bindingToken); 3166 i = -1; 3167 // Continue with just the slash 3168 tok = '/'; 3169 } 3170 // Increment depth for parentheses, braces, and brackets so that interior commas are ignored 3171 } else if (c === 40 || c === 123 || c === 91) { // '(', '{', '[' 3172 ++depth; 3173 } else if (c === 41 || c === 125 || c === 93) { // ')', '}', ']' 3174 --depth; 3175 // The key will be the first token; if it's a string, trim the quotes 3176 } else if (!key && !values.length && (c === 34 || c === 39)) { // '"', "'" 3177 tok = tok.slice(1, -1); 3178 } 3179 values.push(tok); 3180 } 3181 if (depth > 0) { 3182 throw Error("Unbalanced parentheses, braces, or brackets"); 3183 } 3184 } 3185 return result; 3186 } 3187 3188 // Two-way bindings include a write function that allow the handler to update the value even if it's not an observable. 3189 var twoWayBindings = {}; 3190 3191 function preProcessBindings(bindingsStringOrKeyValueArray, bindingOptions) { 3192 bindingOptions = bindingOptions || {}; 3193 3194 function processKeyValue(key, val) { 3195 var writableVal; 3196 function callPreprocessHook(obj) { 3197 return (obj && obj['preprocess']) ? (val = obj['preprocess'](val, key, processKeyValue)) : true; 3198 } 3199 if (!bindingParams) { 3200 if (!callPreprocessHook(ko['getBindingHandler'](key))) 3201 return; 3202 3203 if (twoWayBindings[key] && (writableVal = getWriteableValue(val))) { 3204 // For two-way bindings, provide a write method in case the value 3205 // isn't a writable observable. 3206 var writeKey = typeof twoWayBindings[key] == 'string' ? twoWayBindings[key] : key; 3207 propertyAccessorResultStrings.push("'" + writeKey + "':function(_z){" + writableVal + "=_z}"); 3208 } 3209 } 3210 // Values are wrapped in a function so that each value can be accessed independently 3211 if (makeValueAccessors) { 3212 val = 'function(){return ' + val + ' }'; 3213 } 3214 resultStrings.push("'" + key + "':" + val); 3215 } 3216 3217 var resultStrings = [], 3218 propertyAccessorResultStrings = [], 3219 makeValueAccessors = bindingOptions['valueAccessors'], 3220 bindingParams = bindingOptions['bindingParams'], 3221 keyValueArray = typeof bindingsStringOrKeyValueArray === "string" ? 3222 parseObjectLiteral(bindingsStringOrKeyValueArray) : bindingsStringOrKeyValueArray; 3223 3224 ko.utils.arrayForEach(keyValueArray, function (keyValue) { 3225 processKeyValue(keyValue.key || keyValue['unknown'], keyValue.value); 3226 }); 3227 3228 if (propertyAccessorResultStrings.length) 3229 processKeyValue('_ko_property_writers', "{" + propertyAccessorResultStrings.join(",") + " }"); 3230 3231 return resultStrings.join(","); 3232 } 3233 3234 return { 3235 bindingRewriteValidators: [], 3236 3237 twoWayBindings: twoWayBindings, 3238 3239 parseObjectLiteral: parseObjectLiteral, 3240 3241 preProcessBindings: preProcessBindings, 3242 3243 keyValueArrayContainsKey: function (keyValueArray, key) { 3244 for (var i = 0; i < keyValueArray.length; i++) 3245 if (keyValueArray[i]['key'] == key) 3246 return true; 3247 return false; 3248 }, 3249 3250 // Internal, private KO utility for updating model properties from within bindings 3251 // property: If the property being updated is (or might be) an observable, pass it here 3252 // If it turns out to be a writable observable, it will be written to directly 3253 // allBindings: An object with a get method to retrieve bindings in the current execution context. 3254 // This will be searched for a '_ko_property_writers' property in case you're writing to a non-observable 3255 // key: The key identifying the property to be written. Example: for { hasFocus: myValue }, write to 'myValue' by specifying the key 'hasFocus' 3256 // value: The value to be written 3257 // checkIfDifferent: If true, and if the property being written is a writable observable, the value will only be written if 3258 // it is !== existing value on that writable observable 3259 writeValueToProperty: function (property, allBindings, key, value, checkIfDifferent) { 3260 if (!property || !ko.isObservable(property)) { 3261 var propWriters = allBindings.get('_ko_property_writers'); 3262 if (propWriters && propWriters[key]) 3263 propWriters[key](value); 3264 } else if (ko.isWriteableObservable(property) && (!checkIfDifferent || property.peek() !== value)) { 3265 property(value); 3266 } 3267 } 3268 }; 3269 })(); 3270 3271 ko.exportSymbol('expressionRewriting', ko.expressionRewriting); 3272 ko.exportSymbol('expressionRewriting.bindingRewriteValidators', ko.expressionRewriting.bindingRewriteValidators); 3273 ko.exportSymbol('expressionRewriting.parseObjectLiteral', ko.expressionRewriting.parseObjectLiteral); 3274 ko.exportSymbol('expressionRewriting.preProcessBindings', ko.expressionRewriting.preProcessBindings); 3275 3276 // Making bindings explicitly declare themselves as "two way" isn't ideal in the long term (it would be better if 3277 // all bindings could use an official 'property writer' API without needing to declare that they might). However, 3278 // since this is not, and has never been, a public API (_ko_property_writers was never documented), it's acceptable 3279 // as an internal implementation detail in the short term. 3280 // For those developers who rely on _ko_property_writers in their custom bindings, we expose _twoWayBindings as an 3281 // undocumented feature that makes it relatively easy to upgrade to KO 3.0. However, this is still not an official 3282 // public API, and we reserve the right to remove it at any time if we create a real public property writers API. 3283 ko.exportSymbol('expressionRewriting._twoWayBindings', ko.expressionRewriting.twoWayBindings); 3284 3285 // For backward compatibility, define the following aliases. (Previously, these function names were misleading because 3286 // they referred to JSON specifically, even though they actually work with arbitrary JavaScript object literal expressions.) 3287 ko.exportSymbol('jsonExpressionRewriting', ko.expressionRewriting); 3288 ko.exportSymbol('jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.expressionRewriting.preProcessBindings); 3289 (function () { 3290 // "Virtual elements" is an abstraction on top of the usual DOM API which understands the notion that comment nodes 3291 // may be used to represent hierarchy (in addition to the DOM's natural hierarchy). 3292 // If you call the DOM-manipulating functions on ko.virtualElements, you will be able to read and write the state 3293 // of that virtual hierarchy 3294 // 3295 // The point of all this is to support containerless templates (e.g., <!-- ko foreach:someCollection -->blah<!-- /ko -->) 3296 // without having to scatter special cases all over the binding and templating code. 3297 3298 // IE 9 cannot reliably read the "nodeValue" property of a comment node (see https://github.com/SteveSanderson/knockout/issues/186) 3299 // but it does give them a nonstandard alternative property called "text" that it can read reliably. Other browsers don't have that property. 3300 // So, use node.text where available, and node.nodeValue elsewhere 3301 var commentNodesHaveTextProperty = document && document.createComment("test").text === "<!--test-->"; 3302 3303 var startCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*ko(?:\s+([\s\S]+))?\s*-->$/ : /^\s*ko(?:\s+([\s\S]+))?\s*$/; 3304 var endCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*\/ko\s*-->$/ : /^\s*\/ko\s*$/; 3305 var htmlTagsWithOptionallyClosingChildren = { 'ul': true, 'ol': true }; 3306 3307 function isStartComment(node) { 3308 return (node.nodeType == 8) && startCommentRegex.test(commentNodesHaveTextProperty ? node.text : node.nodeValue); 3309 } 3310 3311 function isEndComment(node) { 3312 return (node.nodeType == 8) && endCommentRegex.test(commentNodesHaveTextProperty ? node.text : node.nodeValue); 3313 } 3314 3315 function isUnmatchedEndComment(node) { 3316 return isEndComment(node) && !(ko.utils.domData.get(node, matchedEndCommentDataKey)); 3317 } 3318 3319 var matchedEndCommentDataKey = "__ko_matchedEndComment__" 3320 3321 function getVirtualChildren(startComment, allowUnbalanced) { 3322 var currentNode = startComment; 3323 var depth = 1; 3324 var children = []; 3325 while (currentNode = currentNode.nextSibling) { 3326 if (isEndComment(currentNode)) { 3327 ko.utils.domData.set(currentNode, matchedEndCommentDataKey, true); 3328 depth--; 3329 if (depth === 0) 3330 return children; 3331 } 3332 3333 children.push(currentNode); 3334 3335 if (isStartComment(currentNode)) 3336 depth++; 3337 } 3338 if (!allowUnbalanced) 3339 throw new Error("Cannot find closing comment tag to match: " + startComment.nodeValue); 3340 return null; 3341 } 3342 3343 function getMatchingEndComment(startComment, allowUnbalanced) { 3344 var allVirtualChildren = getVirtualChildren(startComment, allowUnbalanced); 3345 if (allVirtualChildren) { 3346 if (allVirtualChildren.length > 0) 3347 return allVirtualChildren[allVirtualChildren.length - 1].nextSibling; 3348 return startComment.nextSibling; 3349 } else 3350 return null; // Must have no matching end comment, and allowUnbalanced is true 3351 } 3352 3353 function getUnbalancedChildTags(node) { 3354 // e.g., from <div>OK</div><!-- ko blah --><span>Another</span>, returns: <!-- ko blah --><span>Another</span> 3355 // from <div>OK</div><!-- /ko --><!-- /ko -->, returns: <!-- /ko --><!-- /ko --> 3356 var childNode = node.firstChild, captureRemaining = null; 3357 if (childNode) { 3358 do { 3359 if (captureRemaining) // We already hit an unbalanced node and are now just scooping up all subsequent nodes 3360 captureRemaining.push(childNode); 3361 else if (isStartComment(childNode)) { 3362 var matchingEndComment = getMatchingEndComment(childNode, /* allowUnbalanced: */ true); 3363 if (matchingEndComment) // It's a balanced tag, so skip immediately to the end of this virtual set 3364 childNode = matchingEndComment; 3365 else 3366 captureRemaining = [childNode]; // It's unbalanced, so start capturing from this point 3367 } else if (isEndComment(childNode)) { 3368 captureRemaining = [childNode]; // It's unbalanced (if it wasn't, we'd have skipped over it already), so start capturing 3369 } 3370 } while (childNode = childNode.nextSibling); 3371 } 3372 return captureRemaining; 3373 } 3374 3375 ko.virtualElements = { 3376 allowedBindings: {}, 3377 3378 childNodes: function (node) { 3379 return isStartComment(node) ? getVirtualChildren(node) : node.childNodes; 3380 }, 3381 3382 emptyNode: function (node) { 3383 if (!isStartComment(node)) 3384 ko.utils.emptyDomNode(node); 3385 else { 3386 var virtualChildren = ko.virtualElements.childNodes(node); 3387 for (var i = 0, j = virtualChildren.length; i < j; i++) 3388 ko.removeNode(virtualChildren[i]); 3389 } 3390 }, 3391 3392 setDomNodeChildren: function (node, childNodes) { 3393 if (!isStartComment(node)) 3394 ko.utils.setDomNodeChildren(node, childNodes); 3395 else { 3396 ko.virtualElements.emptyNode(node); 3397 var endCommentNode = node.nextSibling; // Must be the next sibling, as we just emptied the children 3398 for (var i = 0, j = childNodes.length; i < j; i++) 3399 endCommentNode.parentNode.insertBefore(childNodes[i], endCommentNode); 3400 } 3401 }, 3402 3403 prepend: function (containerNode, nodeToPrepend) { 3404 var insertBeforeNode; 3405 3406 if (isStartComment(containerNode)) { 3407 // Start comments must always have a parent and at least one following sibling (the end comment) 3408 insertBeforeNode = containerNode.nextSibling; 3409 containerNode = containerNode.parentNode; 3410 } else { 3411 insertBeforeNode = containerNode.firstChild; 3412 } 3413 3414 if (!insertBeforeNode) { 3415 containerNode.appendChild(nodeToPrepend); 3416 } else if (nodeToPrepend !== insertBeforeNode) { // IE will sometimes crash if you try to insert a node before itself 3417 containerNode.insertBefore(nodeToPrepend, insertBeforeNode); 3418 } 3419 }, 3420 3421 insertAfter: function (containerNode, nodeToInsert, insertAfterNode) { 3422 if (!insertAfterNode) { 3423 ko.virtualElements.prepend(containerNode, nodeToInsert); 3424 } else { 3425 // Children of start comments must always have a parent and at least one following sibling (the end comment) 3426 var insertBeforeNode = insertAfterNode.nextSibling; 3427 3428 if (isStartComment(containerNode)) { 3429 containerNode = containerNode.parentNode; 3430 } 3431 3432 if (!insertBeforeNode) { 3433 containerNode.appendChild(nodeToInsert); 3434 } else if (nodeToInsert !== insertBeforeNode) { // IE will sometimes crash if you try to insert a node before itself 3435 containerNode.insertBefore(nodeToInsert, insertBeforeNode); 3436 } 3437 } 3438 }, 3439 3440 firstChild: function (node) { 3441 if (!isStartComment(node)) { 3442 if (node.firstChild && isEndComment(node.firstChild)) { 3443 throw new Error("Found invalid end comment, as the first child of " + node); 3444 } 3445 return node.firstChild; 3446 } else if (!node.nextSibling || isEndComment(node.nextSibling)) { 3447 return null; 3448 } else { 3449 return node.nextSibling; 3450 } 3451 }, 3452 3453 nextSibling: function (node) { 3454 if (isStartComment(node)) { 3455 node = getMatchingEndComment(node); 3456 } 3457 3458 if (node.nextSibling && isEndComment(node.nextSibling)) { 3459 if (isUnmatchedEndComment(node.nextSibling)) { 3460 throw Error("Found end comment without a matching opening comment, as child of " + node); 3461 } else { 3462 return null; 3463 } 3464 } else { 3465 return node.nextSibling; 3466 } 3467 }, 3468 3469 hasBindingValue: isStartComment, 3470 3471 virtualNodeBindingValue: function (node) { 3472 var regexMatch = (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(startCommentRegex); 3473 return regexMatch ? regexMatch[1] : null; 3474 }, 3475 3476 normaliseVirtualElementDomStructure: function (elementVerified) { 3477 // Workaround for https://github.com/SteveSanderson/knockout/issues/155 3478 // (IE <= 8 or IE 9 quirks mode parses your HTML weirdly, treating closing </li> tags as if they don't exist, thereby moving comment nodes 3479 // that are direct descendants of <ul> into the preceding <li>) 3480 if (!htmlTagsWithOptionallyClosingChildren[ko.utils.tagNameLower(elementVerified)]) 3481 return; 3482 3483 // Scan immediate children to see if they contain unbalanced comment tags. If they do, those comment tags 3484 // must be intended to appear *after* that child, so move them there. 3485 var childNode = elementVerified.firstChild; 3486 if (childNode) { 3487 do { 3488 if (childNode.nodeType === 1) { 3489 var unbalancedTags = getUnbalancedChildTags(childNode); 3490 if (unbalancedTags) { 3491 // Fix up the DOM by moving the unbalanced tags to where they most likely were intended to be placed - *after* the child 3492 var nodeToInsertBefore = childNode.nextSibling; 3493 for (var i = 0; i < unbalancedTags.length; i++) { 3494 if (nodeToInsertBefore) 3495 elementVerified.insertBefore(unbalancedTags[i], nodeToInsertBefore); 3496 else 3497 elementVerified.appendChild(unbalancedTags[i]); 3498 } 3499 } 3500 } 3501 } while (childNode = childNode.nextSibling); 3502 } 3503 } 3504 }; 3505 })(); 3506 ko.exportSymbol('virtualElements', ko.virtualElements); 3507 ko.exportSymbol('virtualElements.allowedBindings', ko.virtualElements.allowedBindings); 3508 ko.exportSymbol('virtualElements.emptyNode', ko.virtualElements.emptyNode); 3509 //ko.exportSymbol('virtualElements.firstChild', ko.virtualElements.firstChild); // firstChild is not minified 3510 ko.exportSymbol('virtualElements.insertAfter', ko.virtualElements.insertAfter); 3511 //ko.exportSymbol('virtualElements.nextSibling', ko.virtualElements.nextSibling); // nextSibling is not minified 3512 ko.exportSymbol('virtualElements.prepend', ko.virtualElements.prepend); 3513 ko.exportSymbol('virtualElements.setDomNodeChildren', ko.virtualElements.setDomNodeChildren); 3514 (function () { 3515 var defaultBindingAttributeName = "data-bind"; 3516 3517 ko.bindingProvider = function () { 3518 this.bindingCache = {}; 3519 }; 3520 3521 ko.utils.extend(ko.bindingProvider.prototype, { 3522 'nodeHasBindings': function (node) { 3523 switch (node.nodeType) { 3524 case 1: // Element 3525 return node.getAttribute(defaultBindingAttributeName) != null 3526 || ko.components['getComponentNameForNode'](node); 3527 case 8: // Comment node 3528 return ko.virtualElements.hasBindingValue(node); 3529 default: return false; 3530 } 3531 }, 3532 3533 'getBindings': function (node, bindingContext) { 3534 var bindingsString = this['getBindingsString'](node, bindingContext), 3535 parsedBindings = bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node) : null; 3536 return ko.components.addBindingsForCustomElement(parsedBindings, node, bindingContext, /* valueAccessors */ false); 3537 }, 3538 3539 'getBindingAccessors': function (node, bindingContext) { 3540 var bindingsString = this['getBindingsString'](node, bindingContext), 3541 parsedBindings = bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node, { 'valueAccessors': true }) : null; 3542 return ko.components.addBindingsForCustomElement(parsedBindings, node, bindingContext, /* valueAccessors */ true); 3543 }, 3544 3545 // The following function is only used internally by this default provider. 3546 // It's not part of the interface definition for a general binding provider. 3547 'getBindingsString': function (node, bindingContext) { 3548 switch (node.nodeType) { 3549 case 1: return node.getAttribute(defaultBindingAttributeName); // Element 3550 case 8: return ko.virtualElements.virtualNodeBindingValue(node); // Comment node 3551 default: return null; 3552 } 3553 }, 3554 3555 // The following function is only used internally by this default provider. 3556 // It's not part of the interface definition for a general binding provider. 3557 'parseBindingsString': function (bindingsString, bindingContext, node, options) { 3558 try { 3559 var bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, this.bindingCache, options); 3560 return bindingFunction(bindingContext, node); 3561 } catch (ex) { 3562 ex.message = "Unable to parse bindings.\nBindings value: " + bindingsString + "\nMessage: " + ex.message; 3563 throw ex; 3564 } 3565 } 3566 }); 3567 3568 ko.bindingProvider['instance'] = new ko.bindingProvider(); 3569 3570 function createBindingsStringEvaluatorViaCache(bindingsString, cache, options) { 3571 var cacheKey = bindingsString + (options && options['valueAccessors'] || ''); 3572 return cache[cacheKey] 3573 || (cache[cacheKey] = createBindingsStringEvaluator(bindingsString, options)); 3574 } 3575 3576 function createBindingsStringEvaluator(bindingsString, options) { 3577 // Build the source for a function that evaluates "expression" 3578 // For each scope variable, add an extra level of "with" nesting 3579 // Example result: with(sc1) { with(sc0) { return (expression) } } 3580 var rewrittenBindings = ko.expressionRewriting.preProcessBindings(bindingsString, options), 3581 functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}"; 3582 return new Function("$context", "$element", functionBody); 3583 } 3584 })(); 3585 3586 ko.exportSymbol('bindingProvider', ko.bindingProvider); 3587 (function () { 3588 // Hide or don't minify context properties, see https://github.com/knockout/knockout/issues/2294 3589 var contextSubscribable = ko.utils.createSymbolOrString('_subscribable'); 3590 var contextAncestorBindingInfo = ko.utils.createSymbolOrString('_ancestorBindingInfo'); 3591 var contextDataDependency = ko.utils.createSymbolOrString('_dataDependency'); 3592 3593 ko.bindingHandlers = {}; 3594 3595 // The following element types will not be recursed into during binding. 3596 var bindingDoesNotRecurseIntoElementTypes = { 3597 // Don't want bindings that operate on text nodes to mutate <script> and <textarea> contents, 3598 // because it's unexpected and a potential XSS issue. 3599 // Also bindings should not operate on <template> elements since this breaks in Internet Explorer 3600 // and because such elements' contents are always intended to be bound in a different context 3601 // from where they appear in the document. 3602 'script': true, 3603 'textarea': true, 3604 'template': true 3605 }; 3606 3607 // Use an overridable method for retrieving binding handlers so that plugins may support dynamically created handlers 3608 ko['getBindingHandler'] = function (bindingKey) { 3609 return ko.bindingHandlers[bindingKey]; 3610 }; 3611 3612 var inheritParentVm = {}; 3613 3614 // The ko.bindingContext constructor is only called directly to create the root context. For child 3615 // contexts, use bindingContext.createChildContext or bindingContext.extend. 3616 ko.bindingContext = function (dataItemOrAccessor, parentContext, dataItemAlias, extendCallback, options) { 3617 3618 // The binding context object includes static properties for the current, parent, and root view models. 3619 // If a view model is actually stored in an observable, the corresponding binding context object, and 3620 // any child contexts, must be updated when the view model is changed. 3621 function updateContext() { 3622 // Most of the time, the context will directly get a view model object, but if a function is given, 3623 // we call the function to retrieve the view model. If the function accesses any observables or returns 3624 // an observable, the dependency is tracked, and those observables can later cause the binding 3625 // context to be updated. 3626 var dataItemOrObservable = isFunc ? realDataItemOrAccessor() : realDataItemOrAccessor, 3627 dataItem = ko.utils.unwrapObservable(dataItemOrObservable); 3628 3629 if (parentContext) { 3630 // Copy $root and any custom properties from the parent context 3631 ko.utils.extend(self, parentContext); 3632 3633 // Copy Symbol properties 3634 if (contextAncestorBindingInfo in parentContext) { 3635 self[contextAncestorBindingInfo] = parentContext[contextAncestorBindingInfo]; 3636 } 3637 } else { 3638 self['$parents'] = []; 3639 self['$root'] = dataItem; 3640 3641 // Export 'ko' in the binding context so it will be available in bindings and templates 3642 // even if 'ko' isn't exported as a global, such as when using an AMD loader. 3643 // See https://github.com/SteveSanderson/knockout/issues/490 3644 self['ko'] = ko; 3645 } 3646 3647 self[contextSubscribable] = subscribable; 3648 3649 if (shouldInheritData) { 3650 dataItem = self['$data']; 3651 } else { 3652 self['$rawData'] = dataItemOrObservable; 3653 self['$data'] = dataItem; 3654 } 3655 3656 if (dataItemAlias) 3657 self[dataItemAlias] = dataItem; 3658 3659 // The extendCallback function is provided when creating a child context or extending a context. 3660 // It handles the specific actions needed to finish setting up the binding context. Actions in this 3661 // function could also add dependencies to this binding context. 3662 if (extendCallback) 3663 extendCallback(self, parentContext, dataItem); 3664 3665 // When a "parent" context is given and we don't already have a dependency on its context, register a dependency on it. 3666 // Thus whenever the parent context is updated, this context will also be updated. 3667 if (parentContext && parentContext[contextSubscribable] && !ko.computedContext.computed().hasAncestorDependency(parentContext[contextSubscribable])) { 3668 parentContext[contextSubscribable](); 3669 } 3670 3671 if (dataDependency) { 3672 self[contextDataDependency] = dataDependency; 3673 } 3674 3675 return self['$data']; 3676 } 3677 3678 var self = this, 3679 shouldInheritData = dataItemOrAccessor === inheritParentVm, 3680 realDataItemOrAccessor = shouldInheritData ? undefined : dataItemOrAccessor, 3681 isFunc = typeof (realDataItemOrAccessor) == "function" && !ko.isObservable(realDataItemOrAccessor), 3682 nodes, 3683 subscribable, 3684 dataDependency = options && options['dataDependency']; 3685 3686 if (options && options['exportDependencies']) { 3687 // The "exportDependencies" option means that the calling code will track any dependencies and re-create 3688 // the binding context when they change. 3689 updateContext(); 3690 } else { 3691 subscribable = ko.pureComputed(updateContext); 3692 subscribable.peek(); 3693 3694 // At this point, the binding context has been initialized, and the "subscribable" computed observable is 3695 // subscribed to any observables that were accessed in the process. If there is nothing to track, the 3696 // computed will be inactive, and we can safely throw it away. If it's active, the computed is stored in 3697 // the context object. 3698 if (subscribable.isActive()) { 3699 // Always notify because even if the model ($data) hasn't changed, other context properties might have changed 3700 subscribable['equalityComparer'] = null; 3701 } else { 3702 self[contextSubscribable] = undefined; 3703 } 3704 } 3705 } 3706 3707 // Extend the binding context hierarchy with a new view model object. If the parent context is watching 3708 // any observables, the new child context will automatically get a dependency on the parent context. 3709 // But this does not mean that the $data value of the child context will also get updated. If the child 3710 // view model also depends on the parent view model, you must provide a function that returns the correct 3711 // view model on each update. 3712 ko.bindingContext.prototype['createChildContext'] = function (dataItemOrAccessor, dataItemAlias, extendCallback, options) { 3713 if (!options && dataItemAlias && typeof dataItemAlias == "object") { 3714 options = dataItemAlias; 3715 dataItemAlias = options['as']; 3716 extendCallback = options['extend']; 3717 } 3718 3719 if (dataItemAlias && options && options['noChildContext']) { 3720 var isFunc = typeof (dataItemOrAccessor) == "function" && !ko.isObservable(dataItemOrAccessor); 3721 return new ko.bindingContext(inheritParentVm, this, null, function (self) { 3722 if (extendCallback) 3723 extendCallback(self); 3724 self[dataItemAlias] = isFunc ? dataItemOrAccessor() : dataItemOrAccessor; 3725 }, options); 3726 } 3727 3728 return new ko.bindingContext(dataItemOrAccessor, this, dataItemAlias, function (self, parentContext) { 3729 // Extend the context hierarchy by setting the appropriate pointers 3730 self['$parentContext'] = parentContext; 3731 self['$parent'] = parentContext['$data']; 3732 self['$parents'] = (parentContext['$parents'] || []).slice(0); 3733 self['$parents'].unshift(self['$parent']); 3734 if (extendCallback) 3735 extendCallback(self); 3736 }, options); 3737 }; 3738 3739 // Extend the binding context with new custom properties. This doesn't change the context hierarchy. 3740 // Similarly to "child" contexts, provide a function here to make sure that the correct values are set 3741 // when an observable view model is updated. 3742 ko.bindingContext.prototype['extend'] = function (properties, options) { 3743 return new ko.bindingContext(inheritParentVm, this, null, function (self, parentContext) { 3744 ko.utils.extend(self, typeof (properties) == "function" ? properties(self) : properties); 3745 }, options); 3746 }; 3747 3748 var boundElementDomDataKey = ko.utils.domData.nextKey(); 3749 3750 function asyncContextDispose(node) { 3751 var bindingInfo = ko.utils.domData.get(node, boundElementDomDataKey), 3752 asyncContext = bindingInfo && bindingInfo.asyncContext; 3753 if (asyncContext) { 3754 bindingInfo.asyncContext = null; 3755 asyncContext.notifyAncestor(); 3756 } 3757 } 3758 function AsyncCompleteContext(node, bindingInfo, ancestorBindingInfo) { 3759 this.node = node; 3760 this.bindingInfo = bindingInfo; 3761 this.asyncDescendants = []; 3762 this.childrenComplete = false; 3763 3764 if (!bindingInfo.asyncContext) { 3765 ko.utils.domNodeDisposal.addDisposeCallback(node, asyncContextDispose); 3766 } 3767 3768 if (ancestorBindingInfo && ancestorBindingInfo.asyncContext) { 3769 ancestorBindingInfo.asyncContext.asyncDescendants.push(node); 3770 this.ancestorBindingInfo = ancestorBindingInfo; 3771 } 3772 } 3773 AsyncCompleteContext.prototype.notifyAncestor = function () { 3774 if (this.ancestorBindingInfo && this.ancestorBindingInfo.asyncContext) { 3775 this.ancestorBindingInfo.asyncContext.descendantComplete(this.node); 3776 } 3777 }; 3778 AsyncCompleteContext.prototype.descendantComplete = function (node) { 3779 ko.utils.arrayRemoveItem(this.asyncDescendants, node); 3780 if (!this.asyncDescendants.length && this.childrenComplete) { 3781 this.completeChildren(); 3782 } 3783 }; 3784 AsyncCompleteContext.prototype.completeChildren = function () { 3785 this.childrenComplete = true; 3786 if (this.bindingInfo.asyncContext && !this.asyncDescendants.length) { 3787 this.bindingInfo.asyncContext = null; 3788 ko.utils.domNodeDisposal.removeDisposeCallback(this.node, asyncContextDispose); 3789 ko.bindingEvent.notify(this.node, ko.bindingEvent.descendantsComplete); 3790 this.notifyAncestor(); 3791 } 3792 }; 3793 3794 ko.bindingEvent = { 3795 childrenComplete: "childrenComplete", 3796 descendantsComplete: "descendantsComplete", 3797 3798 subscribe: function (node, event, callback, context, options) { 3799 var bindingInfo = ko.utils.domData.getOrSet(node, boundElementDomDataKey, {}); 3800 if (!bindingInfo.eventSubscribable) { 3801 bindingInfo.eventSubscribable = new ko.subscribable; 3802 } 3803 if (options && options['notifyImmediately'] && bindingInfo.notifiedEvents[event]) { 3804 ko.dependencyDetection.ignore(callback, context, [node]); 3805 } 3806 return bindingInfo.eventSubscribable.subscribe(callback, context, event); 3807 }, 3808 3809 notify: function (node, event) { 3810 var bindingInfo = ko.utils.domData.get(node, boundElementDomDataKey); 3811 if (bindingInfo) { 3812 bindingInfo.notifiedEvents[event] = true; 3813 if (bindingInfo.eventSubscribable) { 3814 bindingInfo.eventSubscribable['notifySubscribers'](node, event); 3815 } 3816 if (event == ko.bindingEvent.childrenComplete) { 3817 if (bindingInfo.asyncContext) { 3818 bindingInfo.asyncContext.completeChildren(); 3819 } else if (bindingInfo.asyncContext === undefined && bindingInfo.eventSubscribable && bindingInfo.eventSubscribable.hasSubscriptionsForEvent(ko.bindingEvent.descendantsComplete)) { 3820 // It's currently an error to register a descendantsComplete handler for a node that was never registered as completing asynchronously. 3821 // That's because without the asyncContext, we don't have a way to know that all descendants have completed. 3822 throw new Error("descendantsComplete event not supported for bindings on this node"); 3823 } 3824 } 3825 } 3826 }, 3827 3828 startPossiblyAsyncContentBinding: function (node, bindingContext) { 3829 var bindingInfo = ko.utils.domData.getOrSet(node, boundElementDomDataKey, {}); 3830 3831 if (!bindingInfo.asyncContext) { 3832 bindingInfo.asyncContext = new AsyncCompleteContext(node, bindingInfo, bindingContext[contextAncestorBindingInfo]); 3833 } 3834 3835 // If the provided context was already extended with this node's binding info, just return the extended context 3836 if (bindingContext[contextAncestorBindingInfo] == bindingInfo) { 3837 return bindingContext; 3838 } 3839 3840 return bindingContext['extend'](function (ctx) { 3841 ctx[contextAncestorBindingInfo] = bindingInfo; 3842 }); 3843 } 3844 }; 3845 3846 // Returns the valueAccessor function for a binding value 3847 function makeValueAccessor(value) { 3848 return function () { 3849 return value; 3850 }; 3851 } 3852 3853 // Returns the value of a valueAccessor function 3854 function evaluateValueAccessor(valueAccessor) { 3855 return valueAccessor(); 3856 } 3857 3858 // Given a function that returns bindings, create and return a new object that contains 3859 // binding value-accessors functions. Each accessor function calls the original function 3860 // so that it always gets the latest value and all dependencies are captured. This is used 3861 // by ko.applyBindingsToNode and getBindingsAndMakeAccessors. 3862 function makeAccessorsFromFunction(callback) { 3863 return ko.utils.objectMap(ko.dependencyDetection.ignore(callback), function (value, key) { 3864 return function () { 3865 return callback()[key]; 3866 }; 3867 }); 3868 } 3869 3870 // Given a bindings function or object, create and return a new object that contains 3871 // binding value-accessors functions. This is used by ko.applyBindingsToNode. 3872 function makeBindingAccessors(bindings, context, node) { 3873 if (typeof bindings === 'function') { 3874 return makeAccessorsFromFunction(bindings.bind(null, context, node)); 3875 } else { 3876 return ko.utils.objectMap(bindings, makeValueAccessor); 3877 } 3878 } 3879 3880 // This function is used if the binding provider doesn't include a getBindingAccessors function. 3881 // It must be called with 'this' set to the provider instance. 3882 function getBindingsAndMakeAccessors(node, context) { 3883 return makeAccessorsFromFunction(this['getBindings'].bind(this, node, context)); 3884 } 3885 3886 function validateThatBindingIsAllowedForVirtualElements(bindingName) { 3887 var validator = ko.virtualElements.allowedBindings[bindingName]; 3888 if (!validator) 3889 throw new Error("The binding '" + bindingName + "' cannot be used with virtual elements") 3890 } 3891 3892 function applyBindingsToDescendantsInternal(bindingContext, elementOrVirtualElement) { 3893 var nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement); 3894 3895 if (nextInQueue) { 3896 var currentChild, 3897 provider = ko.bindingProvider['instance'], 3898 preprocessNode = provider['preprocessNode']; 3899 3900 // Preprocessing allows a binding provider to mutate a node before bindings are applied to it. For example it's 3901 // possible to insert new siblings after it, and/or replace the node with a different one. This can be used to 3902 // implement custom binding syntaxes, such as {{ value }} for string interpolation, or custom element types that 3903 // trigger insertion of <template> contents at that point in the document. 3904 if (preprocessNode) { 3905 while (currentChild = nextInQueue) { 3906 nextInQueue = ko.virtualElements.nextSibling(currentChild); 3907 preprocessNode.call(provider, currentChild); 3908 } 3909 // Reset nextInQueue for the next loop 3910 nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement); 3911 } 3912 3913 while (currentChild = nextInQueue) { 3914 // Keep a record of the next child *before* applying bindings, in case the binding removes the current child from its position 3915 nextInQueue = ko.virtualElements.nextSibling(currentChild); 3916 applyBindingsToNodeAndDescendantsInternal(bindingContext, currentChild); 3917 } 3918 } 3919 ko.bindingEvent.notify(elementOrVirtualElement, ko.bindingEvent.childrenComplete); 3920 } 3921 3922 function applyBindingsToNodeAndDescendantsInternal(bindingContext, nodeVerified) { 3923 var bindingContextForDescendants = bindingContext; 3924 3925 var isElement = (nodeVerified.nodeType === 1); 3926 if (isElement) // Workaround IE <= 8 HTML parsing weirdness 3927 ko.virtualElements.normaliseVirtualElementDomStructure(nodeVerified); 3928 3929 // Perf optimisation: Apply bindings only if... 3930 // (1) We need to store the binding info for the node (all element nodes) 3931 // (2) It might have bindings (e.g., it has a data-bind attribute, or it's a marker for a containerless template) 3932 var shouldApplyBindings = isElement || ko.bindingProvider['instance']['nodeHasBindings'](nodeVerified); 3933 if (shouldApplyBindings) 3934 bindingContextForDescendants = applyBindingsToNodeInternal(nodeVerified, null, bindingContext)['bindingContextForDescendants']; 3935 3936 if (bindingContextForDescendants && !bindingDoesNotRecurseIntoElementTypes[ko.utils.tagNameLower(nodeVerified)]) { 3937 applyBindingsToDescendantsInternal(bindingContextForDescendants, nodeVerified); 3938 } 3939 } 3940 3941 function topologicalSortBindings(bindings) { 3942 // Depth-first sort 3943 var result = [], // The list of key/handler pairs that we will return 3944 bindingsConsidered = {}, // A temporary record of which bindings are already in 'result' 3945 cyclicDependencyStack = []; // Keeps track of a depth-search so that, if there's a cycle, we know which bindings caused it 3946 ko.utils.objectForEach(bindings, function pushBinding(bindingKey) { 3947 if (!bindingsConsidered[bindingKey]) { 3948 var binding = ko['getBindingHandler'](bindingKey); 3949 if (binding) { 3950 // First add dependencies (if any) of the current binding 3951 if (binding['after']) { 3952 cyclicDependencyStack.push(bindingKey); 3953 ko.utils.arrayForEach(binding['after'], function (bindingDependencyKey) { 3954 if (bindings[bindingDependencyKey]) { 3955 if (ko.utils.arrayIndexOf(cyclicDependencyStack, bindingDependencyKey) !== -1) { 3956 throw Error("Cannot combine the following bindings, because they have a cyclic dependency: " + cyclicDependencyStack.join(", ")); 3957 } else { 3958 pushBinding(bindingDependencyKey); 3959 } 3960 } 3961 }); 3962 cyclicDependencyStack.length--; 3963 } 3964 // Next add the current binding 3965 result.push({ key: bindingKey, handler: binding }); 3966 } 3967 bindingsConsidered[bindingKey] = true; 3968 } 3969 }); 3970 3971 return result; 3972 } 3973 3974 function applyBindingsToNodeInternal(node, sourceBindings, bindingContext) { 3975 var bindingInfo = ko.utils.domData.getOrSet(node, boundElementDomDataKey, {}); 3976 3977 // Prevent multiple applyBindings calls for the same node, except when a binding value is specified 3978 var alreadyBound = bindingInfo.alreadyBound; 3979 if (!sourceBindings) { 3980 if (alreadyBound) { 3981 throw Error("You cannot apply bindings multiple times to the same element."); 3982 } 3983 bindingInfo.alreadyBound = true; 3984 } 3985 if (!alreadyBound) { 3986 bindingInfo.context = bindingContext; 3987 } 3988 if (!bindingInfo.notifiedEvents) { 3989 bindingInfo.notifiedEvents = {}; 3990 } 3991 3992 // Use bindings if given, otherwise fall back on asking the bindings provider to give us some bindings 3993 var bindings; 3994 if (sourceBindings && typeof sourceBindings !== 'function') { 3995 bindings = sourceBindings; 3996 } else { 3997 var provider = ko.bindingProvider['instance'], 3998 getBindings = provider['getBindingAccessors'] || getBindingsAndMakeAccessors; 3999 4000 // Get the binding from the provider within a computed observable so that we can update the bindings whenever 4001 // the binding context is updated or if the binding provider accesses observables. 4002 var bindingsUpdater = ko.dependentObservable( 4003 function () { 4004 bindings = sourceBindings ? sourceBindings(bindingContext, node) : getBindings.call(provider, node, bindingContext); 4005 // Register a dependency on the binding context to support observable view models. 4006 if (bindings) { 4007 if (bindingContext[contextSubscribable]) { 4008 bindingContext[contextSubscribable](); 4009 } 4010 if (bindingContext[contextDataDependency]) { 4011 bindingContext[contextDataDependency](); 4012 } 4013 } 4014 return bindings; 4015 }, 4016 null, { disposeWhenNodeIsRemoved: node } 4017 ); 4018 4019 if (!bindings || !bindingsUpdater.isActive()) 4020 bindingsUpdater = null; 4021 } 4022 4023 var contextToExtend = bindingContext; 4024 var bindingHandlerThatControlsDescendantBindings; 4025 if (bindings) { 4026 // Return the value accessor for a given binding. When bindings are static (won't be updated because of a binding 4027 // context update), just return the value accessor from the binding. Otherwise, return a function that always gets 4028 // the latest binding value and registers a dependency on the binding updater. 4029 var getValueAccessor = bindingsUpdater 4030 ? function (bindingKey) { 4031 return function () { 4032 return evaluateValueAccessor(bindingsUpdater()[bindingKey]); 4033 }; 4034 } : function (bindingKey) { 4035 return bindings[bindingKey]; 4036 }; 4037 4038 // Use of allBindings as a function is maintained for backwards compatibility, but its use is deprecated 4039 function allBindings() { 4040 return ko.utils.objectMap(bindingsUpdater ? bindingsUpdater() : bindings, evaluateValueAccessor); 4041 } 4042 // The following is the 3.x allBindings API 4043 allBindings['get'] = function (key) { 4044 return bindings[key] && evaluateValueAccessor(getValueAccessor(key)); 4045 }; 4046 allBindings['has'] = function (key) { 4047 return key in bindings; 4048 }; 4049 4050 if (ko.bindingEvent.childrenComplete in bindings) { 4051 ko.bindingEvent.subscribe(node, ko.bindingEvent.childrenComplete, function () { 4052 var callback = evaluateValueAccessor(bindings[ko.bindingEvent.childrenComplete]); 4053 if (callback) { 4054 var nodes = ko.virtualElements.childNodes(node); 4055 if (nodes.length) { 4056 callback(nodes, ko.dataFor(nodes[0])); 4057 } 4058 } 4059 }); 4060 } 4061 4062 if (ko.bindingEvent.descendantsComplete in bindings) { 4063 contextToExtend = ko.bindingEvent.startPossiblyAsyncContentBinding(node, bindingContext); 4064 ko.bindingEvent.subscribe(node, ko.bindingEvent.descendantsComplete, function () { 4065 var callback = evaluateValueAccessor(bindings[ko.bindingEvent.descendantsComplete]); 4066 if (callback && ko.virtualElements.firstChild(node)) { 4067 callback(node); 4068 } 4069 }); 4070 } 4071 4072 // First put the bindings into the right order 4073 var orderedBindings = topologicalSortBindings(bindings); 4074 4075 // Go through the sorted bindings, calling init and update for each 4076 ko.utils.arrayForEach(orderedBindings, function (bindingKeyAndHandler) { 4077 // Note that topologicalSortBindings has already filtered out any nonexistent binding handlers, 4078 // so bindingKeyAndHandler.handler will always be nonnull. 4079 var handlerInitFn = bindingKeyAndHandler.handler["init"], 4080 handlerUpdateFn = bindingKeyAndHandler.handler["update"], 4081 bindingKey = bindingKeyAndHandler.key; 4082 4083 if (node.nodeType === 8) { 4084 validateThatBindingIsAllowedForVirtualElements(bindingKey); 4085 } 4086 4087 try { 4088 // Run init, ignoring any dependencies 4089 if (typeof handlerInitFn == "function") { 4090 ko.dependencyDetection.ignore(function () { 4091 var initResult = handlerInitFn(node, getValueAccessor(bindingKey), allBindings, contextToExtend['$data'], contextToExtend); 4092 4093 // If this binding handler claims to control descendant bindings, make a note of this 4094 if (initResult && initResult['controlsDescendantBindings']) { 4095 if (bindingHandlerThatControlsDescendantBindings !== undefined) 4096 throw new Error("Multiple bindings (" + bindingHandlerThatControlsDescendantBindings + " and " + bindingKey + ") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element."); 4097 bindingHandlerThatControlsDescendantBindings = bindingKey; 4098 } 4099 }); 4100 } 4101 4102 // Run update in its own computed wrapper 4103 if (typeof handlerUpdateFn == "function") { 4104 ko.dependentObservable( 4105 function () { 4106 handlerUpdateFn(node, getValueAccessor(bindingKey), allBindings, contextToExtend['$data'], contextToExtend); 4107 }, 4108 null, 4109 { disposeWhenNodeIsRemoved: node } 4110 ); 4111 } 4112 } catch (ex) { 4113 ex.message = "Unable to process binding \"" + bindingKey + ": " + bindings[bindingKey] + "\"\nMessage: " + ex.message; 4114 throw ex; 4115 } 4116 }); 4117 } 4118 4119 var shouldBindDescendants = bindingHandlerThatControlsDescendantBindings === undefined; 4120 return { 4121 'shouldBindDescendants': shouldBindDescendants, 4122 'bindingContextForDescendants': shouldBindDescendants && contextToExtend 4123 }; 4124 }; 4125 4126 ko.storedBindingContextForNode = function (node) { 4127 var bindingInfo = ko.utils.domData.get(node, boundElementDomDataKey); 4128 return bindingInfo && bindingInfo.context; 4129 } 4130 4131 function getBindingContext(viewModelOrBindingContext, extendContextCallback) { 4132 return viewModelOrBindingContext && (viewModelOrBindingContext instanceof ko.bindingContext) 4133 ? viewModelOrBindingContext 4134 : new ko.bindingContext(viewModelOrBindingContext, undefined, undefined, extendContextCallback); 4135 } 4136 4137 ko.applyBindingAccessorsToNode = function (node, bindings, viewModelOrBindingContext) { 4138 if (node.nodeType === 1) // If it's an element, workaround IE <= 8 HTML parsing weirdness 4139 ko.virtualElements.normaliseVirtualElementDomStructure(node); 4140 return applyBindingsToNodeInternal(node, bindings, getBindingContext(viewModelOrBindingContext)); 4141 }; 4142 4143 ko.applyBindingsToNode = function (node, bindings, viewModelOrBindingContext) { 4144 var context = getBindingContext(viewModelOrBindingContext); 4145 return ko.applyBindingAccessorsToNode(node, makeBindingAccessors(bindings, context, node), context); 4146 }; 4147 4148 ko.applyBindingsToDescendants = function (viewModelOrBindingContext, rootNode) { 4149 if (rootNode.nodeType === 1 || rootNode.nodeType === 8) 4150 applyBindingsToDescendantsInternal(getBindingContext(viewModelOrBindingContext), rootNode); 4151 }; 4152 4153 ko.applyBindings = function (viewModelOrBindingContext, rootNode, extendContextCallback) { 4154 // If jQuery is loaded after Knockout, we won't initially have access to it. So save it here. 4155 if (!jQueryInstance && window['jQuery']) { 4156 jQueryInstance = window['jQuery']; 4157 } 4158 4159 if (arguments.length < 2) { 4160 rootNode = document.body; 4161 if (!rootNode) { 4162 throw Error("ko.applyBindings: could not find document.body; has the document been loaded?"); 4163 } 4164 } else if (!rootNode || (rootNode.nodeType !== 1 && rootNode.nodeType !== 8)) { 4165 throw Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node"); 4166 } 4167 4168 applyBindingsToNodeAndDescendantsInternal(getBindingContext(viewModelOrBindingContext, extendContextCallback), rootNode); 4169 }; 4170 4171 // Retrieving binding context from arbitrary nodes 4172 ko.contextFor = function (node) { 4173 // We can only do something meaningful for elements and comment nodes (in particular, not text nodes, as IE can't store domdata for them) 4174 if (node && (node.nodeType === 1 || node.nodeType === 8)) { 4175 return ko.storedBindingContextForNode(node); 4176 } 4177 return undefined; 4178 }; 4179 ko.dataFor = function (node) { 4180 var context = ko.contextFor(node); 4181 return context ? context['$data'] : undefined; 4182 }; 4183 4184 ko.exportSymbol('bindingHandlers', ko.bindingHandlers); 4185 ko.exportSymbol('bindingEvent', ko.bindingEvent); 4186 ko.exportSymbol('bindingEvent.subscribe', ko.bindingEvent.subscribe); 4187 ko.exportSymbol('bindingEvent.startPossiblyAsyncContentBinding', ko.bindingEvent.startPossiblyAsyncContentBinding); 4188 ko.exportSymbol('applyBindings', ko.applyBindings); 4189 ko.exportSymbol('applyBindingsToDescendants', ko.applyBindingsToDescendants); 4190 ko.exportSymbol('applyBindingAccessorsToNode', ko.applyBindingAccessorsToNode); 4191 ko.exportSymbol('applyBindingsToNode', ko.applyBindingsToNode); 4192 ko.exportSymbol('contextFor', ko.contextFor); 4193 ko.exportSymbol('dataFor', ko.dataFor); 4194 })(); 4195 (function (undefined) { 4196 var loadingSubscribablesCache = {}, // Tracks component loads that are currently in flight 4197 loadedDefinitionsCache = {}; // Tracks component loads that have already completed 4198 4199 ko.components = { 4200 get: function (componentName, callback) { 4201 var cachedDefinition = getObjectOwnProperty(loadedDefinitionsCache, componentName); 4202 if (cachedDefinition) { 4203 // It's already loaded and cached. Reuse the same definition object. 4204 // Note that for API consistency, even cache hits complete asynchronously by default. 4205 // You can bypass this by putting synchronous:true on your component config. 4206 if (cachedDefinition.isSynchronousComponent) { 4207 ko.dependencyDetection.ignore(function () { // See comment in loaderRegistryBehaviors.js for reasoning 4208 callback(cachedDefinition.definition); 4209 }); 4210 } else { 4211 ko.tasks.schedule(function () { callback(cachedDefinition.definition); }); 4212 } 4213 } else { 4214 // Join the loading process that is already underway, or start a new one. 4215 loadComponentAndNotify(componentName, callback); 4216 } 4217 }, 4218 4219 clearCachedDefinition: function (componentName) { 4220 delete loadedDefinitionsCache[componentName]; 4221 }, 4222 4223 _getFirstResultFromLoaders: getFirstResultFromLoaders 4224 }; 4225 4226 function getObjectOwnProperty(obj, propName) { 4227 return Object.prototype.hasOwnProperty.call(obj, propName) ? obj[propName] : undefined; 4228 } 4229 4230 function loadComponentAndNotify(componentName, callback) { 4231 var subscribable = getObjectOwnProperty(loadingSubscribablesCache, componentName), 4232 completedAsync; 4233 if (!subscribable) { 4234 // It's not started loading yet. Start loading, and when it's done, move it to loadedDefinitionsCache. 4235 subscribable = loadingSubscribablesCache[componentName] = new ko.subscribable(); 4236 subscribable.subscribe(callback); 4237 4238 beginLoadingComponent(componentName, function (definition, config) { 4239 var isSynchronousComponent = !!(config && config['synchronous']); 4240 loadedDefinitionsCache[componentName] = { definition: definition, isSynchronousComponent: isSynchronousComponent }; 4241 delete loadingSubscribablesCache[componentName]; 4242 4243 // For API consistency, all loads complete asynchronously. However we want to avoid 4244 // adding an extra task schedule if it's unnecessary (i.e., the completion is already 4245 // async). 4246 // 4247 // You can bypass the 'always asynchronous' feature by putting the synchronous:true 4248 // flag on your component configuration when you register it. 4249 if (completedAsync || isSynchronousComponent) { 4250 // Note that notifySubscribers ignores any dependencies read within the callback. 4251 // See comment in loaderRegistryBehaviors.js for reasoning 4252 subscribable['notifySubscribers'](definition); 4253 } else { 4254 ko.tasks.schedule(function () { 4255 subscribable['notifySubscribers'](definition); 4256 }); 4257 } 4258 }); 4259 completedAsync = true; 4260 } else { 4261 subscribable.subscribe(callback); 4262 } 4263 } 4264 4265 function beginLoadingComponent(componentName, callback) { 4266 getFirstResultFromLoaders('getConfig', [componentName], function (config) { 4267 if (config) { 4268 // We have a config, so now load its definition 4269 getFirstResultFromLoaders('loadComponent', [componentName, config], function (definition) { 4270 callback(definition, config); 4271 }); 4272 } else { 4273 // The component has no config - it's unknown to all the loaders. 4274 // Note that this is not an error (e.g., a module loading error) - that would abort the 4275 // process and this callback would not run. For this callback to run, all loaders must 4276 // have confirmed they don't know about this component. 4277 callback(null, null); 4278 } 4279 }); 4280 } 4281 4282 function getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders) { 4283 // On the first call in the stack, start with the full set of loaders 4284 if (!candidateLoaders) { 4285 candidateLoaders = ko.components['loaders'].slice(0); // Use a copy, because we'll be mutating this array 4286 } 4287 4288 // Try the next candidate 4289 var currentCandidateLoader = candidateLoaders.shift(); 4290 if (currentCandidateLoader) { 4291 var methodInstance = currentCandidateLoader[methodName]; 4292 if (methodInstance) { 4293 var wasAborted = false, 4294 synchronousReturnValue = methodInstance.apply(currentCandidateLoader, argsExceptCallback.concat(function (result) { 4295 if (wasAborted) { 4296 callback(null); 4297 } else if (result !== null) { 4298 // This candidate returned a value. Use it. 4299 callback(result); 4300 } else { 4301 // Try the next candidate 4302 getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders); 4303 } 4304 })); 4305 4306 // Currently, loaders may not return anything synchronously. This leaves open the possibility 4307 // that we'll extend the API to support synchronous return values in the future. It won't be 4308 // a breaking change, because currently no loader is allowed to return anything except undefined. 4309 if (synchronousReturnValue !== undefined) { 4310 wasAborted = true; 4311 4312 // Method to suppress exceptions will remain undocumented. This is only to keep 4313 // KO's specs running tidily, since we can observe the loading got aborted without 4314 // having exceptions cluttering up the console too. 4315 if (!currentCandidateLoader['suppressLoaderExceptions']) { 4316 throw new Error('Component loaders must supply values by invoking the callback, not by returning values synchronously.'); 4317 } 4318 } 4319 } else { 4320 // This candidate doesn't have the relevant handler. Synchronously move on to the next one. 4321 getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders); 4322 } 4323 } else { 4324 // No candidates returned a value 4325 callback(null); 4326 } 4327 } 4328 4329 // Reference the loaders via string name so it's possible for developers 4330 // to replace the whole array by assigning to ko.components.loaders 4331 ko.components['loaders'] = []; 4332 4333 ko.exportSymbol('components', ko.components); 4334 ko.exportSymbol('components.get', ko.components.get); 4335 ko.exportSymbol('components.clearCachedDefinition', ko.components.clearCachedDefinition); 4336 })(); 4337 (function (undefined) { 4338 4339 // The default loader is responsible for two things: 4340 // 1. Maintaining the default in-memory registry of component configuration objects 4341 // (i.e., the thing you're writing to when you call ko.components.register(someName, ...)) 4342 // 2. Answering requests for components by fetching configuration objects 4343 // from that default in-memory registry and resolving them into standard 4344 // component definition objects (of the form { createViewModel: ..., template: ... }) 4345 // Custom loaders may override either of these facilities, i.e., 4346 // 1. To supply configuration objects from some other source (e.g., conventions) 4347 // 2. Or, to resolve configuration objects by loading viewmodels/templates via arbitrary logic. 4348 4349 var defaultConfigRegistry = {}; 4350 4351 ko.components.register = function (componentName, config) { 4352 if (!config) { 4353 throw new Error('Invalid configuration for ' + componentName); 4354 } 4355 4356 if (ko.components.isRegistered(componentName)) { 4357 throw new Error('Component ' + componentName + ' is already registered'); 4358 } 4359 4360 defaultConfigRegistry[componentName] = config; 4361 }; 4362 4363 ko.components.isRegistered = function (componentName) { 4364 return Object.prototype.hasOwnProperty.call(defaultConfigRegistry, componentName); 4365 }; 4366 4367 ko.components.unregister = function (componentName) { 4368 delete defaultConfigRegistry[componentName]; 4369 ko.components.clearCachedDefinition(componentName); 4370 }; 4371 4372 ko.components.defaultLoader = { 4373 'getConfig': function (componentName, callback) { 4374 var result = ko.components.isRegistered(componentName) 4375 ? defaultConfigRegistry[componentName] 4376 : null; 4377 callback(result); 4378 }, 4379 4380 'loadComponent': function (componentName, config, callback) { 4381 var errorCallback = makeErrorCallback(componentName); 4382 possiblyGetConfigFromAmd(errorCallback, config, function (loadedConfig) { 4383 resolveConfig(componentName, errorCallback, loadedConfig, callback); 4384 }); 4385 }, 4386 4387 'loadTemplate': function (componentName, templateConfig, callback) { 4388 resolveTemplate(makeErrorCallback(componentName), templateConfig, callback); 4389 }, 4390 4391 'loadViewModel': function (componentName, viewModelConfig, callback) { 4392 resolveViewModel(makeErrorCallback(componentName), viewModelConfig, callback); 4393 } 4394 }; 4395 4396 var createViewModelKey = 'createViewModel'; 4397 4398 // Takes a config object of the form { template: ..., viewModel: ... }, and asynchronously convert it 4399 // into the standard component definition format: 4400 // { template: <ArrayOfDomNodes>, createViewModel: function(params, componentInfo) { ... } }. 4401 // Since both template and viewModel may need to be resolved asynchronously, both tasks are performed 4402 // in parallel, and the results joined when both are ready. We don't depend on any promises infrastructure, 4403 // so this is implemented manually below. 4404 function resolveConfig(componentName, errorCallback, config, callback) { 4405 var result = {}, 4406 makeCallBackWhenZero = 2, 4407 tryIssueCallback = function () { 4408 if (--makeCallBackWhenZero === 0) { 4409 callback(result); 4410 } 4411 }, 4412 templateConfig = config['template'], 4413 viewModelConfig = config['viewModel']; 4414 4415 if (templateConfig) { 4416 possiblyGetConfigFromAmd(errorCallback, templateConfig, function (loadedConfig) { 4417 ko.components._getFirstResultFromLoaders('loadTemplate', [componentName, loadedConfig], function (resolvedTemplate) { 4418 result['template'] = resolvedTemplate; 4419 tryIssueCallback(); 4420 }); 4421 }); 4422 } else { 4423 tryIssueCallback(); 4424 } 4425 4426 if (viewModelConfig) { 4427 possiblyGetConfigFromAmd(errorCallback, viewModelConfig, function (loadedConfig) { 4428 ko.components._getFirstResultFromLoaders('loadViewModel', [componentName, loadedConfig], function (resolvedViewModel) { 4429 result[createViewModelKey] = resolvedViewModel; 4430 tryIssueCallback(); 4431 }); 4432 }); 4433 } else { 4434 tryIssueCallback(); 4435 } 4436 } 4437 4438 function resolveTemplate(errorCallback, templateConfig, callback) { 4439 if (typeof templateConfig === 'string') { 4440 // Markup - parse it 4441 callback(ko.utils.parseHtmlFragment(templateConfig)); 4442 } else if (templateConfig instanceof Array) { 4443 // Assume already an array of DOM nodes - pass through unchanged 4444 callback(templateConfig); 4445 } else if (isDocumentFragment(templateConfig)) { 4446 // Document fragment - use its child nodes 4447 callback(ko.utils.makeArray(templateConfig.childNodes)); 4448 } else if (templateConfig['element']) { 4449 var element = templateConfig['element']; 4450 if (isDomElement(element)) { 4451 // Element instance - copy its child nodes 4452 callback(cloneNodesFromTemplateSourceElement(element)); 4453 } else if (typeof element === 'string') { 4454 // Element ID - find it, then copy its child nodes 4455 var elemInstance = document.getElementById(element); 4456 if (elemInstance) { 4457 callback(cloneNodesFromTemplateSourceElement(elemInstance)); 4458 } else { 4459 errorCallback('Cannot find element with ID ' + element); 4460 } 4461 } else { 4462 errorCallback('Unknown element type: ' + element); 4463 } 4464 } else { 4465 errorCallback('Unknown template value: ' + templateConfig); 4466 } 4467 } 4468 4469 function resolveViewModel(errorCallback, viewModelConfig, callback) { 4470 if (typeof viewModelConfig === 'function') { 4471 // Constructor - convert to standard factory function format 4472 // By design, this does *not* supply componentInfo to the constructor, as the intent is that 4473 // componentInfo contains non-viewmodel data (e.g., the component's element) that should only 4474 // be used in factory functions, not viewmodel constructors. 4475 callback(function (params /*, componentInfo */) { 4476 return new viewModelConfig(params); 4477 }); 4478 } else if (typeof viewModelConfig[createViewModelKey] === 'function') { 4479 // Already a factory function - use it as-is 4480 callback(viewModelConfig[createViewModelKey]); 4481 } else if ('instance' in viewModelConfig) { 4482 // Fixed object instance - promote to createViewModel format for API consistency 4483 var fixedInstance = viewModelConfig['instance']; 4484 callback(function (params, componentInfo) { 4485 return fixedInstance; 4486 }); 4487 } else if ('viewModel' in viewModelConfig) { 4488 // Resolved AMD module whose value is of the form { viewModel: ... } 4489 resolveViewModel(errorCallback, viewModelConfig['viewModel'], callback); 4490 } else { 4491 errorCallback('Unknown viewModel value: ' + viewModelConfig); 4492 } 4493 } 4494 4495 function cloneNodesFromTemplateSourceElement(elemInstance) { 4496 switch (ko.utils.tagNameLower(elemInstance)) { 4497 case 'script': 4498 return ko.utils.parseHtmlFragment(elemInstance.text); 4499 case 'textarea': 4500 return ko.utils.parseHtmlFragment(elemInstance.value); 4501 case 'template': 4502 // For browsers with proper <template> element support (i.e., where the .content property 4503 // gives a document fragment), use that document fragment. 4504 if (isDocumentFragment(elemInstance.content)) { 4505 return ko.utils.cloneNodes(elemInstance.content.childNodes); 4506 } 4507 } 4508 4509 // Regular elements such as <div>, and <template> elements on old browsers that don't really 4510 // understand <template> and just treat it as a regular container 4511 return ko.utils.cloneNodes(elemInstance.childNodes); 4512 } 4513 4514 function isDomElement(obj) { 4515 if (window['HTMLElement']) { 4516 return obj instanceof HTMLElement; 4517 } else { 4518 return obj && obj.tagName && obj.nodeType === 1; 4519 } 4520 } 4521 4522 function isDocumentFragment(obj) { 4523 if (window['DocumentFragment']) { 4524 return obj instanceof DocumentFragment; 4525 } else { 4526 return obj && obj.nodeType === 11; 4527 } 4528 } 4529 4530 function possiblyGetConfigFromAmd(errorCallback, config, callback) { 4531 if (typeof config['require'] === 'string') { 4532 // The config is the value of an AMD module 4533 if (amdRequire || window['require']) { 4534 (amdRequire || window['require'])([config['require']], function (module) { 4535 if (module && typeof module === 'object' && module.__esModule && module.default) { 4536 module = module.default; 4537 } 4538 callback(module); 4539 }); 4540 } else { 4541 errorCallback('Uses require, but no AMD loader is present'); 4542 } 4543 } else { 4544 callback(config); 4545 } 4546 } 4547 4548 function makeErrorCallback(componentName) { 4549 return function (message) { 4550 throw new Error('Component \'' + componentName + '\': ' + message); 4551 }; 4552 } 4553 4554 ko.exportSymbol('components.register', ko.components.register); 4555 ko.exportSymbol('components.isRegistered', ko.components.isRegistered); 4556 ko.exportSymbol('components.unregister', ko.components.unregister); 4557 4558 // Expose the default loader so that developers can directly ask it for configuration 4559 // or to resolve configuration 4560 ko.exportSymbol('components.defaultLoader', ko.components.defaultLoader); 4561 4562 // By default, the default loader is the only registered component loader 4563 ko.components['loaders'].push(ko.components.defaultLoader); 4564 4565 // Privately expose the underlying config registry for use in old-IE shim 4566 ko.components._allRegisteredComponents = defaultConfigRegistry; 4567 })(); 4568 (function (undefined) { 4569 // Overridable API for determining which component name applies to a given node. By overriding this, 4570 // you can for example map specific tagNames to components that are not preregistered. 4571 ko.components['getComponentNameForNode'] = function (node) { 4572 var tagNameLower = ko.utils.tagNameLower(node); 4573 if (ko.components.isRegistered(tagNameLower)) { 4574 // Try to determine that this node can be considered a *custom* element; see https://github.com/knockout/knockout/issues/1603 4575 if (tagNameLower.indexOf('-') != -1 || ('' + node) == "[object HTMLUnknownElement]" || (ko.utils.ieVersion <= 8 && node.tagName === tagNameLower)) { 4576 return tagNameLower; 4577 } 4578 } 4579 }; 4580 4581 ko.components.addBindingsForCustomElement = function (allBindings, node, bindingContext, valueAccessors) { 4582 // Determine if it's really a custom element matching a component 4583 if (node.nodeType === 1) { 4584 var componentName = ko.components['getComponentNameForNode'](node); 4585 if (componentName) { 4586 // It does represent a component, so add a component binding for it 4587 allBindings = allBindings || {}; 4588 4589 if (allBindings['component']) { 4590 // Avoid silently overwriting some other 'component' binding that may already be on the element 4591 throw new Error('Cannot use the "component" binding on a custom element matching a component'); 4592 } 4593 4594 var componentBindingValue = { 'name': componentName, 'params': getComponentParamsFromCustomElement(node, bindingContext) }; 4595 4596 allBindings['component'] = valueAccessors 4597 ? function () { return componentBindingValue; } 4598 : componentBindingValue; 4599 } 4600 } 4601 4602 return allBindings; 4603 } 4604 4605 var nativeBindingProviderInstance = new ko.bindingProvider(); 4606 4607 function getComponentParamsFromCustomElement(elem, bindingContext) { 4608 var paramsAttribute = elem.getAttribute('params'); 4609 4610 if (paramsAttribute) { 4611 var params = nativeBindingProviderInstance['parseBindingsString'](paramsAttribute, bindingContext, elem, { 'valueAccessors': true, 'bindingParams': true }), 4612 rawParamComputedValues = ko.utils.objectMap(params, function (paramValue, paramName) { 4613 return ko.computed(paramValue, null, { disposeWhenNodeIsRemoved: elem }); 4614 }), 4615 result = ko.utils.objectMap(rawParamComputedValues, function (paramValueComputed, paramName) { 4616 var paramValue = paramValueComputed.peek(); 4617 // Does the evaluation of the parameter value unwrap any observables? 4618 if (!paramValueComputed.isActive()) { 4619 // No it doesn't, so there's no need for any computed wrapper. Just pass through the supplied value directly. 4620 // Example: "someVal: firstName, age: 123" (whether or not firstName is an observable/computed) 4621 return paramValue; 4622 } else { 4623 // Yes it does. Supply a computed property that unwraps both the outer (binding expression) 4624 // level of observability, and any inner (resulting model value) level of observability. 4625 // This means the component doesn't have to worry about multiple unwrapping. If the value is a 4626 // writable observable, the computed will also be writable and pass the value on to the observable. 4627 return ko.computed({ 4628 'read': function () { 4629 return ko.utils.unwrapObservable(paramValueComputed()); 4630 }, 4631 'write': ko.isWriteableObservable(paramValue) && function (value) { 4632 paramValueComputed()(value); 4633 }, 4634 disposeWhenNodeIsRemoved: elem 4635 }); 4636 } 4637 }); 4638 4639 // Give access to the raw computeds, as long as that wouldn't overwrite any custom param also called '$raw' 4640 // This is in case the developer wants to react to outer (binding) observability separately from inner 4641 // (model value) observability, or in case the model value observable has subobservables. 4642 if (!Object.prototype.hasOwnProperty.call(result, '$raw')) { 4643 result['$raw'] = rawParamComputedValues; 4644 } 4645 4646 return result; 4647 } else { 4648 // For consistency, absence of a "params" attribute is treated the same as the presence of 4649 // any empty one. Otherwise component viewmodels need special code to check whether or not 4650 // 'params' or 'params.$raw' is null/undefined before reading subproperties, which is annoying. 4651 return { '$raw': {} }; 4652 } 4653 } 4654 4655 // -------------------------------------------------------------------------------- 4656 // Compatibility code for older (pre-HTML5) IE browsers 4657 4658 if (ko.utils.ieVersion < 9) { 4659 // Whenever you preregister a component, enable it as a custom element in the current document 4660 ko.components['register'] = (function (originalFunction) { 4661 return function (componentName) { 4662 document.createElement(componentName); // Allows IE<9 to parse markup containing the custom element 4663 return originalFunction.apply(this, arguments); 4664 } 4665 })(ko.components['register']); 4666 4667 // Whenever you create a document fragment, enable all preregistered component names as custom elements 4668 // This is needed to make innerShiv/jQuery HTML parsing correctly handle the custom elements 4669 document.createDocumentFragment = (function (originalFunction) { 4670 return function () { 4671 var newDocFrag = originalFunction(), 4672 allComponents = ko.components._allRegisteredComponents; 4673 for (var componentName in allComponents) { 4674 if (Object.prototype.hasOwnProperty.call(allComponents, componentName)) { 4675 newDocFrag.createElement(componentName); 4676 } 4677 } 4678 return newDocFrag; 4679 }; 4680 })(document.createDocumentFragment); 4681 } 4682 })(); (function (undefined) { 4683 var componentLoadingOperationUniqueId = 0; 4684 4685 ko.bindingHandlers['component'] = { 4686 'init': function (element, valueAccessor, ignored1, ignored2, bindingContext) { 4687 var currentViewModel, 4688 currentLoadingOperationId, 4689 afterRenderSub, 4690 disposeAssociatedComponentViewModel = function () { 4691 var currentViewModelDispose = currentViewModel && currentViewModel['dispose']; 4692 if (typeof currentViewModelDispose === 'function') { 4693 currentViewModelDispose.call(currentViewModel); 4694 } 4695 if (afterRenderSub) { 4696 afterRenderSub.dispose(); 4697 } 4698 afterRenderSub = null; 4699 currentViewModel = null; 4700 // Any in-flight loading operation is no longer relevant, so make sure we ignore its completion 4701 currentLoadingOperationId = null; 4702 }, 4703 originalChildNodes = ko.utils.makeArray(ko.virtualElements.childNodes(element)); 4704 4705 ko.virtualElements.emptyNode(element); 4706 ko.utils.domNodeDisposal.addDisposeCallback(element, disposeAssociatedComponentViewModel); 4707 4708 ko.computed(function () { 4709 var value = ko.utils.unwrapObservable(valueAccessor()), 4710 componentName, componentParams; 4711 4712 if (typeof value === 'string') { 4713 componentName = value; 4714 } else { 4715 componentName = ko.utils.unwrapObservable(value['name']); 4716 componentParams = ko.utils.unwrapObservable(value['params']); 4717 } 4718 4719 if (!componentName) { 4720 throw new Error('No component name specified'); 4721 } 4722 4723 var asyncContext = ko.bindingEvent.startPossiblyAsyncContentBinding(element, bindingContext); 4724 4725 var loadingOperationId = currentLoadingOperationId = ++componentLoadingOperationUniqueId; 4726 ko.components.get(componentName, function (componentDefinition) { 4727 // If this is not the current load operation for this element, ignore it. 4728 if (currentLoadingOperationId !== loadingOperationId) { 4729 return; 4730 } 4731 4732 // Clean up previous state 4733 disposeAssociatedComponentViewModel(); 4734 4735 // Instantiate and bind new component. Implicitly this cleans any old DOM nodes. 4736 if (!componentDefinition) { 4737 throw new Error('Unknown component \'' + componentName + '\''); 4738 } 4739 cloneTemplateIntoElement(componentName, componentDefinition, element); 4740 4741 var componentInfo = { 4742 'element': element, 4743 'templateNodes': originalChildNodes 4744 }; 4745 4746 var componentViewModel = createViewModel(componentDefinition, componentParams, componentInfo), 4747 childBindingContext = asyncContext['createChildContext'](componentViewModel, { 4748 'extend': function (ctx) { 4749 ctx['$component'] = componentViewModel; 4750 ctx['$componentTemplateNodes'] = originalChildNodes; 4751 } 4752 }); 4753 4754 if (componentViewModel && componentViewModel['koDescendantsComplete']) { 4755 afterRenderSub = ko.bindingEvent.subscribe(element, ko.bindingEvent.descendantsComplete, componentViewModel['koDescendantsComplete'], componentViewModel); 4756 } 4757 4758 currentViewModel = componentViewModel; 4759 ko.applyBindingsToDescendants(childBindingContext, element); 4760 }); 4761 }, null, { disposeWhenNodeIsRemoved: element }); 4762 4763 return { 'controlsDescendantBindings': true }; 4764 } 4765 }; 4766 4767 ko.virtualElements.allowedBindings['component'] = true; 4768 4769 function cloneTemplateIntoElement(componentName, componentDefinition, element) { 4770 var template = componentDefinition['template']; 4771 if (!template) { 4772 throw new Error('Component \'' + componentName + '\' has no template'); 4773 } 4774 4775 var clonedNodesArray = ko.utils.cloneNodes(template); 4776 ko.virtualElements.setDomNodeChildren(element, clonedNodesArray); 4777 } 4778 4779 function createViewModel(componentDefinition, componentParams, componentInfo) { 4780 var componentViewModelFactory = componentDefinition['createViewModel']; 4781 return componentViewModelFactory 4782 ? componentViewModelFactory.call(componentDefinition, componentParams, componentInfo) 4783 : componentParams; // Template-only component 4784 } 4785 4786 })(); 4787 var attrHtmlToJavaScriptMap = { 'class': 'className', 'for': 'htmlFor' }; 4788 ko.bindingHandlers['attr'] = { 4789 'update': function (element, valueAccessor, allBindings) { 4790 var value = ko.utils.unwrapObservable(valueAccessor()) || {}; 4791 ko.utils.objectForEach(value, function (attrName, attrValue) { 4792 attrValue = ko.utils.unwrapObservable(attrValue); 4793 4794 // Find the namespace of this attribute, if any. 4795 var prefixLen = attrName.indexOf(':'); 4796 var namespace = "lookupNamespaceURI" in element && prefixLen > 0 && element.lookupNamespaceURI(attrName.substr(0, prefixLen)); 4797 4798 // To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely 4799 // when someProp is a "no value"-like value (strictly null, false, or undefined) 4800 // (because the absence of the "checked" attr is how to mark an element as not checked, etc.) 4801 var toRemove = (attrValue === false) || (attrValue === null) || (attrValue === undefined); 4802 if (toRemove) { 4803 namespace ? element.removeAttributeNS(namespace, attrName) : element.removeAttribute(attrName); 4804 } else { 4805 attrValue = attrValue.toString(); 4806 } 4807 4808 // In IE <= 7 and IE8 Quirks Mode, you have to use the JavaScript property name instead of the 4809 // HTML attribute name for certain attributes. IE8 Standards Mode supports the correct behavior, 4810 // but instead of figuring out the mode, we'll just set the attribute through the JavaScript 4811 // property for IE <= 8. 4812 if (ko.utils.ieVersion <= 8 && attrName in attrHtmlToJavaScriptMap) { 4813 attrName = attrHtmlToJavaScriptMap[attrName]; 4814 if (toRemove) 4815 element.removeAttribute(attrName); 4816 else 4817 element[attrName] = attrValue; 4818 } else if (!toRemove) { 4819 namespace ? element.setAttributeNS(namespace, attrName, attrValue) : element.setAttribute(attrName, attrValue); 4820 } 4821 4822 // Treat "name" specially - although you can think of it as an attribute, it also needs 4823 // special handling on older versions of IE (https://github.com/SteveSanderson/knockout/pull/333) 4824 // Deliberately being case-sensitive here because XHTML would regard "Name" as a different thing 4825 // entirely, and there's no strong reason to allow for such casing in HTML. 4826 if (attrName === "name") { 4827 ko.utils.setElementName(element, toRemove ? "" : attrValue); 4828 } 4829 }); 4830 } 4831 }; 4832 (function () { 4833 4834 ko.bindingHandlers['checked'] = { 4835 'after': ['value', 'attr'], 4836 'init': function (element, valueAccessor, allBindings) { 4837 var checkedValue = ko.pureComputed(function () { 4838 // Treat "value" like "checkedValue" when it is included with "checked" binding 4839 if (allBindings['has']('checkedValue')) { 4840 return ko.utils.unwrapObservable(allBindings.get('checkedValue')); 4841 } else if (useElementValue) { 4842 if (allBindings['has']('value')) { 4843 return ko.utils.unwrapObservable(allBindings.get('value')); 4844 } else { 4845 return element.value; 4846 } 4847 } 4848 }); 4849 4850 function updateModel() { 4851 // This updates the model value from the view value. 4852 // It runs in response to DOM events (click) and changes in checkedValue. 4853 var isChecked = element.checked, 4854 elemValue = checkedValue(); 4855 4856 // When we're first setting up this computed, don't change any model state. 4857 if (ko.computedContext.isInitial()) { 4858 return; 4859 } 4860 4861 // We can ignore unchecked radio buttons, because some other radio 4862 // button will be checked, and that one can take care of updating state. 4863 // Also ignore value changes to an already unchecked checkbox. 4864 if (!isChecked && (isRadio || ko.computedContext.getDependenciesCount())) { 4865 return; 4866 } 4867 4868 var modelValue = ko.dependencyDetection.ignore(valueAccessor); 4869 if (valueIsArray) { 4870 var writableValue = rawValueIsNonArrayObservable ? modelValue.peek() : modelValue, 4871 saveOldValue = oldElemValue; 4872 oldElemValue = elemValue; 4873 4874 if (saveOldValue !== elemValue) { 4875 // When we're responding to the checkedValue changing, and the element is 4876 // currently checked, replace the old elem value with the new elem value 4877 // in the model array. 4878 if (isChecked) { 4879 ko.utils.addOrRemoveItem(writableValue, elemValue, true); 4880 ko.utils.addOrRemoveItem(writableValue, saveOldValue, false); 4881 } 4882 } else { 4883 // When we're responding to the user having checked/unchecked a checkbox, 4884 // add/remove the element value to the model array. 4885 ko.utils.addOrRemoveItem(writableValue, elemValue, isChecked); 4886 } 4887 4888 if (rawValueIsNonArrayObservable && ko.isWriteableObservable(modelValue)) { 4889 modelValue(writableValue); 4890 } 4891 } else { 4892 if (isCheckbox) { 4893 if (elemValue === undefined) { 4894 elemValue = isChecked; 4895 } else if (!isChecked) { 4896 elemValue = undefined; 4897 } 4898 } 4899 ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'checked', elemValue, true); 4900 } 4901 }; 4902 4903 function updateView() { 4904 // This updates the view value from the model value. 4905 // It runs in response to changes in the bound (checked) value. 4906 var modelValue = ko.utils.unwrapObservable(valueAccessor()), 4907 elemValue = checkedValue(); 4908 4909 if (valueIsArray) { 4910 // When a checkbox is bound to an array, being checked represents its value being present in that array 4911 element.checked = ko.utils.arrayIndexOf(modelValue, elemValue) >= 0; 4912 oldElemValue = elemValue; 4913 } else if (isCheckbox && elemValue === undefined) { 4914 // When a checkbox is bound to any other value (not an array) and "checkedValue" is not defined, 4915 // being checked represents the value being trueish 4916 element.checked = !!modelValue; 4917 } else { 4918 // Otherwise, being checked means that the checkbox or radio button's value corresponds to the model value 4919 element.checked = (checkedValue() === modelValue); 4920 } 4921 }; 4922 4923 var isCheckbox = element.type == "checkbox", 4924 isRadio = element.type == "radio"; 4925 4926 // Only bind to check boxes and radio buttons 4927 if (!isCheckbox && !isRadio) { 4928 return; 4929 } 4930 4931 var rawValue = valueAccessor(), 4932 valueIsArray = isCheckbox && (ko.utils.unwrapObservable(rawValue) instanceof Array), 4933 rawValueIsNonArrayObservable = !(valueIsArray && rawValue.push && rawValue.splice), 4934 useElementValue = isRadio || valueIsArray, 4935 oldElemValue = valueIsArray ? checkedValue() : undefined; 4936 4937 // IE 6 won't allow radio buttons to be selected unless they have a name 4938 if (isRadio && !element.name) 4939 ko.bindingHandlers['uniqueName']['init'](element, function () { return true }); 4940 4941 // Set up two computeds to update the binding: 4942 4943 // The first responds to changes in the checkedValue value and to element clicks 4944 ko.computed(updateModel, null, { disposeWhenNodeIsRemoved: element }); 4945 ko.utils.registerEventHandler(element, "click", updateModel); 4946 4947 // The second responds to changes in the model value (the one associated with the checked binding) 4948 ko.computed(updateView, null, { disposeWhenNodeIsRemoved: element }); 4949 4950 rawValue = undefined; 4951 } 4952 }; 4953 ko.expressionRewriting.twoWayBindings['checked'] = true; 4954 4955 ko.bindingHandlers['checkedValue'] = { 4956 'update': function (element, valueAccessor) { 4957 element.value = ko.utils.unwrapObservable(valueAccessor()); 4958 } 4959 }; 4960 4961 })(); var classesWrittenByBindingKey = '__ko__cssValue'; 4962 ko.bindingHandlers['class'] = { 4963 'update': function (element, valueAccessor) { 4964 var value = ko.utils.stringTrim(ko.utils.unwrapObservable(valueAccessor())); 4965 ko.utils.toggleDomNodeCssClass(element, element[classesWrittenByBindingKey], false); 4966 element[classesWrittenByBindingKey] = value; 4967 ko.utils.toggleDomNodeCssClass(element, value, true); 4968 } 4969 }; 4970 4971 ko.bindingHandlers['css'] = { 4972 'update': function (element, valueAccessor) { 4973 var value = ko.utils.unwrapObservable(valueAccessor()); 4974 if (value !== null && typeof value == "object") { 4975 ko.utils.objectForEach(value, function (className, shouldHaveClass) { 4976 shouldHaveClass = ko.utils.unwrapObservable(shouldHaveClass); 4977 ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass); 4978 }); 4979 } else { 4980 ko.bindingHandlers['class']['update'](element, valueAccessor); 4981 } 4982 } 4983 }; 4984 ko.bindingHandlers['enable'] = { 4985 'update': function (element, valueAccessor) { 4986 var value = ko.utils.unwrapObservable(valueAccessor()); 4987 if (value && element.disabled) 4988 element.removeAttribute("disabled"); 4989 else if ((!value) && (!element.disabled)) 4990 element.disabled = true; 4991 } 4992 }; 4993 4994 ko.bindingHandlers['disable'] = { 4995 'update': function (element, valueAccessor) { 4996 ko.bindingHandlers['enable']['update'](element, function () { return !ko.utils.unwrapObservable(valueAccessor()) }); 4997 } 4998 }; 4999 // For certain common events (currently just 'click'), allow a simplified data-binding syntax 5000 // e.g. click:handler instead of the usual full-length event:{click:handler} 5001 function makeEventHandlerShortcut(eventName) { 5002 ko.bindingHandlers[eventName] = { 5003 'init': function (element, valueAccessor, allBindings, viewModel, bindingContext) { 5004 var newValueAccessor = function () { 5005 var result = {}; 5006 result[eventName] = valueAccessor(); 5007 return result; 5008 }; 5009 return ko.bindingHandlers['event']['init'].call(this, element, newValueAccessor, allBindings, viewModel, bindingContext); 5010 } 5011 } 5012 } 5013 5014 ko.bindingHandlers['event'] = { 5015 'init': function (element, valueAccessor, allBindings, viewModel, bindingContext) { 5016 var eventsToHandle = valueAccessor() || {}; 5017 ko.utils.objectForEach(eventsToHandle, function (eventName) { 5018 if (typeof eventName == "string") { 5019 ko.utils.registerEventHandler(element, eventName, function (event) { 5020 var handlerReturnValue; 5021 var handlerFunction = valueAccessor()[eventName]; 5022 if (!handlerFunction) 5023 return; 5024 5025 try { 5026 // Take all the event args, and prefix with the viewmodel 5027 var argsForHandler = ko.utils.makeArray(arguments); 5028 viewModel = bindingContext['$data']; 5029 argsForHandler.unshift(viewModel); 5030 handlerReturnValue = handlerFunction.apply(viewModel, argsForHandler); 5031 } finally { 5032 if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true. 5033 if (event.preventDefault) 5034 event.preventDefault(); 5035 else 5036 event.returnValue = false; 5037 } 5038 } 5039 5040 var bubble = allBindings.get(eventName + 'Bubble') !== false; 5041 if (!bubble) { 5042 event.cancelBubble = true; 5043 if (event.stopPropagation) 5044 event.stopPropagation(); 5045 } 5046 }); 5047 } 5048 }); 5049 } 5050 }; 5051 // "foreach: someExpression" is equivalent to "template: { foreach: someExpression }" 5052 // "foreach: { data: someExpression, afterAdd: myfn }" is equivalent to "template: { foreach: someExpression, afterAdd: myfn }" 5053 ko.bindingHandlers['foreach'] = { 5054 makeTemplateValueAccessor: function (valueAccessor) { 5055 return function () { 5056 var modelValue = valueAccessor(), 5057 unwrappedValue = ko.utils.peekObservable(modelValue); // Unwrap without setting a dependency here 5058 5059 // If unwrappedValue is the array, pass in the wrapped value on its own 5060 // The value will be unwrapped and tracked within the template binding 5061 // (See https://github.com/SteveSanderson/knockout/issues/523) 5062 if ((!unwrappedValue) || typeof unwrappedValue.length == "number") 5063 return { 'foreach': modelValue, 'templateEngine': ko.nativeTemplateEngine.instance }; 5064 5065 // If unwrappedValue.data is the array, preserve all relevant options and unwrap again value so we get updates 5066 ko.utils.unwrapObservable(modelValue); 5067 return { 5068 'foreach': unwrappedValue['data'], 5069 'as': unwrappedValue['as'], 5070 'noChildContext': unwrappedValue['noChildContext'], 5071 'includeDestroyed': unwrappedValue['includeDestroyed'], 5072 'afterAdd': unwrappedValue['afterAdd'], 5073 'beforeRemove': unwrappedValue['beforeRemove'], 5074 'afterRender': unwrappedValue['afterRender'], 5075 'beforeMove': unwrappedValue['beforeMove'], 5076 'afterMove': unwrappedValue['afterMove'], 5077 'templateEngine': ko.nativeTemplateEngine.instance 5078 }; 5079 }; 5080 }, 5081 'init': function (element, valueAccessor, allBindings, viewModel, bindingContext) { 5082 return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor)); 5083 }, 5084 'update': function (element, valueAccessor, allBindings, viewModel, bindingContext) { 5085 return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor), allBindings, viewModel, bindingContext); 5086 } 5087 }; 5088 ko.expressionRewriting.bindingRewriteValidators['foreach'] = false; // Can't rewrite control flow bindings 5089 ko.virtualElements.allowedBindings['foreach'] = true; 5090 var hasfocusUpdatingProperty = '__ko_hasfocusUpdating'; 5091 var hasfocusLastValue = '__ko_hasfocusLastValue'; 5092 ko.bindingHandlers['hasfocus'] = { 5093 'init': function (element, valueAccessor, allBindings) { 5094 var handleElementFocusChange = function (isFocused) { 5095 // Where possible, ignore which event was raised and determine focus state using activeElement, 5096 // as this avoids phantom focus/blur events raised when changing tabs in modern browsers. 5097 // However, not all KO-targeted browsers (Firefox 2) support activeElement. For those browsers, 5098 // prevent a loss of focus when changing tabs/windows by setting a flag that prevents hasfocus 5099 // from calling 'blur()' on the element when it loses focus. 5100 // Discussion at https://github.com/SteveSanderson/knockout/pull/352 5101 element[hasfocusUpdatingProperty] = true; 5102 var ownerDoc = element.ownerDocument; 5103 if ("activeElement" in ownerDoc) { 5104 var active; 5105 try { 5106 active = ownerDoc.activeElement; 5107 } catch (e) { 5108 // IE9 throws if you access activeElement during page load (see issue #703) 5109 active = ownerDoc.body; 5110 } 5111 isFocused = (active === element); 5112 } 5113 var modelValue = valueAccessor(); 5114 ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'hasfocus', isFocused, true); 5115 5116 //cache the latest value, so we can avoid unnecessarily calling focus/blur in the update function 5117 element[hasfocusLastValue] = isFocused; 5118 element[hasfocusUpdatingProperty] = false; 5119 }; 5120 var handleElementFocusIn = handleElementFocusChange.bind(null, true); 5121 var handleElementFocusOut = handleElementFocusChange.bind(null, false); 5122 5123 ko.utils.registerEventHandler(element, "focus", handleElementFocusIn); 5124 ko.utils.registerEventHandler(element, "focusin", handleElementFocusIn); // For IE 5125 ko.utils.registerEventHandler(element, "blur", handleElementFocusOut); 5126 ko.utils.registerEventHandler(element, "focusout", handleElementFocusOut); // For IE 5127 5128 // Assume element is not focused (prevents "blur" being called initially) 5129 element[hasfocusLastValue] = false; 5130 }, 5131 'update': function (element, valueAccessor) { 5132 var value = !!ko.utils.unwrapObservable(valueAccessor()); 5133 5134 if (!element[hasfocusUpdatingProperty] && element[hasfocusLastValue] !== value) { 5135 value ? element.focus() : element.blur(); 5136 5137 // In IE, the blur method doesn't always cause the element to lose focus (for example, if the window is not in focus). 5138 // Setting focus to the body element does seem to be reliable in IE, but should only be used if we know that the current 5139 // element was focused already. 5140 if (!value && element[hasfocusLastValue]) { 5141 element.ownerDocument.body.focus(); 5142 } 5143 5144 // For IE, which doesn't reliably fire "focus" or "blur" events synchronously 5145 ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, value ? "focusin" : "focusout"]); 5146 } 5147 } 5148 }; 5149 ko.expressionRewriting.twoWayBindings['hasfocus'] = true; 5150 5151 ko.bindingHandlers['hasFocus'] = ko.bindingHandlers['hasfocus']; // Make "hasFocus" an alias 5152 ko.expressionRewriting.twoWayBindings['hasFocus'] = 'hasfocus'; 5153 ko.bindingHandlers['html'] = { 5154 'init': function () { 5155 // Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications) 5156 return { 'controlsDescendantBindings': true }; 5157 }, 5158 'update': function (element, valueAccessor) { 5159 // setHtml will unwrap the value if needed 5160 ko.utils.setHtml(element, valueAccessor()); 5161 } 5162 }; 5163 (function () { 5164 5165 // Makes a binding like with or if 5166 function makeWithIfBinding(bindingKey, isWith, isNot) { 5167 ko.bindingHandlers[bindingKey] = { 5168 'init': function (element, valueAccessor, allBindings, viewModel, bindingContext) { 5169 var didDisplayOnLastUpdate, savedNodes, contextOptions = {}, completeOnRender, needAsyncContext, renderOnEveryChange; 5170 5171 if (isWith) { 5172 var as = allBindings.get('as'), noChildContext = allBindings.get('noChildContext'); 5173 renderOnEveryChange = !(as && noChildContext); 5174 contextOptions = { 'as': as, 'noChildContext': noChildContext, 'exportDependencies': renderOnEveryChange }; 5175 } 5176 5177 completeOnRender = allBindings.get("completeOn") == "render"; 5178 needAsyncContext = completeOnRender || allBindings['has'](ko.bindingEvent.descendantsComplete); 5179 5180 ko.computed(function () { 5181 var value = ko.utils.unwrapObservable(valueAccessor()), 5182 shouldDisplay = !isNot !== !value, // equivalent to isNot ? !value : !!value, 5183 isInitial = !savedNodes, 5184 childContext; 5185 5186 if (!renderOnEveryChange && shouldDisplay === didDisplayOnLastUpdate) { 5187 return; 5188 } 5189 5190 if (needAsyncContext) { 5191 bindingContext = ko.bindingEvent.startPossiblyAsyncContentBinding(element, bindingContext); 5192 } 5193 5194 if (shouldDisplay) { 5195 if (!isWith || renderOnEveryChange) { 5196 contextOptions['dataDependency'] = ko.computedContext.computed(); 5197 } 5198 5199 if (isWith) { 5200 childContext = bindingContext['createChildContext'](typeof value == "function" ? value : valueAccessor, contextOptions); 5201 } else if (ko.computedContext.getDependenciesCount()) { 5202 childContext = bindingContext['extend'](null, contextOptions); 5203 } else { 5204 childContext = bindingContext; 5205 } 5206 } 5207 5208 // Save a copy of the inner nodes on the initial update, but only if we have dependencies. 5209 if (isInitial && ko.computedContext.getDependenciesCount()) { 5210 savedNodes = ko.utils.cloneNodes(ko.virtualElements.childNodes(element), true /* shouldCleanNodes */); 5211 } 5212 5213 if (shouldDisplay) { 5214 if (!isInitial) { 5215 ko.virtualElements.setDomNodeChildren(element, ko.utils.cloneNodes(savedNodes)); 5216 } 5217 5218 ko.applyBindingsToDescendants(childContext, element); 5219 } else { 5220 ko.virtualElements.emptyNode(element); 5221 5222 if (!completeOnRender) { 5223 ko.bindingEvent.notify(element, ko.bindingEvent.childrenComplete); 5224 } 5225 } 5226 5227 didDisplayOnLastUpdate = shouldDisplay; 5228 5229 }, null, { disposeWhenNodeIsRemoved: element }); 5230 5231 return { 'controlsDescendantBindings': true }; 5232 } 5233 }; 5234 ko.expressionRewriting.bindingRewriteValidators[bindingKey] = false; // Can't rewrite control flow bindings 5235 ko.virtualElements.allowedBindings[bindingKey] = true; 5236 } 5237 5238 // Construct the actual binding handlers 5239 makeWithIfBinding('if'); 5240 makeWithIfBinding('ifnot', false /* isWith */, true /* isNot */); 5241 makeWithIfBinding('with', true /* isWith */); 5242 5243 })(); ko.bindingHandlers['let'] = { 5244 'init': function (element, valueAccessor, allBindings, viewModel, bindingContext) { 5245 // Make a modified binding context, with extra properties, and apply it to descendant elements 5246 var innerContext = bindingContext['extend'](valueAccessor); 5247 ko.applyBindingsToDescendants(innerContext, element); 5248 5249 return { 'controlsDescendantBindings': true }; 5250 } 5251 }; 5252 ko.virtualElements.allowedBindings['let'] = true; 5253 var captionPlaceholder = {}; 5254 ko.bindingHandlers['options'] = { 5255 'init': function (element) { 5256 if (ko.utils.tagNameLower(element) !== "select") 5257 throw new Error("options binding applies only to SELECT elements"); 5258 5259 // Remove all existing <option>s. 5260 while (element.length > 0) { 5261 element.remove(0); 5262 } 5263 5264 // Ensures that the binding processor doesn't try to bind the options 5265 return { 'controlsDescendantBindings': true }; 5266 }, 5267 'update': function (element, valueAccessor, allBindings) { 5268 function selectedOptions() { 5269 return ko.utils.arrayFilter(element.options, function (node) { return node.selected; }); 5270 } 5271 5272 var selectWasPreviouslyEmpty = element.length == 0, 5273 multiple = element.multiple, 5274 previousScrollTop = (!selectWasPreviouslyEmpty && multiple) ? element.scrollTop : null, 5275 unwrappedArray = ko.utils.unwrapObservable(valueAccessor()), 5276 valueAllowUnset = allBindings.get('valueAllowUnset') && allBindings['has']('value'), 5277 includeDestroyed = allBindings.get('optionsIncludeDestroyed'), 5278 arrayToDomNodeChildrenOptions = {}, 5279 captionValue, 5280 filteredArray, 5281 previousSelectedValues = []; 5282 5283 if (!valueAllowUnset) { 5284 if (multiple) { 5285 previousSelectedValues = ko.utils.arrayMap(selectedOptions(), ko.selectExtensions.readValue); 5286 } else if (element.selectedIndex >= 0) { 5287 previousSelectedValues.push(ko.selectExtensions.readValue(element.options[element.selectedIndex])); 5288 } 5289 } 5290 5291 if (unwrappedArray) { 5292 if (typeof unwrappedArray.length == "undefined") // Coerce single value into array 5293 unwrappedArray = [unwrappedArray]; 5294 5295 // Filter out any entries marked as destroyed 5296 filteredArray = ko.utils.arrayFilter(unwrappedArray, function (item) { 5297 return includeDestroyed || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']); 5298 }); 5299 5300 // If caption is included, add it to the array 5301 if (allBindings['has']('optionsCaption')) { 5302 captionValue = ko.utils.unwrapObservable(allBindings.get('optionsCaption')); 5303 // If caption value is null or undefined, don't show a caption 5304 if (captionValue !== null && captionValue !== undefined) { 5305 filteredArray.unshift(captionPlaceholder); 5306 } 5307 } 5308 } else { 5309 // If a falsy value is provided (e.g. null), we'll simply empty the select element 5310 } 5311 5312 function applyToObject(object, predicate, defaultValue) { 5313 var predicateType = typeof predicate; 5314 if (predicateType == "function") // Given a function; run it against the data value 5315 return predicate(object); 5316 else if (predicateType == "string") // Given a string; treat it as a property name on the data value 5317 return object[predicate]; 5318 else // Given no optionsText arg; use the data value itself 5319 return defaultValue; 5320 } 5321 5322 // The following functions can run at two different times: 5323 // The first is when the whole array is being updated directly from this binding handler. 5324 // The second is when an observable value for a specific array entry is updated. 5325 // oldOptions will be empty in the first case, but will be filled with the previously generated option in the second. 5326 var itemUpdate = false; 5327 function optionForArrayItem(arrayEntry, index, oldOptions) { 5328 if (oldOptions.length) { 5329 previousSelectedValues = !valueAllowUnset && oldOptions[0].selected ? [ko.selectExtensions.readValue(oldOptions[0])] : []; 5330 itemUpdate = true; 5331 } 5332 var option = element.ownerDocument.createElement("option"); 5333 if (arrayEntry === captionPlaceholder) { 5334 ko.utils.setTextContent(option, allBindings.get('optionsCaption')); 5335 ko.selectExtensions.writeValue(option, undefined); 5336 } else { 5337 // Apply a value to the option element 5338 var optionValue = applyToObject(arrayEntry, allBindings.get('optionsValue'), arrayEntry); 5339 ko.selectExtensions.writeValue(option, ko.utils.unwrapObservable(optionValue)); 5340 5341 // Apply some text to the option element 5342 var optionText = applyToObject(arrayEntry, allBindings.get('optionsText'), optionValue); 5343 ko.utils.setTextContent(option, optionText); 5344 } 5345 return [option]; 5346 } 5347 5348 // By using a beforeRemove callback, we delay the removal until after new items are added. This fixes a selection 5349 // problem in IE<=8 and Firefox. See https://github.com/knockout/knockout/issues/1208 5350 arrayToDomNodeChildrenOptions['beforeRemove'] = 5351 function (option) { 5352 element.removeChild(option); 5353 }; 5354 5355 function setSelectionCallback(arrayEntry, newOptions) { 5356 if (itemUpdate && valueAllowUnset) { 5357 // The model value is authoritative, so make sure its value is the one selected 5358 ko.bindingEvent.notify(element, ko.bindingEvent.childrenComplete); 5359 } else if (previousSelectedValues.length) { 5360 // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document. 5361 // That's why we first added them without selection. Now it's time to set the selection. 5362 var isSelected = ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[0])) >= 0; 5363 ko.utils.setOptionNodeSelectionState(newOptions[0], isSelected); 5364 5365 // If this option was changed from being selected during a single-item update, notify the change 5366 if (itemUpdate && !isSelected) { 5367 ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]); 5368 } 5369 } 5370 } 5371 5372 var callback = setSelectionCallback; 5373 if (allBindings['has']('optionsAfterRender') && typeof allBindings.get('optionsAfterRender') == "function") { 5374 callback = function (arrayEntry, newOptions) { 5375 setSelectionCallback(arrayEntry, newOptions); 5376 ko.dependencyDetection.ignore(allBindings.get('optionsAfterRender'), null, [newOptions[0], arrayEntry !== captionPlaceholder ? arrayEntry : undefined]); 5377 } 5378 } 5379 5380 ko.utils.setDomNodeChildrenFromArrayMapping(element, filteredArray, optionForArrayItem, arrayToDomNodeChildrenOptions, callback); 5381 5382 if (!valueAllowUnset) { 5383 // Determine if the selection has changed as a result of updating the options list 5384 var selectionChanged; 5385 if (multiple) { 5386 // For a multiple-select box, compare the new selection count to the previous one 5387 // But if nothing was selected before, the selection can't have changed 5388 selectionChanged = previousSelectedValues.length && selectedOptions().length < previousSelectedValues.length; 5389 } else { 5390 // For a single-select box, compare the current value to the previous value 5391 // But if nothing was selected before or nothing is selected now, just look for a change in selection 5392 selectionChanged = (previousSelectedValues.length && element.selectedIndex >= 0) 5393 ? (ko.selectExtensions.readValue(element.options[element.selectedIndex]) !== previousSelectedValues[0]) 5394 : (previousSelectedValues.length || element.selectedIndex >= 0); 5395 } 5396 5397 // Ensure consistency between model value and selected option. 5398 // If the dropdown was changed so that selection is no longer the same, 5399 // notify the value or selectedOptions binding. 5400 if (selectionChanged) { 5401 ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]); 5402 } 5403 } 5404 5405 if (valueAllowUnset || ko.computedContext.isInitial()) { 5406 ko.bindingEvent.notify(element, ko.bindingEvent.childrenComplete); 5407 } 5408 5409 // Workaround for IE bug 5410 ko.utils.ensureSelectElementIsRenderedCorrectly(element); 5411 5412 if (previousScrollTop && Math.abs(previousScrollTop - element.scrollTop) > 20) 5413 element.scrollTop = previousScrollTop; 5414 } 5415 }; 5416 ko.bindingHandlers['options'].optionValueDomDataKey = ko.utils.domData.nextKey(); 5417 ko.bindingHandlers['selectedOptions'] = { 5418 'init': function (element, valueAccessor, allBindings) { 5419 function updateFromView() { 5420 var value = valueAccessor(), valueToWrite = []; 5421 ko.utils.arrayForEach(element.getElementsByTagName("option"), function (node) { 5422 if (node.selected) 5423 valueToWrite.push(ko.selectExtensions.readValue(node)); 5424 }); 5425 ko.expressionRewriting.writeValueToProperty(value, allBindings, 'selectedOptions', valueToWrite); 5426 } 5427 5428 function updateFromModel() { 5429 var newValue = ko.utils.unwrapObservable(valueAccessor()), 5430 previousScrollTop = element.scrollTop; 5431 5432 if (newValue && typeof newValue.length == "number") { 5433 ko.utils.arrayForEach(element.getElementsByTagName("option"), function (node) { 5434 var isSelected = ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0; 5435 if (node.selected != isSelected) { // This check prevents flashing of the select element in IE 5436 ko.utils.setOptionNodeSelectionState(node, isSelected); 5437 } 5438 }); 5439 } 5440 5441 element.scrollTop = previousScrollTop; 5442 } 5443 5444 if (ko.utils.tagNameLower(element) != "select") { 5445 throw new Error("selectedOptions binding applies only to SELECT elements"); 5446 } 5447 5448 var updateFromModelComputed; 5449 ko.bindingEvent.subscribe(element, ko.bindingEvent.childrenComplete, function () { 5450 if (!updateFromModelComputed) { 5451 ko.utils.registerEventHandler(element, "change", updateFromView); 5452 updateFromModelComputed = ko.computed(updateFromModel, null, { disposeWhenNodeIsRemoved: element }); 5453 } else { 5454 updateFromView(); 5455 } 5456 }, null, { 'notifyImmediately': true }); 5457 }, 5458 'update': function () { } // Keep for backwards compatibility with code that may have wrapped binding 5459 }; 5460 ko.expressionRewriting.twoWayBindings['selectedOptions'] = true; 5461 ko.bindingHandlers['style'] = { 5462 'update': function (element, valueAccessor) { 5463 var value = ko.utils.unwrapObservable(valueAccessor() || {}); 5464 ko.utils.objectForEach(value, function (styleName, styleValue) { 5465 styleValue = ko.utils.unwrapObservable(styleValue); 5466 5467 if (styleValue === null || styleValue === undefined || styleValue === false) { 5468 // Empty string removes the value, whereas null/undefined have no effect 5469 styleValue = ""; 5470 } 5471 5472 if (jQueryInstance) { 5473 jQueryInstance(element)['css'](styleName, styleValue); 5474 } else if (/^--/.test(styleName)) { 5475 // Is styleName a custom CSS property? 5476 element.style.setProperty(styleName, styleValue); 5477 } else { 5478 styleName = styleName.replace(/-(\w)/g, function (all, letter) { 5479 return letter.toUpperCase(); 5480 }); 5481 5482 var previousStyle = element.style[styleName]; 5483 element.style[styleName] = styleValue; 5484 5485 if (styleValue !== previousStyle && element.style[styleName] == previousStyle && !isNaN(styleValue)) { 5486 element.style[styleName] = styleValue + "px"; 5487 } 5488 } 5489 }); 5490 } 5491 }; 5492 ko.bindingHandlers['submit'] = { 5493 'init': function (element, valueAccessor, allBindings, viewModel, bindingContext) { 5494 if (typeof valueAccessor() != "function") 5495 throw new Error("The value for a submit binding must be a function"); 5496 ko.utils.registerEventHandler(element, "submit", function (event) { 5497 var handlerReturnValue; 5498 var value = valueAccessor(); 5499 try { handlerReturnValue = value.call(bindingContext['$data'], element); } 5500 finally { 5501 if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true. 5502 if (event.preventDefault) 5503 event.preventDefault(); 5504 else 5505 event.returnValue = false; 5506 } 5507 } 5508 }); 5509 } 5510 }; 5511 ko.bindingHandlers['text'] = { 5512 'init': function () { 5513 // Prevent binding on the dynamically-injected text node (as developers are unlikely to expect that, and it has security implications). 5514 // It should also make things faster, as we no longer have to consider whether the text node might be bindable. 5515 return { 'controlsDescendantBindings': true }; 5516 }, 5517 'update': function (element, valueAccessor) { 5518 ko.utils.setTextContent(element, valueAccessor()); 5519 } 5520 }; 5521 ko.virtualElements.allowedBindings['text'] = true; 5522 (function () { 5523 5524 if (window && window.navigator) { 5525 var parseVersion = function (matches) { 5526 if (matches) { 5527 return parseFloat(matches[1]); 5528 } 5529 }; 5530 5531 // Detect various browser versions because some old versions don't fully support the 'input' event 5532 var userAgent = window.navigator.userAgent, 5533 operaVersion, chromeVersion, safariVersion, firefoxVersion, ieVersion, edgeVersion; 5534 5535 (operaVersion = window.opera && window.opera.version && parseInt(window.opera.version())) 5536 || (edgeVersion = parseVersion(userAgent.match(/Edge\/([^ ]+)$/))) 5537 || (chromeVersion = parseVersion(userAgent.match(/Chrome\/([^ ]+)/))) 5538 || (safariVersion = parseVersion(userAgent.match(/Version\/([^ ]+) Safari/))) 5539 || (firefoxVersion = parseVersion(userAgent.match(/Firefox\/([^ ]+)/))) 5540 || (ieVersion = ko.utils.ieVersion || parseVersion(userAgent.match(/MSIE ([^ ]+)/))) // Detects up to IE 10 5541 || (ieVersion = parseVersion(userAgent.match(/rv:([^ )]+)/))); // Detects IE 11 5542 } 5543 5544 // IE 8 and 9 have bugs that prevent the normal events from firing when the value changes. 5545 // But it does fire the 'selectionchange' event on many of those, presumably because the 5546 // cursor is moving and that counts as the selection changing. The 'selectionchange' event is 5547 // fired at the document level only and doesn't directly indicate which element changed. We 5548 // set up just one event handler for the document and use 'activeElement' to determine which 5549 // element was changed. 5550 if (ieVersion >= 8 && ieVersion < 10) { 5551 var selectionChangeRegisteredName = ko.utils.domData.nextKey(), 5552 selectionChangeHandlerName = ko.utils.domData.nextKey(); 5553 var selectionChangeHandler = function (event) { 5554 var target = this.activeElement, 5555 handler = target && ko.utils.domData.get(target, selectionChangeHandlerName); 5556 if (handler) { 5557 handler(event); 5558 } 5559 }; 5560 var registerForSelectionChangeEvent = function (element, handler) { 5561 var ownerDoc = element.ownerDocument; 5562 if (!ko.utils.domData.get(ownerDoc, selectionChangeRegisteredName)) { 5563 ko.utils.domData.set(ownerDoc, selectionChangeRegisteredName, true); 5564 ko.utils.registerEventHandler(ownerDoc, 'selectionchange', selectionChangeHandler); 5565 } 5566 ko.utils.domData.set(element, selectionChangeHandlerName, handler); 5567 }; 5568 } 5569 5570 ko.bindingHandlers['textInput'] = { 5571 'init': function (element, valueAccessor, allBindings) { 5572 5573 var previousElementValue = element.value, 5574 timeoutHandle, 5575 elementValueBeforeEvent; 5576 5577 var updateModel = function (event) { 5578 clearTimeout(timeoutHandle); 5579 elementValueBeforeEvent = timeoutHandle = undefined; 5580 5581 var elementValue = element.value; 5582 if (previousElementValue !== elementValue) { 5583 // Provide a way for tests to know exactly which event was processed 5584 if (DEBUG && event) element['_ko_textInputProcessedEvent'] = event.type; 5585 previousElementValue = elementValue; 5586 ko.expressionRewriting.writeValueToProperty(valueAccessor(), allBindings, 'textInput', elementValue); 5587 } 5588 }; 5589 5590 var deferUpdateModel = function (event) { 5591 if (!timeoutHandle) { 5592 // The elementValueBeforeEvent variable is set *only* during the brief gap between an 5593 // event firing and the updateModel function running. This allows us to ignore model 5594 // updates that are from the previous state of the element, usually due to techniques 5595 // such as rateLimit. Such updates, if not ignored, can cause keystrokes to be lost. 5596 elementValueBeforeEvent = element.value; 5597 var handler = DEBUG ? updateModel.bind(element, { type: event.type }) : updateModel; 5598 timeoutHandle = ko.utils.setTimeout(handler, 4); 5599 } 5600 }; 5601 5602 // IE9 will mess up the DOM if you handle events synchronously which results in DOM changes (such as other bindings); 5603 // so we'll make sure all updates are asynchronous 5604 var ieUpdateModel = ko.utils.ieVersion == 9 ? deferUpdateModel : updateModel, 5605 ourUpdate = false; 5606 5607 var updateView = function () { 5608 var modelValue = ko.utils.unwrapObservable(valueAccessor()); 5609 5610 if (modelValue === null || modelValue === undefined) { 5611 modelValue = ''; 5612 } 5613 5614 if (elementValueBeforeEvent !== undefined && modelValue === elementValueBeforeEvent) { 5615 ko.utils.setTimeout(updateView, 4); 5616 return; 5617 } 5618 5619 // Update the element only if the element and model are different. On some browsers, updating the value 5620 // will move the cursor to the end of the input, which would be bad while the user is typing. 5621 if (element.value !== modelValue) { 5622 ourUpdate = true; // Make sure we ignore events (propertychange) that result from updating the value 5623 element.value = modelValue; 5624 ourUpdate = false; 5625 previousElementValue = element.value; // In case the browser changes the value (see #2281) 5626 } 5627 }; 5628 5629 var onEvent = function (event, handler) { 5630 ko.utils.registerEventHandler(element, event, handler); 5631 }; 5632 5633 if (DEBUG && ko.bindingHandlers['textInput']['_forceUpdateOn']) { 5634 // Provide a way for tests to specify exactly which events are bound 5635 ko.utils.arrayForEach(ko.bindingHandlers['textInput']['_forceUpdateOn'], function (eventName) { 5636 if (eventName.slice(0, 5) == 'after') { 5637 onEvent(eventName.slice(5), deferUpdateModel); 5638 } else { 5639 onEvent(eventName, updateModel); 5640 } 5641 }); 5642 } else { 5643 if (ieVersion) { 5644 // All versions (including 11) of Internet Explorer have a bug that they don't generate an input or propertychange event when ESC is pressed 5645 onEvent('keypress', updateModel); 5646 } 5647 if (ieVersion < 11) { 5648 // Internet Explorer <= 8 doesn't support the 'input' event, but does include 'propertychange' that fires whenever 5649 // any property of an element changes. Unlike 'input', it also fires if a property is changed from JavaScript code, 5650 // but that's an acceptable compromise for this binding. IE 9 and 10 support 'input', but since they don't always 5651 // fire it when using autocomplete, we'll use 'propertychange' for them also. 5652 onEvent('propertychange', function (event) { 5653 if (!ourUpdate && event.propertyName === 'value') { 5654 ieUpdateModel(event); 5655 } 5656 }); 5657 } 5658 if (ieVersion == 8) { 5659 // IE 8 has a bug where it fails to fire 'propertychange' on the first update following a value change from 5660 // JavaScript code. It also doesn't fire if you clear the entire value. To fix this, we bind to the following 5661 // events too. 5662 onEvent('keyup', updateModel); // A single keystoke 5663 onEvent('keydown', updateModel); // The first character when a key is held down 5664 } 5665 if (registerForSelectionChangeEvent) { 5666 // Internet Explorer 9 doesn't fire the 'input' event when deleting text, including using 5667 // the backspace, delete, or ctrl-x keys, clicking the 'x' to clear the input, dragging text 5668 // out of the field, and cutting or deleting text using the context menu. 'selectionchange' 5669 // can detect all of those except dragging text out of the field, for which we use 'dragend'. 5670 // These are also needed in IE8 because of the bug described above. 5671 registerForSelectionChangeEvent(element, ieUpdateModel); // 'selectionchange' covers cut, paste, drop, delete, etc. 5672 onEvent('dragend', deferUpdateModel); 5673 } 5674 5675 if (!ieVersion || ieVersion >= 9) { 5676 // All other supported browsers support the 'input' event, which fires whenever the content of the element is changed 5677 // through the user interface. 5678 onEvent('input', ieUpdateModel); 5679 } 5680 5681 if (safariVersion < 5 && ko.utils.tagNameLower(element) === "textarea") { 5682 // Safari <5 doesn't fire the 'input' event for <textarea> elements (it does fire 'textInput' 5683 // but only when typing). So we'll just catch as much as we can with keydown, cut, and paste. 5684 onEvent('keydown', deferUpdateModel); 5685 onEvent('paste', deferUpdateModel); 5686 onEvent('cut', deferUpdateModel); 5687 } else if (operaVersion < 11) { 5688 // Opera 10 doesn't always fire the 'input' event for cut, paste, undo & drop operations. 5689 // We can try to catch some of those using 'keydown'. 5690 onEvent('keydown', deferUpdateModel); 5691 } else if (firefoxVersion < 4.0) { 5692 // Firefox <= 3.6 doesn't fire the 'input' event when text is filled in through autocomplete 5693 onEvent('DOMAutoComplete', updateModel); 5694 5695 // Firefox <=3.5 doesn't fire the 'input' event when text is dropped into the input. 5696 onEvent('dragdrop', updateModel); // <3.5 5697 onEvent('drop', updateModel); // 3.5 5698 } else if (edgeVersion && element.type === "number") { 5699 // Microsoft Edge doesn't fire 'input' or 'change' events for number inputs when 5700 // the value is changed via the up / down arrow keys 5701 onEvent('keydown', deferUpdateModel); 5702 } 5703 } 5704 5705 // Bind to the change event so that we can catch programmatic updates of the value that fire this event. 5706 onEvent('change', updateModel); 5707 5708 // To deal with browsers that don't notify any kind of event for some changes (IE, Safari, etc.) 5709 onEvent('blur', updateModel); 5710 5711 ko.computed(updateView, null, { disposeWhenNodeIsRemoved: element }); 5712 } 5713 }; 5714 ko.expressionRewriting.twoWayBindings['textInput'] = true; 5715 5716 // textinput is an alias for textInput 5717 ko.bindingHandlers['textinput'] = { 5718 // preprocess is the only way to set up a full alias 5719 'preprocess': function (value, name, addBinding) { 5720 addBinding('textInput', value); 5721 } 5722 }; 5723 5724 })(); ko.bindingHandlers['uniqueName'] = { 5725 'init': function (element, valueAccessor) { 5726 if (valueAccessor()) { 5727 var name = "ko_unique_" + (++ko.bindingHandlers['uniqueName'].currentIndex); 5728 ko.utils.setElementName(element, name); 5729 } 5730 } 5731 }; 5732 ko.bindingHandlers['uniqueName'].currentIndex = 0; 5733 ko.bindingHandlers['using'] = { 5734 'init': function (element, valueAccessor, allBindings, viewModel, bindingContext) { 5735 var options; 5736 5737 if (allBindings['has']('as')) { 5738 options = { 'as': allBindings.get('as'), 'noChildContext': allBindings.get('noChildContext') }; 5739 } 5740 5741 var innerContext = bindingContext['createChildContext'](valueAccessor, options); 5742 ko.applyBindingsToDescendants(innerContext, element); 5743 5744 return { 'controlsDescendantBindings': true }; 5745 } 5746 }; 5747 ko.virtualElements.allowedBindings['using'] = true; 5748 ko.bindingHandlers['value'] = { 5749 'init': function (element, valueAccessor, allBindings) { 5750 var tagName = ko.utils.tagNameLower(element), 5751 isInputElement = tagName == "input"; 5752 5753 // If the value binding is placed on a radio/checkbox, then just pass through to checkedValue and quit 5754 if (isInputElement && (element.type == "checkbox" || element.type == "radio")) { 5755 ko.applyBindingAccessorsToNode(element, { 'checkedValue': valueAccessor }); 5756 return; 5757 } 5758 5759 var eventsToCatch = []; 5760 var requestedEventsToCatch = allBindings.get("valueUpdate"); 5761 var propertyChangedFired = false; 5762 var elementValueBeforeEvent = null; 5763 5764 if (requestedEventsToCatch) { 5765 // Allow both individual event names, and arrays of event names 5766 if (typeof requestedEventsToCatch == "string") { 5767 eventsToCatch = [requestedEventsToCatch]; 5768 } else { 5769 eventsToCatch = ko.utils.arrayGetDistinctValues(requestedEventsToCatch); 5770 } 5771 ko.utils.arrayRemoveItem(eventsToCatch, "change"); // We'll subscribe to "change" events later 5772 } 5773 5774 var valueUpdateHandler = function () { 5775 elementValueBeforeEvent = null; 5776 propertyChangedFired = false; 5777 var modelValue = valueAccessor(); 5778 var elementValue = ko.selectExtensions.readValue(element); 5779 ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'value', elementValue); 5780 } 5781 5782 // Workaround for https://github.com/SteveSanderson/knockout/issues/122 5783 // IE doesn't fire "change" events on textboxes if the user selects a value from its autocomplete list 5784 var ieAutoCompleteHackNeeded = ko.utils.ieVersion && isInputElement && element.type == "text" 5785 && element.autocomplete != "off" && (!element.form || element.form.autocomplete != "off"); 5786 if (ieAutoCompleteHackNeeded && ko.utils.arrayIndexOf(eventsToCatch, "propertychange") == -1) { 5787 ko.utils.registerEventHandler(element, "propertychange", function () { propertyChangedFired = true }); 5788 ko.utils.registerEventHandler(element, "focus", function () { propertyChangedFired = false }); 5789 ko.utils.registerEventHandler(element, "blur", function () { 5790 if (propertyChangedFired) { 5791 valueUpdateHandler(); 5792 } 5793 }); 5794 } 5795 5796 ko.utils.arrayForEach(eventsToCatch, function (eventName) { 5797 // The syntax "after<eventname>" means "run the handler asynchronously after the event" 5798 // This is useful, for example, to catch "keydown" events after the browser has updated the control 5799 // (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event) 5800 var handler = valueUpdateHandler; 5801 if (ko.utils.stringStartsWith(eventName, "after")) { 5802 handler = function () { 5803 // The elementValueBeforeEvent variable is non-null *only* during the brief gap between 5804 // a keyX event firing and the valueUpdateHandler running, which is scheduled to happen 5805 // at the earliest asynchronous opportunity. We store this temporary information so that 5806 // if, between keyX and valueUpdateHandler, the underlying model value changes separately, 5807 // we can overwrite that model value change with the value the user just typed. Otherwise, 5808 // techniques like rateLimit can trigger model changes at critical moments that will 5809 // override the user's inputs, causing keystrokes to be lost. 5810 elementValueBeforeEvent = ko.selectExtensions.readValue(element); 5811 ko.utils.setTimeout(valueUpdateHandler, 0); 5812 }; 5813 eventName = eventName.substring("after".length); 5814 } 5815 ko.utils.registerEventHandler(element, eventName, handler); 5816 }); 5817 5818 var updateFromModel; 5819 5820 if (isInputElement && element.type == "file") { 5821 // For file input elements, can only write the empty string 5822 updateFromModel = function () { 5823 var newValue = ko.utils.unwrapObservable(valueAccessor()); 5824 if (newValue === null || newValue === undefined || newValue === "") { 5825 element.value = ""; 5826 } else { 5827 ko.dependencyDetection.ignore(valueUpdateHandler); // reset the model to match the element 5828 } 5829 } 5830 } else { 5831 updateFromModel = function () { 5832 var newValue = ko.utils.unwrapObservable(valueAccessor()); 5833 var elementValue = ko.selectExtensions.readValue(element); 5834 5835 if (elementValueBeforeEvent !== null && newValue === elementValueBeforeEvent) { 5836 ko.utils.setTimeout(updateFromModel, 0); 5837 return; 5838 } 5839 5840 var valueHasChanged = newValue !== elementValue; 5841 5842 if (valueHasChanged || elementValue === undefined) { 5843 if (tagName === "select") { 5844 var allowUnset = allBindings.get('valueAllowUnset'); 5845 ko.selectExtensions.writeValue(element, newValue, allowUnset); 5846 if (!allowUnset && newValue !== ko.selectExtensions.readValue(element)) { 5847 // If you try to set a model value that can't be represented in an already-populated dropdown, reject that change, 5848 // because you're not allowed to have a model value that disagrees with a visible UI selection. 5849 ko.dependencyDetection.ignore(valueUpdateHandler); 5850 } 5851 } else { 5852 ko.selectExtensions.writeValue(element, newValue); 5853 } 5854 } 5855 }; 5856 } 5857 5858 if (tagName === "select") { 5859 var updateFromModelComputed; 5860 ko.bindingEvent.subscribe(element, ko.bindingEvent.childrenComplete, function () { 5861 if (!updateFromModelComputed) { 5862 ko.utils.registerEventHandler(element, "change", valueUpdateHandler); 5863 updateFromModelComputed = ko.computed(updateFromModel, null, { disposeWhenNodeIsRemoved: element }); 5864 } else if (allBindings.get('valueAllowUnset')) { 5865 updateFromModel(); 5866 } else { 5867 valueUpdateHandler(); 5868 } 5869 }, null, { 'notifyImmediately': true }); 5870 } else { 5871 ko.utils.registerEventHandler(element, "change", valueUpdateHandler); 5872 ko.computed(updateFromModel, null, { disposeWhenNodeIsRemoved: element }); 5873 } 5874 }, 5875 'update': function () { } // Keep for backwards compatibility with code that may have wrapped value binding 5876 }; 5877 ko.expressionRewriting.twoWayBindings['value'] = true; 5878 ko.bindingHandlers['visible'] = { 5879 'update': function (element, valueAccessor) { 5880 var value = ko.utils.unwrapObservable(valueAccessor()); 5881 var isCurrentlyVisible = !(element.style.display == "none"); 5882 if (value && !isCurrentlyVisible) 5883 element.style.display = ""; 5884 else if ((!value) && isCurrentlyVisible) 5885 element.style.display = "none"; 5886 } 5887 }; 5888 5889 ko.bindingHandlers['hidden'] = { 5890 'update': function (element, valueAccessor) { 5891 ko.bindingHandlers['visible']['update'](element, function () { return !ko.utils.unwrapObservable(valueAccessor()) }); 5892 } 5893 }; 5894 // 'click' is just a shorthand for the usual full-length event:{click:handler} 5895 makeEventHandlerShortcut('click'); 5896 // If you want to make a custom template engine, 5897 // 5898 // [1] Inherit from this class (like ko.nativeTemplateEngine does) 5899 // [2] Override 'renderTemplateSource', supplying a function with this signature: 5900 // 5901 // function (templateSource, bindingContext, options) { 5902 // // - templateSource.text() is the text of the template you should render 5903 // // - bindingContext.$data is the data you should pass into the template 5904 // // - you might also want to make bindingContext.$parent, bindingContext.$parents, 5905 // // and bindingContext.$root available in the template too 5906 // // - options gives you access to any other properties set on "data-bind: { template: options }" 5907 // // - templateDocument is the document object of the template 5908 // // 5909 // // Return value: an array of DOM nodes 5910 // } 5911 // 5912 // [3] Override 'createJavaScriptEvaluatorBlock', supplying a function with this signature: 5913 // 5914 // function (script) { 5915 // // Return value: Whatever syntax means "Evaluate the JavaScript statement 'script' and output the result" 5916 // // For example, the jquery.tmpl template engine converts 'someScript' to '${ someScript }' 5917 // } 5918 // 5919 // This is only necessary if you want to allow data-bind attributes to reference arbitrary template variables. 5920 // If you don't want to allow that, you can set the property 'allowTemplateRewriting' to false (like ko.nativeTemplateEngine does) 5921 // and then you don't need to override 'createJavaScriptEvaluatorBlock'. 5922 5923 ko.templateEngine = function () { }; 5924 5925 ko.templateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options, templateDocument) { 5926 throw new Error("Override renderTemplateSource"); 5927 }; 5928 5929 ko.templateEngine.prototype['createJavaScriptEvaluatorBlock'] = function (script) { 5930 throw new Error("Override createJavaScriptEvaluatorBlock"); 5931 }; 5932 5933 ko.templateEngine.prototype['makeTemplateSource'] = function (template, templateDocument) { 5934 // Named template 5935 if (typeof template == "string") { 5936 templateDocument = templateDocument || document; 5937 var elem = templateDocument.getElementById(template); 5938 if (!elem) 5939 throw new Error("Cannot find template with ID " + template); 5940 return new ko.templateSources.domElement(elem); 5941 } else if ((template.nodeType == 1) || (template.nodeType == 8)) { 5942 // Anonymous template 5943 return new ko.templateSources.anonymousTemplate(template); 5944 } else 5945 throw new Error("Unknown template type: " + template); 5946 }; 5947 5948 ko.templateEngine.prototype['renderTemplate'] = function (template, bindingContext, options, templateDocument) { 5949 var templateSource = this['makeTemplateSource'](template, templateDocument); 5950 return this['renderTemplateSource'](templateSource, bindingContext, options, templateDocument); 5951 }; 5952 5953 ko.templateEngine.prototype['isTemplateRewritten'] = function (template, templateDocument) { 5954 // Skip rewriting if requested 5955 if (this['allowTemplateRewriting'] === false) 5956 return true; 5957 return this['makeTemplateSource'](template, templateDocument)['data']("isRewritten"); 5958 }; 5959 5960 ko.templateEngine.prototype['rewriteTemplate'] = function (template, rewriterCallback, templateDocument) { 5961 var templateSource = this['makeTemplateSource'](template, templateDocument); 5962 var rewritten = rewriterCallback(templateSource['text']()); 5963 templateSource['text'](rewritten); 5964 templateSource['data']("isRewritten", true); 5965 }; 5966 5967 ko.exportSymbol('templateEngine', ko.templateEngine); 5968 5969 ko.templateRewriting = (function () { 5970 var memoizeDataBindingAttributeSyntaxRegex = /(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'|[^>]*))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi; 5971 var memoizeVirtualContainerBindingSyntaxRegex = /<!--\s*ko\b\s*([\s\S]*?)\s*-->/g; 5972 5973 function validateDataBindValuesForRewriting(keyValueArray) { 5974 var allValidators = ko.expressionRewriting.bindingRewriteValidators; 5975 for (var i = 0; i < keyValueArray.length; i++) { 5976 var key = keyValueArray[i]['key']; 5977 if (Object.prototype.hasOwnProperty.call(allValidators, key)) { 5978 var validator = allValidators[key]; 5979 5980 if (typeof validator === "function") { 5981 var possibleErrorMessage = validator(keyValueArray[i]['value']); 5982 if (possibleErrorMessage) 5983 throw new Error(possibleErrorMessage); 5984 } else if (!validator) { 5985 throw new Error("This template engine does not support the '" + key + "' binding within its templates"); 5986 } 5987 } 5988 } 5989 } 5990 5991 function constructMemoizedTagReplacement(dataBindAttributeValue, tagToRetain, nodeName, templateEngine) { 5992 var dataBindKeyValueArray = ko.expressionRewriting.parseObjectLiteral(dataBindAttributeValue); 5993 validateDataBindValuesForRewriting(dataBindKeyValueArray); 5994 var rewrittenDataBindAttributeValue = ko.expressionRewriting.preProcessBindings(dataBindKeyValueArray, { 'valueAccessors': true }); 5995 5996 // For no obvious reason, Opera fails to evaluate rewrittenDataBindAttributeValue unless it's wrapped in an additional 5997 // anonymous function, even though Opera's built-in debugger can evaluate it anyway. No other browser requires this 5998 // extra indirection. 5999 var applyBindingsToNextSiblingScript = 6000 "ko.__tr_ambtns(function($context,$element){return(function(){return{ " + rewrittenDataBindAttributeValue + " } })()},'" + nodeName.toLowerCase() + "')"; 6001 return templateEngine['createJavaScriptEvaluatorBlock'](applyBindingsToNextSiblingScript) + tagToRetain; 6002 } 6003 6004 return { 6005 ensureTemplateIsRewritten: function (template, templateEngine, templateDocument) { 6006 if (!templateEngine['isTemplateRewritten'](template, templateDocument)) 6007 templateEngine['rewriteTemplate'](template, function (htmlString) { 6008 return ko.templateRewriting.memoizeBindingAttributeSyntax(htmlString, templateEngine); 6009 }, templateDocument); 6010 }, 6011 6012 memoizeBindingAttributeSyntax: function (htmlString, templateEngine) { 6013 return htmlString.replace(memoizeDataBindingAttributeSyntaxRegex, function () { 6014 return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[4], /* tagToRetain: */ arguments[1], /* nodeName: */ arguments[2], templateEngine); 6015 }).replace(memoizeVirtualContainerBindingSyntaxRegex, function () { 6016 return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[1], /* tagToRetain: */ "<!-- ko -->", /* nodeName: */ "#comment", templateEngine); 6017 }); 6018 }, 6019 6020 applyMemoizedBindingsToNextSibling: function (bindings, nodeName) { 6021 return ko.memoization.memoize(function (domNode, bindingContext) { 6022 var nodeToBind = domNode.nextSibling; 6023 if (nodeToBind && nodeToBind.nodeName.toLowerCase() === nodeName) { 6024 ko.applyBindingAccessorsToNode(nodeToBind, bindings, bindingContext); 6025 } 6026 }); 6027 } 6028 } 6029 })(); 6030 6031 6032 // Exported only because it has to be referenced by string lookup from within rewritten template 6033 ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextSibling); 6034 (function () { 6035 // A template source represents a read/write way of accessing a template. This is to eliminate the need for template loading/saving 6036 // logic to be duplicated in every template engine (and means they can all work with anonymous templates, etc.) 6037 // 6038 // Two are provided by default: 6039 // 1. ko.templateSources.domElement - reads/writes the text content of an arbitrary DOM element 6040 // 2. ko.templateSources.anonymousElement - uses ko.utils.domData to read/write text *associated* with the DOM element, but 6041 // without reading/writing the actual element text content, since it will be overwritten 6042 // with the rendered template output. 6043 // You can implement your own template source if you want to fetch/store templates somewhere other than in DOM elements. 6044 // Template sources need to have the following functions: 6045 // text() - returns the template text from your storage location 6046 // text(value) - writes the supplied template text to your storage location 6047 // data(key) - reads values stored using data(key, value) - see below 6048 // data(key, value) - associates "value" with this template and the key "key". Is used to store information like "isRewritten". 6049 // 6050 // Optionally, template sources can also have the following functions: 6051 // nodes() - returns a DOM element containing the nodes of this template, where available 6052 // nodes(value) - writes the given DOM element to your storage location 6053 // If a DOM element is available for a given template source, template engines are encouraged to use it in preference over text() 6054 // for improved speed. However, all templateSources must supply text() even if they don't supply nodes(). 6055 // 6056 // Once you've implemented a templateSource, make your template engine use it by subclassing whatever template engine you were 6057 // using and overriding "makeTemplateSource" to return an instance of your custom template source. 6058 6059 ko.templateSources = {}; 6060 6061 // ---- ko.templateSources.domElement ----- 6062 6063 // template types 6064 var templateScript = 1, 6065 templateTextArea = 2, 6066 templateTemplate = 3, 6067 templateElement = 4; 6068 6069 ko.templateSources.domElement = function (element) { 6070 this.domElement = element; 6071 6072 if (element) { 6073 var tagNameLower = ko.utils.tagNameLower(element); 6074 this.templateType = 6075 tagNameLower === "script" ? templateScript : 6076 tagNameLower === "textarea" ? templateTextArea : 6077 // For browsers with proper <template> element support, where the .content property gives a document fragment 6078 tagNameLower == "template" && element.content && element.content.nodeType === 11 ? templateTemplate : 6079 templateElement; 6080 } 6081 } 6082 6083 ko.templateSources.domElement.prototype['text'] = function (/* valueToWrite */) { 6084 var elemContentsProperty = this.templateType === templateScript ? "text" 6085 : this.templateType === templateTextArea ? "value" 6086 : "innerHTML"; 6087 6088 if (arguments.length == 0) { 6089 return this.domElement[elemContentsProperty]; 6090 } else { 6091 var valueToWrite = arguments[0]; 6092 if (elemContentsProperty === "innerHTML") 6093 ko.utils.setHtml(this.domElement, valueToWrite); 6094 else 6095 this.domElement[elemContentsProperty] = valueToWrite; 6096 } 6097 }; 6098 6099 var dataDomDataPrefix = ko.utils.domData.nextKey() + "_"; 6100 ko.templateSources.domElement.prototype['data'] = function (key /*, valueToWrite */) { 6101 if (arguments.length === 1) { 6102 return ko.utils.domData.get(this.domElement, dataDomDataPrefix + key); 6103 } else { 6104 ko.utils.domData.set(this.domElement, dataDomDataPrefix + key, arguments[1]); 6105 } 6106 }; 6107 6108 var templatesDomDataKey = ko.utils.domData.nextKey(); 6109 function getTemplateDomData(element) { 6110 return ko.utils.domData.get(element, templatesDomDataKey) || {}; 6111 } 6112 function setTemplateDomData(element, data) { 6113 ko.utils.domData.set(element, templatesDomDataKey, data); 6114 } 6115 6116 ko.templateSources.domElement.prototype['nodes'] = function (/* valueToWrite */) { 6117 var element = this.domElement; 6118 if (arguments.length == 0) { 6119 var templateData = getTemplateDomData(element), 6120 nodes = templateData.containerData || ( 6121 this.templateType === templateTemplate ? element.content : 6122 this.templateType === templateElement ? element : 6123 undefined); 6124 if (!nodes || templateData.alwaysCheckText) { 6125 // If the template is associated with an element that stores the template as text, 6126 // parse and cache the nodes whenever there's new text content available. This allows 6127 // the user to update the template content by updating the text of template node. 6128 var text = this['text'](); 6129 if (text && text !== templateData.textData) { 6130 nodes = ko.utils.parseHtmlForTemplateNodes(text, element.ownerDocument); 6131 setTemplateDomData(element, { containerData: nodes, textData: text, alwaysCheckText: true }); 6132 } 6133 } 6134 return nodes; 6135 } else { 6136 var valueToWrite = arguments[0]; 6137 if (this.templateType !== undefined) { 6138 this['text'](""); // clear the text from the node 6139 } 6140 setTemplateDomData(element, { containerData: valueToWrite }); 6141 } 6142 }; 6143 6144 // ---- ko.templateSources.anonymousTemplate ----- 6145 // Anonymous templates are normally saved/retrieved as DOM nodes through "nodes". 6146 // For compatibility, you can also read "text"; it will be serialized from the nodes on demand. 6147 // Writing to "text" is still supported, but then the template data will not be available as DOM nodes. 6148 6149 ko.templateSources.anonymousTemplate = function (element) { 6150 this.domElement = element; 6151 } 6152 ko.templateSources.anonymousTemplate.prototype = new ko.templateSources.domElement(); 6153 ko.templateSources.anonymousTemplate.prototype.constructor = ko.templateSources.anonymousTemplate; 6154 ko.templateSources.anonymousTemplate.prototype['text'] = function (/* valueToWrite */) { 6155 if (arguments.length == 0) { 6156 var templateData = getTemplateDomData(this.domElement); 6157 if (templateData.textData === undefined && templateData.containerData) 6158 templateData.textData = templateData.containerData.innerHTML; 6159 return templateData.textData; 6160 } else { 6161 var valueToWrite = arguments[0]; 6162 setTemplateDomData(this.domElement, { textData: valueToWrite }); 6163 } 6164 }; 6165 6166 ko.exportSymbol('templateSources', ko.templateSources); 6167 ko.exportSymbol('templateSources.domElement', ko.templateSources.domElement); 6168 ko.exportSymbol('templateSources.anonymousTemplate', ko.templateSources.anonymousTemplate); 6169 })(); 6170 (function () { 6171 var _templateEngine; 6172 ko.setTemplateEngine = function (templateEngine) { 6173 if ((templateEngine != undefined) && !(templateEngine instanceof ko.templateEngine)) 6174 throw new Error("templateEngine must inherit from ko.templateEngine"); 6175 _templateEngine = templateEngine; 6176 } 6177 6178 function invokeForEachNodeInContinuousRange(firstNode, lastNode, action) { 6179 var node, nextInQueue = firstNode, firstOutOfRangeNode = ko.virtualElements.nextSibling(lastNode); 6180 while (nextInQueue && ((node = nextInQueue) !== firstOutOfRangeNode)) { 6181 nextInQueue = ko.virtualElements.nextSibling(node); 6182 action(node, nextInQueue); 6183 } 6184 } 6185 6186 function activateBindingsOnContinuousNodeArray(continuousNodeArray, bindingContext) { 6187 // To be used on any nodes that have been rendered by a template and have been inserted into some parent element 6188 // Walks through continuousNodeArray (which *must* be continuous, i.e., an uninterrupted sequence of sibling nodes, because 6189 // the algorithm for walking them relies on this), and for each top-level item in the virtual-element sense, 6190 // (1) Does a regular "applyBindings" to associate bindingContext with this node and to activate any non-memoized bindings 6191 // (2) Unmemoizes any memos in the DOM subtree (e.g., to activate bindings that had been memoized during template rewriting) 6192 6193 if (continuousNodeArray.length) { 6194 var firstNode = continuousNodeArray[0], 6195 lastNode = continuousNodeArray[continuousNodeArray.length - 1], 6196 parentNode = firstNode.parentNode, 6197 provider = ko.bindingProvider['instance'], 6198 preprocessNode = provider['preprocessNode']; 6199 6200 if (preprocessNode) { 6201 invokeForEachNodeInContinuousRange(firstNode, lastNode, function (node, nextNodeInRange) { 6202 var nodePreviousSibling = node.previousSibling; 6203 var newNodes = preprocessNode.call(provider, node); 6204 if (newNodes) { 6205 if (node === firstNode) 6206 firstNode = newNodes[0] || nextNodeInRange; 6207 if (node === lastNode) 6208 lastNode = newNodes[newNodes.length - 1] || nodePreviousSibling; 6209 } 6210 }); 6211 6212 // Because preprocessNode can change the nodes, including the first and last nodes, update continuousNodeArray to match. 6213 // We need the full set, including inner nodes, because the unmemoize step might remove the first node (and so the real 6214 // first node needs to be in the array). 6215 continuousNodeArray.length = 0; 6216 if (!firstNode) { // preprocessNode might have removed all the nodes, in which case there's nothing left to do 6217 return; 6218 } 6219 if (firstNode === lastNode) { 6220 continuousNodeArray.push(firstNode); 6221 } else { 6222 continuousNodeArray.push(firstNode, lastNode); 6223 ko.utils.fixUpContinuousNodeArray(continuousNodeArray, parentNode); 6224 } 6225 } 6226 6227 // Need to applyBindings *before* unmemoziation, because unmemoization might introduce extra nodes (that we don't want to re-bind) 6228 // whereas a regular applyBindings won't introduce new memoized nodes 6229 invokeForEachNodeInContinuousRange(firstNode, lastNode, function (node) { 6230 if (node.nodeType === 1 || node.nodeType === 8) 6231 ko.applyBindings(bindingContext, node); 6232 }); 6233 invokeForEachNodeInContinuousRange(firstNode, lastNode, function (node) { 6234 if (node.nodeType === 1 || node.nodeType === 8) 6235 ko.memoization.unmemoizeDomNodeAndDescendants(node, [bindingContext]); 6236 }); 6237 6238 // Make sure any changes done by applyBindings or unmemoize are reflected in the array 6239 ko.utils.fixUpContinuousNodeArray(continuousNodeArray, parentNode); 6240 } 6241 } 6242 6243 function getFirstNodeFromPossibleArray(nodeOrNodeArray) { 6244 return nodeOrNodeArray.nodeType ? nodeOrNodeArray 6245 : nodeOrNodeArray.length > 0 ? nodeOrNodeArray[0] 6246 : null; 6247 } 6248 6249 function executeTemplate(targetNodeOrNodeArray, renderMode, template, bindingContext, options) { 6250 options = options || {}; 6251 var firstTargetNode = targetNodeOrNodeArray && getFirstNodeFromPossibleArray(targetNodeOrNodeArray); 6252 var templateDocument = (firstTargetNode || template || {}).ownerDocument; 6253 var templateEngineToUse = (options['templateEngine'] || _templateEngine); 6254 ko.templateRewriting.ensureTemplateIsRewritten(template, templateEngineToUse, templateDocument); 6255 var renderedNodesArray = templateEngineToUse['renderTemplate'](template, bindingContext, options, templateDocument); 6256 6257 // Loosely check result is an array of DOM nodes 6258 if ((typeof renderedNodesArray.length != "number") || (renderedNodesArray.length > 0 && typeof renderedNodesArray[0].nodeType != "number")) 6259 throw new Error("Template engine must return an array of DOM nodes"); 6260 6261 var haveAddedNodesToParent = false; 6262 switch (renderMode) { 6263 case "replaceChildren": 6264 ko.virtualElements.setDomNodeChildren(targetNodeOrNodeArray, renderedNodesArray); 6265 haveAddedNodesToParent = true; 6266 break; 6267 case "replaceNode": 6268 ko.utils.replaceDomNodes(targetNodeOrNodeArray, renderedNodesArray); 6269 haveAddedNodesToParent = true; 6270 break; 6271 case "ignoreTargetNode": break; 6272 default: 6273 throw new Error("Unknown renderMode: " + renderMode); 6274 } 6275 6276 if (haveAddedNodesToParent) { 6277 activateBindingsOnContinuousNodeArray(renderedNodesArray, bindingContext); 6278 if (options['afterRender']) { 6279 ko.dependencyDetection.ignore(options['afterRender'], null, [renderedNodesArray, bindingContext[options['as'] || '$data']]); 6280 } 6281 if (renderMode == "replaceChildren") { 6282 ko.bindingEvent.notify(targetNodeOrNodeArray, ko.bindingEvent.childrenComplete); 6283 } 6284 } 6285 6286 return renderedNodesArray; 6287 } 6288 6289 function resolveTemplateName(template, data, context) { 6290 // The template can be specified as: 6291 if (ko.isObservable(template)) { 6292 // 1. An observable, with string value 6293 return template(); 6294 } else if (typeof template === 'function') { 6295 // 2. A function of (data, context) returning a string 6296 return template(data, context); 6297 } else { 6298 // 3. A string 6299 return template; 6300 } 6301 } 6302 6303 ko.renderTemplate = function (template, dataOrBindingContext, options, targetNodeOrNodeArray, renderMode) { 6304 options = options || {}; 6305 if ((options['templateEngine'] || _templateEngine) == undefined) 6306 throw new Error("Set a template engine before calling renderTemplate"); 6307 renderMode = renderMode || "replaceChildren"; 6308 6309 if (targetNodeOrNodeArray) { 6310 var firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray); 6311 6312 var whenToDispose = function () { return (!firstTargetNode) || !ko.utils.domNodeIsAttachedToDocument(firstTargetNode); }; // Passive disposal (on next evaluation) 6313 var activelyDisposeWhenNodeIsRemoved = (firstTargetNode && renderMode == "replaceNode") ? firstTargetNode.parentNode : firstTargetNode; 6314 6315 return ko.dependentObservable( // So the DOM is automatically updated when any dependency changes 6316 function () { 6317 // Ensure we've got a proper binding context to work with 6318 var bindingContext = (dataOrBindingContext && (dataOrBindingContext instanceof ko.bindingContext)) 6319 ? dataOrBindingContext 6320 : new ko.bindingContext(dataOrBindingContext, null, null, null, { "exportDependencies": true }); 6321 6322 var templateName = resolveTemplateName(template, bindingContext['$data'], bindingContext), 6323 renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, templateName, bindingContext, options); 6324 6325 if (renderMode == "replaceNode") { 6326 targetNodeOrNodeArray = renderedNodesArray; 6327 firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray); 6328 } 6329 }, 6330 null, 6331 { disposeWhen: whenToDispose, disposeWhenNodeIsRemoved: activelyDisposeWhenNodeIsRemoved } 6332 ); 6333 } else { 6334 // We don't yet have a DOM node to evaluate, so use a memo and render the template later when there is a DOM node 6335 return ko.memoization.memoize(function (domNode) { 6336 ko.renderTemplate(template, dataOrBindingContext, options, domNode, "replaceNode"); 6337 }); 6338 } 6339 }; 6340 6341 ko.renderTemplateForEach = function (template, arrayOrObservableArray, options, targetNode, parentBindingContext) { 6342 // Since setDomNodeChildrenFromArrayMapping always calls executeTemplateForArrayItem and then 6343 // activateBindingsCallback for added items, we can store the binding context in the former to use in the latter. 6344 var arrayItemContext, asName = options['as']; 6345 6346 // This will be called by setDomNodeChildrenFromArrayMapping to get the nodes to add to targetNode 6347 var executeTemplateForArrayItem = function (arrayValue, index) { 6348 // Support selecting template as a function of the data being rendered 6349 arrayItemContext = parentBindingContext['createChildContext'](arrayValue, { 6350 'as': asName, 6351 'noChildContext': options['noChildContext'], 6352 'extend': function (context) { 6353 context['$index'] = index; 6354 if (asName) { 6355 context[asName + "Index"] = index; 6356 } 6357 } 6358 }); 6359 6360 var templateName = resolveTemplateName(template, arrayValue, arrayItemContext); 6361 return executeTemplate(targetNode, "ignoreTargetNode", templateName, arrayItemContext, options); 6362 }; 6363 6364 // This will be called whenever setDomNodeChildrenFromArrayMapping has added nodes to targetNode 6365 var activateBindingsCallback = function (arrayValue, addedNodesArray, index) { 6366 activateBindingsOnContinuousNodeArray(addedNodesArray, arrayItemContext); 6367 if (options['afterRender']) 6368 options['afterRender'](addedNodesArray, arrayValue); 6369 6370 // release the "cache" variable, so that it can be collected by 6371 // the GC when its value isn't used from within the bindings anymore. 6372 arrayItemContext = null; 6373 }; 6374 6375 var setDomNodeChildrenFromArrayMapping = function (newArray, changeList) { 6376 // Call setDomNodeChildrenFromArrayMapping, ignoring any observables unwrapped within (most likely from a callback function). 6377 // If the array items are observables, though, they will be unwrapped in executeTemplateForArrayItem and managed within setDomNodeChildrenFromArrayMapping. 6378 ko.dependencyDetection.ignore(ko.utils.setDomNodeChildrenFromArrayMapping, null, [targetNode, newArray, executeTemplateForArrayItem, options, activateBindingsCallback, changeList]); 6379 ko.bindingEvent.notify(targetNode, ko.bindingEvent.childrenComplete); 6380 }; 6381 6382 var shouldHideDestroyed = (options['includeDestroyed'] === false) || (ko.options['foreachHidesDestroyed'] && !options['includeDestroyed']); 6383 6384 if (!shouldHideDestroyed && !options['beforeRemove'] && ko.isObservableArray(arrayOrObservableArray)) { 6385 setDomNodeChildrenFromArrayMapping(arrayOrObservableArray.peek()); 6386 6387 var subscription = arrayOrObservableArray.subscribe(function (changeList) { 6388 setDomNodeChildrenFromArrayMapping(arrayOrObservableArray(), changeList); 6389 }, null, "arrayChange"); 6390 subscription.disposeWhenNodeIsRemoved(targetNode); 6391 6392 return subscription; 6393 } else { 6394 return ko.dependentObservable(function () { 6395 var unwrappedArray = ko.utils.unwrapObservable(arrayOrObservableArray) || []; 6396 if (typeof unwrappedArray.length == "undefined") // Coerce single value into array 6397 unwrappedArray = [unwrappedArray]; 6398 6399 if (shouldHideDestroyed) { 6400 // Filter out any entries marked as destroyed 6401 unwrappedArray = ko.utils.arrayFilter(unwrappedArray, function (item) { 6402 return item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']); 6403 }); 6404 } 6405 setDomNodeChildrenFromArrayMapping(unwrappedArray); 6406 6407 }, null, { disposeWhenNodeIsRemoved: targetNode }); 6408 } 6409 }; 6410 6411 var templateComputedDomDataKey = ko.utils.domData.nextKey(); 6412 function disposeOldComputedAndStoreNewOne(element, newComputed) { 6413 var oldComputed = ko.utils.domData.get(element, templateComputedDomDataKey); 6414 if (oldComputed && (typeof (oldComputed.dispose) == 'function')) 6415 oldComputed.dispose(); 6416 ko.utils.domData.set(element, templateComputedDomDataKey, (newComputed && (!newComputed.isActive || newComputed.isActive())) ? newComputed : undefined); 6417 } 6418 6419 var cleanContainerDomDataKey = ko.utils.domData.nextKey(); 6420 ko.bindingHandlers['template'] = { 6421 'init': function (element, valueAccessor) { 6422 // Support anonymous templates 6423 var bindingValue = ko.utils.unwrapObservable(valueAccessor()); 6424 if (typeof bindingValue == "string" || 'name' in bindingValue) { 6425 // It's a named template - clear the element 6426 ko.virtualElements.emptyNode(element); 6427 } else if ('nodes' in bindingValue) { 6428 // We've been given an array of DOM nodes. Save them as the template source. 6429 // There is no known use case for the node array being an observable array (if the output 6430 // varies, put that behavior *into* your template - that's what templates are for), and 6431 // the implementation would be a mess, so assert that it's not observable. 6432 var nodes = bindingValue['nodes'] || []; 6433 if (ko.isObservable(nodes)) { 6434 throw new Error('The "nodes" option must be a plain, non-observable array.'); 6435 } 6436 6437 // If the nodes are already attached to a KO-generated container, we reuse that container without moving the 6438 // elements to a new one (we check only the first node, as the nodes are always moved together) 6439 var container = nodes[0] && nodes[0].parentNode; 6440 if (!container || !ko.utils.domData.get(container, cleanContainerDomDataKey)) { 6441 container = ko.utils.moveCleanedNodesToContainerElement(nodes); 6442 ko.utils.domData.set(container, cleanContainerDomDataKey, true); 6443 } 6444 6445 new ko.templateSources.anonymousTemplate(element)['nodes'](container); 6446 } else { 6447 // It's an anonymous template - store the element contents, then clear the element 6448 var templateNodes = ko.virtualElements.childNodes(element); 6449 if (templateNodes.length > 0) { 6450 var container = ko.utils.moveCleanedNodesToContainerElement(templateNodes); // This also removes the nodes from their current parent 6451 new ko.templateSources.anonymousTemplate(element)['nodes'](container); 6452 } else { 6453 throw new Error("Anonymous template defined, but no template content was provided"); 6454 } 6455 } 6456 return { 'controlsDescendantBindings': true }; 6457 }, 6458 'update': function (element, valueAccessor, allBindings, viewModel, bindingContext) { 6459 var value = valueAccessor(), 6460 options = ko.utils.unwrapObservable(value), 6461 shouldDisplay = true, 6462 templateComputed = null, 6463 template; 6464 6465 if (typeof options == "string") { 6466 template = value; 6467 options = {}; 6468 } else { 6469 template = 'name' in options ? options['name'] : element; 6470 6471 // Support "if"/"ifnot" conditions 6472 if ('if' in options) 6473 shouldDisplay = ko.utils.unwrapObservable(options['if']); 6474 if (shouldDisplay && 'ifnot' in options) 6475 shouldDisplay = !ko.utils.unwrapObservable(options['ifnot']); 6476 6477 // Don't show anything if an empty name is given (see #2446) 6478 if (shouldDisplay && !template) { 6479 shouldDisplay = false; 6480 } 6481 } 6482 6483 if ('foreach' in options) { 6484 // Render once for each data point (treating data set as empty if shouldDisplay==false) 6485 var dataArray = (shouldDisplay && options['foreach']) || []; 6486 templateComputed = ko.renderTemplateForEach(template, dataArray, options, element, bindingContext); 6487 } else if (!shouldDisplay) { 6488 ko.virtualElements.emptyNode(element); 6489 } else { 6490 // Render once for this single data point (or use the viewModel if no data was provided) 6491 var innerBindingContext = bindingContext; 6492 if ('data' in options) { 6493 innerBindingContext = bindingContext['createChildContext'](options['data'], { 6494 'as': options['as'], 6495 'noChildContext': options['noChildContext'], 6496 'exportDependencies': true 6497 }); 6498 } 6499 templateComputed = ko.renderTemplate(template, innerBindingContext, options, element); 6500 } 6501 6502 // It only makes sense to have a single template computed per element (otherwise which one should have its output displayed?) 6503 disposeOldComputedAndStoreNewOne(element, templateComputed); 6504 } 6505 }; 6506 6507 // Anonymous templates can't be rewritten. Give a nice error message if you try to do it. 6508 ko.expressionRewriting.bindingRewriteValidators['template'] = function (bindingValue) { 6509 var parsedBindingValue = ko.expressionRewriting.parseObjectLiteral(bindingValue); 6510 6511 if ((parsedBindingValue.length == 1) && parsedBindingValue[0]['unknown']) 6512 return null; // It looks like a string literal, not an object literal, so treat it as a named template (which is allowed for rewriting) 6513 6514 if (ko.expressionRewriting.keyValueArrayContainsKey(parsedBindingValue, "name")) 6515 return null; // Named templates can be rewritten, so return "no error" 6516 return "This template engine does not support anonymous templates nested within its templates"; 6517 }; 6518 6519 ko.virtualElements.allowedBindings['template'] = true; 6520 })(); 6521 6522 ko.exportSymbol('setTemplateEngine', ko.setTemplateEngine); 6523 ko.exportSymbol('renderTemplate', ko.renderTemplate); 6524 // Go through the items that have been added and deleted and try to find matches between them. 6525 ko.utils.findMovesInArrayComparison = function (left, right, limitFailedCompares) { 6526 if (left.length && right.length) { 6527 var failedCompares, l, r, leftItem, rightItem; 6528 for (failedCompares = l = 0; (!limitFailedCompares || failedCompares < limitFailedCompares) && (leftItem = left[l]); ++l) { 6529 for (r = 0; rightItem = right[r]; ++r) { 6530 if (leftItem['value'] === rightItem['value']) { 6531 leftItem['moved'] = rightItem['index']; 6532 rightItem['moved'] = leftItem['index']; 6533 right.splice(r, 1); // This item is marked as moved; so remove it from right list 6534 failedCompares = r = 0; // Reset failed compares count because we're checking for consecutive failures 6535 break; 6536 } 6537 } 6538 failedCompares += r; 6539 } 6540 } 6541 }; 6542 6543 ko.utils.compareArrays = (function () { 6544 var statusNotInOld = 'added', statusNotInNew = 'deleted'; 6545 6546 // Simple calculation based on Levenshtein distance. 6547 function compareArrays(oldArray, newArray, options) { 6548 // For backward compatibility, if the third arg is actually a bool, interpret 6549 // it as the old parameter 'dontLimitMoves'. Newer code should use { dontLimitMoves: true }. 6550 options = (typeof options === 'boolean') ? { 'dontLimitMoves': options } : (options || {}); 6551 oldArray = oldArray || []; 6552 newArray = newArray || []; 6553 6554 if (oldArray.length < newArray.length) 6555 return compareSmallArrayToBigArray(oldArray, newArray, statusNotInOld, statusNotInNew, options); 6556 else 6557 return compareSmallArrayToBigArray(newArray, oldArray, statusNotInNew, statusNotInOld, options); 6558 } 6559 6560 function compareSmallArrayToBigArray(smlArray, bigArray, statusNotInSml, statusNotInBig, options) { 6561 var myMin = Math.min, 6562 myMax = Math.max, 6563 editDistanceMatrix = [], 6564 smlIndex, smlIndexMax = smlArray.length, 6565 bigIndex, bigIndexMax = bigArray.length, 6566 compareRange = (bigIndexMax - smlIndexMax) || 1, 6567 maxDistance = smlIndexMax + bigIndexMax + 1, 6568 thisRow, lastRow, 6569 bigIndexMaxForRow, bigIndexMinForRow; 6570 6571 for (smlIndex = 0; smlIndex <= smlIndexMax; smlIndex++) { 6572 lastRow = thisRow; 6573 editDistanceMatrix.push(thisRow = []); 6574 bigIndexMaxForRow = myMin(bigIndexMax, smlIndex + compareRange); 6575 bigIndexMinForRow = myMax(0, smlIndex - 1); 6576 for (bigIndex = bigIndexMinForRow; bigIndex <= bigIndexMaxForRow; bigIndex++) { 6577 if (!bigIndex) 6578 thisRow[bigIndex] = smlIndex + 1; 6579 else if (!smlIndex) // Top row - transform empty array into new array via additions 6580 thisRow[bigIndex] = bigIndex + 1; 6581 else if (smlArray[smlIndex - 1] === bigArray[bigIndex - 1]) 6582 thisRow[bigIndex] = lastRow[bigIndex - 1]; // copy value (no edit) 6583 else { 6584 var northDistance = lastRow[bigIndex] || maxDistance; // not in big (deletion) 6585 var westDistance = thisRow[bigIndex - 1] || maxDistance; // not in small (addition) 6586 thisRow[bigIndex] = myMin(northDistance, westDistance) + 1; 6587 } 6588 } 6589 } 6590 6591 var editScript = [], meMinusOne, notInSml = [], notInBig = []; 6592 for (smlIndex = smlIndexMax, bigIndex = bigIndexMax; smlIndex || bigIndex;) { 6593 meMinusOne = editDistanceMatrix[smlIndex][bigIndex] - 1; 6594 if (bigIndex && meMinusOne === editDistanceMatrix[smlIndex][bigIndex - 1]) { 6595 notInSml.push(editScript[editScript.length] = { // added 6596 'status': statusNotInSml, 6597 'value': bigArray[--bigIndex], 6598 'index': bigIndex 6599 }); 6600 } else if (smlIndex && meMinusOne === editDistanceMatrix[smlIndex - 1][bigIndex]) { 6601 notInBig.push(editScript[editScript.length] = { // deleted 6602 'status': statusNotInBig, 6603 'value': smlArray[--smlIndex], 6604 'index': smlIndex 6605 }); 6606 } else { 6607 --bigIndex; 6608 --smlIndex; 6609 if (!options['sparse']) { 6610 editScript.push({ 6611 'status': "retained", 6612 'value': bigArray[bigIndex] 6613 }); 6614 } 6615 } 6616 } 6617 6618 // Set a limit on the number of consecutive non-matching comparisons; having it a multiple of 6619 // smlIndexMax keeps the time complexity of this algorithm linear. 6620 ko.utils.findMovesInArrayComparison(notInBig, notInSml, !options['dontLimitMoves'] && smlIndexMax * 10); 6621 6622 return editScript.reverse(); 6623 } 6624 6625 return compareArrays; 6626 })(); 6627 6628 ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays); 6629 (function () { 6630 // Objective: 6631 // * Given an input array, a container DOM node, and a function from array elements to arrays of DOM nodes, 6632 // map the array elements to arrays of DOM nodes, concatenate together all these arrays, and use them to populate the container DOM node 6633 // * Next time we're given the same combination of things (with the array possibly having mutated), update the container DOM node 6634 // so that its children is again the concatenation of the mappings of the array elements, but don't re-map any array elements that we 6635 // previously mapped - retain those nodes, and just insert/delete other ones 6636 6637 // "callbackAfterAddingNodes" will be invoked after any "mapping"-generated nodes are inserted into the container node 6638 // You can use this, for example, to activate bindings on those nodes. 6639 6640 function mapNodeAndRefreshWhenChanged(containerNode, mapping, valueToMap, callbackAfterAddingNodes, index) { 6641 // Map this array value inside a dependentObservable so we re-map when any dependency changes 6642 var mappedNodes = []; 6643 var dependentObservable = ko.dependentObservable(function () { 6644 var newMappedNodes = mapping(valueToMap, index, ko.utils.fixUpContinuousNodeArray(mappedNodes, containerNode)) || []; 6645 6646 // On subsequent evaluations, just replace the previously-inserted DOM nodes 6647 if (mappedNodes.length > 0) { 6648 ko.utils.replaceDomNodes(mappedNodes, newMappedNodes); 6649 if (callbackAfterAddingNodes) 6650 ko.dependencyDetection.ignore(callbackAfterAddingNodes, null, [valueToMap, newMappedNodes, index]); 6651 } 6652 6653 // Replace the contents of the mappedNodes array, thereby updating the record 6654 // of which nodes would be deleted if valueToMap was itself later removed 6655 mappedNodes.length = 0; 6656 ko.utils.arrayPushAll(mappedNodes, newMappedNodes); 6657 }, null, { disposeWhenNodeIsRemoved: containerNode, disposeWhen: function () { return !ko.utils.anyDomNodeIsAttachedToDocument(mappedNodes); } }); 6658 return { mappedNodes: mappedNodes, dependentObservable: (dependentObservable.isActive() ? dependentObservable : undefined) }; 6659 } 6660 6661 var lastMappingResultDomDataKey = ko.utils.domData.nextKey(), 6662 deletedItemDummyValue = ko.utils.domData.nextKey(); 6663 6664 ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options, callbackAfterAddingNodes, editScript) { 6665 array = array || []; 6666 if (typeof array.length == "undefined") // Coerce single value into array 6667 array = [array]; 6668 6669 options = options || {}; 6670 var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey); 6671 var isFirstExecution = !lastMappingResult; 6672 6673 // Build the new mapping result 6674 var newMappingResult = []; 6675 var lastMappingResultIndex = 0; 6676 var currentArrayIndex = 0; 6677 6678 var nodesToDelete = []; 6679 var itemsToMoveFirstIndexes = []; 6680 var itemsForBeforeRemoveCallbacks = []; 6681 var itemsForMoveCallbacks = []; 6682 var itemsForAfterAddCallbacks = []; 6683 var mapData; 6684 var countWaitingForRemove = 0; 6685 6686 function itemAdded(value) { 6687 mapData = { arrayEntry: value, indexObservable: ko.observable(currentArrayIndex++) }; 6688 newMappingResult.push(mapData); 6689 if (!isFirstExecution) { 6690 itemsForAfterAddCallbacks.push(mapData); 6691 } 6692 } 6693 6694 function itemMovedOrRetained(oldPosition) { 6695 mapData = lastMappingResult[oldPosition]; 6696 if (currentArrayIndex !== mapData.indexObservable.peek()) 6697 itemsForMoveCallbacks.push(mapData); 6698 // Since updating the index might change the nodes, do so before calling fixUpContinuousNodeArray 6699 mapData.indexObservable(currentArrayIndex++); 6700 ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode); 6701 newMappingResult.push(mapData); 6702 } 6703 6704 function callCallback(callback, items) { 6705 if (callback) { 6706 for (var i = 0, n = items.length; i < n; i++) { 6707 ko.utils.arrayForEach(items[i].mappedNodes, function (node) { 6708 callback(node, i, items[i].arrayEntry); 6709 }); 6710 } 6711 } 6712 } 6713 6714 if (isFirstExecution) { 6715 ko.utils.arrayForEach(array, itemAdded); 6716 } else { 6717 if (!editScript || (lastMappingResult && lastMappingResult['_countWaitingForRemove'])) { 6718 // Compare the provided array against the previous one 6719 var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; }), 6720 compareOptions = { 6721 'dontLimitMoves': options['dontLimitMoves'], 6722 'sparse': true 6723 }; 6724 editScript = ko.utils.compareArrays(lastArray, array, compareOptions); 6725 } 6726 6727 for (var i = 0, editScriptItem, movedIndex, itemIndex; editScriptItem = editScript[i]; i++) { 6728 movedIndex = editScriptItem['moved']; 6729 itemIndex = editScriptItem['index']; 6730 switch (editScriptItem['status']) { 6731 case "deleted": 6732 while (lastMappingResultIndex < itemIndex) { 6733 itemMovedOrRetained(lastMappingResultIndex++); 6734 } 6735 if (movedIndex === undefined) { 6736 mapData = lastMappingResult[lastMappingResultIndex]; 6737 6738 // Stop tracking changes to the mapping for these nodes 6739 if (mapData.dependentObservable) { 6740 mapData.dependentObservable.dispose(); 6741 mapData.dependentObservable = undefined; 6742 } 6743 6744 // Queue these nodes for later removal 6745 if (ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode).length) { 6746 if (options['beforeRemove']) { 6747 newMappingResult.push(mapData); 6748 countWaitingForRemove++; 6749 if (mapData.arrayEntry === deletedItemDummyValue) { 6750 mapData = null; 6751 } else { 6752 itemsForBeforeRemoveCallbacks.push(mapData); 6753 } 6754 } 6755 if (mapData) { 6756 nodesToDelete.push.apply(nodesToDelete, mapData.mappedNodes); 6757 } 6758 } 6759 } 6760 lastMappingResultIndex++; 6761 break; 6762 6763 case "added": 6764 while (currentArrayIndex < itemIndex) { 6765 itemMovedOrRetained(lastMappingResultIndex++); 6766 } 6767 if (movedIndex !== undefined) { 6768 itemsToMoveFirstIndexes.push(newMappingResult.length); 6769 itemMovedOrRetained(movedIndex); 6770 } else { 6771 itemAdded(editScriptItem['value']); 6772 } 6773 break; 6774 } 6775 } 6776 6777 while (currentArrayIndex < array.length) { 6778 itemMovedOrRetained(lastMappingResultIndex++); 6779 } 6780 6781 // Record that the current view may still contain deleted items 6782 // because it means we won't be able to use a provided editScript. 6783 newMappingResult['_countWaitingForRemove'] = countWaitingForRemove; 6784 } 6785 6786 // Store a copy of the array items we just considered so we can difference it next time 6787 ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult); 6788 6789 // Call beforeMove first before any changes have been made to the DOM 6790 callCallback(options['beforeMove'], itemsForMoveCallbacks); 6791 6792 // Next remove nodes for deleted items (or just clean if there's a beforeRemove callback) 6793 ko.utils.arrayForEach(nodesToDelete, options['beforeRemove'] ? ko.cleanNode : ko.removeNode); 6794 6795 var i, j, lastNode, nodeToInsert, mappedNodes, activeElement; 6796 6797 // Since most browsers remove the focus from an element when it's moved to another location, 6798 // save the focused element and try to restore it later. 6799 try { 6800 activeElement = domNode.ownerDocument.activeElement; 6801 } catch (e) { 6802 // IE9 throws if you access activeElement during page load (see issue #703) 6803 } 6804 6805 // Try to reduce overall moved nodes by first moving the ones that were marked as moved by the edit script 6806 if (itemsToMoveFirstIndexes.length) { 6807 while ((i = itemsToMoveFirstIndexes.shift()) != undefined) { 6808 mapData = newMappingResult[i]; 6809 for (lastNode = undefined; i;) { 6810 if ((mappedNodes = newMappingResult[--i].mappedNodes) && mappedNodes.length) { 6811 lastNode = mappedNodes[mappedNodes.length - 1]; 6812 break; 6813 } 6814 } 6815 for (j = 0; nodeToInsert = mapData.mappedNodes[j]; lastNode = nodeToInsert, j++) { 6816 ko.virtualElements.insertAfter(domNode, nodeToInsert, lastNode); 6817 } 6818 } 6819 } 6820 6821 // Next add/reorder the remaining items (will include deleted items if there's a beforeRemove callback) 6822 for (i = 0; mapData = newMappingResult[i]; i++) { 6823 // Get nodes for newly added items 6824 if (!mapData.mappedNodes) 6825 ko.utils.extend(mapData, mapNodeAndRefreshWhenChanged(domNode, mapping, mapData.arrayEntry, callbackAfterAddingNodes, mapData.indexObservable)); 6826 6827 // Put nodes in the right place if they aren't there already 6828 for (j = 0; nodeToInsert = mapData.mappedNodes[j]; lastNode = nodeToInsert, j++) { 6829 ko.virtualElements.insertAfter(domNode, nodeToInsert, lastNode); 6830 } 6831 6832 // Run the callbacks for newly added nodes (for example, to apply bindings, etc.) 6833 if (!mapData.initialized && callbackAfterAddingNodes) { 6834 callbackAfterAddingNodes(mapData.arrayEntry, mapData.mappedNodes, mapData.indexObservable); 6835 mapData.initialized = true; 6836 lastNode = mapData.mappedNodes[mapData.mappedNodes.length - 1]; // get the last node again since it may have been changed by a preprocessor 6837 } 6838 } 6839 6840 // Restore the focused element if it had lost focus 6841 if (activeElement && domNode.ownerDocument.activeElement != activeElement) { 6842 activeElement.focus(); 6843 } 6844 6845 // If there's a beforeRemove callback, call it after reordering. 6846 // Note that we assume that the beforeRemove callback will usually be used to remove the nodes using 6847 // some sort of animation, which is why we first reorder the nodes that will be removed. If the 6848 // callback instead removes the nodes right away, it would be more efficient to skip reordering them. 6849 // Perhaps we'll make that change in the future if this scenario becomes more common. 6850 callCallback(options['beforeRemove'], itemsForBeforeRemoveCallbacks); 6851 6852 // Replace the stored values of deleted items with a dummy value. This provides two benefits: it marks this item 6853 // as already "removed" so we won't call beforeRemove for it again, and it ensures that the item won't match up 6854 // with an actual item in the array and appear as "retained" or "moved". 6855 for (i = 0; i < itemsForBeforeRemoveCallbacks.length; ++i) { 6856 itemsForBeforeRemoveCallbacks[i].arrayEntry = deletedItemDummyValue; 6857 } 6858 6859 // Finally call afterMove and afterAdd callbacks 6860 callCallback(options['afterMove'], itemsForMoveCallbacks); 6861 callCallback(options['afterAdd'], itemsForAfterAddCallbacks); 6862 } 6863 })(); 6864 6865 ko.exportSymbol('utils.setDomNodeChildrenFromArrayMapping', ko.utils.setDomNodeChildrenFromArrayMapping); 6866 ko.nativeTemplateEngine = function () { 6867 this['allowTemplateRewriting'] = false; 6868 } 6869 6870 ko.nativeTemplateEngine.prototype = new ko.templateEngine(); 6871 ko.nativeTemplateEngine.prototype.constructor = ko.nativeTemplateEngine; 6872 ko.nativeTemplateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options, templateDocument) { 6873 var useNodesIfAvailable = !(ko.utils.ieVersion < 9), // IE<9 cloneNode doesn't work properly 6874 templateNodesFunc = useNodesIfAvailable ? templateSource['nodes'] : null, 6875 templateNodes = templateNodesFunc ? templateSource['nodes']() : null; 6876 6877 if (templateNodes) { 6878 return ko.utils.makeArray(templateNodes.cloneNode(true).childNodes); 6879 } else { 6880 var templateText = templateSource['text'](); 6881 return ko.utils.parseHtmlFragment(templateText, templateDocument); 6882 } 6883 }; 6884 6885 ko.nativeTemplateEngine.instance = new ko.nativeTemplateEngine(); 6886 ko.setTemplateEngine(ko.nativeTemplateEngine.instance); 6887 6888 ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine); 6889 (function () { 6890 ko.jqueryTmplTemplateEngine = function () { 6891 // Detect which version of jquery-tmpl you're using. Unfortunately jquery-tmpl 6892 // doesn't expose a version number, so we have to infer it. 6893 // Note that as of Knockout 1.3, we only support jQuery.tmpl 1.0.0pre and later, 6894 // which KO internally refers to as version "2", so older versions are no longer detected. 6895 var jQueryTmplVersion = this.jQueryTmplVersion = (function () { 6896 if (!jQueryInstance || !(jQueryInstance['tmpl'])) 6897 return 0; 6898 // Since it exposes no official version number, we use our own numbering system. To be updated as jquery-tmpl evolves. 6899 try { 6900 if (jQueryInstance['tmpl']['tag']['tmpl']['open'].toString().indexOf('__') >= 0) { 6901 // Since 1.0.0pre, custom tags should append markup to an array called "__" 6902 return 2; // Final version of jquery.tmpl 6903 } 6904 } catch (ex) { /* Apparently not the version we were looking for */ } 6905 6906 return 1; // Any older version that we don't support 6907 })(); 6908 6909 function ensureHasReferencedJQueryTemplates() { 6910 if (jQueryTmplVersion < 2) 6911 throw new Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later."); 6912 } 6913 6914 function executeTemplate(compiledTemplate, data, jQueryTemplateOptions) { 6915 return jQueryInstance['tmpl'](compiledTemplate, data, jQueryTemplateOptions); 6916 } 6917 6918 this['renderTemplateSource'] = function (templateSource, bindingContext, options, templateDocument) { 6919 templateDocument = templateDocument || document; 6920 options = options || {}; 6921 ensureHasReferencedJQueryTemplates(); 6922 6923 // Ensure we have stored a precompiled version of this template (don't want to reparse on every render) 6924 var precompiled = templateSource['data']('precompiled'); 6925 if (!precompiled) { 6926 var templateText = templateSource['text']() || ""; 6927 // Wrap in "with($whatever.koBindingContext) { ... }" 6928 templateText = "{{ko_with $item.koBindingContext}}" + templateText + "{{/ko_with}}"; 6929 6930 precompiled = jQueryInstance['template'](null, templateText); 6931 templateSource['data']('precompiled', precompiled); 6932 } 6933 6934 var data = [bindingContext['$data']]; // Prewrap the data in an array to stop jquery.tmpl from trying to unwrap any arrays 6935 var jQueryTemplateOptions = jQueryInstance['extend']({ 'koBindingContext': bindingContext }, options['templateOptions']); 6936 6937 var resultNodes = executeTemplate(precompiled, data, jQueryTemplateOptions); 6938 resultNodes['appendTo'](templateDocument.createElement("div")); // Using "appendTo" forces jQuery/jQuery.tmpl to perform necessary cleanup work 6939 6940 jQueryInstance['fragments'] = {}; // Clear jQuery's fragment cache to avoid a memory leak after a large number of template renders 6941 return resultNodes; 6942 }; 6943
6944 this['createJavaScriptEvaluatorBlock'] = function (script) { 6945 return "{{ko_code ((function() { return " + script + " })()) }}"; 6946 }; 6947 6948 this['addTemplate'] = function (templateName, templateMarkup) { 6949 document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "<" + "/script>"); 6950 }; 6951 6952 if (jQueryTmplVersion > 0) { 6953 jQueryInstance['tmpl']['tag']['ko_code'] = { 6954 open: "__.push($1 || '');" 6955 }; 6956 jQueryInstance['tmpl']['tag']['ko_with'] = { 6957 open: "with($1) {", 6958 close: "} " 6959 }; 6960 } 6961 }; 6962 6963 ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine(); 6964 ko.jqueryTmplTemplateEngine.prototype.constructor = ko.jqueryTmplTemplateEngine; 6965 6966 // Use this one by default *only if jquery.tmpl is referenced* 6967 var jqueryTmplTemplateEngineInstance = new ko.jqueryTmplTemplateEngine(); 6968 if (jqueryTmplTemplateEngineInstance.jQueryTmplVersion > 0) 6969 ko.setTemplateEngine(jqueryTmplTemplateEngineInstance); 6970 6971 ko.exportSymbol('jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine); 6972 })(); 6973 })); 6974 }()); 6975})(); 6976(function () { function n(n) { var u, i, r, f, t; if (typeof resources == "undefined") throw "ko.localationbinding.getLocalizedText: resources object is not defined"; if (typeof locale == "undefined") throw "ko.localationbinding.getLocalizedText: locale object is not defined"; if (Object.prototype.toString.call(n) === "[object String]" && (n = { key: n }), u = n.key, i = resources[u], !i) throw "ko.localationbinding.getLocalizedText: Could not find key '" + u + "'."; if (r = i[locale] || i["default"] || "", n.params) for (f in n.params) t = n.params[f], typeof t == "function" && (t = ko.utils.unwrapObservable(t())), r = r.replace("#" + f, t); return r } ko.bindingHandlers.restext = { update: function (t, i, r, u, f) { var e = n(ko.utils.unwrapObservable(i())); ko.bindingHandlers.text.update(t, function () { return e }, r, u, f) } }; ko.bindingHandlers.reshtml = { update: function (t, i, r, u, f) { var e = n(ko.utils.unwrapObservable(i())); ko.bindingHandlers.html.update(t, function () { return e }, r, u, f) } }; ko.bindingHandlers.reshref = { update: function (t, i, r, u, f) { var e = n(ko.utils.unwrapObservable(i())); ko.bindingHandlers.attr.update(t, function () { return { href: e } }, r, u, f) } }; ko.bindingHandlers.ressrc = { update: function (t, i, r, u, f) { var e = n(ko.utils.unwrapObservable(i())); ko.bindingHandlers.attr.update(t, function () { return { src: e } }, r, u, f) } }; ko.bindingHandlers.resattr = { update: function (t, i, r, u, f) { var o = ko.utils.unwrapObservable(i()), e, s; for (e in o) s = n(o[e]), ko.bindingHandlers.attr.update(t, function () { var n = {}; return n[e] = s, n }, r, u, f) } }; ko.bindingHandlers.restext.getText = n })(); 6977var nith, locale, parldataWidgetUrl, membersHomeUrl, prevDescription, isMeetingInProgress; 6978 6979var Hoc = { 6980 6981 GetParameterByName: function (name) { 6982 name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); 6983 var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), 6984 results = regex.exec(location.search); 6985 return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); 6986 6987 }, 6988 6989 //Update Intervention ViewModel 6990 //clear Interventions array 6991 UpdateIntervention: function (e) { 6992 6993 nith.resetInterventions(); 6994 $(".interventionsList").empty(); 6995 if (e.Interventions) { 6996 for (var i = 0, len = e.Interventions.length; i < len; i++) { 6997 var memberPhotoPath, personTitleEn, personTitleFr; 6998 6999 if (e.Members !== null && e.Members[i] !== null) { 7000 7001 switch (e.Members[i].AffRolId) { 7002 7003 case 15: //Speaker of the House 7004 memberPhotoPath = e.Interventions[i].Photo.replace('.jpg', '_alt.jpg'); 7005 if (e.Interventions[i].PersonGender === 'F') { 7006 personTitleEn = resources.the_speakerFe[resources.short_language.en]; 7007 personTitleFr = resources.the_speakerFe[resources.short_language.fr]; 7008 } else if (e.Interventions[i].PersonGender === 'M') { 7009 personTitleEn = resources.the_speaker[resources.short_language.en]; 7010 personTitleFr = resources.the_speaker[resources.short_language.fr]; 7011 } 7012 else { 7013 personTitleEn = resources.the_speakerOther[resources.short_language.en]; 7014 personTitleFr = resources.the_speakerOther[resources.short_language.fr]; 7015 } 7016 break; 7017 case 22: //Deputy Speaker and Chair of the Committees of the Whole 7018 memberPhotoPath = e.Interventions[i].Photo.replace('.jpg', '_alt.jpg'); 7019 if (e.Interventions[i].PersonGender === 'F') { 7020 personTitleEn = resources.deputy_speakerFe[resources.short_language.en]; 7021 personTitleFr = resources.deputy_speakerFe[resources.short_language.fr]; 7022 } else if (e.Interventions[i].PersonGender === 'M') { 7023 personTitleEn = resources.deputy_speaker[resources.short_language.en]; 7024 personTitleFr = resources.deputy_speaker[resources.short_language.fr]; 7025 } else { 7026 personTitleEn = resources.deputy_speakerOther[resources.short_language.en]; 7027 personTitleFr = resources.deputy_speakerOther[resources.short_language.fr]; 7028 } 7029 break; 7030 case 92: //Deputy Chair Of Committees of the Whole 7031 memberPhotoPath = e.Interventions[i].Photo.replace('.jpg', '_alt.jpg'); 7032 if (e.Interventions[i].PersonGender === 'F') { 7033 personTitleEn = resources.deputy_chairFe[resources.short_language.en]; 7034 personTitleFr = resources.deputy_chairFe[resources.short_language.fr]; 7035 } else if (e.Interventions[i].PersonGender === 'M') { 7036 personTitleEn = resources.deputy_chair[resources.short_language.en]; 7037 personTitleFr = resources.deputy_chair[resources.short_language.fr]; 7038 } else { 7039 personTitleEn = resources.deputy_chairOther[resources.short_language.en]; 7040 personTitleFr = resources.deputy_chairOther[resources.short_language.fr]; 7041 } 7042 break; 7043 case 93: //Assistant Deputy Chair Of Committees of the Whole 7044 memberPhotoPath = e.Interventions[i].Photo.replace('.jpg', '_alt.jpg'); 7045 if (e.Interventions[i].PersonGender === 'F') { 7046 personTitleEn = resources.assistant_deputy_chairFe[resources.short_language.en]; 7047 personTitleFr = resources.assistant_deputy_chairFe[resources.short_language.fr]; 7048 } else if (e.Interventions[i].PersonGender === 'M') { 7049 personTitleEn = resources.assistant_deputy_chair[resources.short_language.en]; 7050 personTitleFr = resources.assistant_deputy_chair[resources.short_language.fr]; 7051 } else { 7052 personTitleEn = resources.assistant_deputy_chairOther[resources.short_language.en]; 7053 personTitleFr = resources.assistant_deputy_chairOther[resources.short_language.fr]; 7054 } 7055 break; 7056 default: 7057 if (e.Interventions[i].Photo !== null) { 7058 memberPhotoPath = e.Interventions[i].Photo; 7059 } 7060 else { 7061 if (e.Interventions[i].PersonGender === 'F') { 7062 memberPhotoPath = resources.silhouette.f; 7063 } else { 7064 memberPhotoPath = resources.silhouette.m; 7065 } 7066 } 7067 personTitleEn = e.Interventions[i].PersonLongHonorificEn; 7068 personTitleFr = e.Interventions[i].PersonLongHonorificFr; 7069 7070 } 7071 } 7072 7073 nith.addInterventions( 7074 e.Interventions[i].Name, 7075 e.Interventions[i].PartiShortEn, 7076 e.Interventions[i].PartiShortFr, 7077 e.Interventions[i].CaucusWebsiteColor, 7078 personTitleEn,
7079 personTitleFr, 7080 e.Interventions[i].ProvinceEn, 7081 e.Interventions[i].ProvinceFr, 7082 e.Interventions[i].RegionEn, 7083 e.Interventions[i].RegionFr, 7084 memberPhotoPath, 7085 e.Interventions[i].PersonGender, 7086 e.Interventions[i].CurrentPersonId); 7087 } 7088 } 7089 7090 }, 7091 7092 //Initialize SignalR connection 7093 InitSignalR: function () { 7094 var prevNithId = 0; 7095 var debug = Hoc.GetParameterByName('debug'); 7096 var disablejs = Hoc.GetParameterByName('disablejs'); 7097 //Without generated proxy 7098 var url = $('#hoc-real-time-service').data('url'); //TO DO: set var path server side 7099 var connection = $.hubConnection(url); 7100 7101 if (debug === 'true') { 7102 connection.logging = true; 7103 } 7104 7105 //Initialize hub 7106 var parliamentNitHHub = connection.createHubProxy('RealTimeEventHub'); 7107 7108 // register a client method on hub to be invoked by the server 7109 parliamentNitHHub.on('updateNowInTheHouse', function (eventMsg) { 7110 // Debugger: show signalR Events in browser console when enabled 7111 if (debug === 'true') { 7112 console.info("RealTimeEventHub Event received:"); 7113 console.info(eventMsg); 7114 } 7115 if (disablejs === 'true') { 7116 //do nothing, 7117 //emulate javascript disabled 7118 //do not update view from signalR 7119 } else { 7120 //Do not update view from signalR if isMeetingInProgress flag (from Parldata) is false or if signalR EventType is not an adjournement (id:340) 7121 if (prevNithId <= eventMsg.Id && (isMeetingInProgress || eventMsg.EventType !== '340')) { 7122 prevNithId = eventMsg.Id; 7123 Hoc.UpdateNithModel(eventMsg); 7124 } 7125 else { 7126 //If adjournemt event comes from signalR, update view, previNithId will not be 0 7127 if (eventMsg.EventType === '340' && prevNithId > 0) { 7128 Hoc.UpdateNithModel(eventMsg); 7129 } 7130 if (debug === 'true') { 7131 if (prevNithId > eventMsg.Id) { 7132 console.info("*** Incorrect sequence detected, nith.Id(" + eventMsg.Id + ") has been ignored, previous Id was " + prevNithId); 7133 } 7134 if (prevNithId !== 0) { 7135 console.info('Adjournement from signalR'); 7136 } 7137 if (!isMeetingInProgress) { 7138 console.info("Meeting is not in Progress, signalR updates disabled (source Parldata)"); 7139 } 7140 } 7141 } 7142 } 7143 }); 7144 7145 // Start the connection with the server 7146 connection.start({ transport: ['webSockets', 'longPolling'], jsonp: true }) 7147 .done(function (data) { 7148 if (debug === 'true') { 7149 console.info('RealTimeEventHub connected ' + data.transport.name + ', connection ID= ' + data.id); 7150 } 7151 }).fail(function (error) { 7152 if (debug === 'true') { 7153 console.info('Error: ' + error); 7154 } 7155 }); 7156 7157 // SignalR Timeout set by default at 30 seconds 7158 // SginalR KeepAlive set at 10 seconds 7159 // www.asp.net/signalr/overview/guide-to-the-api/handling-connection-lifetime-events#timeoutkeepalive 7160 //Raised when the connection has disconnected, try to restart connection every 5 seconds 7161 7162 connection.disconnected(function () { 7163 7164 $('.connectionStatus').html("<span class='connectionStatusMessage'>" + "<p class='connectionStatusText-slow'>" + resources.error_signalR_connection[locale] + '</p>' + '</span>'); 7165 setTimeout(function () { 7166 console.info('Trying to reconnect...'); 7167 connection.start({ transport: ['webSockets', 'longPolling'], jsonp: true }).done(function () { 7168 console.info('Reconnect succeeded...'); 7169 $('.connectionStatus').html(""); 7170 }).fail(function (error) { 7171 console.info('Error: ' + error); 7172 }); 7173 }, 5000); 7174 }); 7175 7176 //SignalR Slow: raised when the client detects a slow or frequently dropping connection. 7177 connection.connectionSlow(function () { 7178 $('.connectionStatus').html("<span class='connectionStatusMessage-slow'>" + "<p class='connectionStatusText-slow'>" + resources.error_signalR_slow[locale] + '</p>' + '</span>'); 7179 }); 7180 //On reconnect, remove connection status message 7181 connection.reconnected(function () { 7182 $(".connectionStatus").html(); 7183 }); 7184 7185 }, 7186 7187 // Helper to find data in a json object by key and value. 7188 GetObjects: function (obj, key, val) { 7189 7190 var objects = []; 7191 for (var i in obj) { 7192 if (!obj.hasOwnProperty(i)) 7193 continue; 7194 if (typeof obj[i] === 'object') { 7195 objects = objects.concat(this.GetObjects(obj[i], key, val)); 7196 } else if (i === key && obj[key] === val) { 7197 objects.push(obj); 7198 } 7199 } 7200 return objects; 7201 }, 7202 7203 // Generate tooltip Icon with desciption 7204 GetDescription: function (description, obj, lang) { 7205 var html = '<i class="hoc-icons icon-info-circle" data-container="body" data-toggle="tooltip" data-html="true" title="{{title}}" role="tooltip"><span class="visually-hidden">{{title}}</span></i>'; 7206 var tooltip; 7207 if (obj.length > 0) { 7208 html = html.replace("{{title}}", obj[0][lang]); 7209 tooltip = description + ' ' + html; 7210 return tooltip; 7211 } else { 7212 return description; 7213 } 7214 }, 7215 7216 // Find tool tip data if defined in resources.Tooltips array 7217 FindTooltipData: function (description) { 7218 7219 return Hoc.GetObjects(resources.Tooltips, 'titleEn', description); 7220 7221 }, 7222 7223 //Updates NowInTheHouse View Model 7224 UpdateNithModel: function (e) { 7225 7226 Hoc.UpdateIntervention(e); 7227 Hoc.UpdateMember(e); 7228 Hoc.UpdateDecision(e); 7229 Hoc.UpdateItemOfBusiness(e); 7230 Hoc.UpdateExplanatoryContext(e); 7231 Hoc.UpdateSubsequentDivisions(e); 7232 7233 //Set Order Of Business 7234 7235 // if Order of Business is different from previous one - clear tooltip to prevent the popup to be stuck 7236 if (e.DescriptionEn !== prevDescription) { 7237 $('[data-toggle="tooltip"]').tooltip('hide'); 7238 prevDescription = e.DescriptionEn; 7239 } 7240 //Set ParlVU links 7241 nith.parlVuDesktopUrlEn(e.ParlVuDesktopUrlEn); 7242 nith.parlVuDesktopUrlFr(e.ParlVuDesktopUrlFr); 7243 // find Tool tip data 7244 var toolTipObj = Hoc.FindTooltipData(e.DescriptionEn); 7245 7246 // inject tool tip in DOM 7247 nith.descriptionEn(Hoc.GetDescription(e.DescriptionEn, toolTipObj, "en")); 7248 nith.descriptionFr(Hoc.GetDescription(e.DescriptionFr, toolTipObj, "fr")); 7249 7250 //Set EventType 7251 nith.eventType(e.EventType); 7252 7253 //Bells Are Ringing 7254 if (e.BellsAreRinging) { 7255 Hoc.InitBells(e.BellCountdown, e.BellReasonEn, e.BellReasonFr, e.BellStartDateTime); 7256 if (e.BellReasonEn !== "Other") { 7257 nith.bellReasonEn(e.BellReasonEn); 7258 nith.bellReasonFr(e.BellReasonFr); 7259 $(".clock").show(); 7260 $("#clock").show(); 7261 } else { 7262 nith.bellReasonEn(""); 7263 nith.bellReasonFr(""); 7264 $(".clock").show(); 7265 $("#clock").hide(); 7266 } 7267 } else { 7268 $(".clock").hide(); 7269 } 7270 7271 // House Status Message 7272 // If Adjourned hide ParlVu button and remove Description 7273 7274 if (e.EventType === 340) { 7275 nith.houseStatusMessage(resources.house_is_adjourned[locale]); 7276 $('.in-the-house-watchlive-server-side').remove(); 7277 $('.in-the-house-watchlive-client-side').hide(); 7278 $('#now-in-the-housedescription').html(''); 7279 } else { 7280 nith.houseStatusMessage(resources.house_is_sitting[locale]); 7281 $('.in-the-house-watchlive-server-side').remove(); 7282 $('.in-the-house-watchlive-client-side').show(); 7283 } 7284 7285 // Vote in progress message 7286 // Show Live vote message and button link when vote in progress 7287 // =============================================================================================== 7288 // ======= Do to many EventTypeId, we had to use the text compare ================================ 7289 // ======= We don't know for how much time the feature will live, for live Vote - PT 52957 ======= 7290 // =============================================================================================== 7291 if (e.DescriptionEn === 'Votes' || e.DescriptionFr === 'Votes') { 7292 $('.live-vote-message').show(); 7293 } else { 7294 $('.live-vote-message').hide(); 7295 } 7296 7297 //Silence Event 7298 nith.silenceReasonEn(e.SilenceReasonEn); 7299 nith.silenceReasonFr(e.SilenceReasonFr); 7300 7301 //If Event Capture flag is on, show message 7302 if (e.IsOffline === true) { 7303 //$('.connectionStatus').html("<span class='connectionStatusMessage ion-ios-close-outline-custom'>" + "<div class='connectionStatusBar'> </div>" + "<p class='connectionStatusText'>" + resources.error_event_capture_offline[locale] + '</p>' + '</span>'); 7304 console.info(resources.error_event_capture_offline[locale] + " Source Event Capture"); 7305 } 7306 else { 7307 $('.connectionStatus').html(""); 7308 } 7309 7310 if (!(e.EventType === 340 || e.EventType === 337)) { 7311 $('.sync-view').remove(); 7312 $('.async-view').show(); 7313 } 7314 7315 Hoc.InTheHouseContent(); 7316 $('[data-toggle="tooltip"]').tooltip({ delay: { "show": 300, "hide": 50 }, placement: "top" }); 7317 7318 }, 7319 //Update Members ViewModel 7320 //Clear Members array 7321 UpdateMember: function (e) { 7322 7323 nith.resetMembers(); 7324 for (var j = 0, length = e.Members.length; j < length; j++) { 7325 nith.addMembers( 7326 e.Members[j].AffId, 7327 e.Members[j].AffRolId,
7328 e.Members[j].ConId); 7329 } 7330 }, 7331 //Update Decisions ViewModel 7332 //Clear Decision array 7333 UpdateDecision: function (e) { 7334 7335 nith.resetDecisions(); 7336 7337 if (e.Decision) { 7338 nith.addDecision( 7339 e.Decision.BillId, 7340 e.Decision.DecisionDescriptionEn, 7341 e.Decision.DecisionDescriptionFr, 7342 e.Decision.DecisionIdentifierEn, 7343 e.Decision.DecisionIdentifierFr, 7344 e.Decision.DecisionQualifierEn, 7345 e.Decision.DecisionQualifierFr, 7346 e.Decision.SectionTitleEn, 7347 e.Decision.SectionTitleFr, 7348 e.Decision.VoteNays, 7349 e.Decision.VoteResultEn, 7350 e.Decision.VoteResultFr, 7351 e.Decision.VoteYeas, 7352 e.Decision.SpeakerCastingVoteEn, 7353 e.Decision.SpeakerCastingVoteFr 7354 ); 7355 } else { 7356 //When there are no decisions, create the orbject key with no value 7357 nith.addDecision("", "", "", "", "", "", "", "", "", "", "", "", ""); 7358 } 7359 7360 }, 7361 //Update Item of Business ViewModel 7362 UpdateItemOfBusiness: function (e) { 7363 7364 nith.resetItemOfBusiness(); 7365 nith.addItemOfBusiness( 7366 e.IoB.BillId, 7367 e.IoB.ItemDescriptionEn, 7368 e.IoB.ItemDescriptionFr, 7369 e.IoB.ItemId, 7370 e.IoB.ItemIdentifierEn, 7371 e.IoB.ItemIdentifierFr, 7372 e.IoB.ItemQualifierEn, 7373 e.IoB.ItemQualifierFr, 7374 e.IoB.MotionId 7375 ); 7376 7377 }, 7378 //Update Explanatory Context ViewModel 7379 UpdateExplanatoryContext: function (e) { 7380 7381 nith.resetExplanatoryContext(); 7382 nith.addExplanatoryContext( 7383 e.ExplanatoryContext.ContextDescriptionEn, 7384 e.ExplanatoryContext.ContextDescriptionFr, 7385 e.ExplanatoryContext.ContextTitleEn, 7386 e.ExplanatoryContext.ContextTitleFr 7387 ); 7388 7389 }, 7390 //Update SubsequentDivsions ViewModel 7391 UpdateSubsequentDivisions: function (e) { 7392 7393 nith.resetSubsequentDivisions(); 7394 if (e.SubsequentDivisions) { 7395 for (var k = 0, l = e.SubsequentDivisions.length; k < l; k++) { 7396 nith.addSubsequentDivisions( 7397 e.SubsequentDivisions[k].BillId, 7398 e.SubsequentDivisions[k].ItemDescriptionEn, 7399 e.SubsequentDivisions[k].ItemDescriptionFr, 7400 e.SubsequentDivisions[k].ItemId, 7401 e.SubsequentDivisions[k].ItemIdentifierEn, 7402 e.SubsequentDivisions[k].ItemIdentifierFr, 7403 e.SubsequentDivisions[k].ItemQualifierEn, 7404 e.SubsequentDivisions[k].ItemQualifierFr, 7405 e.SubsequentDivisions[k].MotionId 7406 ); 7407 } 7408 } else { 7409 //When there are no subsequentDivisions, create the orbject key with no value 7410 nith.addSubsequentDivisions("", "", "", "", "", "", "", "", ""); 7411 } 7412 7413 }, 7414 // Handle Show More/Show Less logic depending on content height 7415 InTheHouseContent: function () { 7416 7417 var inTheHouseContentHeight = 327; 7418 7419 if ($("#in-the-house-content").hasClass("in")) { 7420 //do nothing 7421 } else if ($(window).width() > 767) { 7422 7423 var inTheHouseHeaderHeight = $('#in-the-house-header').outerHeight(true); 7424 var widgetHeight = parseInt($('.tabbed-widget-content-wrapper').css('min-height'), 10); 7425 var inTheHouseFooterHeight = $('#in-the-house-footer').outerHeight(); 7426 7427 inTheHouseContentHeight = widgetHeight - inTheHouseHeaderHeight - inTheHouseFooterHeight; 7428 7429 $('#in-the-house-content').css('min-height', inTheHouseContentHeight); 7430 $('#in-the-house-content').height(inTheHouseContentHeight); 7431 } 7432 7433 if ($("#in-the-house-content-list").height() > inTheHouseContentHeight) { 7434 $("#in-the-house-show-more").removeClass("invisible"); 7435 } else { 7436 $("#in-the-house-show-more").addClass("invisible"); 7437 } 7438 }, 7439 // Init Bells Are Rining countdown timer 7440 InitBells: function (BellCountdown, BellReasonEn, BellReasonFr, BellStartDateTime) { 7441 7442 console.info('initilizing Bells'); 7443 var dateToCountdownTo = moment(BellStartDateTime); 7444 var bellCountdown = parseInt(BellCountdown); 7445 dateToCountdownTo.add(bellCountdown, 'm'); 7446 7447 //start countdown 7448 $('#clock').countdown(dateToCountdownTo.format('YYYY/MM/DD HH:mm:ss'), function (event) { 7449 var totalHours = event.offset.totalDays * 24 + event.offset.hours; 7450 $(this).html(event.strftime(totalHours + '%-H:%M:%S')); 7451 }); 7452 7453 }, 7454 7455 NowInTheHouseWidget: $(function () { 7456 7457 // set local variables 7458 locale = document.getElementById('culture').value; 7459 parldataWidgetUrl = document.getElementById('parldataWidgets-url').value; 7460 membersHomeUrl = document.getElementById('membersHome-url').value; 7461 isMeetingInProgress = document.getElementById('isMeetingInProgress').value === 'True'; 7462 7463 // init DecisionViewModel and apply knockout bindings 7464 nith = new NowInTheHouseViewModel(); 7465 ko.applyBindings(nith); 7466 7467 Hoc.InitSignalR(); 7468 7469 7470 // Call InTheHouseContent on resize 7471 $(window).resize(function () { 7472 Hoc.InTheHouseContent(); 7473 }); 7474 // TODO change the data-toggle below.
7475 $('#tabbed-widget-in-the-house-tab[data-toggle="tab"]').on('shown.bs.tab', function () { 7476 Hoc.InTheHouseContent(); 7477 }); 7478 7479 $('#in-the-house-show-more-less-link').click(function () { 7480 if ($("#in-the-house-content").hasClass("in")) { 7481 $("#in-the-house-show-more-less-link").html(resources.show_more[locale] + " <i class='hoc-icons icon-down'></i>"); 7482 } 7483 else { 7484 $("#in-the-house-show-more-less-link").html(resources.show_less[locale] + " <i class='hoc-icons icon-up'></i>"); 7485 } 7486 }); 7487 7488 }) 7489}; 7490 7491 7492var NowInTheHouseViewModel = function () { 7493 var self = this; 7494 self.eventId = ko.observable(0); 7495 self.eventType = ko.observable(""); 7496 self.descriptionEn = ko.observable(""); 7497 self.descriptionFr = ko.observable(""); 7498 self.bellsAreRinging = ko.observable(false); 7499 self.bellCountdown = ko.observable(""); 7500 self.bellReasonEn = ko.observable(""); 7501 self.bellReasonFr = ko.observable(""); 7502 self.isSilenced = ko.observable(""); 7503 self.silenceReasonEn = ko.observable(""); 7504 self.silenceReasonFr = ko.observable(""); 7505 self.bellStartDateTime = ko.observable(""); 7506 self.members = ko.observable(""); 7507 self.meetingId = ko.observable(""); 7508 self.isOffline = ko.observable(""); 7509 self.eventId = ko.observable(""); 7510 self.parlVuDesktopUrlEn = ko.observable(""); 7511 self.parlVuDesktopUrlFr = ko.observable(""); 7512 self.parlVuMobileUrlVideoEn = ko.observable(""); 7513 self.parlVuMobileUrlVideoFr = ko.observable(""); 7514 self.parlVuMobileUrlAudioEn = ko.observable(""); 7515 self.parlVuMobileUrlAudioFr = ko.observable(""); 7516 self.houseStatusMessage = ko.observable(""); 7517 7518 //Decision observable 7519 self.decisions = ko.observableArray([]); 7520 self.addDecision = function (billId, decisionDescriptionEn, decisionDescriptionFr, decisionIdentifierEn, decisionIdentifierFr, decisionQualifierEn, decisionQualifierFr, sectionTitleEn, sectionTitleFr, voteNays, voteResultEn, voteResultFr, voteYeas, speakerCastingVoteEn, speakerCastingVoteFr) { 7521 self.decisions.push( 7522 { 7523 billId: billId, 7524 decisionDescriptionEn: decisionDescriptionEn, 7525 decisionDescriptionFr: decisionDescriptionFr, 7526 decisionIdentifierEn: decisionIdentifierEn, 7527 decisionIdentifierFr: decisionIdentifierFr, 7528 decisionQualifierEn: decisionQualifierEn ? " (" + decisionQualifierEn + ")" : "", 7529 decisionQualifierFr: decisionQualifierFr ? " (" + decisionQualifierFr + ")" : "", 7530 sectionTitleEn: sectionTitleEn, 7531 sectionTitleFr: sectionTitleFr, 7532 voteNays: voteNays, 7533 voteNaysEn: (voteYeas > 0 || voteNays > 0) ? " Nays: " + voteNays : "", 7534 voteNaysFr: (voteYeas > 0 || voteNays > 0) ? " Contre : " + voteNays : "", 7535 voteResultEn: voteResultEn + " ", 7536 voteResultFr: voteResultFr + " ", 7537 voteYeas: voteYeas, 7538 voteYeasEn: (voteYeas > 0 || voteNays > 0) ? "Yeas: " + voteYeas + " " : "", 7539 voteYeasFr: (voteYeas > 0 || voteNays > 0) ? "Pour : " + voteYeas + " " : "", 7540 speakerCastingVoteEn: speakerCastingVoteEn ? "Speaker's Casting Vote: " + speakerCastingVoteEn : "", 7541 speakerCastingVoteFr: speakerCastingVoteFr ? "Voix prépondérante du Président : " + speakerCastingVoteFr : "" 7542 }); 7543 }; 7544 7545 //interventions observable 7546 self.interventions = ko.observableArray([]); 7547 self.addInterventions = function (name, caucusEn, caucusFr, caucusWebsiteColor, personLongHonorificEn, personLongHonorificFr, provinceEn, provinceFr, regionEn, regionFr, photo, personGender, personId) { 7548 self.interventions.push( 7549 { 7550 name: name, 7551 caucusEn: caucusEn, 7552 caucusFr: caucusFr, 7553 caucusWebsiteColor: caucusWebsiteColor, 7554 personLongHonorificEn: personLongHonorificEn, 7555 personLongHonorificFr: personLongHonorificFr, 7556 provinceEn: provinceEn, 7557 provinceFr: provinceFr, 7558 regionEn: regionEn, 7559 regionFr: regionFr, 7560 photo: photo, 7561 personGender: ko.observable(personGender), 7562 urlEn: membersHomeUrl.replace("{0}", "en") + "/" + personId, 7563 urlFr: membersHomeUrl.replace("{0}", "fr") + "/" + personId 7564 }); 7565 }; 7566 7567 //members observable 7568 self.members = ko.observableArray([]); 7569 self.addMembers = function (affId, affRoleId, conId) { 7570 self.members.push( 7571 { 7572 affId: ko.observable(affId), 7573 affRoleId: ko.observable(affRoleId), 7574 conId: ko.observable(conId) 7575 }); 7576 }; 7577 7578 //itemOfBusiness observable 7579 self.itemOfBusiness = ko.observableArray([]); 7580 self.addItemOfBusiness = function (billId, itemDescriptionEn, itemDescriptionFr,itemId,itemIdentifierEn,itemIdentifierFr,itemQualifierEn,itemQualifierFr,motionId) { 7581 7582 self.itemOfBusiness.push( 7583 { 7584 billId: billId, 7585 itemDescriptionEn: itemDescriptionEn, 7586 itemDescriptionFr: itemDescriptionFr, 7587 itemId: itemId, 7588 itemIdentifierEn: itemIdentifierEn, 7589 itemIdentifierFr: itemIdentifierFr, 7590 itemQualifierEn: itemQualifierEn !== "" ? " (" + itemQualifierEn + ")" : "", 7591 itemQualifierFr: itemQualifierFr !== "" ? " (" + itemQualifierFr + ")" : "", 7592 motionId: motionId, 7593 urlEn: (billId !== "") ? parldataWidgetUrl + "en/bill/" + billId : ((motionId !== "") ? parldataWidgetUrl + "en/motion/" + motionId : ""), 7594 urlFr: (billId !== "") ? parldataWidgetUrl + "fr/bill/" + billId : ((motionId !== "") ? parldataWidgetUrl + "fr/motion/" + motionId : "") 7595 }); 7596 }; 7597 7598 //explanatoryContext observable 7599 self.explanatoryContext = ko.observableArray([]); 7600 self.addExplanatoryContext = function (contextDescriptionEn, c
vendor: 11,320 bytes, lines 7600-7884
7600ontextDescriptionFr, contextTitleEn, contextTitleFr) { 7601 self.explanatoryContext.push( 7602 { 7603 contextDescriptionEn: contextDescriptionEn, 7604 contextDescriptionFr: contextDescriptionFr, 7605 contextTitleEn: contextTitleEn, 7606 contextTitleFr: contextTitleFr 7607 }); 7608 }; 7609 7610 //subsequentDivisions observable 7611 self.subsequentDivisions = ko.observableArray([]); 7612 self.addSubsequentDivisions = function (billId, itemDescriptionEn, itemDescriptionFr, itemId, itemIdentifierEn, itemIdentifierFr, itemQualifierEn, itemQualifierFr, motionId) { 7613 self.subsequentDivisions.push( 7614 { 7615 titleEn: ko.observable("Votes"), 7616 titleFr: ko.observable("Votes"), 7617 billId: billId, 7618 itemDescriptionEn: itemDescriptionEn, 7619 itemDescriptionFr: itemDescriptionFr, 7620 itemId: itemId, 7621 itemIdentifierEn: itemIdentifierEn, 7622 itemIdentifierFr: itemIdentifierFr, 7623 itemQualifierEn: (itemQualifierEn !== "") ? " (" + itemQualifierEn + ")" : "", 7624 itemQualifierFr: (itemQualifierFr !== "") ? " (" + itemQualifierFr + ")" : "", 7625 motionId: motionId, 7626 urlEn: (billId !== "") ? parldataWidgetUrl + "en/bill/" + billId : parldataWidgetUrl + "en/motion/" + motionId, 7627 urlFr: (billId !== "") ? parldataWidgetUrl + "fr/bill/" + billId : parldataWidgetUrl + "fr/motion/" + motionId 7628 }); 7629 }; 7630 7631 self.resetInterventions = function () { 7632 self.interventions.removeAll(); 7633 }; 7634 7635 self.resetDecisions = function () { 7636 self.decisions.removeAll(); 7637 }; 7638 7639 self.resetItemOfBusiness = function () { 7640 self.itemOfBusiness.removeAll(); 7641 }; 7642 7643 self.resetExplanatoryContext = function () { 7644 self.explanatoryContext.removeAll(); 7645 }; 7646 7647 self.resetSubsequentDivisions = function () { 7648 self.subsequentDivisions.removeAll(); 7649 }; 7650 7651 self.resetMembers = function () { 7652 self.members.removeAll(); 7653 }; 7654}; 7655 7656/*! 7657 * The Final Countdown for jQuery v2.2.0 (http://hilios.github.io/jQuery.countdown/) 7658 * Copyright (c) 2016 Edson Hilios 7659 * 7660 * Permission is hereby granted, free of charge, to any person obtaining a copy of 7661 * this software and associated documentation files (the "Software"), to deal in 7662 * the Software without restriction, including without limitation the rights to 7663 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7664 * the Software, and to permit persons to whom the Software is furnished to do so, 7665 * subject to the following conditions: 7666 * 7667 * The above copyright notice and this permission notice shall be included in all 7668 * copies or substantial portions of the Software. 7669 * 7670 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 7671 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 7672 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 7673 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 7674 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 7675 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 7676 */ 7677(function (factory) { 7678 "use strict"; 7679 if (typeof define === "function" && define.amd) { 7680 define(["jquery"], factory); 7681 } else { 7682 factory(jQuery); 7683 } 7684})(function ($) { 7685 "use strict"; 7686 var instances = [], matchers = [], defaultOptions = { 7687 precision: 100, 7688 elapse: false, 7689 defer: false 7690 }; 7691 matchers.push(/^[0-9]*$/.source); 7692 matchers.push(/([0-9]{1,2}\/){2}[0-9]{4}( [0-9]{1,2}(:[0-9]{2}){2})?/.source); 7693 matchers.push(/[0-9]{4}([\/\-][0-9]{1,2}){2}( [0-9]{1,2}(:[0-9]{2}){2})?/.source); 7694 matchers = new RegExp(matchers.join("|")); 7695 function parseDateString(dateString) { 7696 if (dateString instanceof Date) { 7697 return dateString; 7698 } 7699 if (String(dateString).match(matchers)) { 7700 if (String(dateString).match(/^[0-9]*$/)) { 7701 dateString = Number(dateString); 7702 } 7703 if (String(dateString).match(/\-/)) { 7704 dateString = String(dateString).replace(/\-/g, "/"); 7705 } 7706 return new Date(dateString); 7707 } else { 7708 throw new Error("Couldn't cast `" + dateString + "` to a date object."); 7709 } 7710 } 7711 var DIRECTIVE_KEY_MAP = { 7712 Y: "years", 7713 m: "months", 7714 n: "daysToMonth", 7715 d: "daysToWeek", 7716 w: "weeks", 7717 W: "weeksToMonth", 7718 H: "hours", 7719 M: "minutes", 7720 S: "seconds", 7721 D: "totalDays", 7722 I: "totalHours", 7723 N: "totalMinutes", 7724 T: "totalSeconds" 7725 }; 7726 function escapedRegExp(str) { 7727 var sanitize = str.toString().replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); 7728 return new RegExp(sanitize); 7729 } 7730 function strftime(offsetObject) { 7731 return function (format) { 7732 var directives = format.match(/%(-|!)?[A-Z]{1}(:[^;]+;)?/gi); 7733 if (directives) { 7734 for (var i = 0, len = directives.length; i < len; ++i) { 7735 var directive = directives[i].match(/%(-|!)?([a-zA-Z]{1})(:[^;]+;)?/), regexp = escapedRegExp(directive[0]), modifier = directive[1] || "", plural = directive[3] || "", value = null; 7736 directive = directive[2]; 7737 if (DIRECTIVE_KEY_MAP.hasOwnProperty(directive)) { 7738 value = DIRECTIVE_KEY_MAP[directive]; 7739 value = Number(offsetObject[value]); 7740 } 7741 if (value !== null) { 7742 if (modifier === "!") { 7743 value = pluralize(plural, value); 7744 } 7745 if (modifier === "") { 7746 if (value < 10) { 7747 value = "0" + value.toString(); 7748 } 7749 } 7750 format = format.replace(regexp, value.toString()); 7751 } 7752 } 7753 } 7754 format = format.replace(/%%/, "%"); 7755 return format; 7756 }; 7757 } 7758 function pluralize(format, count) { 7759 var plural = "s", singular = ""; 7760 if (format) { 7761 format = format.replace(/(:|;|\s)/gi, "").split(/\,/); 7762 if (format.length === 1) { 7763 plural = format[0]; 7764 } else { 7765 singular = format[0]; 7766 plural = format[1]; 7767 } 7768 } 7769 if (Math.abs(count) > 1) { 7770 return plural; 7771 } else { 7772 return singular; 7773 } 7774 } 7775 var Countdown = function (el, finalDate, options) { 7776 this.el = el; 7777 this.$el = $(el); 7778 this.interval = null; 7779 this.offset = {}; 7780 this.options = $.extend({}, defaultOptions); 7781 this.instanceNumber = instances.length; 7782 instances.push(this); 7783 this.$el.data("countdown-instance", this.instanceNumber); 7784 if (options) { 7785 if (typeof options === "function") { 7786 this.$el.on("update.countdown", options); 7787 this.$el.on("stoped.countdown", options); 7788 this.$el.on("finish.countdown", options); 7789 } else { 7790 this.options = $.extend({}, defaultOptions, options); 7791 } 7792 } 7793 this.setFinalDate(finalDate); 7794 if (this.options.defer === false) { 7795 this.start(); 7796 } 7797 }; 7798 $.extend(Countdown.prototype, { 7799 start: function () { 7800 if (this.interval !== null) { 7801 clearInterval(this.interval); 7802 } 7803 var self = this; 7804 this.update(); 7805 this.interval = setInterval(function () { 7806 self.update.call(self); 7807 }, this.options.precision); 7808 }, 7809 stop: function () { 7810 clearInterval(this.interval); 7811 this.interval = null; 7812 this.dispatchEvent("stoped"); 7813 }, 7814 toggle: function () { 7815 if (this.interval) { 7816 this.stop(); 7817 } else { 7818 this.start(); 7819 } 7820 }, 7821 pause: function () { 7822 this.stop(); 7823 }, 7824 resume: function () { 7825 this.start(); 7826 }, 7827 remove: function () { 7828 this.stop.call(this); 7829 instances[this.instanceNumber] = null; 7830 delete this.$el.data().countdownInstance; 7831 }, 7832 setFinalDate: function (value) { 7833 this.finalDate = parseDateString(value); 7834 }, 7835 update: function () { 7836 if (this.$el.closest("html").length === 0) { 7837 this.remove(); 7838 return; 7839 } 7840 var hasEventsAttached = $._data(this.el, "events") !== undefined, now = new Date(), newTotalSecsLeft; 7841 newTotalSecsLeft = this.finalDate.getTime() - now.getTime(); 7842 newTotalSecsLeft = Math.ceil(newTotalSecsLeft / 1e3); 7843 newTotalSecsLeft = !this.options.elapse && newTotalSecsLeft < 0 ? 0 : Math.abs(newTotalSecsLeft); 7844 if (this.totalSecsLeft === newTotalSecsLeft || !hasEventsAttached) { 7845 return; 7846 } else { 7847 this.totalSecsLeft = newTotalSecsLeft; 7848 } 7849 this.elapsed = now >= this.finalDate; 7850 this.offset = { 7851 seconds: this.totalSecsLeft % 60, 7852 minutes: Math.floor(this.totalSecsLeft / 60) % 60, 7853 hours: Math.floor(this.totalSecsLeft / 60 / 60) % 24, 7854 days: Math.floor(this.totalSecsLeft / 60 / 60 / 24) % 7, 7855 daysToWeek: Math.floor(this.totalSecsLeft / 60 / 60 / 24) % 7, 7856 daysToMonth: Math.floor(this.totalSecsLeft / 60 / 60 / 24 % 30.4368), 7857 weeks: Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 7), 7858 weeksToMonth: Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 7) % 4, 7859 months: Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 30.4368), 7860 years: Math.abs(this.finalDate.getFullYear() - now.getFullYear()), 7861 totalDays: Math.floor(this.totalSecsLeft / 60 / 60 / 24), 7862 totalHours: Math.floor(this.totalSecsLeft / 60 / 60), 7863 totalMinutes: Math.floor(this.totalSecsLeft / 60), 7864 totalSeconds: this.totalSecsLeft 7865 }; 7866 if (!this.options.elapse && this.totalSecsLeft === 0) { 7867 this.stop(); 7868 this.dispatchEvent("finish"); 7869 } else { 7870 this.dispatchEvent("update"); 7871 } 7872 }, 7873 dispatchEvent: function (eventName) { 7874 var event = $.Event(eventName + ".countdown"); 7875 event.finalDate = this.finalDate; 7876 event.elapsed = this.elapsed; 7877 event.offset = $.extend({}, this.offset); 7878 event.strftime = strftime(this.offset); 7879 this.$el.trigger(event); 7880 } 7881 }); 7882 $.fn.countdown = function () { 7883 var argumentsArray = Array.prototype.slice.call(arguments, 0); 7884 return this.each(function
vendor: 819 bytes, lines 7884-7901
7884() { 7885 var instanceNumber = $(this).data("countdown-instance"); 7886 if (instanceNumber !== undefined) { 7887 var instance = instances[instanceNumber], method = argumentsArray[0]; 7888 if (Countdown.prototype.hasOwnProperty(method)) { 7889 instance[method].apply(instance, argumentsArray.slice(1)); 7890 } else if (String(method).match(/^[$A-Z_][0-9A-Z_$]*$/i) === null) { 7891 instance.setFinalDate.call(instance, method); 7892 instance.start(); 7893 } else { 7894 $.error("Method %s does not exist on jQuery.countdown".replace(/\%s/gi, method)); 7895 } 7896 } else { 7897 new Countdown(this, argumentsArray[0], argumentsArray[1]); 7898 } 7899 }); 7900 }; 7901});
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.