fanforce-plugin-factory 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +21 -0
  3. data/Gemfile +2 -0
  4. data/README.md +218 -0
  5. data/Rakefile +16 -0
  6. data/fanforce-plugin-factory.gemspec +42 -0
  7. data/lib/fanforce/plugin_factory.rake +71 -0
  8. data/lib/fanforce/plugin_factory.rb +60 -0
  9. data/lib/fanforce/plugin_factory/_init_sinatra.rb +53 -0
  10. data/lib/fanforce/plugin_factory/assets/css/add_edit_initiative.scss +1 -0
  11. data/lib/fanforce/plugin_factory/assets/css/add_source.scss +1 -0
  12. data/lib/fanforce/plugin_factory/assets/css/engage.scss +1 -0
  13. data/lib/fanforce/plugin_factory/assets/css/new_message.scss +1 -0
  14. data/lib/fanforce/plugin_factory/assets/css/promotional.scss +1 -0
  15. data/lib/fanforce/plugin_factory/assets/css/source_details.scss +1 -0
  16. data/lib/fanforce/plugin_factory/assets/img/icon-16.png +0 -0
  17. data/lib/fanforce/plugin_factory/assets/img/icon-32.png +0 -0
  18. data/lib/fanforce/plugin_factory/assets/img/icon-42.png +0 -0
  19. data/lib/fanforce/plugin_factory/assets/js/add_edit_initiative.js +1 -0
  20. data/lib/fanforce/plugin_factory/assets/js/add_source.js +1 -0
  21. data/lib/fanforce/plugin_factory/assets/js/add_source_popup.js +0 -0
  22. data/lib/fanforce/plugin_factory/assets/js/engage.js +1 -0
  23. data/lib/fanforce/plugin_factory/assets/js/new_message.js +1 -0
  24. data/lib/fanforce/plugin_factory/assets/js/promotional.js +2 -0
  25. data/lib/fanforce/plugin_factory/assets/js/source_details.js +0 -0
  26. data/lib/fanforce/plugin_factory/assets/lib/common/_mixins.scss +97 -0
  27. data/lib/fanforce/plugin_factory/assets/lib/common/_module-error.scss +11 -0
  28. data/lib/fanforce/plugin_factory/assets/lib/common/_module-search-to-add-initiative.scss +66 -0
  29. data/lib/fanforce/plugin_factory/assets/lib/common/_module-video-thumbnail.scss +15 -0
  30. data/lib/fanforce/plugin_factory/assets/lib/common/_tags.scss +83 -0
  31. data/lib/fanforce/plugin_factory/assets/lib/common/_variables.scss +27 -0
  32. data/lib/fanforce/plugin_factory/assets/lib/common/broadcaster-divider-arrow.png +0 -0
  33. data/lib/fanforce/plugin_factory/assets/lib/common/icons/dropdown-arrow.png +0 -0
  34. data/lib/fanforce/plugin_factory/assets/lib/common/icons/initiative.png +0 -0
  35. data/lib/fanforce/plugin_factory/assets/lib/common/layouts/_add_edit_initiative.scss +23 -0
  36. data/lib/fanforce/plugin_factory/assets/lib/common/layouts/_add_source.scss +17 -0
  37. data/lib/fanforce/plugin_factory/assets/lib/common/layouts/_engage.scss +39 -0
  38. data/lib/fanforce/plugin_factory/assets/lib/common/layouts/_new_message.scss +26 -0
  39. data/lib/fanforce/plugin_factory/assets/lib/common/layouts/_promotional.scss +26 -0
  40. data/lib/fanforce/plugin_factory/assets/lib/common/layouts/_source_details.scss +16 -0
  41. data/lib/fanforce/plugin_factory/assets/lib/common/layouts/add_edit_initiative.js +6 -0
  42. data/lib/fanforce/plugin_factory/assets/lib/common/layouts/add_source.js +6 -0
  43. data/lib/fanforce/plugin_factory/assets/lib/common/layouts/engage.js +0 -0
  44. data/lib/fanforce/plugin_factory/assets/lib/common/layouts/new_message.js +5 -0
  45. data/lib/fanforce/plugin_factory/assets/lib/common/layouts/promotional.js +1 -0
  46. data/lib/fanforce/plugin_factory/assets/lib/common/layouts/source_details.js +5 -0
  47. data/lib/fanforce/plugin_factory/assets/lib/common/module-error/arrow-left.png +0 -0
  48. data/lib/fanforce/plugin_factory/assets/lib/common/module-error/icon-error.png +0 -0
  49. data/lib/fanforce/plugin_factory/assets/lib/common/module-search-to-add-initiative/icon-search.png +0 -0
  50. data/lib/fanforce/plugin_factory/assets/lib/common/module-search-to-add-initiative/spinner.gif +0 -0
  51. data/lib/fanforce/plugin_factory/assets/lib/common/module-search-to-add-initiative/sprite-arrows.png +0 -0
  52. data/lib/fanforce/plugin_factory/assets/lib/common/module-video-thumbnail/icon-play.png +0 -0
  53. data/lib/fanforce/plugin_factory/assets/lib/promotional/fanforce_bg.png +0 -0
  54. data/lib/fanforce/plugin_factory/assets/lib/promotional/icon-42.png +0 -0
  55. data/lib/fanforce/plugin_factory/assets/lib/promotional/icon-behavior.png +0 -0
  56. data/lib/fanforce/plugin_factory/assets/lib/promotional/icon-broadcaster.png +0 -0
  57. data/lib/fanforce/plugin_factory/assets/lib/promotional/icon-data-connector.png +0 -0
  58. data/lib/fanforce/plugin_factory/assets/lib/promotional/icon-data-processor.png +0 -0
  59. data/lib/fanforce/plugin_factory/assets/lib/promotional/icon-identifier.png +0 -0
  60. data/lib/fanforce/plugin_factory/assets/lib/vendors/bootstrap-datepicker/datepicker.css +7 -0
  61. data/lib/fanforce/plugin_factory/assets/lib/vendors/bootstrap-datepicker/datepicker.js +454 -0
  62. data/lib/fanforce/plugin_factory/assets/lib/vendors/bootstrap-timepicker/timepicker.css +82 -0
  63. data/lib/fanforce/plugin_factory/assets/lib/vendors/bootstrap-timepicker/timepicker.js +803 -0
  64. data/lib/fanforce/plugin_factory/assets/lib/vendors/bootstrap/css/bootstrap.css +6103 -0
  65. data/lib/fanforce/plugin_factory/assets/lib/vendors/bootstrap/css/bootstrap.min.css +868 -0
  66. data/lib/fanforce/plugin_factory/assets/lib/vendors/bootstrap/img/glyphicons-halflings-white.png +0 -0
  67. data/lib/fanforce/plugin_factory/assets/lib/vendors/bootstrap/img/glyphicons-halflings.png +0 -0
  68. data/lib/fanforce/plugin_factory/assets/lib/vendors/bootstrap/js/bootstrap.js +2170 -0
  69. data/lib/fanforce/plugin_factory/assets/lib/vendors/bootstrap/js/bootstrap.min.js +7 -0
  70. data/lib/fanforce/plugin_factory/assets/lib/vendors/chosen/chosen-sprite.png +0 -0
  71. data/lib/fanforce/plugin_factory/assets/lib/vendors/chosen/chosen.jquery.min.js +10 -0
  72. data/lib/fanforce/plugin_factory/assets/lib/vendors/chosen/chosen.scss +399 -0
  73. data/lib/fanforce/plugin_factory/assets/lib/vendors/font-awesome/css/font-awesome.css +540 -0
  74. data/lib/fanforce/plugin_factory/assets/lib/vendors/font-awesome/css/font-awesome.min.css +33 -0
  75. data/lib/fanforce/plugin_factory/assets/lib/vendors/font-awesome/font/FontAwesome.otf +0 -0
  76. data/lib/fanforce/plugin_factory/assets/lib/vendors/font-awesome/font/fontawesome-webfont.eot +0 -0
  77. data/lib/fanforce/plugin_factory/assets/lib/vendors/font-awesome/font/fontawesome-webfont.svg +284 -0
  78. data/lib/fanforce/plugin_factory/assets/lib/vendors/font-awesome/font/fontawesome-webfont.ttf +0 -0
  79. data/lib/fanforce/plugin_factory/assets/lib/vendors/font-awesome/font/fontawesome-webfont.woff +0 -0
  80. data/lib/fanforce/plugin_factory/assets/lib/vendors/jquery/jquery.inputHint.js +154 -0
  81. data/lib/fanforce/plugin_factory/assets/lib/vendors/jquery/jquery.tmpl.debug.js +484 -0
  82. data/lib/fanforce/plugin_factory/assets/lib/vendors/knockout/knockout.custom-handlers.coffee +131 -0
  83. data/lib/fanforce/plugin_factory/assets/lib/vendors/knockout/knockout.debug.js +3223 -0
  84. data/lib/fanforce/plugin_factory/assets/lib/vendors/underscore/underscore.debug.js +999 -0
  85. data/lib/fanforce/plugin_factory/config/_error_handling.rb +26 -0
  86. data/lib/fanforce/plugin_factory/config/helpers/assets.rb +23 -0
  87. data/lib/fanforce/plugin_factory/config/helpers/fanforce.rb +21 -0
  88. data/lib/fanforce/plugin_factory/config/helpers/json.rb +10 -0
  89. data/lib/fanforce/plugin_factory/config/sinatra_config.rb +65 -0
  90. data/lib/fanforce/plugin_factory/initializers/ff_globals.rb +72 -0
  91. data/lib/fanforce/plugin_factory/layouts/add_edit_initiative.haml +14 -0
  92. data/lib/fanforce/plugin_factory/layouts/add_source.haml +14 -0
  93. data/lib/fanforce/plugin_factory/layouts/engage.haml +27 -0
  94. data/lib/fanforce/plugin_factory/layouts/new_message.haml +14 -0
  95. data/lib/fanforce/plugin_factory/layouts/promotional.haml +14 -0
  96. data/lib/fanforce/plugin_factory/layouts/source_details.haml +14 -0
  97. data/lib/fanforce/plugin_factory/public/favicon.ico +0 -0
  98. data/lib/fanforce/plugin_factory/routes.rb +35 -0
  99. data/lib/fanforce/plugin_factory/routes_behavior.rb +28 -0
  100. data/lib/fanforce/plugin_factory/routes_broadcaster.rb +9 -0
  101. data/lib/fanforce/plugin_factory/routes_data_connector.rb +21 -0
  102. data/lib/fanforce/plugin_factory/sprockets/compiler.rb +107 -0
  103. data/lib/fanforce/plugin_factory/sprockets/hacks.rb +43 -0
  104. data/lib/fanforce/plugin_factory/version.rb +5 -0
  105. data/lib/fanforce/plugin_factory/views/add_initiative.haml +1 -0
  106. data/lib/fanforce/plugin_factory/views/add_source.haml +1 -0
  107. data/lib/fanforce/plugin_factory/views/close_popup.haml +8 -0
  108. data/lib/fanforce/plugin_factory/views/edit_initiative.haml +1 -0
  109. data/lib/fanforce/plugin_factory/views/engage.haml +1 -0
  110. data/lib/fanforce/plugin_factory/views/index.haml +38 -0
  111. data/lib/fanforce/plugin_factory/views/new_message.haml +1 -0
  112. data/lib/fanforce/plugin_factory/views/source_details.haml +1 -0
  113. data/lib/fanforce/plugin_factory/views/widget_tmpls.haml +1 -0
  114. data/lib/fanforce/plugin_factory_workers.rb +1 -0
  115. metadata +436 -0
@@ -0,0 +1,131 @@
1
+ ko.bindingHandlers.placeholder =
2
+ init: (elem, valueAccessor, allBindingsAccessor, viewModel) ->
3
+ update: (elem, valueAccessor, allBindingsAccessor) ->
4
+ text = ko.utils.unwrapObservable valueAccessor()
5
+ $(elem).attr('placeholder', text)
6
+ $(elem).inputHint()
7
+
8
+ ko.bindingHandlers.validation =
9
+ init: (elem, valueAccessor, allBindingsAccessor, viewModel) ->
10
+ validation = ko.utils.unwrapObservable valueAccessor()
11
+ validation(elem)
12
+
13
+ ko.bindingHandlers.showWithSlide =
14
+ init: (elem, valueAccessor, allBindingsAccessor, viewModel) ->
15
+ shouldBeVisible = ko.utils.unwrapObservable valueAccessor()
16
+ if shouldBeVisible then $(elem).show() else $(elem).hide()
17
+ $(elem).removeClass('hide')
18
+
19
+ update: (elem, valueAccessor, allBindingsAccessor) ->
20
+ shouldBeVisible = ko.utils.unwrapObservable valueAccessor()
21
+ duration = allBindingsAccessor().slideDuration || 600
22
+ if shouldBeVisible then $(elem).slideDown(duration)
23
+ else $(elem).slideUp(duration)
24
+
25
+ ko.bindingHandlers.fadeIn =
26
+ init: (elem, valueAccessor, allBindingsAccessor, viewModel) ->
27
+ shouldBeVisible = ko.utils.unwrapObservable valueAccessor()
28
+ if shouldBeVisible then $(elem).show() else $(elem).hide()
29
+ $(elem).removeClass('hide')
30
+
31
+ update: (elem, valueAccessor, allBindingsAccessor) ->
32
+ shouldBeVisible = ko.utils.unwrapObservable valueAccessor()
33
+ duration = allBindingsAccessor().slideDuration || 600
34
+ if shouldBeVisible then $(elem).fadeIn(duration)
35
+ else $(elem).fadeOut(duration)
36
+
37
+ ko.bindingHandlers.show =
38
+ update: (elem, valueAccessor, allBindingsAccessor, viewModel) ->
39
+ shouldBeVisible = ko.utils.unwrapObservable valueAccessor()
40
+ if shouldBeVisible then $(elem).show() else $(elem).hide()
41
+ $(elem).removeClass('hide')
42
+
43
+ ko.bindingHandlers.hide =
44
+ update: (elem, valueAccessor, allBindingsAccessor, viewModel) ->
45
+ shouldBeHidden = ko.utils.unwrapObservable valueAccessor()
46
+ if shouldBeHidden then $(elem).hide() else $(elem).show()
47
+ $(elem).removeClass('hide')
48
+
49
+ ko.bindingHandlers.chosen =
50
+ init: (element, valueAccessor, allBindingsAccessor, viewModel)->
51
+ chosen = allBindingsAccessor().chosen
52
+ settings = {default: 'Select one...'}
53
+ $.extend(settings, chosen.settings) if chosen.settings?
54
+ $(element).attr('data-placeholder', settings.default)
55
+
56
+ update: (element, valueAccessor, allBindingsAccessor, viewModel)->
57
+ chosen = allBindingsAccessor().chosen
58
+ settings = {default: 'Select one...', disable_search_threshold: 10}
59
+ $.extend(settings, chosen.settings) if chosen.settings?
60
+
61
+ selectedOptions = if chosen.selectedOptions? then chosen.selectedOptions() else []
62
+ selectedOptions.push(chosen.selectedOption()) if chosen.selectedOption?
63
+
64
+ $(element).html('')
65
+ $(element).append("<option value=''></options>") if settings.allow_single_deselect? and settings.allow_single_deselect
66
+
67
+ for option in chosen.data()
68
+ otitle = (if typeof option == 'string' then option else if $.isPlainObject(option) then option.title)
69
+ ovalue = (if typeof option == 'string' then option else if $.isPlainObject(option) then option.value)
70
+ selected_attr = if $.inArray(ovalue, selectedOptions) >= 0 then ' selected=selected' else ''
71
+ $(element).append("<option value='#{ovalue}'#{selected_attr}>#{otitle}</option>")
72
+
73
+ if option.sub_options? and option.sub_options.length > 0 then for suboption in option.sub_options
74
+ otitle = (if typeof suboption == 'string' then suboption else if $.isPlainObject(suboption) then suboption.title)
75
+ ovalue = (if typeof suboption == 'string' then suboption else if $.isPlainObject(suboption) then suboption.value)
76
+ selected_attr = if $.inArray(ovalue, selectedOptions) >= 0 then ' selected=selected' else ''
77
+ $(element).append("<option value='#{ovalue}'#{selected_attr}>&nbsp;&nbsp; - #{otitle}</option>")
78
+
79
+ $(element).unbind('change.chosen').chosen(settings).bind 'change.chosen', (event)->
80
+ if $(element).context.type == 'select-multiple'
81
+ chosen.writeSelectedOptions(if $(element).val()? then $(element).val() else []) if chosen.writeSelectedOptions?
82
+ return
83
+ ids_array = $(element).val().split('::')
84
+ if ids_array.length > 1
85
+ module_title = $(element).find("option[value='#{ids_array[0]}']").text()
86
+ fs_title = $(element).find('option:selected').html().replace('&nbsp;&nbsp; - ', '')
87
+ title = "<span class='parent_module'>#{module_title}</span>: #{fs_title}"
88
+ $('#' + $(element).attr('id') + '_chzn').find('a.chzn-single span').html(title)
89
+
90
+ $(element).trigger("liszt:updated")# if $(element).context.type != 'select-multiple'
91
+
92
+ ko.bindingHandlers.class =
93
+ update: (element, valueAccessor)->
94
+ if element['__ko__previousClassValue__']
95
+ $(element).removeClass(element['__ko__previousClassValue__'])
96
+
97
+ value = ko.utils.unwrapObservable(valueAccessor())
98
+ $(element).addClass(value)
99
+ element['__ko__previousClassValue__'] = value
100
+
101
+ ko.bindingHandlers.pageSlider =
102
+ init: (element, valueAccessor, allBindingsAccessor, viewModel) ->
103
+ allBindings = allBindingsAccessor()
104
+ maxPageObservable = if typeof allBindings.maxPage == 'undefined' then viewModel.maxPage else allBindings.maxPage
105
+ maxPageUnwrapped = ko.utils.unwrapObservable(maxPageObservable)
106
+ pageObservable = valueAccessor()
107
+
108
+ if typeof maxPageUnwrapped == 'undefined'
109
+ throw Error("A maxPage needs to exist in the model or specified as maxPage on the data-bind")
110
+ return false
111
+
112
+ $(element).addClass 'paging_slider'
113
+
114
+ # Setup the paginateSlider to update the page observable
115
+ $(element).paginateSlider(maxPageUnwrapped).bind 'slidechange', (e, ui)->
116
+ pageObservable ui.value
117
+
118
+ # Any changes to the maxPage will change the pagination numPage
119
+ maxPageObservable.subscribe (newValue) ->
120
+ $(element).paginateSlider 'numPages', newValue
121
+
122
+ update: (element, valueAccessor) ->
123
+ $(element).paginateSlider 'currentPage', ko.utils.unwrapObservable(valueAccessor())
124
+
125
+
126
+ BINDINGS_TO_APPLY = {}
127
+ ko.addBindings = (bindings) ->
128
+ for k,v of bindings
129
+ BINDINGS_TO_APPLY[k] = v
130
+
131
+ $ -> ko.applyBindings BINDINGS_TO_APPLY
@@ -0,0 +1,3223 @@
1
+ // Knockout JavaScript library v2.0.0
2
+ // (c) Steven Sanderson - http://knockoutjs.com/
3
+ // License: MIT (http://www.opensource.org/licenses/mit-license.php)
4
+
5
+ (function(window,undefined){
6
+ var ko = window["ko"] = {};
7
+ // Google Closure Compiler helpers (used only to make the minified file smaller)
8
+ ko.exportSymbol = function(publicPath, object) {
9
+ var tokens = publicPath.split(".");
10
+ var target = window;
11
+ for (var i = 0; i < tokens.length - 1; i++)
12
+ target = target[tokens[i]];
13
+ target[tokens[tokens.length - 1]] = object;
14
+ };
15
+ ko.exportProperty = function(owner, publicName, object) {
16
+ owner[publicName] = object;
17
+ };
18
+ ko.utils = new (function () {
19
+ var stringTrimRegex = /^(\s|\u00A0)+|(\s|\u00A0)+$/g;
20
+
21
+ // 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)
22
+ var knownEvents = {}, knownEventTypesByEventName = {};
23
+ var keyEventTypeName = /Firefox\/2/i.test(navigator.userAgent) ? 'KeyboardEvent' : 'UIEvents';
24
+ knownEvents[keyEventTypeName] = ['keyup', 'keydown', 'keypress'];
25
+ knownEvents['MouseEvents'] = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseover', 'mouseout', 'mouseenter', 'mouseleave'];
26
+ for (var eventType in knownEvents) {
27
+ var knownEventsForType = knownEvents[eventType];
28
+ if (knownEventsForType.length) {
29
+ for (var i = 0, j = knownEventsForType.length; i < j; i++)
30
+ knownEventTypesByEventName[knownEventsForType[i]] = eventType;
31
+ }
32
+ }
33
+
34
+ // Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness)
35
+ var ieVersion = (function() {
36
+ var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i');
37
+
38
+ // Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment
39
+ while (
40
+ div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->',
41
+ iElems[0]
42
+ );
43
+ return version > 4 ? version : undefined;
44
+ }());
45
+ var isIe6 = ieVersion === 6,
46
+ isIe7 = ieVersion === 7;
47
+
48
+ function isClickOnCheckableElement(element, eventType) {
49
+ if ((element.tagName != "INPUT") || !element.type) return false;
50
+ if (eventType.toLowerCase() != "click") return false;
51
+ var inputType = element.type.toLowerCase();
52
+ return (inputType == "checkbox") || (inputType == "radio");
53
+ }
54
+
55
+ return {
56
+ fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/],
57
+
58
+ arrayForEach: function (array, action) {
59
+ for (var i = 0, j = array.length; i < j; i++)
60
+ action(array[i]);
61
+ },
62
+
63
+ arrayIndexOf: function (array, item) {
64
+ if (typeof Array.prototype.indexOf == "function")
65
+ return Array.prototype.indexOf.call(array, item);
66
+ for (var i = 0, j = array.length; i < j; i++)
67
+ if (array[i] === item)
68
+ return i;
69
+ return -1;
70
+ },
71
+
72
+ arrayFirst: function (array, predicate, predicateOwner) {
73
+ for (var i = 0, j = array.length; i < j; i++)
74
+ if (predicate.call(predicateOwner, array[i]))
75
+ return array[i];
76
+ return null;
77
+ },
78
+
79
+ arrayRemoveItem: function (array, itemToRemove) {
80
+ var index = ko.utils.arrayIndexOf(array, itemToRemove);
81
+ if (index >= 0)
82
+ array.splice(index, 1);
83
+ },
84
+
85
+ arrayGetDistinctValues: function (array) {
86
+ array = array || [];
87
+ var result = [];
88
+ for (var i = 0, j = array.length; i < j; i++) {
89
+ if (ko.utils.arrayIndexOf(result, array[i]) < 0)
90
+ result.push(array[i]);
91
+ }
92
+ return result;
93
+ },
94
+
95
+ arrayMap: function (array, mapping) {
96
+ array = array || [];
97
+ var result = [];
98
+ for (var i = 0, j = array.length; i < j; i++)
99
+ result.push(mapping(array[i]));
100
+ return result;
101
+ },
102
+
103
+ arrayFilter: function (array, predicate) {
104
+ array = array || [];
105
+ var result = [];
106
+ for (var i = 0, j = array.length; i < j; i++)
107
+ if (predicate(array[i]))
108
+ result.push(array[i]);
109
+ return result;
110
+ },
111
+
112
+ arrayPushAll: function (array, valuesToPush) {
113
+ for (var i = 0, j = valuesToPush.length; i < j; i++)
114
+ array.push(valuesToPush[i]);
115
+ return array;
116
+ },
117
+
118
+ extend: function (target, source) {
119
+ for(var prop in source) {
120
+ if(source.hasOwnProperty(prop)) {
121
+ target[prop] = source[prop];
122
+ }
123
+ }
124
+ return target;
125
+ },
126
+
127
+ emptyDomNode: function (domNode) {
128
+ while (domNode.firstChild) {
129
+ ko.removeNode(domNode.firstChild);
130
+ }
131
+ },
132
+
133
+ setDomNodeChildren: function (domNode, childNodes) {
134
+ ko.utils.emptyDomNode(domNode);
135
+ if (childNodes) {
136
+ ko.utils.arrayForEach(childNodes, function (childNode) {
137
+ domNode.appendChild(childNode);
138
+ });
139
+ }
140
+ },
141
+
142
+ replaceDomNodes: function (nodeToReplaceOrNodeArray, newNodesArray) {
143
+ var nodesToReplaceArray = nodeToReplaceOrNodeArray.nodeType ? [nodeToReplaceOrNodeArray] : nodeToReplaceOrNodeArray;
144
+ if (nodesToReplaceArray.length > 0) {
145
+ var insertionPoint = nodesToReplaceArray[0];
146
+ var parent = insertionPoint.parentNode;
147
+ for (var i = 0, j = newNodesArray.length; i < j; i++)
148
+ parent.insertBefore(newNodesArray[i], insertionPoint);
149
+ for (var i = 0, j = nodesToReplaceArray.length; i < j; i++) {
150
+ ko.removeNode(nodesToReplaceArray[i]);
151
+ }
152
+ }
153
+ },
154
+
155
+ setOptionNodeSelectionState: function (optionNode, isSelected) {
156
+ // IE6 sometimes throws "unknown error" if you try to write to .selected directly, whereas Firefox struggles with setAttribute. Pick one based on browser.
157
+ if (navigator.userAgent.indexOf("MSIE 6") >= 0)
158
+ optionNode.setAttribute("selected", isSelected);
159
+ else
160
+ optionNode.selected = isSelected;
161
+ },
162
+
163
+ stringTrim: function (string) {
164
+ return (string || "").replace(stringTrimRegex, "");
165
+ },
166
+
167
+ stringTokenize: function (string, delimiter) {
168
+ var result = [];
169
+ var tokens = (string || "").split(delimiter);
170
+ for (var i = 0, j = tokens.length; i < j; i++) {
171
+ var trimmed = ko.utils.stringTrim(tokens[i]);
172
+ if (trimmed !== "")
173
+ result.push(trimmed);
174
+ }
175
+ return result;
176
+ },
177
+
178
+ stringStartsWith: function (string, startsWith) {
179
+ string = string || "";
180
+ if (startsWith.length > string.length)
181
+ return false;
182
+ return string.substring(0, startsWith.length) === startsWith;
183
+ },
184
+
185
+ evalWithinScope: function (expression /*, scope1, scope2, scope3... */) {
186
+ // Build the source for a function that evaluates "expression"
187
+ // For each scope variable, add an extra level of "with" nesting
188
+ // Example result: with(sc[1]) { with(sc[0]) { return (expression) } }
189
+ var scopes = Array.prototype.slice.call(arguments, 1);
190
+ var functionBody = "return (" + expression + ")";
191
+ for (var i = 0; i < scopes.length; i++) {
192
+ if (scopes[i] && typeof scopes[i] == "object")
193
+ functionBody = "with(sc[" + i + "]) { " + functionBody + " } ";
194
+ }
195
+ return (new Function("sc", functionBody))(scopes);
196
+ },
197
+
198
+ domNodeIsContainedBy: function (node, containedByNode) {
199
+ if (containedByNode.compareDocumentPosition)
200
+ return (containedByNode.compareDocumentPosition(node) & 16) == 16;
201
+ while (node != null) {
202
+ if (node == containedByNode)
203
+ return true;
204
+ node = node.parentNode;
205
+ }
206
+ return false;
207
+ },
208
+
209
+ domNodeIsAttachedToDocument: function (node) {
210
+ return ko.utils.domNodeIsContainedBy(node, document);
211
+ },
212
+
213
+ registerEventHandler: function (element, eventType, handler) {
214
+ if (typeof jQuery != "undefined") {
215
+ if (isClickOnCheckableElement(element, eventType)) {
216
+ // For click events on checkboxes, jQuery interferes with the event handling in an awkward way:
217
+ // it toggles the element checked state *after* the click event handlers run, whereas native
218
+ // click events toggle the checked state *before* the event handler.
219
+ // Fix this by intecepting the handler and applying the correct checkedness before it runs.
220
+ var originalHandler = handler;
221
+ handler = function(event, eventData) {
222
+ var jQuerySuppliedCheckedState = this.checked;
223
+ if (eventData)
224
+ this.checked = eventData.checkedStateBeforeEvent !== true;
225
+ originalHandler.call(this, event);
226
+ this.checked = jQuerySuppliedCheckedState; // Restore the state jQuery applied
227
+ };
228
+ }
229
+ jQuery(element)['bind'](eventType, handler);
230
+ } else if (typeof element.addEventListener == "function")
231
+ element.addEventListener(eventType, handler, false);
232
+ else if (typeof element.attachEvent != "undefined")
233
+ element.attachEvent("on" + eventType, function (event) {
234
+ handler.call(element, event);
235
+ });
236
+ else
237
+ throw new Error("Browser doesn't support addEventListener or attachEvent");
238
+ },
239
+
240
+ triggerEvent: function (element, eventType) {
241
+ if (!(element && element.nodeType))
242
+ throw new Error("element must be a DOM node when calling triggerEvent");
243
+
244
+ if (typeof jQuery != "undefined") {
245
+ var eventData = [];
246
+ if (isClickOnCheckableElement(element, eventType)) {
247
+ // Work around the jQuery "click events on checkboxes" issue described above by storing the original checked state before triggering the handler
248
+ eventData.push({ checkedStateBeforeEvent: element.checked });
249
+ }
250
+ jQuery(element)['trigger'](eventType, eventData);
251
+ } else if (typeof document.createEvent == "function") {
252
+ if (typeof element.dispatchEvent == "function") {
253
+ var eventCategory = knownEventTypesByEventName[eventType] || "HTMLEvents";
254
+ var event = document.createEvent(eventCategory);
255
+ event.initEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element);
256
+ element.dispatchEvent(event);
257
+ }
258
+ else
259
+ throw new Error("The supplied element doesn't support dispatchEvent");
260
+ } else if (typeof element.fireEvent != "undefined") {
261
+ // Unlike other browsers, IE doesn't change the checked state of checkboxes/radiobuttons when you trigger their "click" event
262
+ // so to make it consistent, we'll do it manually here
263
+ if (eventType == "click") {
264
+ if ((element.tagName == "INPUT") && ((element.type.toLowerCase() == "checkbox") || (element.type.toLowerCase() == "radio")))
265
+ element.checked = element.checked !== true;
266
+ }
267
+ element.fireEvent("on" + eventType);
268
+ }
269
+ else
270
+ throw new Error("Browser doesn't support triggering events");
271
+ },
272
+
273
+ unwrapObservable: function (value) {
274
+ return ko.isObservable(value) ? value() : value;
275
+ },
276
+
277
+ domNodeHasCssClass: function (node, className) {
278
+ var currentClassNames = (node.className || "").split(/\s+/);
279
+ return ko.utils.arrayIndexOf(currentClassNames, className) >= 0;
280
+ },
281
+
282
+ toggleDomNodeCssClass: function (node, className, shouldHaveClass) {
283
+ var hasClass = ko.utils.domNodeHasCssClass(node, className);
284
+ if (shouldHaveClass && !hasClass) {
285
+ node.className = (node.className || "") + " " + className;
286
+ } else if (hasClass && !shouldHaveClass) {
287
+ var currentClassNames = (node.className || "").split(/\s+/);
288
+ var newClassName = "";
289
+ for (var i = 0; i < currentClassNames.length; i++)
290
+ if (currentClassNames[i] != className)
291
+ newClassName += currentClassNames[i] + " ";
292
+ node.className = ko.utils.stringTrim(newClassName);
293
+ }
294
+ },
295
+
296
+ outerHTML: function(node) {
297
+ // For Chrome on non-text nodes
298
+ // (Although IE supports outerHTML, the way it formats HTML is inconsistent - sometimes closing </li> tags are omitted, sometimes not. That caused https://github.com/SteveSanderson/knockout/issues/212.)
299
+ if (ieVersion === undefined) {
300
+ var nativeOuterHtml = node.outerHTML;
301
+ if (typeof nativeOuterHtml == "string")
302
+ return nativeOuterHtml;
303
+ }
304
+
305
+ // Other browsers
306
+ var dummyContainer = window.document.createElement("div");
307
+ dummyContainer.appendChild(node.cloneNode(true));
308
+ return dummyContainer.innerHTML;
309
+ },
310
+
311
+ setTextContent: function(element, textContent) {
312
+ var value = ko.utils.unwrapObservable(textContent);
313
+ if ((value === null) || (value === undefined))
314
+ value = "";
315
+
316
+ 'innerText' in element ? element.innerText = value
317
+ : element.textContent = value;
318
+
319
+ if (ieVersion >= 9) {
320
+ // Believe it or not, this actually fixes an IE9 rendering bug. Insane. https://github.com/SteveSanderson/knockout/issues/209
321
+ element.innerHTML = element.innerHTML;
322
+ }
323
+ },
324
+
325
+ range: function (min, max) {
326
+ min = ko.utils.unwrapObservable(min);
327
+ max = ko.utils.unwrapObservable(max);
328
+ var result = [];
329
+ for (var i = min; i <= max; i++)
330
+ result.push(i);
331
+ return result;
332
+ },
333
+
334
+ makeArray: function(arrayLikeObject) {
335
+ var result = [];
336
+ for (var i = 0, j = arrayLikeObject.length; i < j; i++) {
337
+ result.push(arrayLikeObject[i]);
338
+ };
339
+ return result;
340
+ },
341
+
342
+ isIe6 : isIe6,
343
+ isIe7 : isIe7,
344
+
345
+ getFormFields: function(form, fieldName) {
346
+ var fields = ko.utils.makeArray(form.getElementsByTagName("INPUT")).concat(ko.utils.makeArray(form.getElementsByTagName("TEXTAREA")));
347
+ var isMatchingField = (typeof fieldName == 'string')
348
+ ? function(field) { return field.name === fieldName }
349
+ : function(field) { return fieldName.test(field.name) }; // Treat fieldName as regex or object containing predicate
350
+ var matches = [];
351
+ for (var i = fields.length - 1; i >= 0; i--) {
352
+ if (isMatchingField(fields[i]))
353
+ matches.push(fields[i]);
354
+ };
355
+ return matches;
356
+ },
357
+
358
+ parseJson: function (jsonString) {
359
+ if (typeof jsonString == "string") {
360
+ jsonString = ko.utils.stringTrim(jsonString);
361
+ if (jsonString) {
362
+ if (window.JSON && window.JSON.parse) // Use native parsing where available
363
+ return window.JSON.parse(jsonString);
364
+ return (new Function("return " + jsonString))(); // Fallback on less safe parsing for older browsers
365
+ }
366
+ }
367
+ return null;
368
+ },
369
+
370
+ stringifyJson: function (data) {
371
+ if ((typeof JSON == "undefined") || (typeof JSON.stringify == "undefined"))
372
+ 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");
373
+ return JSON.stringify(ko.utils.unwrapObservable(data));
374
+ },
375
+
376
+ postJson: function (urlOrForm, data, options) {
377
+ options = options || {};
378
+ var params = options['params'] || {};
379
+ var includeFields = options['includeFields'] || this.fieldsIncludedWithJsonPost;
380
+ var url = urlOrForm;
381
+
382
+ // If we were given a form, use its 'action' URL and pick out any requested field values
383
+ if((typeof urlOrForm == 'object') && (urlOrForm.tagName == "FORM")) {
384
+ var originalForm = urlOrForm;
385
+ url = originalForm.action;
386
+ for (var i = includeFields.length - 1; i >= 0; i--) {
387
+ var fields = ko.utils.getFormFields(originalForm, includeFields[i]);
388
+ for (var j = fields.length - 1; j >= 0; j--)
389
+ params[fields[j].name] = fields[j].value;
390
+ }
391
+ }
392
+
393
+ data = ko.utils.unwrapObservable(data);
394
+ var form = document.createElement("FORM");
395
+ form.style.display = "none";
396
+ form.action = url;
397
+ form.method = "post";
398
+ for (var key in data) {
399
+ var input = document.createElement("INPUT");
400
+ input.name = key;
401
+ input.value = ko.utils.stringifyJson(ko.utils.unwrapObservable(data[key]));
402
+ form.appendChild(input);
403
+ }
404
+ for (var key in params) {
405
+ var input = document.createElement("INPUT");
406
+ input.name = key;
407
+ input.value = params[key];
408
+ form.appendChild(input);
409
+ }
410
+ document.body.appendChild(form);
411
+ options['submitter'] ? options['submitter'](form) : form.submit();
412
+ setTimeout(function () { form.parentNode.removeChild(form); }, 0);
413
+ }
414
+ }
415
+ })();
416
+
417
+ ko.exportSymbol('ko.utils', ko.utils);
418
+ ko.utils.arrayForEach([
419
+ ['arrayForEach', ko.utils.arrayForEach],
420
+ ['arrayFirst', ko.utils.arrayFirst],
421
+ ['arrayFilter', ko.utils.arrayFilter],
422
+ ['arrayGetDistinctValues', ko.utils.arrayGetDistinctValues],
423
+ ['arrayIndexOf', ko.utils.arrayIndexOf],
424
+ ['arrayMap', ko.utils.arrayMap],
425
+ ['arrayPushAll', ko.utils.arrayPushAll],
426
+ ['arrayRemoveItem', ko.utils.arrayRemoveItem],
427
+ ['extend', ko.utils.extend],
428
+ ['fieldsIncludedWithJsonPost', ko.utils.fieldsIncludedWithJsonPost],
429
+ ['getFormFields', ko.utils.getFormFields],
430
+ ['postJson', ko.utils.postJson],
431
+ ['parseJson', ko.utils.parseJson],
432
+ ['registerEventHandler', ko.utils.registerEventHandler],
433
+ ['stringifyJson', ko.utils.stringifyJson],
434
+ ['range', ko.utils.range],
435
+ ['toggleDomNodeCssClass', ko.utils.toggleDomNodeCssClass],
436
+ ['triggerEvent', ko.utils.triggerEvent],
437
+ ['unwrapObservable', ko.utils.unwrapObservable]
438
+ ], function(item) {
439
+ ko.exportSymbol('ko.utils.' + item[0], item[1]);
440
+ });
441
+
442
+ if (!Function.prototype['bind']) {
443
+ // 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)
444
+ // In case the browser doesn't implement it natively, provide a JavaScript implementation. This implementation is based on the one in prototype.js
445
+ Function.prototype['bind'] = function (object) {
446
+ var originalFunction = this, args = Array.prototype.slice.call(arguments), object = args.shift();
447
+ return function () {
448
+ return originalFunction.apply(object, args.concat(Array.prototype.slice.call(arguments)));
449
+ };
450
+ };
451
+ }
452
+ ko.utils.domData = new (function () {
453
+ var uniqueId = 0;
454
+ var dataStoreKeyExpandoPropertyName = "__ko__" + (new Date).getTime();
455
+ var dataStore = {};
456
+ return {
457
+ get: function (node, key) {
458
+ var allDataForNode = ko.utils.domData.getAll(node, false);
459
+ return allDataForNode === undefined ? undefined : allDataForNode[key];
460
+ },
461
+ set: function (node, key, value) {
462
+ if (value === undefined) {
463
+ // Make sure we don't actually create a new domData key if we are actually deleting a value
464
+ if (ko.utils.domData.getAll(node, false) === undefined)
465
+ return;
466
+ }
467
+ var allDataForNode = ko.utils.domData.getAll(node, true);
468
+ allDataForNode[key] = value;
469
+ },
470
+ getAll: function (node, createIfNotFound) {
471
+ var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
472
+ var hasExistingDataStore = dataStoreKey && (dataStoreKey !== "null");
473
+ if (!hasExistingDataStore) {
474
+ if (!createIfNotFound)
475
+ return undefined;
476
+ dataStoreKey = node[dataStoreKeyExpandoPropertyName] = "ko" + uniqueId++;
477
+ dataStore[dataStoreKey] = {};
478
+ }
479
+ return dataStore[dataStoreKey];
480
+ },
481
+ clear: function (node) {
482
+ var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
483
+ if (dataStoreKey) {
484
+ delete dataStore[dataStoreKey];
485
+ node[dataStoreKeyExpandoPropertyName] = null;
486
+ }
487
+ }
488
+ }
489
+ })();
490
+
491
+ ko.exportSymbol('ko.utils.domData', ko.utils.domData);
492
+ ko.exportSymbol('ko.utils.domData.clear', ko.utils.domData.clear); // Exporting only so specs can clear up after themselves fully
493
+ ko.utils.domNodeDisposal = new (function () {
494
+ var domDataKey = "__ko_domNodeDisposal__" + (new Date).getTime();
495
+
496
+ function getDisposeCallbacksCollection(node, createIfNotFound) {
497
+ var allDisposeCallbacks = ko.utils.domData.get(node, domDataKey);
498
+ if ((allDisposeCallbacks === undefined) && createIfNotFound) {
499
+ allDisposeCallbacks = [];
500
+ ko.utils.domData.set(node, domDataKey, allDisposeCallbacks);
501
+ }
502
+ return allDisposeCallbacks;
503
+ }
504
+ function destroyCallbacksCollection(node) {
505
+ ko.utils.domData.set(node, domDataKey, undefined);
506
+ }
507
+
508
+ function cleanSingleNode(node) {
509
+ // Run all the dispose callbacks
510
+ var callbacks = getDisposeCallbacksCollection(node, false);
511
+ if (callbacks) {
512
+ callbacks = callbacks.slice(0); // Clone, as the array may be modified during iteration (typically, callbacks will remove themselves)
513
+ for (var i = 0; i < callbacks.length; i++)
514
+ callbacks[i](node);
515
+ }
516
+
517
+ // Also erase the DOM data
518
+ ko.utils.domData.clear(node);
519
+
520
+ // Special support for jQuery here because it's so commonly used.
521
+ // Many jQuery plugins (including jquery.tmpl) store data using jQuery's equivalent of domData
522
+ // so notify it to tear down any resources associated with the node & descendants here.
523
+ if ((typeof jQuery == "function") && (typeof jQuery['cleanData'] == "function"))
524
+ jQuery['cleanData']([node]);
525
+ }
526
+
527
+ return {
528
+ addDisposeCallback : function(node, callback) {
529
+ if (typeof callback != "function")
530
+ throw new Error("Callback must be a function");
531
+ getDisposeCallbacksCollection(node, true).push(callback);
532
+ },
533
+
534
+ removeDisposeCallback : function(node, callback) {
535
+ var callbacksCollection = getDisposeCallbacksCollection(node, false);
536
+ if (callbacksCollection) {
537
+ ko.utils.arrayRemoveItem(callbacksCollection, callback);
538
+ if (callbacksCollection.length == 0)
539
+ destroyCallbacksCollection(node);
540
+ }
541
+ },
542
+
543
+ cleanNode : function(node) {
544
+ if ((node.nodeType != 1) && (node.nodeType != 9))
545
+ return;
546
+ cleanSingleNode(node);
547
+
548
+ // Clone the descendants list in case it changes during iteration
549
+ var descendants = [];
550
+ ko.utils.arrayPushAll(descendants, node.getElementsByTagName("*"));
551
+ for (var i = 0, j = descendants.length; i < j; i++)
552
+ cleanSingleNode(descendants[i]);
553
+ },
554
+
555
+ removeNode : function(node) {
556
+ ko.cleanNode(node);
557
+ if (node.parentNode)
558
+ node.parentNode.removeChild(node);
559
+ }
560
+ }
561
+ })();
562
+ ko.cleanNode = ko.utils.domNodeDisposal.cleanNode; // Shorthand name for convenience
563
+ ko.removeNode = ko.utils.domNodeDisposal.removeNode; // Shorthand name for convenience
564
+ ko.exportSymbol('ko.cleanNode', ko.cleanNode);
565
+ ko.exportSymbol('ko.removeNode', ko.removeNode);
566
+ ko.exportSymbol('ko.utils.domNodeDisposal', ko.utils.domNodeDisposal);
567
+ ko.exportSymbol('ko.utils.domNodeDisposal.addDisposeCallback', ko.utils.domNodeDisposal.addDisposeCallback);
568
+ ko.exportSymbol('ko.utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeDisposal.removeDisposeCallback);(function () {
569
+ var leadingCommentRegex = /^(\s*)<!--(.*?)-->/;
570
+
571
+ function simpleHtmlParse(html) {
572
+ // Based on jQuery's "clean" function, but only accounting for table-related elements.
573
+ // If you have referenced jQuery, this won't be used anyway - KO will use jQuery's "clean" function directly
574
+
575
+ // Note that there's still an issue in IE < 9 whereby it will discard comment nodes that are the first child of
576
+ // a descendant node. For example: "<div><!-- mycomment -->abc</div>" will get parsed as "<div>abc</div>"
577
+ // This won't affect anyone who has referenced jQuery, and there's always the workaround of inserting a dummy node
578
+ // (possibly a text node) in front of the comment. So, KO does not attempt to workaround this IE issue automatically at present.
579
+
580
+ // Trim whitespace, otherwise indexOf won't work as expected
581
+ var tags = ko.utils.stringTrim(html).toLowerCase(), div = document.createElement("div");
582
+
583
+ // Finds the first match from the left column, and returns the corresponding "wrap" data from the right column
584
+ var wrap = tags.match(/^<(thead|tbody|tfoot)/) && [1, "<table>", "</table>"] ||
585
+ !tags.indexOf("<tr") && [2, "<table><tbody>", "</tbody></table>"] ||
586
+ (!tags.indexOf("<td") || !tags.indexOf("<th")) && [3, "<table><tbody><tr>", "</tr></tbody></table>"] ||
587
+ /* anything else */ [0, "", ""];
588
+
589
+ // Go to html and back, then peel off extra wrappers
590
+ // Note that we always prefix with some dummy text, because otherwise, IE<9 will strip out leading comment nodes in descendants. Total madness.
591
+ var markup = "ignored<div>" + wrap[1] + html + wrap[2] + "</div>";
592
+ if (typeof window['innerShiv'] == "function") {
593
+ div.appendChild(window['innerShiv'](markup));
594
+ } else {
595
+ div.innerHTML = markup;
596
+ }
597
+
598
+ // Move to the right depth
599
+ while (wrap[0]--)
600
+ div = div.lastChild;
601
+
602
+ return ko.utils.makeArray(div.lastChild.childNodes);
603
+ }
604
+
605
+ function jQueryHtmlParse(html) {
606
+ var elems = jQuery['clean']([html]);
607
+
608
+ // 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.
609
+ // Unfortunately, it never clears the dummy parent nodes from the document fragment, so it leaks memory over time.
610
+ // Fix this by finding the top-most dummy parent element, and detaching it from its owner fragment.
611
+ if (elems && elems[0]) {
612
+ // Find the top-most parent element that's a direct child of a document fragment
613
+ var elem = elems[0];
614
+ while (elem.parentNode && elem.parentNode.nodeType !== 11 /* i.e., DocumentFragment */)
615
+ elem = elem.parentNode;
616
+ // ... then detach it
617
+ if (elem.parentNode)
618
+ elem.parentNode.removeChild(elem);
619
+ }
620
+
621
+ return elems;
622
+ }
623
+
624
+ ko.utils.parseHtmlFragment = function(html) {
625
+ return typeof jQuery != 'undefined' ? jQueryHtmlParse(html) // As below, benefit from jQuery's optimisations where possible
626
+ : simpleHtmlParse(html); // ... otherwise, this simple logic will do in most common cases.
627
+ };
628
+
629
+ ko.utils.setHtml = function(node, html) {
630
+ ko.utils.emptyDomNode(node);
631
+
632
+ if ((html !== null) && (html !== undefined)) {
633
+ if (typeof html != 'string')
634
+ html = html.toString();
635
+
636
+ // jQuery contains a lot of sophisticated code to parse arbitrary HTML fragments,
637
+ // for example <tr> elements which are not normally allowed to exist on their own.
638
+ // If you've referenced jQuery we'll use that rather than duplicating its code.
639
+ if (typeof jQuery != 'undefined') {
640
+ jQuery(node)['html'](html);
641
+ } else {
642
+ // ... otherwise, use KO's own parsing logic.
643
+ var parsedNodes = ko.utils.parseHtmlFragment(html);
644
+ for (var i = 0; i < parsedNodes.length; i++)
645
+ node.appendChild(parsedNodes[i]);
646
+ }
647
+ }
648
+ };
649
+ })();
650
+
651
+ ko.exportSymbol('ko.utils.parseHtmlFragment', ko.utils.parseHtmlFragment);
652
+ ko.exportSymbol('ko.utils.setHtml', ko.utils.setHtml);
653
+ ko.memoization = (function () {
654
+ var memos = {};
655
+
656
+ function randomMax8HexChars() {
657
+ return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1);
658
+ }
659
+ function generateRandomId() {
660
+ return randomMax8HexChars() + randomMax8HexChars();
661
+ }
662
+ function findMemoNodes(rootNode, appendToArray) {
663
+ if (!rootNode)
664
+ return;
665
+ if (rootNode.nodeType == 8) {
666
+ var memoId = ko.memoization.parseMemoText(rootNode.nodeValue);
667
+ if (memoId != null)
668
+ appendToArray.push({ domNode: rootNode, memoId: memoId });
669
+ } else if (rootNode.nodeType == 1) {
670
+ for (var i = 0, childNodes = rootNode.childNodes, j = childNodes.length; i < j; i++)
671
+ findMemoNodes(childNodes[i], appendToArray);
672
+ }
673
+ }
674
+
675
+ return {
676
+ memoize: function (callback) {
677
+ if (typeof callback != "function")
678
+ throw new Error("You can only pass a function to ko.memoization.memoize()");
679
+ var memoId = generateRandomId();
680
+ memos[memoId] = callback;
681
+ return "<!--[ko_memo:" + memoId + "]-->";
682
+ },
683
+
684
+ unmemoize: function (memoId, callbackParams) {
685
+ var callback = memos[memoId];
686
+ if (callback === undefined)
687
+ throw new Error("Couldn't find any memo with ID " + memoId + ". Perhaps it's already been unmemoized.");
688
+ try {
689
+ callback.apply(null, callbackParams || []);
690
+ return true;
691
+ }
692
+ finally { delete memos[memoId]; }
693
+ },
694
+
695
+ unmemoizeDomNodeAndDescendants: function (domNode, extraCallbackParamsArray) {
696
+ var memos = [];
697
+ findMemoNodes(domNode, memos);
698
+ for (var i = 0, j = memos.length; i < j; i++) {
699
+ var node = memos[i].domNode;
700
+ var combinedParams = [node];
701
+ if (extraCallbackParamsArray)
702
+ ko.utils.arrayPushAll(combinedParams, extraCallbackParamsArray);
703
+ ko.memoization.unmemoize(memos[i].memoId, combinedParams);
704
+ node.nodeValue = ""; // Neuter this node so we don't try to unmemoize it again
705
+ if (node.parentNode)
706
+ 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)
707
+ }
708
+ },
709
+
710
+ parseMemoText: function (memoText) {
711
+ var match = memoText.match(/^\[ko_memo\:(.*?)\]$/);
712
+ return match ? match[1] : null;
713
+ }
714
+ };
715
+ })();
716
+
717
+ ko.exportSymbol('ko.memoization', ko.memoization);
718
+ ko.exportSymbol('ko.memoization.memoize', ko.memoization.memoize);
719
+ ko.exportSymbol('ko.memoization.unmemoize', ko.memoization.unmemoize);
720
+ ko.exportSymbol('ko.memoization.parseMemoText', ko.memoization.parseMemoText);
721
+ ko.exportSymbol('ko.memoization.unmemoizeDomNodeAndDescendants', ko.memoization.unmemoizeDomNodeAndDescendants);
722
+ ko.extenders = {
723
+ 'throttle': function(target, timeout) {
724
+ // Throttling means two things:
725
+
726
+ // (1) For dependent observables, we throttle *evaluations* so that, no matter how fast its dependencies
727
+ // notify updates, the target doesn't re-evaluate (and hence doesn't notify) faster than a certain rate
728
+ target['throttleEvaluation'] = timeout;
729
+
730
+ // (2) For writable targets (observables, or writable dependent observables), we throttle *writes*
731
+ // so the target cannot change value synchronously or faster than a certain rate
732
+ var writeTimeoutInstance = null;
733
+ return ko.dependentObservable({
734
+ 'read': target,
735
+ 'write': function(value) {
736
+ clearTimeout(writeTimeoutInstance);
737
+ writeTimeoutInstance = setTimeout(function() {
738
+ target(value);
739
+ }, timeout);
740
+ }
741
+ });
742
+ },
743
+
744
+ 'notify': function(target, notifyWhen) {
745
+ target["equalityComparer"] = notifyWhen == "always"
746
+ ? function() { return false } // Treat all values as not equal
747
+ : ko.observable["fn"]["equalityComparer"];
748
+ return target;
749
+ }
750
+ };
751
+
752
+ function applyExtenders(requestedExtenders) {
753
+ var target = this;
754
+ if (requestedExtenders) {
755
+ for (var key in requestedExtenders) {
756
+ var extenderHandler = ko.extenders[key];
757
+ if (typeof extenderHandler == 'function') {
758
+ target = extenderHandler(target, requestedExtenders[key]);
759
+ }
760
+ }
761
+ }
762
+ return target;
763
+ }
764
+
765
+ ko.exportSymbol('ko.extenders', ko.extenders);
766
+ ko.subscription = function (callback, disposeCallback) {
767
+ this.callback = callback;
768
+ this.disposeCallback = disposeCallback;
769
+ ko.exportProperty(this, 'dispose', this.dispose);
770
+ };
771
+ ko.subscription.prototype.dispose = function () {
772
+ this.isDisposed = true;
773
+ this.disposeCallback();
774
+ };
775
+
776
+ ko.subscribable = function () {
777
+ this._subscriptions = {};
778
+
779
+ ko.utils.extend(this, ko.subscribable['fn']);
780
+ ko.exportProperty(this, 'subscribe', this.subscribe);
781
+ ko.exportProperty(this, 'extend', this.extend);
782
+ ko.exportProperty(this, 'getSubscriptionsCount', this.getSubscriptionsCount);
783
+ }
784
+
785
+ var defaultEvent = "change";
786
+
787
+ ko.subscribable['fn'] = {
788
+ subscribe: function (callback, callbackTarget, event) {
789
+ event = event || defaultEvent;
790
+ var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback;
791
+
792
+ var subscription = new ko.subscription(boundCallback, function () {
793
+ ko.utils.arrayRemoveItem(this._subscriptions[event], subscription);
794
+ }.bind(this));
795
+
796
+ if (!this._subscriptions[event])
797
+ this._subscriptions[event] = [];
798
+ this._subscriptions[event].push(subscription);
799
+ return subscription;
800
+ },
801
+
802
+ "notifySubscribers": function (valueToNotify, event) {
803
+ event = event || defaultEvent;
804
+ if (this._subscriptions[event]) {
805
+ ko.utils.arrayForEach(this._subscriptions[event].slice(0), function (subscription) {
806
+ // In case a subscription was disposed during the arrayForEach cycle, check
807
+ // for isDisposed on each subscription before invoking its callback
808
+ if (subscription && (subscription.isDisposed !== true))
809
+ subscription.callback(valueToNotify);
810
+ });
811
+ }
812
+ },
813
+
814
+ getSubscriptionsCount: function () {
815
+ var total = 0;
816
+ for (var eventName in this._subscriptions) {
817
+ if (this._subscriptions.hasOwnProperty(eventName))
818
+ total += this._subscriptions[eventName].length;
819
+ }
820
+ return total;
821
+ },
822
+
823
+ extend: applyExtenders
824
+ };
825
+
826
+
827
+ ko.isSubscribable = function (instance) {
828
+ return typeof instance.subscribe == "function" && typeof instance["notifySubscribers"] == "function";
829
+ };
830
+
831
+ ko.exportSymbol('ko.subscribable', ko.subscribable);
832
+ ko.exportSymbol('ko.isSubscribable', ko.isSubscribable);
833
+
834
+ ko.dependencyDetection = (function () {
835
+ var _frames = [];
836
+
837
+ return {
838
+ begin: function (callback) {
839
+ _frames.push({ callback: callback, distinctDependencies:[] });
840
+ },
841
+
842
+ end: function () {
843
+ _frames.pop();
844
+ },
845
+
846
+ registerDependency: function (subscribable) {
847
+ if (!ko.isSubscribable(subscribable))
848
+ throw "Only subscribable things can act as dependencies";
849
+ if (_frames.length > 0) {
850
+ var topFrame = _frames[_frames.length - 1];
851
+ if (ko.utils.arrayIndexOf(topFrame.distinctDependencies, subscribable) >= 0)
852
+ return;
853
+ topFrame.distinctDependencies.push(subscribable);
854
+ topFrame.callback(subscribable);
855
+ }
856
+ }
857
+ };
858
+ })();var primitiveTypes = { 'undefined':true, 'boolean':true, 'number':true, 'string':true };
859
+
860
+ ko.observable = function (initialValue) {
861
+ var _latestValue = initialValue;
862
+
863
+ function observable() {
864
+ if (arguments.length > 0) {
865
+ // Write
866
+
867
+ // Ignore writes if the value hasn't changed
868
+ if ((!observable['equalityComparer']) || !observable['equalityComparer'](_latestValue, arguments[0])) {
869
+ observable.valueWillMutate();
870
+ _latestValue = arguments[0];
871
+ observable.valueHasMutated();
872
+ }
873
+ return this; // Permits chained assignments
874
+ }
875
+ else {
876
+ // Read
877
+ ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
878
+ return _latestValue;
879
+ }
880
+ }
881
+ ko.subscribable.call(observable);
882
+ observable.valueHasMutated = function () { observable["notifySubscribers"](_latestValue); }
883
+ observable.valueWillMutate = function () { observable["notifySubscribers"](_latestValue, "beforeChange"); }
884
+ ko.utils.extend(observable, ko.observable['fn']);
885
+
886
+ ko.exportProperty(observable, "valueHasMutated", observable.valueHasMutated);
887
+ ko.exportProperty(observable, "valueWillMutate", observable.valueWillMutate);
888
+
889
+ return observable;
890
+ }
891
+
892
+ ko.observable['fn'] = {
893
+ __ko_proto__: ko.observable,
894
+
895
+ "equalityComparer": function valuesArePrimitiveAndEqual(a, b) {
896
+ var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes);
897
+ return oldValueIsPrimitive ? (a === b) : false;
898
+ }
899
+ };
900
+
901
+ ko.isObservable = function (instance) {
902
+ if ((instance === null) || (instance === undefined) || (instance.__ko_proto__ === undefined)) return false;
903
+ if (instance.__ko_proto__ === ko.observable) return true;
904
+ return ko.isObservable(instance.__ko_proto__); // Walk the prototype chain
905
+ }
906
+ ko.isWriteableObservable = function (instance) {
907
+ // Observable
908
+ if ((typeof instance == "function") && instance.__ko_proto__ === ko.observable)
909
+ return true;
910
+ // Writeable dependent observable
911
+ if ((typeof instance == "function") && (instance.__ko_proto__ === ko.dependentObservable) && (instance.hasWriteFunction))
912
+ return true;
913
+ // Anything else
914
+ return false;
915
+ }
916
+
917
+
918
+ ko.exportSymbol('ko.observable', ko.observable);
919
+ ko.exportSymbol('ko.isObservable', ko.isObservable);
920
+ ko.exportSymbol('ko.isWriteableObservable', ko.isWriteableObservable);
921
+ ko.observableArray = function (initialValues) {
922
+ if (arguments.length == 0) {
923
+ // Zero-parameter constructor initializes to empty array
924
+ initialValues = [];
925
+ }
926
+ if ((initialValues !== null) && (initialValues !== undefined) && !('length' in initialValues))
927
+ throw new Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");
928
+
929
+ var result = new ko.observable(initialValues);
930
+ ko.utils.extend(result, ko.observableArray['fn']);
931
+
932
+ ko.exportProperty(result, "remove", result.remove);
933
+ ko.exportProperty(result, "removeAll", result.removeAll);
934
+ ko.exportProperty(result, "destroy", result.destroy);
935
+ ko.exportProperty(result, "destroyAll", result.destroyAll);
936
+ ko.exportProperty(result, "indexOf", result.indexOf);
937
+ ko.exportProperty(result, "replace", result.replace);
938
+
939
+ return result;
940
+ }
941
+
942
+ ko.observableArray['fn'] = {
943
+ remove: function (valueOrPredicate) {
944
+ var underlyingArray = this();
945
+ var removedValues = [];
946
+ var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
947
+ for (var i = 0; i < underlyingArray.length; i++) {
948
+ var value = underlyingArray[i];
949
+ if (predicate(value)) {
950
+ if (removedValues.length === 0) {
951
+ this.valueWillMutate();
952
+ }
953
+ removedValues.push(value);
954
+ underlyingArray.splice(i, 1);
955
+ i--;
956
+ }
957
+ }
958
+ if (removedValues.length) {
959
+ this.valueHasMutated();
960
+ }
961
+ return removedValues;
962
+ },
963
+
964
+ removeAll: function (arrayOfValues) {
965
+ // If you passed zero args, we remove everything
966
+ if (arrayOfValues === undefined) {
967
+ var underlyingArray = this();
968
+ var allValues = underlyingArray.slice(0);
969
+ this.valueWillMutate();
970
+ underlyingArray.splice(0, underlyingArray.length);
971
+ this.valueHasMutated();
972
+ return allValues;
973
+ }
974
+ // If you passed an arg, we interpret it as an array of entries to remove
975
+ if (!arrayOfValues)
976
+ return [];
977
+ return this.remove(function (value) {
978
+ return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
979
+ });
980
+ },
981
+
982
+ destroy: function (valueOrPredicate) {
983
+ var underlyingArray = this();
984
+ var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
985
+ this.valueWillMutate();
986
+ for (var i = underlyingArray.length - 1; i >= 0; i--) {
987
+ var value = underlyingArray[i];
988
+ if (predicate(value))
989
+ underlyingArray[i]["_destroy"] = true;
990
+ }
991
+ this.valueHasMutated();
992
+ },
993
+
994
+ destroyAll: function (arrayOfValues) {
995
+ // If you passed zero args, we destroy everything
996
+ if (arrayOfValues === undefined)
997
+ return this.destroy(function() { return true });
998
+
999
+ // If you passed an arg, we interpret it as an array of entries to destroy
1000
+ if (!arrayOfValues)
1001
+ return [];
1002
+ return this.destroy(function (value) {
1003
+ return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
1004
+ });
1005
+ },
1006
+
1007
+ indexOf: function (item) {
1008
+ var underlyingArray = this();
1009
+ return ko.utils.arrayIndexOf(underlyingArray, item);
1010
+ },
1011
+
1012
+ replace: function(oldItem, newItem) {
1013
+ var index = this.indexOf(oldItem);
1014
+ if (index >= 0) {
1015
+ this.valueWillMutate();
1016
+ this()[index] = newItem;
1017
+ this.valueHasMutated();
1018
+ }
1019
+ }
1020
+ }
1021
+
1022
+ // Populate ko.observableArray.fn with read/write functions from native arrays
1023
+ ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function (methodName) {
1024
+ ko.observableArray['fn'][methodName] = function () {
1025
+ var underlyingArray = this();
1026
+ this.valueWillMutate();
1027
+ var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments);
1028
+ this.valueHasMutated();
1029
+ return methodCallResult;
1030
+ };
1031
+ });
1032
+
1033
+ // Populate ko.observableArray.fn with read-only functions from native arrays
1034
+ ko.utils.arrayForEach(["slice"], function (methodName) {
1035
+ ko.observableArray['fn'][methodName] = function () {
1036
+ var underlyingArray = this();
1037
+ return underlyingArray[methodName].apply(underlyingArray, arguments);
1038
+ };
1039
+ });
1040
+
1041
+ ko.exportSymbol('ko.observableArray', ko.observableArray);
1042
+ function prepareOptions(evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) {
1043
+ if (evaluatorFunctionOrOptions && typeof evaluatorFunctionOrOptions == "object") {
1044
+ // Single-parameter syntax - everything is on this "options" param
1045
+ options = evaluatorFunctionOrOptions;
1046
+ } else {
1047
+ // Multi-parameter syntax - construct the options according to the params passed
1048
+ options = options || {};
1049
+ options["read"] = evaluatorFunctionOrOptions || options["read"];
1050
+ }
1051
+ // By here, "options" is always non-null
1052
+
1053
+ if (typeof options["read"] != "function")
1054
+ throw "Pass a function that returns the value of the dependentObservable";
1055
+
1056
+ return options;
1057
+ }
1058
+
1059
+ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) {
1060
+ var _latestValue,
1061
+ _hasBeenEvaluated = false,
1062
+ options = prepareOptions(evaluatorFunctionOrOptions, evaluatorFunctionTarget, options);
1063
+
1064
+ // Build "disposeWhenNodeIsRemoved" and "disposeWhenNodeIsRemovedCallback" option values
1065
+ // (Note: "disposeWhenNodeIsRemoved" option both proactively disposes as soon as the node is removed using ko.removeNode(),
1066
+ // plus adds a "disposeWhen" callback that, on each evaluation, disposes if the node was removed by some other means.)
1067
+ var disposeWhenNodeIsRemoved = (typeof options["disposeWhenNodeIsRemoved"] == "object") ? options["disposeWhenNodeIsRemoved"] : null;
1068
+ var disposeWhenNodeIsRemovedCallback = null;
1069
+ if (disposeWhenNodeIsRemoved) {
1070
+ disposeWhenNodeIsRemovedCallback = function() { dependentObservable.dispose() };
1071
+ ko.utils.domNodeDisposal.addDisposeCallback(disposeWhenNodeIsRemoved, disposeWhenNodeIsRemovedCallback);
1072
+ var existingDisposeWhenFunction = options["disposeWhen"];
1073
+ options["disposeWhen"] = function () {
1074
+ return (!ko.utils.domNodeIsAttachedToDocument(disposeWhenNodeIsRemoved))
1075
+ || ((typeof existingDisposeWhenFunction == "function") && existingDisposeWhenFunction());
1076
+ }
1077
+ }
1078
+
1079
+ var _subscriptionsToDependencies = [];
1080
+ function disposeAllSubscriptionsToDependencies() {
1081
+ ko.utils.arrayForEach(_subscriptionsToDependencies, function (subscription) {
1082
+ subscription.dispose();
1083
+ });
1084
+ _subscriptionsToDependencies = [];
1085
+ }
1086
+
1087
+ var evaluationTimeoutInstance = null;
1088
+ function evaluatePossiblyAsync() {
1089
+ var throttleEvaluationTimeout = dependentObservable['throttleEvaluation'];
1090
+ if (throttleEvaluationTimeout && throttleEvaluationTimeout >= 0) {
1091
+ clearTimeout(evaluationTimeoutInstance);
1092
+ evaluationTimeoutInstance = setTimeout(evaluateImmediate, throttleEvaluationTimeout);
1093
+ } else
1094
+ evaluateImmediate();
1095
+ }
1096
+
1097
+ function evaluateImmediate() {
1098
+ // Don't dispose on first evaluation, because the "disposeWhen" callback might
1099
+ // e.g., dispose when the associated DOM element isn't in the doc, and it's not
1100
+ // going to be in the doc until *after* the first evaluation
1101
+ if ((_hasBeenEvaluated) && typeof options["disposeWhen"] == "function") {
1102
+ if (options["disposeWhen"]()) {
1103
+ dependentObservable.dispose();
1104
+ return;
1105
+ }
1106
+ }
1107
+
1108
+ try {
1109
+ disposeAllSubscriptionsToDependencies();
1110
+ ko.dependencyDetection.begin(function(subscribable) {
1111
+ _subscriptionsToDependencies.push(subscribable.subscribe(evaluatePossiblyAsync));
1112
+ });
1113
+ var valueForThis = options["owner"] || evaluatorFunctionTarget; // If undefined, it will default to "window" by convention. This might change in the future.
1114
+ var newValue = options["read"].call(valueForThis);
1115
+ dependentObservable["notifySubscribers"](_latestValue, "beforeChange");
1116
+ _latestValue = newValue;
1117
+ } finally {
1118
+ ko.dependencyDetection.end();
1119
+ }
1120
+
1121
+ dependentObservable["notifySubscribers"](_latestValue);
1122
+ _hasBeenEvaluated = true;
1123
+ }
1124
+
1125
+ function dependentObservable() {
1126
+ if (arguments.length > 0) {
1127
+ if (typeof options["write"] === "function") {
1128
+ // Writing a value
1129
+ var valueForThis = options["owner"] || evaluatorFunctionTarget; // If undefined, it will default to "window" by convention. This might change in the future.
1130
+ options["write"].apply(valueForThis, arguments);
1131
+ } else {
1132
+ throw "Cannot write a value to a dependentObservable unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.";
1133
+ }
1134
+ } else {
1135
+ // Reading the value
1136
+ if (!_hasBeenEvaluated)
1137
+ evaluateImmediate();
1138
+ ko.dependencyDetection.registerDependency(dependentObservable);
1139
+ return _latestValue;
1140
+ }
1141
+ }
1142
+ dependentObservable.getDependenciesCount = function () { return _subscriptionsToDependencies.length; }
1143
+ dependentObservable.hasWriteFunction = typeof options["write"] === "function";
1144
+ dependentObservable.dispose = function () {
1145
+ if (disposeWhenNodeIsRemoved)
1146
+ ko.utils.domNodeDisposal.removeDisposeCallback(disposeWhenNodeIsRemoved, disposeWhenNodeIsRemovedCallback);
1147
+ disposeAllSubscriptionsToDependencies();
1148
+ };
1149
+
1150
+ ko.subscribable.call(dependentObservable);
1151
+ ko.utils.extend(dependentObservable, ko.dependentObservable['fn']);
1152
+
1153
+ if (options['deferEvaluation'] !== true)
1154
+ evaluateImmediate();
1155
+
1156
+ ko.exportProperty(dependentObservable, 'dispose', dependentObservable.dispose);
1157
+ ko.exportProperty(dependentObservable, 'getDependenciesCount', dependentObservable.getDependenciesCount);
1158
+
1159
+ return dependentObservable;
1160
+ };
1161
+
1162
+ ko.dependentObservable['fn'] = {
1163
+ __ko_proto__: ko.dependentObservable
1164
+ };
1165
+
1166
+ ko.dependentObservable.__ko_proto__ = ko.observable;
1167
+
1168
+ ko.exportSymbol('ko.dependentObservable', ko.dependentObservable);
1169
+ ko.exportSymbol('ko.computed', ko.dependentObservable); // Make "ko.computed" an alias for "ko.dependentObservable"
1170
+ (function() {
1171
+ var maxNestedObservableDepth = 10; // Escape the (unlikely) pathalogical case where an observable's current value is itself (or similar reference cycle)
1172
+
1173
+ ko.toJS = function(rootObject) {
1174
+ if (arguments.length == 0)
1175
+ throw new Error("When calling ko.toJS, pass the object you want to convert.");
1176
+
1177
+ // We just unwrap everything at every level in the object graph
1178
+ return mapJsObjectGraph(rootObject, function(valueToMap) {
1179
+ // Loop because an observable's value might in turn be another observable wrapper
1180
+ for (var i = 0; ko.isObservable(valueToMap) && (i < maxNestedObservableDepth); i++)
1181
+ valueToMap = valueToMap();
1182
+ return valueToMap;
1183
+ });
1184
+ };
1185
+
1186
+ ko.toJSON = function(rootObject) {
1187
+ var plainJavaScriptObject = ko.toJS(rootObject);
1188
+ return ko.utils.stringifyJson(plainJavaScriptObject);
1189
+ };
1190
+
1191
+ function mapJsObjectGraph(rootObject, mapInputCallback, visitedObjects) {
1192
+ visitedObjects = visitedObjects || new objectLookup();
1193
+
1194
+ rootObject = mapInputCallback(rootObject);
1195
+ var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined) && (!(rootObject instanceof Date));
1196
+ if (!canHaveProperties)
1197
+ return rootObject;
1198
+
1199
+ var outputProperties = rootObject instanceof Array ? [] : {};
1200
+ visitedObjects.save(rootObject, outputProperties);
1201
+
1202
+ visitPropertiesOrArrayEntries(rootObject, function(indexer) {
1203
+ var propertyValue = mapInputCallback(rootObject[indexer]);
1204
+
1205
+ switch (typeof propertyValue) {
1206
+ case "boolean":
1207
+ case "number":
1208
+ case "string":
1209
+ case "function":
1210
+ outputProperties[indexer] = propertyValue;
1211
+ break;
1212
+ case "object":
1213
+ case "undefined":
1214
+ var previouslyMappedValue = visitedObjects.get(propertyValue);
1215
+ outputProperties[indexer] = (previouslyMappedValue !== undefined)
1216
+ ? previouslyMappedValue
1217
+ : mapJsObjectGraph(propertyValue, mapInputCallback, visitedObjects);
1218
+ break;
1219
+ }
1220
+ });
1221
+
1222
+ return outputProperties;
1223
+ }
1224
+
1225
+ function visitPropertiesOrArrayEntries(rootObject, visitorCallback) {
1226
+ if (rootObject instanceof Array) {
1227
+ for (var i = 0; i < rootObject.length; i++)
1228
+ visitorCallback(i);
1229
+ } else {
1230
+ for (var propertyName in rootObject)
1231
+ visitorCallback(propertyName);
1232
+ }
1233
+ };
1234
+
1235
+ function objectLookup() {
1236
+ var keys = [];
1237
+ var values = [];
1238
+ this.save = function(key, value) {
1239
+ var existingIndex = ko.utils.arrayIndexOf(keys, key);
1240
+ if (existingIndex >= 0)
1241
+ values[existingIndex] = value;
1242
+ else {
1243
+ keys.push(key);
1244
+ values.push(value);
1245
+ }
1246
+ };
1247
+ this.get = function(key) {
1248
+ var existingIndex = ko.utils.arrayIndexOf(keys, key);
1249
+ return (existingIndex >= 0) ? values[existingIndex] : undefined;
1250
+ };
1251
+ };
1252
+ })();
1253
+
1254
+ ko.exportSymbol('ko.toJS', ko.toJS);
1255
+ ko.exportSymbol('ko.toJSON', ko.toJSON);(function () {
1256
+ var hasDomDataExpandoProperty = '__ko__hasDomDataOptionValue__';
1257
+
1258
+ // Normally, SELECT elements and their OPTIONs can only take value of type 'string' (because the values
1259
+ // are stored on DOM attributes). ko.selectExtensions provides a way for SELECTs/OPTIONs to have values
1260
+ // that are arbitrary objects. This is very convenient when implementing things like cascading dropdowns.
1261
+ ko.selectExtensions = {
1262
+ readValue : function(element) {
1263
+ if (element.tagName == 'OPTION') {
1264
+ if (element[hasDomDataExpandoProperty] === true)
1265
+ return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);
1266
+ return element.getAttribute("value");
1267
+ } else if (element.tagName == 'SELECT')
1268
+ return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined;
1269
+ else
1270
+ return element.value;
1271
+ },
1272
+
1273
+ writeValue: function(element, value) {
1274
+ if (element.tagName == 'OPTION') {
1275
+ switch(typeof value) {
1276
+ case "string":
1277
+ ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined);
1278
+ if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node
1279
+ delete element[hasDomDataExpandoProperty];
1280
+ }
1281
+ element.value = value;
1282
+ break;
1283
+ default:
1284
+ // Store arbitrary object using DomData
1285
+ ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);
1286
+ element[hasDomDataExpandoProperty] = true;
1287
+
1288
+ // Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value.
1289
+ element.value = typeof value === "number" ? value : "";
1290
+ break;
1291
+ }
1292
+ } else if (element.tagName == 'SELECT') {
1293
+ for (var i = element.options.length - 1; i >= 0; i--) {
1294
+ if (ko.selectExtensions.readValue(element.options[i]) == value) {
1295
+ element.selectedIndex = i;
1296
+ break;
1297
+ }
1298
+ }
1299
+ } else {
1300
+ if ((value === null) || (value === undefined))
1301
+ value = "";
1302
+ element.value = value;
1303
+ }
1304
+ }
1305
+ };
1306
+ })();
1307
+
1308
+ ko.exportSymbol('ko.selectExtensions', ko.selectExtensions);
1309
+ ko.exportSymbol('ko.selectExtensions.readValue', ko.selectExtensions.readValue);
1310
+ ko.exportSymbol('ko.selectExtensions.writeValue', ko.selectExtensions.writeValue);
1311
+
1312
+ ko.jsonExpressionRewriting = (function () {
1313
+ var restoreCapturedTokensRegex = /\@ko_token_(\d+)\@/g;
1314
+ var javaScriptAssignmentTarget = /^[\_$a-z][\_$a-z0-9]*(\[.*?\])*(\.[\_$a-z][\_$a-z0-9]*(\[.*?\])*)*$/i;
1315
+ var javaScriptReservedWords = ["true", "false"];
1316
+
1317
+ function restoreTokens(string, tokens) {
1318
+ var prevValue = null;
1319
+ while (string != prevValue) { // Keep restoring tokens until it no longer makes a difference (they may be nested)
1320
+ prevValue = string;
1321
+ string = string.replace(restoreCapturedTokensRegex, function (match, tokenIndex) {
1322
+ return tokens[tokenIndex];
1323
+ });
1324
+ }
1325
+ return string;
1326
+ }
1327
+
1328
+ function isWriteableValue(expression) {
1329
+ if (ko.utils.arrayIndexOf(javaScriptReservedWords, ko.utils.stringTrim(expression).toLowerCase()) >= 0)
1330
+ return false;
1331
+ return expression.match(javaScriptAssignmentTarget) !== null;
1332
+ }
1333
+
1334
+ function ensureQuoted(key) {
1335
+ var trimmedKey = ko.utils.stringTrim(key);
1336
+ switch (trimmedKey.length && trimmedKey.charAt(0)) {
1337
+ case "'":
1338
+ case '"':
1339
+ return key;
1340
+ default:
1341
+ return "'" + trimmedKey + "'";
1342
+ }
1343
+ }
1344
+
1345
+ return {
1346
+ bindingRewriteValidators: [],
1347
+
1348
+ parseObjectLiteral: function(objectLiteralString) {
1349
+ // A full tokeniser+lexer would add too much weight to this library, so here's a simple parser
1350
+ // that is sufficient just to split an object literal string into a set of top-level key-value pairs
1351
+
1352
+ var str = ko.utils.stringTrim(objectLiteralString);
1353
+ if (str.length < 3)
1354
+ return [];
1355
+ if (str.charAt(0) === "{")// Ignore any braces surrounding the whole object literal
1356
+ str = str.substring(1, str.length - 1);
1357
+
1358
+ // Pull out any string literals and regex literals
1359
+ var tokens = [];
1360
+ var tokenStart = null, tokenEndChar;
1361
+ for (var position = 0; position < str.length; position++) {
1362
+ var c = str.charAt(position);
1363
+ if (tokenStart === null) {
1364
+ switch (c) {
1365
+ case '"':
1366
+ case "'":
1367
+ case "/":
1368
+ tokenStart = position;
1369
+ tokenEndChar = c;
1370
+ break;
1371
+ }
1372
+ } else if ((c == tokenEndChar) && (str.charAt(position - 1) !== "\\")) {
1373
+ var token = str.substring(tokenStart, position + 1);
1374
+ tokens.push(token);
1375
+ var replacement = "@ko_token_" + (tokens.length - 1) + "@";
1376
+ str = str.substring(0, tokenStart) + replacement + str.substring(position + 1);
1377
+ position -= (token.length - replacement.length);
1378
+ tokenStart = null;
1379
+ }
1380
+ }
1381
+
1382
+ // Next pull out balanced paren, brace, and bracket blocks
1383
+ tokenStart = null;
1384
+ tokenEndChar = null;
1385
+ var tokenDepth = 0, tokenStartChar = null;
1386
+ for (var position = 0; position < str.length; position++) {
1387
+ var c = str.charAt(position);
1388
+ if (tokenStart === null) {
1389
+ switch (c) {
1390
+ case "{": tokenStart = position; tokenStartChar = c;
1391
+ tokenEndChar = "}";
1392
+ break;
1393
+ case "(": tokenStart = position; tokenStartChar = c;
1394
+ tokenEndChar = ")";
1395
+ break;
1396
+ case "[": tokenStart = position; tokenStartChar = c;
1397
+ tokenEndChar = "]";
1398
+ break;
1399
+ }
1400
+ }
1401
+
1402
+ if (c === tokenStartChar)
1403
+ tokenDepth++;
1404
+ else if (c === tokenEndChar) {
1405
+ tokenDepth--;
1406
+ if (tokenDepth === 0) {
1407
+ var token = str.substring(tokenStart, position + 1);
1408
+ tokens.push(token);
1409
+ var replacement = "@ko_token_" + (tokens.length - 1) + "@";
1410
+ str = str.substring(0, tokenStart) + replacement + str.substring(position + 1);
1411
+ position -= (token.length - replacement.length);
1412
+ tokenStart = null;
1413
+ }
1414
+ }
1415
+ }
1416
+
1417
+ // Now we can safely split on commas to get the key/value pairs
1418
+ var result = [];
1419
+ var keyValuePairs = str.split(",");
1420
+ for (var i = 0, j = keyValuePairs.length; i < j; i++) {
1421
+ var pair = keyValuePairs[i];
1422
+ var colonPos = pair.indexOf(":");
1423
+ if ((colonPos > 0) && (colonPos < pair.length - 1)) {
1424
+ var key = pair.substring(0, colonPos);
1425
+ var value = pair.substring(colonPos + 1);
1426
+ result.push({ 'key': restoreTokens(key, tokens), 'value': restoreTokens(value, tokens) });
1427
+ } else {
1428
+ result.push({ 'unknown': restoreTokens(pair, tokens) });
1429
+ }
1430
+ }
1431
+ return result;
1432
+ },
1433
+
1434
+ insertPropertyAccessorsIntoJson: function (objectLiteralStringOrKeyValueArray) {
1435
+ var keyValueArray = typeof objectLiteralStringOrKeyValueArray === "string"
1436
+ ? ko.jsonExpressionRewriting.parseObjectLiteral(objectLiteralStringOrKeyValueArray)
1437
+ : objectLiteralStringOrKeyValueArray;
1438
+ var resultStrings = [], propertyAccessorResultStrings = [];
1439
+
1440
+ var keyValueEntry;
1441
+ for (var i = 0; keyValueEntry = keyValueArray[i]; i++) {
1442
+ if (resultStrings.length > 0)
1443
+ resultStrings.push(",");
1444
+
1445
+ if (keyValueEntry['key']) {
1446
+ var quotedKey = ensureQuoted(keyValueEntry['key']), val = keyValueEntry['value'];
1447
+ resultStrings.push(quotedKey);
1448
+ resultStrings.push(":");
1449
+ resultStrings.push(val);
1450
+
1451
+ if (isWriteableValue(ko.utils.stringTrim(val))) {
1452
+ if (propertyAccessorResultStrings.length > 0)
1453
+ propertyAccessorResultStrings.push(", ");
1454
+ propertyAccessorResultStrings.push(quotedKey + " : function(__ko_value) { " + val + " = __ko_value; }");
1455
+ }
1456
+ } else if (keyValueEntry['unknown']) {
1457
+ resultStrings.push(keyValueEntry['unknown']);
1458
+ }
1459
+ }
1460
+
1461
+ var combinedResult = resultStrings.join("");
1462
+ if (propertyAccessorResultStrings.length > 0) {
1463
+ var allPropertyAccessors = propertyAccessorResultStrings.join("");
1464
+ combinedResult = combinedResult + ", '_ko_property_writers' : { " + allPropertyAccessors + " } ";
1465
+ }
1466
+
1467
+ return combinedResult;
1468
+ },
1469
+
1470
+ keyValueArrayContainsKey: function(keyValueArray, key) {
1471
+ for (var i = 0; i < keyValueArray.length; i++)
1472
+ if (ko.utils.stringTrim(keyValueArray[i]['key']) == key)
1473
+ return true;
1474
+ return false;
1475
+ }
1476
+ };
1477
+ })();
1478
+
1479
+ ko.exportSymbol('ko.jsonExpressionRewriting', ko.jsonExpressionRewriting);
1480
+ ko.exportSymbol('ko.jsonExpressionRewriting.bindingRewriteValidators', ko.jsonExpressionRewriting.bindingRewriteValidators);
1481
+ ko.exportSymbol('ko.jsonExpressionRewriting.parseObjectLiteral', ko.jsonExpressionRewriting.parseObjectLiteral);
1482
+ ko.exportSymbol('ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson);
1483
+ (function() {
1484
+ // "Virtual elements" is an abstraction on top of the usual DOM API which understands the notion that comment nodes
1485
+ // may be used to represent hierarchy (in addition to the DOM's natural hierarchy).
1486
+ // If you call the DOM-manipulating functions on ko.virtualElements, you will be able to read and write the state
1487
+ // of that virtual hierarchy
1488
+ //
1489
+ // The point of all this is to support containerless templates (e.g., <!-- ko foreach:someCollection -->blah<!-- /ko -->)
1490
+ // without having to scatter special cases all over the binding and templating code.
1491
+
1492
+ // IE 9 cannot reliably read the "nodeValue" property of a comment node (see https://github.com/SteveSanderson/knockout/issues/186)
1493
+ // but it does give them a nonstandard alternative property called "text" that it can read reliably. Other browsers don't have that property.
1494
+ // So, use node.text where available, and node.nodeValue elsewhere
1495
+ var commentNodesHaveTextProperty = document.createComment("test").text === "<!--test-->";
1496
+
1497
+ var startCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*ko\s+(.*\:.*)\s*-->$/ : /^\s*ko\s+(.*\:.*)\s*$/;
1498
+ var endCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*\/ko\s*-->$/ : /^\s*\/ko\s*$/;
1499
+ var htmlTagsWithOptionallyClosingChildren = { 'ul': true, 'ol': true };
1500
+
1501
+ function isStartComment(node) {
1502
+ return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(startCommentRegex);
1503
+ }
1504
+
1505
+ function isEndComment(node) {
1506
+ return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(endCommentRegex);
1507
+ }
1508
+
1509
+ function getVirtualChildren(startComment, allowUnbalanced) {
1510
+ var currentNode = startComment;
1511
+ var depth = 1;
1512
+ var children = [];
1513
+ while (currentNode = currentNode.nextSibling) {
1514
+ if (isEndComment(currentNode)) {
1515
+ depth--;
1516
+ if (depth === 0)
1517
+ return children;
1518
+ }
1519
+
1520
+ children.push(currentNode);
1521
+
1522
+ if (isStartComment(currentNode))
1523
+ depth++;
1524
+ }
1525
+ if (!allowUnbalanced)
1526
+ throw new Error("Cannot find closing comment tag to match: " + startComment.nodeValue);
1527
+ return null;
1528
+ }
1529
+
1530
+ function getMatchingEndComment(startComment, allowUnbalanced) {
1531
+ var allVirtualChildren = getVirtualChildren(startComment, allowUnbalanced);
1532
+ if (allVirtualChildren) {
1533
+ if (allVirtualChildren.length > 0)
1534
+ return allVirtualChildren[allVirtualChildren.length - 1].nextSibling;
1535
+ return startComment.nextSibling;
1536
+ } else
1537
+ return null; // Must have no matching end comment, and allowUnbalanced is true
1538
+ }
1539
+
1540
+ function nodeArrayToText(nodeArray, cleanNodes) {
1541
+ var texts = [];
1542
+ for (var i = 0, j = nodeArray.length; i < j; i++) {
1543
+ if (cleanNodes)
1544
+ ko.utils.domNodeDisposal.cleanNode(nodeArray[i]);
1545
+ texts.push(ko.utils.outerHTML(nodeArray[i]));
1546
+ }
1547
+ return String.prototype.concat.apply("", texts);
1548
+ }
1549
+
1550
+ function getUnbalancedChildTags(node) {
1551
+ // e.g., from <div>OK</div><!-- ko blah --><span>Another</span>, returns: <!-- ko blah --><span>Another</span>
1552
+ // from <div>OK</div><!-- /ko --><!-- /ko -->, returns: <!-- /ko --><!-- /ko -->
1553
+ var childNode = node.firstChild, captureRemaining = null;
1554
+ if (childNode) {
1555
+ do {
1556
+ if (captureRemaining) // We already hit an unbalanced node and are now just scooping up all subsequent nodes
1557
+ captureRemaining.push(childNode);
1558
+ else if (isStartComment(childNode)) {
1559
+ var matchingEndComment = getMatchingEndComment(childNode, /* allowUnbalanced: */ true);
1560
+ if (matchingEndComment) // It's a balanced tag, so skip immediately to the end of this virtual set
1561
+ childNode = matchingEndComment;
1562
+ else
1563
+ captureRemaining = [childNode]; // It's unbalanced, so start capturing from this point
1564
+ } else if (isEndComment(childNode)) {
1565
+ captureRemaining = [childNode]; // It's unbalanced (if it wasn't, we'd have skipped over it already), so start capturing
1566
+ }
1567
+ } while (childNode = childNode.nextSibling);
1568
+ }
1569
+ return captureRemaining;
1570
+ }
1571
+
1572
+ ko.virtualElements = {
1573
+ allowedBindings: {},
1574
+
1575
+ childNodes: function(node) {
1576
+ return isStartComment(node) ? getVirtualChildren(node) : node.childNodes;
1577
+ },
1578
+
1579
+ emptyNode: function(node) {
1580
+ if (!isStartComment(node))
1581
+ ko.utils.emptyDomNode(node);
1582
+ else {
1583
+ var virtualChildren = ko.virtualElements.childNodes(node);
1584
+ for (var i = 0, j = virtualChildren.length; i < j; i++)
1585
+ ko.removeNode(virtualChildren[i]);
1586
+ }
1587
+ },
1588
+
1589
+ setDomNodeChildren: function(node, childNodes) {
1590
+ if (!isStartComment(node))
1591
+ ko.utils.setDomNodeChildren(node, childNodes);
1592
+ else {
1593
+ ko.virtualElements.emptyNode(node);
1594
+ var endCommentNode = node.nextSibling; // Must be the next sibling, as we just emptied the children
1595
+ for (var i = 0, j = childNodes.length; i < j; i++)
1596
+ endCommentNode.parentNode.insertBefore(childNodes[i], endCommentNode);
1597
+ }
1598
+ },
1599
+
1600
+ prepend: function(containerNode, nodeToPrepend) {
1601
+ if (!isStartComment(containerNode)) {
1602
+ if (containerNode.firstChild)
1603
+ containerNode.insertBefore(nodeToPrepend, containerNode.firstChild);
1604
+ else
1605
+ containerNode.appendChild(nodeToPrepend);
1606
+ } else {
1607
+ // Start comments must always have a parent and at least one following sibling (the end comment)
1608
+ containerNode.parentNode.insertBefore(nodeToPrepend, containerNode.nextSibling);
1609
+ }
1610
+ },
1611
+
1612
+ insertAfter: function(containerNode, nodeToInsert, insertAfterNode) {
1613
+ if (!isStartComment(containerNode)) {
1614
+ // Insert after insertion point
1615
+ if (insertAfterNode.nextSibling)
1616
+ containerNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
1617
+ else
1618
+ containerNode.appendChild(nodeToInsert);
1619
+ } else {
1620
+ // Children of start comments must always have a parent and at least one following sibling (the end comment)
1621
+ containerNode.parentNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
1622
+ }
1623
+ },
1624
+
1625
+ nextSibling: function(node) {
1626
+ if (!isStartComment(node)) {
1627
+ if (node.nextSibling && isEndComment(node.nextSibling))
1628
+ return undefined;
1629
+ return node.nextSibling;
1630
+ } else {
1631
+ return getMatchingEndComment(node).nextSibling;
1632
+ }
1633
+ },
1634
+
1635
+ virtualNodeBindingValue: function(node) {
1636
+ var regexMatch = isStartComment(node);
1637
+ return regexMatch ? regexMatch[1] : null;
1638
+ },
1639
+
1640
+ extractAnonymousTemplateIfVirtualElement: function(node) {
1641
+ if (ko.virtualElements.virtualNodeBindingValue(node)) {
1642
+ // Empty out the virtual children, and associate "node" with an anonymous template matching its previous virtual children
1643
+ var virtualChildren = ko.virtualElements.childNodes(node);
1644
+ var anonymousTemplateText = nodeArrayToText(virtualChildren, true);
1645
+ ko.virtualElements.emptyNode(node);
1646
+ new ko.templateSources.anonymousTemplate(node).text(anonymousTemplateText);
1647
+ }
1648
+ },
1649
+
1650
+ normaliseVirtualElementDomStructure: function(elementVerified) {
1651
+ // Workaround for https://github.com/SteveSanderson/knockout/issues/155
1652
+ // (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
1653
+ // that are direct descendants of <ul> into the preceding <li>)
1654
+ if (!htmlTagsWithOptionallyClosingChildren[elementVerified.tagName.toLowerCase()])
1655
+ return;
1656
+
1657
+ // Scan immediate children to see if they contain unbalanced comment tags. If they do, those comment tags
1658
+ // must be intended to appear *after* that child, so move them there.
1659
+ var childNode = elementVerified.firstChild;
1660
+ if (childNode) {
1661
+ do {
1662
+ if (childNode.nodeType === 1) {
1663
+ var unbalancedTags = getUnbalancedChildTags(childNode);
1664
+ if (unbalancedTags) {
1665
+ // Fix up the DOM by moving the unbalanced tags to where they most likely were intended to be placed - *after* the child
1666
+ var nodeToInsertBefore = childNode.nextSibling;
1667
+ for (var i = 0; i < unbalancedTags.length; i++) {
1668
+ if (nodeToInsertBefore)
1669
+ elementVerified.insertBefore(unbalancedTags[i], nodeToInsertBefore);
1670
+ else
1671
+ elementVerified.appendChild(unbalancedTags[i]);
1672
+ }
1673
+ }
1674
+ }
1675
+ } while (childNode = childNode.nextSibling);
1676
+ }
1677
+ }
1678
+ };
1679
+ })();
1680
+ (function() {
1681
+ var defaultBindingAttributeName = "data-bind";
1682
+
1683
+ ko.bindingProvider = function() { };
1684
+
1685
+ ko.utils.extend(ko.bindingProvider.prototype, {
1686
+ 'nodeHasBindings': function(node) {
1687
+ switch (node.nodeType) {
1688
+ case 1: return node.getAttribute(defaultBindingAttributeName) != null; // Element
1689
+ case 8: return ko.virtualElements.virtualNodeBindingValue(node) != null; // Comment node
1690
+ default: return false;
1691
+ }
1692
+ },
1693
+
1694
+ 'getBindings': function(node, bindingContext) {
1695
+ var bindingsString = this['getBindingsString'](node, bindingContext);
1696
+ return bindingsString ? this['parseBindingsString'](bindingsString, bindingContext) : null;
1697
+ },
1698
+
1699
+ // The following function is only used internally by this default provider.
1700
+ // It's not part of the interface definition for a general binding provider.
1701
+ 'getBindingsString': function(node, bindingContext) {
1702
+ switch (node.nodeType) {
1703
+ case 1: return node.getAttribute(defaultBindingAttributeName); // Element
1704
+ case 8: return ko.virtualElements.virtualNodeBindingValue(node); // Comment node
1705
+ default: return null;
1706
+ }
1707
+ },
1708
+
1709
+ // The following function is only used internally by this default provider.
1710
+ // It's not part of the interface definition for a general binding provider.
1711
+ 'parseBindingsString': function(bindingsString, bindingContext) {
1712
+ try {
1713
+ var viewModel = bindingContext['$data'];
1714
+ var rewrittenBindings = " { " + ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson(bindingsString) + " } ";
1715
+ return ko.utils.evalWithinScope(rewrittenBindings, viewModel === null ? window : viewModel, bindingContext);
1716
+ } catch (ex) {
1717
+ throw new Error("Unable to parse bindings.\nMessage: " + ex + ";\nBindings value: " + bindingsString);
1718
+ }
1719
+ }
1720
+ });
1721
+
1722
+ ko.bindingProvider['instance'] = new ko.bindingProvider();
1723
+ })();
1724
+
1725
+ ko.exportSymbol('ko.bindingProvider', ko.bindingProvider);(function () {
1726
+ ko.bindingHandlers = {};
1727
+
1728
+ ko.bindingContext = function(dataItem, parentBindingContext) {
1729
+ this['$data'] = dataItem;
1730
+ if (parentBindingContext) {
1731
+ this['$parent'] = parentBindingContext['$data'];
1732
+ this['$parents'] = (parentBindingContext['$parents'] || []).slice(0);
1733
+ this['$parents'].unshift(this['$parent']);
1734
+ this['$root'] = parentBindingContext['$root'];
1735
+ } else {
1736
+ this['$parents'] = [];
1737
+ this['$root'] = dataItem;
1738
+ }
1739
+ }
1740
+ ko.bindingContext.prototype['createChildContext'] = function (dataItem) {
1741
+ return new ko.bindingContext(dataItem, this);
1742
+ };
1743
+
1744
+ function validateThatBindingIsAllowedForVirtualElements(bindingName) {
1745
+ var validator = ko.virtualElements.allowedBindings[bindingName];
1746
+ if (!validator)
1747
+ throw new Error("The binding '" + bindingName + "' cannot be used with virtual elements")
1748
+ }
1749
+
1750
+ function applyBindingsToDescendantsInternal (viewModel, elementVerified) {
1751
+ var currentChild, nextInQueue = elementVerified.childNodes[0];
1752
+ while (currentChild = nextInQueue) {
1753
+ // Keep a record of the next child *before* applying bindings, in case the binding removes the current child from its position
1754
+ nextInQueue = ko.virtualElements.nextSibling(currentChild);
1755
+ applyBindingsToNodeAndDescendantsInternal(viewModel, currentChild, false);
1756
+ }
1757
+ }
1758
+
1759
+ function applyBindingsToNodeAndDescendantsInternal (viewModel, nodeVerified, isRootNodeForBindingContext) {
1760
+ var shouldBindDescendants = true;
1761
+
1762
+ // Perf optimisation: Apply bindings only if...
1763
+ // (1) It's a root element for this binding context, as we will need to store the binding context on this node
1764
+ // Note that we can't store binding contexts on non-elements (e.g., text nodes), as IE doesn't allow expando properties for those
1765
+ // (2) It might have bindings (e.g., it has a data-bind attribute, or it's a marker for a containerless template)
1766
+ var isElement = (nodeVerified.nodeType == 1);
1767
+ if (isElement) // Workaround IE <= 8 HTML parsing weirdness
1768
+ ko.virtualElements.normaliseVirtualElementDomStructure(nodeVerified);
1769
+
1770
+ var shouldApplyBindings = (isElement && isRootNodeForBindingContext) // Case (1)
1771
+ || ko.bindingProvider['instance']['nodeHasBindings'](nodeVerified); // Case (2)
1772
+ if (shouldApplyBindings)
1773
+ shouldBindDescendants = applyBindingsToNodeInternal(nodeVerified, null, viewModel, isRootNodeForBindingContext).shouldBindDescendants;
1774
+
1775
+ if (isElement && shouldBindDescendants)
1776
+ applyBindingsToDescendantsInternal(viewModel, nodeVerified);
1777
+ }
1778
+
1779
+ function applyBindingsToNodeInternal (node, bindings, viewModelOrBindingContext, isRootNodeForBindingContext) {
1780
+ // Need to be sure that inits are only run once, and updates never run until all the inits have been run
1781
+ var initPhase = 0; // 0 = before all inits, 1 = during inits, 2 = after all inits
1782
+
1783
+ // Pre-process any anonymous template bounded by comment nodes
1784
+ ko.virtualElements.extractAnonymousTemplateIfVirtualElement(node);
1785
+
1786
+ // Each time the dependentObservable is evaluated (after data changes),
1787
+ // the binding attribute is reparsed so that it can pick out the correct
1788
+ // model properties in the context of the changed data.
1789
+ // DOM event callbacks need to be able to access this changed data,
1790
+ // so we need a single parsedBindings variable (shared by all callbacks
1791
+ // associated with this node's bindings) that all the closures can access.
1792
+ var parsedBindings;
1793
+ function makeValueAccessor(bindingKey) {
1794
+ return function () { return parsedBindings[bindingKey] }
1795
+ }
1796
+ function parsedBindingsAccessor() {
1797
+ return parsedBindings;
1798
+ }
1799
+
1800
+ var bindingHandlerThatControlsDescendantBindings;
1801
+ new ko.dependentObservable(
1802
+ function () {
1803
+ // Ensure we have a nonnull binding context to work with
1804
+ var bindingContextInstance = viewModelOrBindingContext && (viewModelOrBindingContext instanceof ko.bindingContext)
1805
+ ? viewModelOrBindingContext
1806
+ : new ko.bindingContext(ko.utils.unwrapObservable(viewModelOrBindingContext));
1807
+ var viewModel = bindingContextInstance['$data'];
1808
+
1809
+ // We only need to store the bindingContext at the root of the subtree where it applies
1810
+ // as all descendants will be able to find it by scanning up their ancestry
1811
+ if (isRootNodeForBindingContext)
1812
+ ko.storedBindingContextForNode(node, bindingContextInstance);
1813
+
1814
+ // Use evaluatedBindings if given, otherwise fall back on asking the bindings provider to give us some bindings
1815
+ var evaluatedBindings = (typeof bindings == "function") ? bindings() : bindings;
1816
+ parsedBindings = evaluatedBindings || ko.bindingProvider['instance']['getBindings'](node, bindingContextInstance);
1817
+
1818
+ if (parsedBindings) {
1819
+ // First run all the inits, so bindings can register for notification on changes
1820
+ if (initPhase === 0) {
1821
+ initPhase = 1;
1822
+ for (var bindingKey in parsedBindings) {
1823
+ var binding = ko.bindingHandlers[bindingKey];
1824
+ if (binding && node.nodeType === 8)
1825
+ validateThatBindingIsAllowedForVirtualElements(bindingKey);
1826
+
1827
+ if (binding && typeof binding["init"] == "function") {
1828
+ var handlerInitFn = binding["init"];
1829
+ var initResult = handlerInitFn(node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel, bindingContextInstance);
1830
+
1831
+ // If this binding handler claims to control descendant bindings, make a note of this
1832
+ if (initResult && initResult['controlsDescendantBindings']) {
1833
+ if (bindingHandlerThatControlsDescendantBindings !== undefined)
1834
+ 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.");
1835
+ bindingHandlerThatControlsDescendantBindings = bindingKey;
1836
+ }
1837
+ }
1838
+ }
1839
+ initPhase = 2;
1840
+ }
1841
+
1842
+ // ... then run all the updates, which might trigger changes even on the first evaluation
1843
+ if (initPhase === 2) {
1844
+ for (var bindingKey in parsedBindings) {
1845
+ var binding = ko.bindingHandlers[bindingKey];
1846
+ if (binding && typeof binding["update"] == "function") {
1847
+ var handlerUpdateFn = binding["update"];
1848
+ handlerUpdateFn(node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel, bindingContextInstance);
1849
+ }
1850
+ }
1851
+ }
1852
+ }
1853
+ },
1854
+ null,
1855
+ { 'disposeWhenNodeIsRemoved' : node }
1856
+ );
1857
+
1858
+ return {
1859
+ shouldBindDescendants: bindingHandlerThatControlsDescendantBindings === undefined
1860
+ };
1861
+ };
1862
+
1863
+ var storedBindingContextDomDataKey = "__ko_bindingContext__";
1864
+ ko.storedBindingContextForNode = function (node, bindingContext) {
1865
+ if (arguments.length == 2)
1866
+ ko.utils.domData.set(node, storedBindingContextDomDataKey, bindingContext);
1867
+ else
1868
+ return ko.utils.domData.get(node, storedBindingContextDomDataKey);
1869
+ }
1870
+
1871
+ ko.applyBindingsToNode = function (node, bindings, viewModel) {
1872
+ if (node.nodeType === 1) // If it's an element, workaround IE <= 8 HTML parsing weirdness
1873
+ ko.virtualElements.normaliseVirtualElementDomStructure(node);
1874
+ return applyBindingsToNodeInternal(node, bindings, viewModel, true);
1875
+ };
1876
+
1877
+ ko.applyBindingsToDescendants = function(viewModel, rootNode) {
1878
+ if (rootNode.nodeType === 1)
1879
+ applyBindingsToDescendantsInternal(viewModel, rootNode);
1880
+ };
1881
+
1882
+ ko.applyBindings = function (viewModel, rootNode) {
1883
+ if (rootNode && (rootNode.nodeType !== 1) && (rootNode.nodeType !== 8))
1884
+ throw new Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");
1885
+ rootNode = rootNode || window.document.body; // Make "rootNode" parameter optional
1886
+
1887
+ applyBindingsToNodeAndDescendantsInternal(viewModel, rootNode, true);
1888
+ };
1889
+
1890
+ // Retrieving binding context from arbitrary nodes
1891
+ ko.contextFor = function(node) {
1892
+ // We can only do something meaningful for elements and comment nodes (in particular, not text nodes, as IE can't store domdata for them)
1893
+ switch (node.nodeType) {
1894
+ case 1:
1895
+ case 8:
1896
+ var context = ko.storedBindingContextForNode(node);
1897
+ if (context) return context;
1898
+ if (node.parentNode) return ko.contextFor(node.parentNode);
1899
+ break;
1900
+ }
1901
+ return undefined;
1902
+ };
1903
+ ko.dataFor = function(node) {
1904
+ var context = ko.contextFor(node);
1905
+ return context ? context['$data'] : undefined;
1906
+ };
1907
+
1908
+ ko.exportSymbol('ko.bindingHandlers', ko.bindingHandlers);
1909
+ ko.exportSymbol('ko.applyBindings', ko.applyBindings);
1910
+ ko.exportSymbol('ko.applyBindingsToDescendants', ko.applyBindingsToDescendants);
1911
+ ko.exportSymbol('ko.applyBindingsToNode', ko.applyBindingsToNode);
1912
+ ko.exportSymbol('ko.contextFor', ko.contextFor);
1913
+ ko.exportSymbol('ko.dataFor', ko.dataFor);
1914
+ })();// For certain common events (currently just 'click'), allow a simplified data-binding syntax
1915
+ // e.g. click:handler instead of the usual full-length event:{click:handler}
1916
+ var eventHandlersWithShortcuts = ['click'];
1917
+ ko.utils.arrayForEach(eventHandlersWithShortcuts, function(eventName) {
1918
+ ko.bindingHandlers[eventName] = {
1919
+ 'init': function(element, valueAccessor, allBindingsAccessor, viewModel) {
1920
+ var newValueAccessor = function () {
1921
+ var result = {};
1922
+ result[eventName] = valueAccessor();
1923
+ return result;
1924
+ };
1925
+ return ko.bindingHandlers['event']['init'].call(this, element, newValueAccessor, allBindingsAccessor, viewModel);
1926
+ }
1927
+ }
1928
+ });
1929
+
1930
+
1931
+ ko.bindingHandlers['event'] = {
1932
+ 'init' : function (element, valueAccessor, allBindingsAccessor, viewModel) {
1933
+ var eventsToHandle = valueAccessor() || {};
1934
+ for(var eventNameOutsideClosure in eventsToHandle) {
1935
+ (function() {
1936
+ var eventName = eventNameOutsideClosure; // Separate variable to be captured by event handler closure
1937
+ if (typeof eventName == "string") {
1938
+ ko.utils.registerEventHandler(element, eventName, function (event) {
1939
+ var handlerReturnValue;
1940
+ var handlerFunction = valueAccessor()[eventName];
1941
+ if (!handlerFunction)
1942
+ return;
1943
+ var allBindings = allBindingsAccessor();
1944
+
1945
+ try {
1946
+ // Take all the event args, and prefix with the viewmodel
1947
+ var argsForHandler = ko.utils.makeArray(arguments);
1948
+ argsForHandler.unshift(viewModel);
1949
+ handlerReturnValue = handlerFunction.apply(viewModel, argsForHandler);
1950
+ } finally {
1951
+ if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
1952
+ if (event.preventDefault)
1953
+ event.preventDefault();
1954
+ else
1955
+ event.returnValue = false;
1956
+ }
1957
+ }
1958
+
1959
+ var bubble = allBindings[eventName + 'Bubble'] !== false;
1960
+ if (!bubble) {
1961
+ event.cancelBubble = true;
1962
+ if (event.stopPropagation)
1963
+ event.stopPropagation();
1964
+ }
1965
+ });
1966
+ }
1967
+ })();
1968
+ }
1969
+ }
1970
+ };
1971
+
1972
+ ko.bindingHandlers['submit'] = {
1973
+ 'init': function (element, valueAccessor, allBindingsAccessor, viewModel) {
1974
+ if (typeof valueAccessor() != "function")
1975
+ throw new Error("The value for a submit binding must be a function");
1976
+ ko.utils.registerEventHandler(element, "submit", function (event) {
1977
+ var handlerReturnValue;
1978
+ var value = valueAccessor();
1979
+ try { handlerReturnValue = value.call(viewModel, element); }
1980
+ finally {
1981
+ if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
1982
+ if (event.preventDefault)
1983
+ event.preventDefault();
1984
+ else
1985
+ event.returnValue = false;
1986
+ }
1987
+ }
1988
+ });
1989
+ }
1990
+ };
1991
+
1992
+ ko.bindingHandlers['visible'] = {
1993
+ 'update': function (element, valueAccessor) {
1994
+ var value = ko.utils.unwrapObservable(valueAccessor());
1995
+ var isCurrentlyVisible = !(element.style.display == "none");
1996
+ if (value && !isCurrentlyVisible)
1997
+ element.style.display = "";
1998
+ else if ((!value) && isCurrentlyVisible)
1999
+ element.style.display = "none";
2000
+ }
2001
+ }
2002
+
2003
+ ko.bindingHandlers['enable'] = {
2004
+ 'update': function (element, valueAccessor) {
2005
+ var value = ko.utils.unwrapObservable(valueAccessor());
2006
+ if (value && element.disabled)
2007
+ element.removeAttribute("disabled");
2008
+ else if ((!value) && (!element.disabled))
2009
+ element.disabled = true;
2010
+ }
2011
+ };
2012
+
2013
+ ko.bindingHandlers['disable'] = {
2014
+ 'update': function (element, valueAccessor) {
2015
+ ko.bindingHandlers['enable']['update'](element, function() { return !ko.utils.unwrapObservable(valueAccessor()) });
2016
+ }
2017
+ };
2018
+
2019
+ function ensureDropdownSelectionIsConsistentWithModelValue(element, modelValue, preferModelValue) {
2020
+ if (preferModelValue) {
2021
+ if (modelValue !== ko.selectExtensions.readValue(element))
2022
+ ko.selectExtensions.writeValue(element, modelValue);
2023
+ }
2024
+
2025
+ // No matter which direction we're syncing in, we want the end result to be equality between dropdown value and model value.
2026
+ // If they aren't equal, either we prefer the dropdown value, or the model value couldn't be represented, so either way,
2027
+ // change the model value to match the dropdown.
2028
+ if (modelValue !== ko.selectExtensions.readValue(element))
2029
+ ko.utils.triggerEvent(element, "change");
2030
+ };
2031
+
2032
+ ko.bindingHandlers['value'] = {
2033
+ 'init': function (element, valueAccessor, allBindingsAccessor) {
2034
+ // Always catch "change" event; possibly other events too if asked
2035
+ var eventsToCatch = ["change"];
2036
+ var requestedEventsToCatch = allBindingsAccessor()["valueUpdate"];
2037
+ if (requestedEventsToCatch) {
2038
+ if (typeof requestedEventsToCatch == "string") // Allow both individual event names, and arrays of event names
2039
+ requestedEventsToCatch = [requestedEventsToCatch];
2040
+ ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch);
2041
+ eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch);
2042
+ }
2043
+
2044
+ ko.utils.arrayForEach(eventsToCatch, function(eventName) {
2045
+ // The syntax "after<eventname>" means "run the handler asynchronously after the event"
2046
+ // This is useful, for example, to catch "keydown" events after the browser has updated the control
2047
+ // (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event)
2048
+ var handleEventAsynchronously = false;
2049
+ if (ko.utils.stringStartsWith(eventName, "after")) {
2050
+ handleEventAsynchronously = true;
2051
+ eventName = eventName.substring("after".length);
2052
+ }
2053
+ var runEventHandler = handleEventAsynchronously ? function(handler) { setTimeout(handler, 0) }
2054
+ : function(handler) { handler() };
2055
+
2056
+ ko.utils.registerEventHandler(element, eventName, function () {
2057
+ runEventHandler(function() {
2058
+ var modelValue = valueAccessor();
2059
+ var elementValue = ko.selectExtensions.readValue(element);
2060
+ if (ko.isWriteableObservable(modelValue))
2061
+ modelValue(elementValue);
2062
+ else {
2063
+ var allBindings = allBindingsAccessor();
2064
+ if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['value'])
2065
+ allBindings['_ko_property_writers']['value'](elementValue);
2066
+ }
2067
+ });
2068
+ });
2069
+ });
2070
+ },
2071
+ 'update': function (element, valueAccessor) {
2072
+ var newValue = ko.utils.unwrapObservable(valueAccessor());
2073
+ var elementValue = ko.selectExtensions.readValue(element);
2074
+ var valueHasChanged = (newValue != elementValue);
2075
+
2076
+ // JavaScript's 0 == "" behavious is unfortunate here as it prevents writing 0 to an empty text box (loose equality suggests the values are the same).
2077
+ // We don't want to do a strict equality comparison as that is more confusing for developers in certain cases, so we specifically special case 0 != "" here.
2078
+ if ((newValue === 0) && (elementValue !== 0) && (elementValue !== "0"))
2079
+ valueHasChanged = true;
2080
+
2081
+ if (valueHasChanged) {
2082
+ var applyValueAction = function () { ko.selectExtensions.writeValue(element, newValue); };
2083
+ applyValueAction();
2084
+
2085
+ // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
2086
+ // right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
2087
+ // to apply the value as well.
2088
+ var alsoApplyAsynchronously = element.tagName == "SELECT";
2089
+ if (alsoApplyAsynchronously)
2090
+ setTimeout(applyValueAction, 0);
2091
+ }
2092
+
2093
+ // If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
2094
+ // because you're not allowed to have a model value that disagrees with a visible UI selection.
2095
+ if ((element.tagName == "SELECT") && (element.length > 0))
2096
+ ensureDropdownSelectionIsConsistentWithModelValue(element, newValue, /* preferModelValue */ false);
2097
+ }
2098
+ };
2099
+
2100
+ ko.bindingHandlers['options'] = {
2101
+ 'update': function (element, valueAccessor, allBindingsAccessor) {
2102
+ if (element.tagName != "SELECT")
2103
+ throw new Error("options binding applies only to SELECT elements");
2104
+
2105
+ var selectWasPreviouslyEmpty = element.length == 0;
2106
+ var previousSelectedValues = ko.utils.arrayMap(ko.utils.arrayFilter(element.childNodes, function (node) {
2107
+ return node.tagName && node.tagName == "OPTION" && node.selected;
2108
+ }), function (node) {
2109
+ return ko.selectExtensions.readValue(node) || node.innerText || node.textContent;
2110
+ });
2111
+ var previousScrollTop = element.scrollTop;
2112
+ element.scrollTop = 0; // Workaround for a Chrome rendering bug. Note that we restore the scroll position later. (https://github.com/SteveSanderson/knockout/issues/215)
2113
+
2114
+ var value = ko.utils.unwrapObservable(valueAccessor());
2115
+ var selectedValue = element.value;
2116
+
2117
+ // Remove all existing <option>s.
2118
+ // Need to use .remove() rather than .removeChild() for <option>s otherwise IE behaves oddly (https://github.com/SteveSanderson/knockout/issues/134)
2119
+ while (element.length > 0) {
2120
+ ko.cleanNode(element.options[0]);
2121
+ element.remove(0);
2122
+ }
2123
+
2124
+ if (value) {
2125
+ var allBindings = allBindingsAccessor();
2126
+ if (typeof value.length != "number")
2127
+ value = [value];
2128
+ if (allBindings['optionsCaption']) {
2129
+ var option = document.createElement("OPTION");
2130
+ ko.utils.setHtml(option, allBindings['optionsCaption']);
2131
+ ko.selectExtensions.writeValue(option, undefined);
2132
+ element.appendChild(option);
2133
+ }
2134
+ for (var i = 0, j = value.length; i < j; i++) {
2135
+ var option = document.createElement("OPTION");
2136
+
2137
+ // Apply a value to the option element
2138
+ var optionValue = typeof allBindings['optionsValue'] == "string" ? value[i][allBindings['optionsValue']] : value[i];
2139
+ optionValue = ko.utils.unwrapObservable(optionValue);
2140
+ ko.selectExtensions.writeValue(option, optionValue);
2141
+
2142
+ // Apply some text to the option element
2143
+ var optionsTextValue = allBindings['optionsText'];
2144
+ var optionText;
2145
+ if (typeof optionsTextValue == "function")
2146
+ optionText = optionsTextValue(value[i]); // Given a function; run it against the data value
2147
+ else if (typeof optionsTextValue == "string")
2148
+ optionText = value[i][optionsTextValue]; // Given a string; treat it as a property name on the data value
2149
+ else
2150
+ optionText = optionValue; // Given no optionsText arg; use the data value itself
2151
+ if ((optionText === null) || (optionText === undefined))
2152
+ optionText = "";
2153
+
2154
+ ko.utils.setTextContent(option, optionText);
2155
+
2156
+ element.appendChild(option);
2157
+ }
2158
+
2159
+ // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
2160
+ // That's why we first added them without selection. Now it's time to set the selection.
2161
+ var newOptions = element.getElementsByTagName("OPTION");
2162
+ var countSelectionsRetained = 0;
2163
+ for (var i = 0, j = newOptions.length; i < j; i++) {
2164
+ if (ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[i])) >= 0) {
2165
+ ko.utils.setOptionNodeSelectionState(newOptions[i], true);
2166
+ countSelectionsRetained++;
2167
+ }
2168
+ }
2169
+
2170
+ if (previousScrollTop)
2171
+ element.scrollTop = previousScrollTop;
2172
+
2173
+ if (selectWasPreviouslyEmpty && ('value' in allBindings)) {
2174
+ // Ensure consistency between model value and selected option.
2175
+ // If the dropdown is being populated for the first time here (or was otherwise previously empty),
2176
+ // the dropdown selection state is meaningless, so we preserve the model value.
2177
+ ensureDropdownSelectionIsConsistentWithModelValue(element, ko.utils.unwrapObservable(allBindings['value']), /* preferModelValue */ true);
2178
+ }
2179
+ }
2180
+ }
2181
+ };
2182
+ ko.bindingHandlers['options'].optionValueDomDataKey = '__ko.optionValueDomData__';
2183
+
2184
+ ko.bindingHandlers['selectedOptions'] = {
2185
+ getSelectedValuesFromSelectNode: function (selectNode) {
2186
+ var result = [];
2187
+ var nodes = selectNode.childNodes;
2188
+ for (var i = 0, j = nodes.length; i < j; i++) {
2189
+ var node = nodes[i];
2190
+ if ((node.tagName == "OPTION") && node.selected)
2191
+ result.push(ko.selectExtensions.readValue(node));
2192
+ }
2193
+ return result;
2194
+ },
2195
+ 'init': function (element, valueAccessor, allBindingsAccessor) {
2196
+ ko.utils.registerEventHandler(element, "change", function () {
2197
+ var value = valueAccessor();
2198
+ if (ko.isWriteableObservable(value))
2199
+ value(ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(this));
2200
+ else {
2201
+ var allBindings = allBindingsAccessor();
2202
+ if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['value'])
2203
+ allBindings['_ko_property_writers']['value'](ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(this));
2204
+ }
2205
+ });
2206
+ },
2207
+ 'update': function (element, valueAccessor) {
2208
+ if (element.tagName != "SELECT")
2209
+ throw new Error("values binding applies only to SELECT elements");
2210
+
2211
+ var newValue = ko.utils.unwrapObservable(valueAccessor());
2212
+ if (newValue && typeof newValue.length == "number") {
2213
+ var nodes = element.childNodes;
2214
+ for (var i = 0, j = nodes.length; i < j; i++) {
2215
+ var node = nodes[i];
2216
+ if (node.tagName == "OPTION")
2217
+ ko.utils.setOptionNodeSelectionState(node, ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0);
2218
+ }
2219
+ }
2220
+ }
2221
+ };
2222
+
2223
+ ko.bindingHandlers['text'] = {
2224
+ 'update': function (element, valueAccessor) {
2225
+ ko.utils.setTextContent(element, valueAccessor());
2226
+ }
2227
+ };
2228
+
2229
+ ko.bindingHandlers['html'] = {
2230
+ 'init': function() {
2231
+ // Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
2232
+ return { 'controlsDescendantBindings': true };
2233
+ },
2234
+ 'update': function (element, valueAccessor) {
2235
+ var value = ko.utils.unwrapObservable(valueAccessor());
2236
+ ko.utils.setHtml(element, value);
2237
+ }
2238
+ };
2239
+
2240
+ ko.bindingHandlers['css'] = {
2241
+ 'update': function (element, valueAccessor) {
2242
+ var value = ko.utils.unwrapObservable(valueAccessor() || {});
2243
+ for (var className in value) {
2244
+ if (typeof className == "string") {
2245
+ var shouldHaveClass = ko.utils.unwrapObservable(value[className]);
2246
+ ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
2247
+ }
2248
+ }
2249
+ }
2250
+ };
2251
+
2252
+ ko.bindingHandlers['style'] = {
2253
+ 'update': function (element, valueAccessor) {
2254
+ var value = ko.utils.unwrapObservable(valueAccessor() || {});
2255
+ for (var styleName in value) {
2256
+ if (typeof styleName == "string") {
2257
+ var styleValue = ko.utils.unwrapObservable(value[styleName]);
2258
+ element.style[styleName] = styleValue || ""; // Empty string removes the value, whereas null/undefined have no effect
2259
+ }
2260
+ }
2261
+ }
2262
+ };
2263
+
2264
+ ko.bindingHandlers['uniqueName'] = {
2265
+ 'init': function (element, valueAccessor) {
2266
+ if (valueAccessor()) {
2267
+ element.name = "ko_unique_" + (++ko.bindingHandlers['uniqueName'].currentIndex);
2268
+
2269
+ // Workaround IE 6/7 issue
2270
+ // - https://github.com/SteveSanderson/knockout/issues/197
2271
+ // - http://www.matts411.com/post/setting_the_name_attribute_in_ie_dom/
2272
+ if (ko.utils.isIe6 || ko.utils.isIe7)
2273
+ element.mergeAttributes(document.createElement("<input name='" + element.name + "'/>"), false);
2274
+ }
2275
+ }
2276
+ };
2277
+ ko.bindingHandlers['uniqueName'].currentIndex = 0;
2278
+
2279
+ ko.bindingHandlers['checked'] = {
2280
+ 'init': function (element, valueAccessor, allBindingsAccessor) {
2281
+ var updateHandler = function() {
2282
+ var valueToWrite;
2283
+ if (element.type == "checkbox") {
2284
+ valueToWrite = element.checked;
2285
+ } else if ((element.type == "radio") && (element.checked)) {
2286
+ valueToWrite = element.value;
2287
+ } else {
2288
+ return; // "checked" binding only responds to checkboxes and selected radio buttons
2289
+ }
2290
+
2291
+ var modelValue = valueAccessor();
2292
+ if ((element.type == "checkbox") && (ko.utils.unwrapObservable(modelValue) instanceof Array)) {
2293
+ // For checkboxes bound to an array, we add/remove the checkbox value to that array
2294
+ // This works for both observable and non-observable arrays
2295
+ var existingEntryIndex = ko.utils.arrayIndexOf(ko.utils.unwrapObservable(modelValue), element.value);
2296
+ if (element.checked && (existingEntryIndex < 0))
2297
+ modelValue.push(element.value);
2298
+ else if ((!element.checked) && (existingEntryIndex >= 0))
2299
+ modelValue.splice(existingEntryIndex, 1);
2300
+ } else if (ko.isWriteableObservable(modelValue)) {
2301
+ if (modelValue() !== valueToWrite) { // Suppress repeated events when there's nothing new to notify (some browsers raise them)
2302
+ modelValue(valueToWrite);
2303
+ }
2304
+ } else {
2305
+ var allBindings = allBindingsAccessor();
2306
+ if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['checked']) {
2307
+ allBindings['_ko_property_writers']['checked'](valueToWrite);
2308
+ }
2309
+ }
2310
+ };
2311
+ ko.utils.registerEventHandler(element, "click", updateHandler);
2312
+
2313
+ // IE 6 won't allow radio buttons to be selected unless they have a name
2314
+ if ((element.type == "radio") && !element.name)
2315
+ ko.bindingHandlers['uniqueName']['init'](element, function() { return true });
2316
+ },
2317
+ 'update': function (element, valueAccessor) {
2318
+ var value = ko.utils.unwrapObservable(valueAccessor());
2319
+
2320
+ if (element.type == "checkbox") {
2321
+ if (value instanceof Array) {
2322
+ // When bound to an array, the checkbox being checked represents its value being present in that array
2323
+ element.checked = ko.utils.arrayIndexOf(value, element.value) >= 0;
2324
+ } else {
2325
+ // When bound to anything other value (not an array), the checkbox being checked represents the value being trueish
2326
+ element.checked = value;
2327
+ }
2328
+ } else if (element.type == "radio") {
2329
+ element.checked = (element.value == value);
2330
+ }
2331
+ }
2332
+ };
2333
+
2334
+ ko.bindingHandlers['attr'] = {
2335
+ 'update': function(element, valueAccessor, allBindingsAccessor) {
2336
+ var value = ko.utils.unwrapObservable(valueAccessor()) || {};
2337
+ for (var attrName in value) {
2338
+ if (typeof attrName == "string") {
2339
+ var attrValue = ko.utils.unwrapObservable(value[attrName]);
2340
+
2341
+ // To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely
2342
+ // when someProp is a "no value"-like value (strictly null, false, or undefined)
2343
+ // (because the absence of the "checked" attr is how to mark an element as not checked, etc.)
2344
+ if ((attrValue === false) || (attrValue === null) || (attrValue === undefined))
2345
+ element.removeAttribute(attrName);
2346
+ else
2347
+ element.setAttribute(attrName, attrValue.toString());
2348
+ }
2349
+ }
2350
+ }
2351
+ };
2352
+
2353
+ ko.bindingHandlers['hasfocus'] = {
2354
+ 'init': function(element, valueAccessor, allBindingsAccessor) {
2355
+ var writeValue = function(valueToWrite) {
2356
+ var modelValue = valueAccessor();
2357
+ if (valueToWrite == ko.utils.unwrapObservable(modelValue))
2358
+ return;
2359
+
2360
+ if (ko.isWriteableObservable(modelValue))
2361
+ modelValue(valueToWrite);
2362
+ else {
2363
+ var allBindings = allBindingsAccessor();
2364
+ if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['hasfocus']) {
2365
+ allBindings['_ko_property_writers']['hasfocus'](valueToWrite);
2366
+ }
2367
+ }
2368
+ };
2369
+ ko.utils.registerEventHandler(element, "focus", function() { writeValue(true) });
2370
+ ko.utils.registerEventHandler(element, "focusin", function() { writeValue(true) }); // For IE
2371
+ ko.utils.registerEventHandler(element, "blur", function() { writeValue(false) });
2372
+ ko.utils.registerEventHandler(element, "focusout", function() { writeValue(false) }); // For IE
2373
+ },
2374
+ 'update': function(element, valueAccessor) {
2375
+ var value = ko.utils.unwrapObservable(valueAccessor());
2376
+ value ? element.focus() : element.blur();
2377
+ ko.utils.triggerEvent(element, value ? "focusin" : "focusout"); // For IE, which doesn't reliably fire "focus" or "blur" events synchronously
2378
+ }
2379
+ };
2380
+
2381
+ // "with: someExpression" is equivalent to "template: { if: someExpression, data: someExpression }"
2382
+ ko.bindingHandlers['with'] = {
2383
+ makeTemplateValueAccessor: function(valueAccessor) {
2384
+ return function() { var value = valueAccessor(); return { 'if': value, 'data': value, 'templateEngine': ko.nativeTemplateEngine.instance } };
2385
+ },
2386
+ 'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
2387
+ return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['with'].makeTemplateValueAccessor(valueAccessor));
2388
+ },
2389
+ 'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
2390
+ return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['with'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
2391
+ }
2392
+ };
2393
+ ko.jsonExpressionRewriting.bindingRewriteValidators['with'] = false; // Can't rewrite control flow bindings
2394
+ ko.virtualElements.allowedBindings['with'] = true;
2395
+
2396
+ // "if: someExpression" is equivalent to "template: { if: someExpression }"
2397
+ ko.bindingHandlers['if'] = {
2398
+ makeTemplateValueAccessor: function(valueAccessor) {
2399
+ return function() { return { 'if': valueAccessor(), 'templateEngine': ko.nativeTemplateEngine.instance } };
2400
+ },
2401
+ 'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
2402
+ return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['if'].makeTemplateValueAccessor(valueAccessor));
2403
+ },
2404
+ 'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
2405
+ return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['if'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
2406
+ }
2407
+ };
2408
+ ko.jsonExpressionRewriting.bindingRewriteValidators['if'] = false; // Can't rewrite control flow bindings
2409
+ ko.virtualElements.allowedBindings['if'] = true;
2410
+
2411
+ // "ifnot: someExpression" is equivalent to "template: { ifnot: someExpression }"
2412
+ ko.bindingHandlers['ifnot'] = {
2413
+ makeTemplateValueAccessor: function(valueAccessor) {
2414
+ return function() { return { 'ifnot': valueAccessor(), 'templateEngine': ko.nativeTemplateEngine.instance } };
2415
+ },
2416
+ 'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
2417
+ return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['ifnot'].makeTemplateValueAccessor(valueAccessor));
2418
+ },
2419
+ 'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
2420
+ return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['ifnot'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
2421
+ }
2422
+ };
2423
+ ko.jsonExpressionRewriting.bindingRewriteValidators['ifnot'] = false; // Can't rewrite control flow bindings
2424
+ ko.virtualElements.allowedBindings['ifnot'] = true;
2425
+
2426
+ // "foreach: someExpression" is equivalent to "template: { foreach: someExpression }"
2427
+ // "foreach: { data: someExpression, afterAdd: myfn }" is equivalent to "template: { foreach: someExpression, afterAdd: myfn }"
2428
+ ko.bindingHandlers['foreach'] = {
2429
+ makeTemplateValueAccessor: function(valueAccessor) {
2430
+ return function() {
2431
+ var bindingValue = ko.utils.unwrapObservable(valueAccessor());
2432
+
2433
+ // If bindingValue is the array, just pass it on its own
2434
+ if ((!bindingValue) || typeof bindingValue.length == "number")
2435
+ return { 'foreach': bindingValue, 'templateEngine': ko.nativeTemplateEngine.instance };
2436
+
2437
+ // If bindingValue.data is the array, preserve all relevant options
2438
+ return {
2439
+ 'foreach': bindingValue['data'],
2440
+ 'includeDestroyed': bindingValue['includeDestroyed'],
2441
+ 'afterAdd': bindingValue['afterAdd'],
2442
+ 'beforeRemove': bindingValue['beforeRemove'],
2443
+ 'afterRender': bindingValue['afterRender'],
2444
+ 'templateEngine': ko.nativeTemplateEngine.instance
2445
+ };
2446
+ };
2447
+ },
2448
+ 'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
2449
+ return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor));
2450
+ },
2451
+ 'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
2452
+ return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
2453
+ }
2454
+ };
2455
+ ko.jsonExpressionRewriting.bindingRewriteValidators['foreach'] = false; // Can't rewrite control flow bindings
2456
+ ko.virtualElements.allowedBindings['foreach'] = true;
2457
+ ko.exportSymbol('ko.allowedVirtualElementBindings', ko.virtualElements.allowedBindings);// If you want to make a custom template engine,
2458
+ //
2459
+ // [1] Inherit from this class (like ko.nativeTemplateEngine does)
2460
+ // [2] Override 'renderTemplateSource', supplying a function with this signature:
2461
+ //
2462
+ // function (templateSource, bindingContext, options) {
2463
+ // // - templateSource.text() is the text of the template you should render
2464
+ // // - bindingContext.$data is the data you should pass into the template
2465
+ // // - you might also want to make bindingContext.$parent, bindingContext.$parents,
2466
+ // // and bindingContext.$root available in the template too
2467
+ // // - options gives you access to any other properties set on "data-bind: { template: options }"
2468
+ // //
2469
+ // // Return value: an array of DOM nodes
2470
+ // }
2471
+ //
2472
+ // [3] Override 'createJavaScriptEvaluatorBlock', supplying a function with this signature:
2473
+ //
2474
+ // function (script) {
2475
+ // // Return value: Whatever syntax means "Evaluate the JavaScript statement 'script' and output the result"
2476
+ // // For example, the jquery.tmpl template engine converts 'someScript' to '${ someScript }'
2477
+ // }
2478
+ //
2479
+ // This is only necessary if you want to allow data-bind attributes to reference arbitrary template variables.
2480
+ // If you don't want to allow that, you can set the property 'allowTemplateRewriting' to false (like ko.nativeTemplateEngine does)
2481
+ // and then you don't need to override 'createJavaScriptEvaluatorBlock'.
2482
+
2483
+ ko.templateEngine = function () { };
2484
+
2485
+ ko.templateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
2486
+ throw "Override renderTemplateSource";
2487
+ };
2488
+
2489
+ ko.templateEngine.prototype['createJavaScriptEvaluatorBlock'] = function (script) {
2490
+ throw "Override createJavaScriptEvaluatorBlock";
2491
+ };
2492
+
2493
+ ko.templateEngine.prototype['makeTemplateSource'] = function(template) {
2494
+ // Named template
2495
+ if (typeof template == "string") {
2496
+ var elem = document.getElementById(template);
2497
+ if (!elem)
2498
+ throw new Error("Cannot find template with ID " + template);
2499
+ return new ko.templateSources.domElement(elem);
2500
+ } else if ((template.nodeType == 1) || (template.nodeType == 8)) {
2501
+ // Anonymous template
2502
+ return new ko.templateSources.anonymousTemplate(template);
2503
+ } else
2504
+ throw new Error("Unknown template type: " + template);
2505
+ };
2506
+
2507
+ ko.templateEngine.prototype['renderTemplate'] = function (template, bindingContext, options) {
2508
+ var templateSource = this['makeTemplateSource'](template);
2509
+ return this['renderTemplateSource'](templateSource, bindingContext, options);
2510
+ };
2511
+
2512
+ ko.templateEngine.prototype['isTemplateRewritten'] = function (template) {
2513
+ // Skip rewriting if requested
2514
+ if (this['allowTemplateRewriting'] === false)
2515
+ return true;
2516
+
2517
+ // Perf optimisation - see below
2518
+ if (this.knownRewrittenTemplates && this.knownRewrittenTemplates[template])
2519
+ return true;
2520
+
2521
+ return this['makeTemplateSource'](template)['data']("isRewritten");
2522
+ };
2523
+
2524
+ ko.templateEngine.prototype['rewriteTemplate'] = function (template, rewriterCallback) {
2525
+ var templateSource = this['makeTemplateSource'](template);
2526
+ var rewritten = rewriterCallback(templateSource['text']());
2527
+ templateSource['text'](rewritten);
2528
+ templateSource['data']("isRewritten", true);
2529
+
2530
+ // Perf optimisation - for named templates, track which ones have been rewritten so we can
2531
+ // answer 'isTemplateRewritten' *without* having to use getElementById (which is slow on IE < 8)
2532
+ if (typeof template == "string") {
2533
+ this.knownRewrittenTemplates = this.knownRewrittenTemplates || {};
2534
+ this.knownRewrittenTemplates[template] = true;
2535
+ }
2536
+ };
2537
+
2538
+ ko.exportSymbol('ko.templateEngine', ko.templateEngine);
2539
+ ko.templateRewriting = (function () {
2540
+ var memoizeDataBindingAttributeSyntaxRegex = /(<[a-z]+\d*(\s+(?!data-bind=)[a-z0-9\-]+(=(\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind=(["'])([\s\S]*?)\5/gi;
2541
+ var memoizeVirtualContainerBindingSyntaxRegex = /<!--\s*ko\b\s*([\s\S]*?)\s*-->/g;
2542
+
2543
+ function validateDataBindValuesForRewriting(keyValueArray) {
2544
+ var allValidators = ko.jsonExpressionRewriting.bindingRewriteValidators;
2545
+ for (var i = 0; i < keyValueArray.length; i++) {
2546
+ var key = keyValueArray[i]['key'];
2547
+ if (allValidators.hasOwnProperty(key)) {
2548
+ var validator = allValidators[key];
2549
+
2550
+ if (typeof validator === "function") {
2551
+ var possibleErrorMessage = validator(keyValueArray[i]['value']);
2552
+ if (possibleErrorMessage)
2553
+ throw new Error(possibleErrorMessage);
2554
+ } else if (!validator) {
2555
+ throw new Error("This template engine does not support the '" + key + "' binding within its templates");
2556
+ }
2557
+ }
2558
+ }
2559
+ }
2560
+
2561
+ function constructMemoizedTagReplacement(dataBindAttributeValue, tagToRetain, templateEngine) {
2562
+ var dataBindKeyValueArray = ko.jsonExpressionRewriting.parseObjectLiteral(dataBindAttributeValue);
2563
+ validateDataBindValuesForRewriting(dataBindKeyValueArray);
2564
+ var rewrittenDataBindAttributeValue = ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson(dataBindKeyValueArray);
2565
+
2566
+ // For no obvious reason, Opera fails to evaluate rewrittenDataBindAttributeValue unless it's wrapped in an additional
2567
+ // anonymous function, even though Opera's built-in debugger can evaluate it anyway. No other browser requires this
2568
+ // extra indirection.
2569
+ var applyBindingsToNextSiblingScript = "ko.templateRewriting.applyMemoizedBindingsToNextSibling(function() { \
2570
+ return (function() { return { " + rewrittenDataBindAttributeValue + " } })() \
2571
+ })";
2572
+ return templateEngine['createJavaScriptEvaluatorBlock'](applyBindingsToNextSiblingScript) + tagToRetain;
2573
+ }
2574
+
2575
+ return {
2576
+ ensureTemplateIsRewritten: function (template, templateEngine) {
2577
+ if (!templateEngine['isTemplateRewritten'](template))
2578
+ templateEngine['rewriteTemplate'](template, function (htmlString) {
2579
+ return ko.templateRewriting.memoizeBindingAttributeSyntax(htmlString, templateEngine);
2580
+ });
2581
+ },
2582
+
2583
+ memoizeBindingAttributeSyntax: function (htmlString, templateEngine) {
2584
+ return htmlString.replace(memoizeDataBindingAttributeSyntaxRegex, function () {
2585
+ return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[6], /* tagToRetain: */ arguments[1], templateEngine);
2586
+ }).replace(memoizeVirtualContainerBindingSyntaxRegex, function() {
2587
+ return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[1], /* tagToRetain: */ "<!-- ko -->", templateEngine);
2588
+ });
2589
+ },
2590
+
2591
+ applyMemoizedBindingsToNextSibling: function (bindings) {
2592
+ return ko.memoization.memoize(function (domNode, bindingContext) {
2593
+ if (domNode.nextSibling)
2594
+ ko.applyBindingsToNode(domNode.nextSibling, bindings, bindingContext);
2595
+ });
2596
+ }
2597
+ }
2598
+ })();
2599
+
2600
+ ko.exportSymbol('ko.templateRewriting', ko.templateRewriting);
2601
+ ko.exportSymbol('ko.templateRewriting.applyMemoizedBindingsToNextSibling', ko.templateRewriting.applyMemoizedBindingsToNextSibling); // Exported only because it has to be referenced by string lookup from within rewritten template
2602
+ (function() {
2603
+ // A template source represents a read/write way of accessing a template. This is to eliminate the need for template loading/saving
2604
+ // logic to be duplicated in every template engine (and means they can all work with anonymous templates, etc.)
2605
+ //
2606
+ // Two are provided by default:
2607
+ // 1. ko.templateSources.domElement - reads/writes the text content of an arbitrary DOM element
2608
+ // 2. ko.templateSources.anonymousElement - uses ko.utils.domData to read/write text *associated* with the DOM element, but
2609
+ // without reading/writing the actual element text content, since it will be overwritten
2610
+ // with the rendered template output.
2611
+ // You can implement your own template source if you want to fetch/store templates somewhere other than in DOM elements.
2612
+ // Template sources need to have the following functions:
2613
+ // text() - returns the template text from your storage location
2614
+ // text(value) - writes the supplied template text to your storage location
2615
+ // data(key) - reads values stored using data(key, value) - see below
2616
+ // data(key, value) - associates "value" with this template and the key "key". Is used to store information like "isRewritten".
2617
+ //
2618
+ // Once you've implemented a templateSource, make your template engine use it by subclassing whatever template engine you were
2619
+ // using and overriding "makeTemplateSource" to return an instance of your custom template source.
2620
+
2621
+ ko.templateSources = {};
2622
+
2623
+ // ---- ko.templateSources.domElement -----
2624
+
2625
+ ko.templateSources.domElement = function(element) {
2626
+ this.domElement = element;
2627
+ }
2628
+
2629
+ ko.templateSources.domElement.prototype['text'] = function(/* valueToWrite */) {
2630
+ if (arguments.length == 0) {
2631
+ return this.domElement.tagName.toLowerCase() == "script" ? this.domElement.text : this.domElement.innerHTML;
2632
+ } else {
2633
+ var valueToWrite = arguments[0];
2634
+ if (this.domElement.tagName.toLowerCase() == "script")
2635
+ this.domElement.text = valueToWrite;
2636
+ else
2637
+ ko.utils.setHtml(this.domElement, valueToWrite);
2638
+ }
2639
+ };
2640
+
2641
+ ko.templateSources.domElement.prototype['data'] = function(key /*, valueToWrite */) {
2642
+ if (arguments.length === 1) {
2643
+ return ko.utils.domData.get(this.domElement, "templateSourceData_" + key);
2644
+ } else {
2645
+ ko.utils.domData.set(this.domElement, "templateSourceData_" + key, arguments[1]);
2646
+ }
2647
+ };
2648
+
2649
+ // ---- ko.templateSources.anonymousTemplate -----
2650
+
2651
+ var anonymousTemplatesDomDataKey = "__ko_anon_template__";
2652
+ ko.templateSources.anonymousTemplate = function(element) {
2653
+ this.domElement = element;
2654
+ }
2655
+ ko.templateSources.anonymousTemplate.prototype = new ko.templateSources.domElement();
2656
+ ko.templateSources.anonymousTemplate.prototype['text'] = function(/* valueToWrite */) {
2657
+ if (arguments.length == 0) {
2658
+ return ko.utils.domData.get(this.domElement, anonymousTemplatesDomDataKey);
2659
+ } else {
2660
+ var valueToWrite = arguments[0];
2661
+ ko.utils.domData.set(this.domElement, anonymousTemplatesDomDataKey, valueToWrite);
2662
+ }
2663
+ };
2664
+
2665
+ ko.exportSymbol('ko.templateSources', ko.templateSources);
2666
+ ko.exportSymbol('ko.templateSources.domElement', ko.templateSources.domElement);
2667
+ ko.exportSymbol('ko.templateSources.anonymousTemplate', ko.templateSources.anonymousTemplate);
2668
+ })();
2669
+ (function () {
2670
+ var _templateEngine;
2671
+ ko.setTemplateEngine = function (templateEngine) {
2672
+ if ((templateEngine != undefined) && !(templateEngine instanceof ko.templateEngine))
2673
+ throw "templateEngine must inherit from ko.templateEngine";
2674
+ _templateEngine = templateEngine;
2675
+ }
2676
+
2677
+ function invokeForEachNodeOrCommentInParent(nodeArray, parent, action) {
2678
+ for (var i = 0; node = nodeArray[i]; i++) {
2679
+ if (node.parentNode !== parent) // Skip anything that has been removed during binding
2680
+ continue;
2681
+ if ((node.nodeType === 1) || (node.nodeType === 8))
2682
+ action(node);
2683
+ }
2684
+ }
2685
+
2686
+ ko.activateBindingsOnTemplateRenderedNodes = function(nodeArray, bindingContext) {
2687
+ // To be used on any nodes that have been rendered by a template and have been inserted into some parent element.
2688
+ // Safely iterates through nodeArray (being tolerant of any changes made to it during binding, e.g.,
2689
+ // if a binding inserts siblings), and for each:
2690
+ // (1) Does a regular "applyBindings" to associate bindingContext with this node and to activate any non-memoized bindings
2691
+ // (2) Unmemoizes any memos in the DOM subtree (e.g., to activate bindings that had been memoized during template rewriting)
2692
+
2693
+ var nodeArrayClone = ko.utils.arrayPushAll([], nodeArray); // So we can tolerate insertions/deletions during binding
2694
+ var commonParentElement = (nodeArray.length > 0) ? nodeArray[0].parentNode : null; // All items must be in the same parent, so this is OK
2695
+
2696
+ // Need to applyBindings *before* unmemoziation, because unmemoization might introduce extra nodes (that we don't want to re-bind)
2697
+ // whereas a regular applyBindings won't introduce new memoized nodes
2698
+
2699
+ invokeForEachNodeOrCommentInParent(nodeArrayClone, commonParentElement, function(node) {
2700
+ ko.applyBindings(bindingContext, node);
2701
+ });
2702
+ invokeForEachNodeOrCommentInParent(nodeArrayClone, commonParentElement, function(node) {
2703
+ ko.memoization.unmemoizeDomNodeAndDescendants(node, [bindingContext]);
2704
+ });
2705
+ }
2706
+
2707
+ function getFirstNodeFromPossibleArray(nodeOrNodeArray) {
2708
+ return nodeOrNodeArray.nodeType ? nodeOrNodeArray
2709
+ : nodeOrNodeArray.length > 0 ? nodeOrNodeArray[0]
2710
+ : null;
2711
+ }
2712
+
2713
+ function executeTemplate(targetNodeOrNodeArray, renderMode, template, bindingContext, options) {
2714
+ options = options || {};
2715
+ var templateEngineToUse = (options['templateEngine'] || _templateEngine);
2716
+ ko.templateRewriting.ensureTemplateIsRewritten(template, templateEngineToUse);
2717
+ var renderedNodesArray = templateEngineToUse['renderTemplate'](template, bindingContext, options);
2718
+
2719
+ // Loosely check result is an array of DOM nodes
2720
+ if ((typeof renderedNodesArray.length != "number") || (renderedNodesArray.length > 0 && typeof renderedNodesArray[0].nodeType != "number"))
2721
+ throw "Template engine must return an array of DOM nodes";
2722
+
2723
+ var haveAddedNodesToParent = false;
2724
+ switch (renderMode) {
2725
+ case "replaceChildren":
2726
+ ko.virtualElements.setDomNodeChildren(targetNodeOrNodeArray, renderedNodesArray);
2727
+ haveAddedNodesToParent = true;
2728
+ break;
2729
+ case "replaceNode":
2730
+ ko.utils.replaceDomNodes(targetNodeOrNodeArray, renderedNodesArray);
2731
+ haveAddedNodesToParent = true;
2732
+ break;
2733
+ case "ignoreTargetNode": break;
2734
+ default:
2735
+ throw new Error("Unknown renderMode: " + renderMode);
2736
+ }
2737
+
2738
+ if (haveAddedNodesToParent) {
2739
+ ko.activateBindingsOnTemplateRenderedNodes(renderedNodesArray, bindingContext);
2740
+ if (options['afterRender'])
2741
+ options['afterRender'](renderedNodesArray, bindingContext['$data']);
2742
+ }
2743
+
2744
+ return renderedNodesArray;
2745
+ }
2746
+
2747
+ ko.renderTemplate = function (template, dataOrBindingContext, options, targetNodeOrNodeArray, renderMode) {
2748
+ options = options || {};
2749
+ if ((options['templateEngine'] || _templateEngine) == undefined)
2750
+ throw "Set a template engine before calling renderTemplate";
2751
+ renderMode = renderMode || "replaceChildren";
2752
+
2753
+ if (targetNodeOrNodeArray) {
2754
+ var firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
2755
+
2756
+ var whenToDispose = function () { return (!firstTargetNode) || !ko.utils.domNodeIsAttachedToDocument(firstTargetNode); }; // Passive disposal (on next evaluation)
2757
+ var activelyDisposeWhenNodeIsRemoved = (firstTargetNode && renderMode == "replaceNode") ? firstTargetNode.parentNode : firstTargetNode;
2758
+
2759
+ return new ko.dependentObservable( // So the DOM is automatically updated when any dependency changes
2760
+ function () {
2761
+ // Ensure we've got a proper binding context to work with
2762
+ var bindingContext = (dataOrBindingContext && (dataOrBindingContext instanceof ko.bindingContext))
2763
+ ? dataOrBindingContext
2764
+ : new ko.bindingContext(ko.utils.unwrapObservable(dataOrBindingContext));
2765
+
2766
+ // Support selecting template as a function of the data being rendered
2767
+ var templateName = typeof(template) == 'function' ? template(bindingContext['$data']) : template;
2768
+
2769
+ var renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, templateName, bindingContext, options);
2770
+ if (renderMode == "replaceNode") {
2771
+ targetNodeOrNodeArray = renderedNodesArray;
2772
+ firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
2773
+ }
2774
+ },
2775
+ null,
2776
+ { 'disposeWhen': whenToDispose, 'disposeWhenNodeIsRemoved': activelyDisposeWhenNodeIsRemoved }
2777
+ );
2778
+ } else {
2779
+ // 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
2780
+ return ko.memoization.memoize(function (domNode) {
2781
+ ko.renderTemplate(template, dataOrBindingContext, options, domNode, "replaceNode");
2782
+ });
2783
+ }
2784
+ };
2785
+
2786
+ ko.renderTemplateForEach = function (template, arrayOrObservableArray, options, targetNode, parentBindingContext) {
2787
+ var createInnerBindingContext = function(arrayValue) {
2788
+ return parentBindingContext['createChildContext'](ko.utils.unwrapObservable(arrayValue));
2789
+ };
2790
+
2791
+ // This will be called whenever setDomNodeChildrenFromArrayMapping has added nodes to targetNode
2792
+ var activateBindingsCallback = function(arrayValue, addedNodesArray) {
2793
+ var bindingContext = createInnerBindingContext(arrayValue);
2794
+ ko.activateBindingsOnTemplateRenderedNodes(addedNodesArray, bindingContext);
2795
+ if (options['afterRender'])
2796
+ options['afterRender'](addedNodesArray, bindingContext['$data']);
2797
+ };
2798
+
2799
+ return new ko.dependentObservable(function () {
2800
+ var unwrappedArray = ko.utils.unwrapObservable(arrayOrObservableArray) || [];
2801
+ if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
2802
+ unwrappedArray = [unwrappedArray];
2803
+
2804
+ // Filter out any entries marked as destroyed
2805
+ var filteredArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
2806
+ return options['includeDestroyed'] || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
2807
+ });
2808
+
2809
+ ko.utils.setDomNodeChildrenFromArrayMapping(targetNode, filteredArray, function (arrayValue) {
2810
+ // Support selecting template as a function of the data being rendered
2811
+ var templateName = typeof(template) == 'function' ? template(arrayValue) : template;
2812
+ return executeTemplate(null, "ignoreTargetNode", templateName, createInnerBindingContext(arrayValue), options);
2813
+ }, options, activateBindingsCallback);
2814
+
2815
+ }, null, { 'disposeWhenNodeIsRemoved': targetNode });
2816
+ };
2817
+
2818
+ var templateSubscriptionDomDataKey = '__ko__templateSubscriptionDomDataKey__';
2819
+ function disposeOldSubscriptionAndStoreNewOne(element, newSubscription) {
2820
+ var oldSubscription = ko.utils.domData.get(element, templateSubscriptionDomDataKey);
2821
+ if (oldSubscription && (typeof(oldSubscription.dispose) == 'function'))
2822
+ oldSubscription.dispose();
2823
+ ko.utils.domData.set(element, templateSubscriptionDomDataKey, newSubscription);
2824
+ }
2825
+
2826
+ ko.bindingHandlers['template'] = {
2827
+ 'init': function(element, valueAccessor) {
2828
+ // Support anonymous templates
2829
+ var bindingValue = ko.utils.unwrapObservable(valueAccessor());
2830
+ if ((typeof bindingValue != "string") && (!bindingValue.name) && (element.nodeType == 1)) {
2831
+ // It's an anonymous template - store the element contents, then clear the element
2832
+ new ko.templateSources.anonymousTemplate(element).text(element.innerHTML);
2833
+ ko.utils.emptyDomNode(element);
2834
+ }
2835
+ return { 'controlsDescendantBindings': true };
2836
+ },
2837
+ 'update': function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
2838
+ var bindingValue = ko.utils.unwrapObservable(valueAccessor());
2839
+ var templateName;
2840
+ var shouldDisplay = true;
2841
+
2842
+ if (typeof bindingValue == "string") {
2843
+ templateName = bindingValue;
2844
+ } else {
2845
+ templateName = bindingValue.name;
2846
+
2847
+ // Support "if"/"ifnot" conditions
2848
+ if ('if' in bindingValue)
2849
+ shouldDisplay = shouldDisplay && ko.utils.unwrapObservable(bindingValue['if']);
2850
+ if ('ifnot' in bindingValue)
2851
+ shouldDisplay = shouldDisplay && !ko.utils.unwrapObservable(bindingValue['ifnot']);
2852
+ }
2853
+
2854
+ var templateSubscription = null;
2855
+
2856
+ if ((typeof bindingValue === 'object') && ('foreach' in bindingValue)) { // Note: can't use 'in' operator on strings
2857
+ // Render once for each data point (treating data set as empty if shouldDisplay==false)
2858
+ var dataArray = (shouldDisplay && bindingValue['foreach']) || [];
2859
+ templateSubscription = ko.renderTemplateForEach(templateName || element, dataArray, /* options: */ bindingValue, element, bindingContext);
2860
+ } else {
2861
+ if (shouldDisplay) {
2862
+ // Render once for this single data point (or use the viewModel if no data was provided)
2863
+ var innerBindingContext = (typeof bindingValue == 'object') && ('data' in bindingValue)
2864
+ ? bindingContext['createChildContext'](ko.utils.unwrapObservable(bindingValue['data'])) // Given an explitit 'data' value, we create a child binding context for it
2865
+ : bindingContext; // Given no explicit 'data' value, we retain the same binding context
2866
+ templateSubscription = ko.renderTemplate(templateName || element, innerBindingContext, /* options: */ bindingValue, element);
2867
+ } else
2868
+ ko.virtualElements.emptyNode(element);
2869
+ }
2870
+
2871
+ // It only makes sense to have a single template subscription per element (otherwise which one should have its output displayed?)
2872
+ disposeOldSubscriptionAndStoreNewOne(element, templateSubscription);
2873
+ }
2874
+ };
2875
+
2876
+ // Anonymous templates can't be rewritten. Give a nice error message if you try to do it.
2877
+ ko.jsonExpressionRewriting.bindingRewriteValidators['template'] = function(bindingValue) {
2878
+ var parsedBindingValue = ko.jsonExpressionRewriting.parseObjectLiteral(bindingValue);
2879
+
2880
+ if ((parsedBindingValue.length == 1) && parsedBindingValue[0]['unknown'])
2881
+ return null; // It looks like a string literal, not an object literal, so treat it as a named template (which is allowed for rewriting)
2882
+
2883
+ if (ko.jsonExpressionRewriting.keyValueArrayContainsKey(parsedBindingValue, "name"))
2884
+ return null; // Named templates can be rewritten, so return "no error"
2885
+ return "This template engine does not support anonymous templates nested within its templates";
2886
+ };
2887
+
2888
+ ko.virtualElements.allowedBindings['template'] = true;
2889
+ })();
2890
+
2891
+ ko.exportSymbol('ko.setTemplateEngine', ko.setTemplateEngine);
2892
+ ko.exportSymbol('ko.renderTemplate', ko.renderTemplate);
2893
+ (function () {
2894
+ // Simple calculation based on Levenshtein distance.
2895
+ function calculateEditDistanceMatrix(oldArray, newArray, maxAllowedDistance) {
2896
+ var distances = [];
2897
+ for (var i = 0; i <= newArray.length; i++)
2898
+ distances[i] = [];
2899
+
2900
+ // Top row - transform old array into empty array via deletions
2901
+ for (var i = 0, j = Math.min(oldArray.length, maxAllowedDistance); i <= j; i++)
2902
+ distances[0][i] = i;
2903
+
2904
+ // Left row - transform empty array into new array via additions
2905
+ for (var i = 1, j = Math.min(newArray.length, maxAllowedDistance); i <= j; i++) {
2906
+ distances[i][0] = i;
2907
+ }
2908
+
2909
+ // Fill out the body of the array
2910
+ var oldIndex, oldIndexMax = oldArray.length, newIndex, newIndexMax = newArray.length;
2911
+ var distanceViaAddition, distanceViaDeletion;
2912
+ for (oldIndex = 1; oldIndex <= oldIndexMax; oldIndex++) {
2913
+ var newIndexMinForRow = Math.max(1, oldIndex - maxAllowedDistance);
2914
+ var newIndexMaxForRow = Math.min(newIndexMax, oldIndex + maxAllowedDistance);
2915
+ for (newIndex = newIndexMinForRow; newIndex <= newIndexMaxForRow; newIndex++) {
2916
+ if (oldArray[oldIndex - 1] === newArray[newIndex - 1])
2917
+ distances[newIndex][oldIndex] = distances[newIndex - 1][oldIndex - 1];
2918
+ else {
2919
+ var northDistance = distances[newIndex - 1][oldIndex] === undefined ? Number.MAX_VALUE : distances[newIndex - 1][oldIndex] + 1;
2920
+ var westDistance = distances[newIndex][oldIndex - 1] === undefined ? Number.MAX_VALUE : distances[newIndex][oldIndex - 1] + 1;
2921
+ distances[newIndex][oldIndex] = Math.min(northDistance, westDistance);
2922
+ }
2923
+ }
2924
+ }
2925
+
2926
+ return distances;
2927
+ }
2928
+
2929
+ function findEditScriptFromEditDistanceMatrix(editDistanceMatrix, oldArray, newArray) {
2930
+ var oldIndex = oldArray.length;
2931
+ var newIndex = newArray.length;
2932
+ var editScript = [];
2933
+ var maxDistance = editDistanceMatrix[newIndex][oldIndex];
2934
+ if (maxDistance === undefined)
2935
+ return null; // maxAllowedDistance must be too small
2936
+ while ((oldIndex > 0) || (newIndex > 0)) {
2937
+ var me = editDistanceMatrix[newIndex][oldIndex];
2938
+ var distanceViaAdd = (newIndex > 0) ? editDistanceMatrix[newIndex - 1][oldIndex] : maxDistance + 1;
2939
+ var distanceViaDelete = (oldIndex > 0) ? editDistanceMatrix[newIndex][oldIndex - 1] : maxDistance + 1;
2940
+ var distanceViaRetain = (newIndex > 0) && (oldIndex > 0) ? editDistanceMatrix[newIndex - 1][oldIndex - 1] : maxDistance + 1;
2941
+ if ((distanceViaAdd === undefined) || (distanceViaAdd < me - 1)) distanceViaAdd = maxDistance + 1;
2942
+ if ((distanceViaDelete === undefined) || (distanceViaDelete < me - 1)) distanceViaDelete = maxDistance + 1;
2943
+ if (distanceViaRetain < me - 1) distanceViaRetain = maxDistance + 1;
2944
+
2945
+ if ((distanceViaAdd <= distanceViaDelete) && (distanceViaAdd < distanceViaRetain)) {
2946
+ editScript.push({ status: "added", value: newArray[newIndex - 1] });
2947
+ newIndex--;
2948
+ } else if ((distanceViaDelete < distanceViaAdd) && (distanceViaDelete < distanceViaRetain)) {
2949
+ editScript.push({ status: "deleted", value: oldArray[oldIndex - 1] });
2950
+ oldIndex--;
2951
+ } else {
2952
+ editScript.push({ status: "retained", value: oldArray[oldIndex - 1] });
2953
+ newIndex--;
2954
+ oldIndex--;
2955
+ }
2956
+ }
2957
+ return editScript.reverse();
2958
+ }
2959
+
2960
+ ko.utils.compareArrays = function (oldArray, newArray, maxEditsToConsider) {
2961
+ if (maxEditsToConsider === undefined) {
2962
+ return ko.utils.compareArrays(oldArray, newArray, 1) // First consider likely case where there is at most one edit (very fast)
2963
+ || ko.utils.compareArrays(oldArray, newArray, 10) // If that fails, account for a fair number of changes while still being fast
2964
+ || ko.utils.compareArrays(oldArray, newArray, Number.MAX_VALUE); // Ultimately give the right answer, even though it may take a long time
2965
+ } else {
2966
+ oldArray = oldArray || [];
2967
+ newArray = newArray || [];
2968
+ var editDistanceMatrix = calculateEditDistanceMatrix(oldArray, newArray, maxEditsToConsider);
2969
+ return findEditScriptFromEditDistanceMatrix(editDistanceMatrix, oldArray, newArray);
2970
+ }
2971
+ };
2972
+ })();
2973
+
2974
+ ko.exportSymbol('ko.utils.compareArrays', ko.utils.compareArrays);
2975
+
2976
+ (function () {
2977
+ // Objective:
2978
+ // * Given an input array, a container DOM node, and a function from array elements to arrays of DOM nodes,
2979
+ // map the array elements to arrays of DOM nodes, concatenate together all these arrays, and use them to populate the container DOM node
2980
+ // * Next time we're given the same combination of things (with the array possibly having mutated), update the container DOM node
2981
+ // 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
2982
+ // previously mapped - retain those nodes, and just insert/delete other ones
2983
+
2984
+ // "callbackAfterAddingNodes" will be invoked after any "mapping"-generated nodes are inserted into the container node
2985
+ // You can use this, for example, to activate bindings on those nodes.
2986
+
2987
+ function fixUpVirtualElements(contiguousNodeArray) {
2988
+ // Ensures that contiguousNodeArray really *is* an array of contiguous siblings, even if some of the interior
2989
+ // ones have changed since your array was first built (e.g., because your array contains virtual elements, and
2990
+ // their virtual children changed when binding was applied to them).
2991
+ // This is needed so that we can reliably remove or update the nodes corresponding to a given array item
2992
+
2993
+ if (contiguousNodeArray.length > 2) {
2994
+ // Build up the actual new contiguous node set
2995
+ var current = contiguousNodeArray[0], last = contiguousNodeArray[contiguousNodeArray.length - 1], newContiguousSet = [current];
2996
+ while (current !== last) {
2997
+ current = current.nextSibling;
2998
+ if (!current) // Won't happen, except if the developer has manually removed some DOM elements (then we're in an undefined scenario)
2999
+ return;
3000
+ newContiguousSet.push(current);
3001
+ }
3002
+
3003
+ // ... then mutate the input array to match this.
3004
+ // (The following line replaces the contents of contiguousNodeArray with newContiguousSet)
3005
+ Array.prototype.splice.apply(contiguousNodeArray, [0, contiguousNodeArray.length].concat(newContiguousSet));
3006
+ }
3007
+ }
3008
+
3009
+ function mapNodeAndRefreshWhenChanged(containerNode, mapping, valueToMap, callbackAfterAddingNodes) {
3010
+ // Map this array value inside a dependentObservable so we re-map when any dependency changes
3011
+ var mappedNodes = [];
3012
+ var dependentObservable = ko.dependentObservable(function() {
3013
+ var newMappedNodes = mapping(valueToMap) || [];
3014
+
3015
+ // On subsequent evaluations, just replace the previously-inserted DOM nodes
3016
+ if (mappedNodes.length > 0) {
3017
+ fixUpVirtualElements(mappedNodes);
3018
+ ko.utils.replaceDomNodes(mappedNodes, newMappedNodes);
3019
+ if (callbackAfterAddingNodes)
3020
+ callbackAfterAddingNodes(valueToMap, newMappedNodes);
3021
+ }
3022
+
3023
+ // Replace the contents of the mappedNodes array, thereby updating the record
3024
+ // of which nodes would be deleted if valueToMap was itself later removed
3025
+ mappedNodes.splice(0, mappedNodes.length);
3026
+ ko.utils.arrayPushAll(mappedNodes, newMappedNodes);
3027
+ }, null, { 'disposeWhenNodeIsRemoved': containerNode, 'disposeWhen': function() { return (mappedNodes.length == 0) || !ko.utils.domNodeIsAttachedToDocument(mappedNodes[0]) } });
3028
+ return { mappedNodes : mappedNodes, dependentObservable : dependentObservable };
3029
+ }
3030
+
3031
+ var lastMappingResultDomDataKey = "setDomNodeChildrenFromArrayMapping_lastMappingResult";
3032
+
3033
+ ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options, callbackAfterAddingNodes) {
3034
+ // Compare the provided array against the previous one
3035
+ array = array || [];
3036
+ options = options || {};
3037
+ var isFirstExecution = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) === undefined;
3038
+ var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) || [];
3039
+ var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; });
3040
+ var editScript = ko.utils.compareArrays(lastArray, array);
3041
+
3042
+ // Build the new mapping result
3043
+ var newMappingResult = [];
3044
+ var lastMappingResultIndex = 0;
3045
+ var nodesToDelete = [];
3046
+ var nodesAdded = [];
3047
+ var insertAfterNode = null;
3048
+ for (var i = 0, j = editScript.length; i < j; i++) {
3049
+ switch (editScript[i].status) {
3050
+ case "retained":
3051
+ // Just keep the information - don't touch the nodes
3052
+ var dataToRetain = lastMappingResult[lastMappingResultIndex];
3053
+ newMappingResult.push(dataToRetain);
3054
+ if (dataToRetain.domNodes.length > 0)
3055
+ insertAfterNode = dataToRetain.domNodes[dataToRetain.domNodes.length - 1];
3056
+ lastMappingResultIndex++;
3057
+ break;
3058
+
3059
+ case "deleted":
3060
+ // Stop tracking changes to the mapping for these nodes
3061
+ lastMappingResult[lastMappingResultIndex].dependentObservable.dispose();
3062
+
3063
+ // Queue these nodes for later removal
3064
+ fixUpVirtualElements(lastMappingResult[lastMappingResultIndex].domNodes);
3065
+ ko.utils.arrayForEach(lastMappingResult[lastMappingResultIndex].domNodes, function (node) {
3066
+ nodesToDelete.push({
3067
+ element: node,
3068
+ index: i,
3069
+ value: editScript[i].value
3070
+ });
3071
+ insertAfterNode = node;
3072
+ });
3073
+ lastMappingResultIndex++;
3074
+ break;
3075
+
3076
+ case "added":
3077
+ var valueToMap = editScript[i].value;
3078
+ var mapData = mapNodeAndRefreshWhenChanged(domNode, mapping, valueToMap, callbackAfterAddingNodes);
3079
+ var mappedNodes = mapData.mappedNodes;
3080
+
3081
+ // On the first evaluation, insert the nodes at the current insertion point
3082
+ newMappingResult.push({ arrayEntry: editScript[i].value, domNodes: mappedNodes, dependentObservable: mapData.dependentObservable });
3083
+ for (var nodeIndex = 0, nodeIndexMax = mappedNodes.length; nodeIndex < nodeIndexMax; nodeIndex++) {
3084
+ var node = mappedNodes[nodeIndex];
3085
+ nodesAdded.push({
3086
+ element: node,
3087
+ index: i,
3088
+ value: editScript[i].value
3089
+ });
3090
+ if (insertAfterNode == null) {
3091
+ // Insert "node" (the newly-created node) as domNode's first child
3092
+ ko.virtualElements.prepend(domNode, node);
3093
+ } else {
3094
+ // Insert "node" into "domNode" immediately after "insertAfterNode"
3095
+ ko.virtualElements.insertAfter(domNode, node, insertAfterNode);
3096
+ }
3097
+ insertAfterNode = node;
3098
+ }
3099
+ if (callbackAfterAddingNodes)
3100
+ callbackAfterAddingNodes(valueToMap, mappedNodes);
3101
+ break;
3102
+ }
3103
+ }
3104
+
3105
+ ko.utils.arrayForEach(nodesToDelete, function (node) { ko.cleanNode(node.element) });
3106
+
3107
+ var invokedBeforeRemoveCallback = false;
3108
+ if (!isFirstExecution) {
3109
+ if (options['afterAdd']) {
3110
+ for (var i = 0; i < nodesAdded.length; i++)
3111
+ options['afterAdd'](nodesAdded[i].element, nodesAdded[i].index, nodesAdded[i].value);
3112
+ }
3113
+ if (options['beforeRemove']) {
3114
+ for (var i = 0; i < nodesToDelete.length; i++)
3115
+ options['beforeRemove'](nodesToDelete[i].element, nodesToDelete[i].index, nodesToDelete[i].value);
3116
+ invokedBeforeRemoveCallback = true;
3117
+ }
3118
+ }
3119
+ if (!invokedBeforeRemoveCallback)
3120
+ ko.utils.arrayForEach(nodesToDelete, function (node) {
3121
+ ko.removeNode(node.element);
3122
+ });
3123
+
3124
+ // Store a copy of the array items we just considered so we can difference it next time
3125
+ ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult);
3126
+ }
3127
+ })();
3128
+
3129
+ ko.exportSymbol('ko.utils.setDomNodeChildrenFromArrayMapping', ko.utils.setDomNodeChildrenFromArrayMapping);
3130
+ ko.nativeTemplateEngine = function () {
3131
+ this['allowTemplateRewriting'] = false;
3132
+ }
3133
+
3134
+ ko.nativeTemplateEngine.prototype = new ko.templateEngine();
3135
+ ko.nativeTemplateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
3136
+ var templateText = templateSource.text();
3137
+ return ko.utils.parseHtmlFragment(templateText);
3138
+ };
3139
+
3140
+ ko.nativeTemplateEngine.instance = new ko.nativeTemplateEngine();
3141
+ ko.setTemplateEngine(ko.nativeTemplateEngine.instance);
3142
+
3143
+ ko.exportSymbol('ko.nativeTemplateEngine', ko.nativeTemplateEngine);(function() {
3144
+ ko.jqueryTmplTemplateEngine = function () {
3145
+ // Detect which version of jquery-tmpl you're using. Unfortunately jquery-tmpl
3146
+ // doesn't expose a version number, so we have to infer it.
3147
+ // Note that as of Knockout 1.3, we only support jQuery.tmpl 1.0.0pre and later,
3148
+ // which KO internally refers to as version "2", so older versions are no longer detected.
3149
+ var jQueryTmplVersion = this.jQueryTmplVersion = (function() {
3150
+ if ((typeof(jQuery) == "undefined") || !(jQuery['tmpl']))
3151
+ return 0;
3152
+ // Since it exposes no official version number, we use our own numbering system. To be updated as jquery-tmpl evolves.
3153
+ try {
3154
+ if (jQuery['tmpl']['tag']['tmpl']['open'].toString().indexOf('__') >= 0) {
3155
+ // Since 1.0.0pre, custom tags should append markup to an array called "__"
3156
+ return 2; // Final version of jquery.tmpl
3157
+ }
3158
+ } catch(ex) { /* Apparently not the version we were looking for */ }
3159
+
3160
+ return 1; // Any older version that we don't support
3161
+ })();
3162
+
3163
+ function ensureHasReferencedJQueryTemplates() {
3164
+ if (jQueryTmplVersion < 2)
3165
+ throw new Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");
3166
+ }
3167
+
3168
+ function executeTemplate(compiledTemplate, data, jQueryTemplateOptions) {
3169
+ return jQuery['tmpl'](compiledTemplate, data, jQueryTemplateOptions);
3170
+ }
3171
+
3172
+ this['renderTemplateSource'] = function(templateSource, bindingContext, options) {
3173
+ options = options || {};
3174
+ ensureHasReferencedJQueryTemplates();
3175
+
3176
+ // Ensure we have stored a precompiled version of this template (don't want to reparse on every render)
3177
+ var precompiled = templateSource['data']('precompiled');
3178
+ if (!precompiled) {
3179
+ var templateText = templateSource.text() || "";
3180
+ // Wrap in "with($whatever.koBindingContext) { ... }"
3181
+ templateText = "{{ko_with $item.koBindingContext}}" + templateText + "{{/ko_with}}";
3182
+
3183
+ precompiled = jQuery['template'](null, templateText);
3184
+ templateSource['data']('precompiled', precompiled);
3185
+ }
3186
+
3187
+ var data = [bindingContext['$data']]; // Prewrap the data in an array to stop jquery.tmpl from trying to unwrap any arrays
3188
+ var jQueryTemplateOptions = jQuery['extend']({ 'koBindingContext': bindingContext }, options['templateOptions']);
3189
+
3190
+ var resultNodes = executeTemplate(precompiled, data, jQueryTemplateOptions);
3191
+ resultNodes['appendTo'](document.createElement("div")); // Using "appendTo" forces jQuery/jQuery.tmpl to perform necessary cleanup work
3192
+ jQuery['fragments'] = {}; // Clear jQuery's fragment cache to avoid a memory leak after a large number of template renders
3193
+ return resultNodes;
3194
+ };
3195
+
3196
+ this['createJavaScriptEvaluatorBlock'] = function(script) {
3197
+ return "{{ko_code ((function() { return " + script + " })()) }}";
3198
+ };
3199
+
3200
+ this['addTemplate'] = function(templateName, templateMarkup) {
3201
+ document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "</script>");
3202
+ };
3203
+
3204
+ if (jQueryTmplVersion > 0) {
3205
+ jQuery['tmpl']['tag']['ko_code'] = {
3206
+ open: "__.push($1 || '');"
3207
+ };
3208
+ jQuery['tmpl']['tag']['ko_with'] = {
3209
+ open: "with($1) {",
3210
+ close: "} "
3211
+ };
3212
+ }
3213
+ };
3214
+
3215
+ ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine();
3216
+
3217
+ // Use this one by default *only if jquery.tmpl is referenced*
3218
+ var jqueryTmplTemplateEngineInstance = new ko.jqueryTmplTemplateEngine();
3219
+ if (jqueryTmplTemplateEngineInstance.jQueryTmplVersion > 0)
3220
+ ko.setTemplateEngine(jqueryTmplTemplateEngineInstance);
3221
+
3222
+ ko.exportSymbol('ko.jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine);
3223
+ })();})(window);