moel@348
|
1 |
// Knockout Mapping plugin v2.1.2
|
moel@348
|
2 |
// (c) 2012 Steven Sanderson, Roy Jacobs - http://knockoutjs.com/
|
moel@348
|
3 |
// License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
moel@348
|
4 |
|
moel@348
|
5 |
(function (factory) {
|
moel@348
|
6 |
// Module systems magic dance.
|
moel@348
|
7 |
|
moel@348
|
8 |
if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
|
moel@348
|
9 |
// CommonJS or Node: hard-coded dependency on "knockout"
|
moel@348
|
10 |
factory(require("knockout"), exports);
|
moel@348
|
11 |
} else if (typeof define === "function" && define["amd"]) {
|
moel@348
|
12 |
// AMD anonymous module with hard-coded dependency on "knockout"
|
moel@348
|
13 |
define(["knockout", "exports"], factory);
|
moel@348
|
14 |
} else {
|
moel@348
|
15 |
// <script> tag: use the global `ko` object, attaching a `mapping` property
|
moel@348
|
16 |
factory(ko, ko.mapping = {});
|
moel@348
|
17 |
}
|
moel@348
|
18 |
}(function (ko, exports) {
|
moel@348
|
19 |
var DEBUG=true;
|
moel@348
|
20 |
var mappingProperty = "__ko_mapping__";
|
moel@348
|
21 |
var realKoDependentObservable = ko.dependentObservable;
|
moel@348
|
22 |
var mappingNesting = 0;
|
moel@348
|
23 |
var dependentObservables;
|
moel@348
|
24 |
var visitedObjects;
|
moel@348
|
25 |
|
moel@348
|
26 |
var _defaultOptions = {
|
moel@348
|
27 |
include: ["_destroy"],
|
moel@348
|
28 |
ignore: [],
|
moel@348
|
29 |
copy: []
|
moel@348
|
30 |
};
|
moel@348
|
31 |
var defaultOptions = _defaultOptions;
|
moel@348
|
32 |
|
moel@348
|
33 |
exports.isMapped = function (viewModel) {
|
moel@348
|
34 |
var unwrapped = ko.utils.unwrapObservable(viewModel);
|
moel@348
|
35 |
return unwrapped && unwrapped[mappingProperty];
|
moel@348
|
36 |
}
|
moel@348
|
37 |
|
moel@348
|
38 |
exports.fromJS = function (jsObject /*, inputOptions, target*/ ) {
|
moel@348
|
39 |
if (arguments.length == 0) throw new Error("When calling ko.fromJS, pass the object you want to convert.");
|
moel@348
|
40 |
|
moel@348
|
41 |
// When mapping is completed, even with an exception, reset the nesting level
|
moel@348
|
42 |
window.setTimeout(function () {
|
moel@348
|
43 |
mappingNesting = 0;
|
moel@348
|
44 |
}, 0);
|
moel@348
|
45 |
|
moel@348
|
46 |
if (!mappingNesting++) {
|
moel@348
|
47 |
dependentObservables = [];
|
moel@348
|
48 |
visitedObjects = new objectLookup();
|
moel@348
|
49 |
}
|
moel@348
|
50 |
|
moel@348
|
51 |
var options;
|
moel@348
|
52 |
var target;
|
moel@348
|
53 |
|
moel@348
|
54 |
if (arguments.length == 2) {
|
moel@348
|
55 |
if (arguments[1][mappingProperty]) {
|
moel@348
|
56 |
target = arguments[1];
|
moel@348
|
57 |
} else {
|
moel@348
|
58 |
options = arguments[1];
|
moel@348
|
59 |
}
|
moel@348
|
60 |
}
|
moel@348
|
61 |
if (arguments.length == 3) {
|
moel@348
|
62 |
options = arguments[1];
|
moel@348
|
63 |
target = arguments[2];
|
moel@348
|
64 |
}
|
moel@348
|
65 |
|
moel@348
|
66 |
if (target) {
|
moel@348
|
67 |
options = mergeOptions(target[mappingProperty], options);
|
moel@348
|
68 |
} else {
|
moel@348
|
69 |
options = mergeOptions(options);
|
moel@348
|
70 |
}
|
moel@348
|
71 |
options.mappedProperties = options.mappedProperties || {};
|
moel@348
|
72 |
|
moel@348
|
73 |
var result = updateViewModel(target, jsObject, options);
|
moel@348
|
74 |
if (target) {
|
moel@348
|
75 |
result = target;
|
moel@348
|
76 |
}
|
moel@348
|
77 |
|
moel@348
|
78 |
// Evaluate any dependent observables that were proxied.
|
moel@348
|
79 |
// Do this in a timeout to defer execution. Basically, any user code that explicitly looks up the DO will perform the first evaluation. Otherwise,
|
moel@348
|
80 |
// it will be done by this code.
|
moel@348
|
81 |
if (!--mappingNesting) {
|
moel@348
|
82 |
window.setTimeout(function () {
|
moel@348
|
83 |
while (dependentObservables.length) {
|
moel@348
|
84 |
var DO = dependentObservables.pop();
|
moel@348
|
85 |
if (DO) DO();
|
moel@348
|
86 |
}
|
moel@348
|
87 |
}, 0);
|
moel@348
|
88 |
}
|
moel@348
|
89 |
|
moel@348
|
90 |
// Save any new mapping options in the view model, so that updateFromJS can use them later.
|
moel@348
|
91 |
result[mappingProperty] = mergeOptions(result[mappingProperty], options);
|
moel@348
|
92 |
|
moel@348
|
93 |
return result;
|
moel@348
|
94 |
};
|
moel@348
|
95 |
|
moel@348
|
96 |
exports.fromJSON = function (jsonString /*, options, target*/ ) {
|
moel@348
|
97 |
var parsed = ko.utils.parseJson(jsonString);
|
moel@348
|
98 |
arguments[0] = parsed;
|
moel@348
|
99 |
return exports.fromJS.apply(this, arguments);
|
moel@348
|
100 |
};
|
moel@348
|
101 |
|
moel@348
|
102 |
exports.updateFromJS = function (viewModel) {
|
moel@348
|
103 |
throw new Error("ko.mapping.updateFromJS, use ko.mapping.fromJS instead. Please note that the order of parameters is different!");
|
moel@348
|
104 |
};
|
moel@348
|
105 |
|
moel@348
|
106 |
exports.updateFromJSON = function (viewModel) {
|
moel@348
|
107 |
throw new Error("ko.mapping.updateFromJSON, use ko.mapping.fromJSON instead. Please note that the order of parameters is different!");
|
moel@348
|
108 |
};
|
moel@348
|
109 |
|
moel@348
|
110 |
exports.toJS = function (rootObject, options) {
|
moel@348
|
111 |
if (arguments.length == 0) throw new Error("When calling ko.mapping.toJS, pass the object you want to convert.");
|
moel@348
|
112 |
// Merge in the options used in fromJS
|
moel@348
|
113 |
options = mergeOptions(rootObject[mappingProperty], options);
|
moel@348
|
114 |
|
moel@348
|
115 |
// We just unwrap everything at every level in the object graph
|
moel@348
|
116 |
return visitModel(rootObject, function (x) {
|
moel@348
|
117 |
return ko.utils.unwrapObservable(x)
|
moel@348
|
118 |
}, options);
|
moel@348
|
119 |
};
|
moel@348
|
120 |
|
moel@348
|
121 |
exports.toJSON = function (rootObject, options) {
|
moel@348
|
122 |
var plainJavaScriptObject = exports.toJS(rootObject, options);
|
moel@348
|
123 |
return ko.utils.stringifyJson(plainJavaScriptObject);
|
moel@348
|
124 |
};
|
moel@348
|
125 |
|
moel@348
|
126 |
exports.visitModel = function (rootObject, callback, options) {
|
moel@348
|
127 |
if (arguments.length == 0) throw new Error("When calling ko.mapping.visitModel, pass the object you want to visit.");
|
moel@348
|
128 |
// Merge in the options used in fromJS
|
moel@348
|
129 |
options = mergeOptions(rootObject[mappingProperty], options);
|
moel@348
|
130 |
|
moel@348
|
131 |
return visitModel(rootObject, callback, options);
|
moel@348
|
132 |
};
|
moel@348
|
133 |
|
moel@348
|
134 |
exports.defaultOptions = function () {
|
moel@348
|
135 |
if (arguments.length > 0) {
|
moel@348
|
136 |
defaultOptions = arguments[0];
|
moel@348
|
137 |
} else {
|
moel@348
|
138 |
return defaultOptions;
|
moel@348
|
139 |
}
|
moel@348
|
140 |
};
|
moel@348
|
141 |
|
moel@348
|
142 |
exports.resetDefaultOptions = function () {
|
moel@348
|
143 |
defaultOptions = {
|
moel@348
|
144 |
include: _defaultOptions.include.slice(0),
|
moel@348
|
145 |
ignore: _defaultOptions.ignore.slice(0),
|
moel@348
|
146 |
copy: _defaultOptions.copy.slice(0)
|
moel@348
|
147 |
};
|
moel@348
|
148 |
};
|
moel@348
|
149 |
|
moel@348
|
150 |
exports.getType = function(x) {
|
moel@348
|
151 |
if ((x) && (typeof (x) === "object")) {
|
moel@348
|
152 |
if (x.constructor == (new Date).constructor) return "date";
|
moel@348
|
153 |
if (x.constructor == (new Array).constructor) return "array";
|
moel@348
|
154 |
}
|
moel@348
|
155 |
return typeof x;
|
moel@348
|
156 |
}
|
moel@348
|
157 |
|
moel@348
|
158 |
function extendOptionsArray(distArray, sourceArray) {
|
moel@348
|
159 |
return ko.utils.arrayGetDistinctValues(
|
moel@348
|
160 |
ko.utils.arrayPushAll(distArray, sourceArray)
|
moel@348
|
161 |
);
|
moel@348
|
162 |
}
|
moel@348
|
163 |
|
moel@348
|
164 |
function extendOptionsObject(target, options) {
|
moel@348
|
165 |
var type = exports.getType,
|
moel@348
|
166 |
name, special = { "include": true, "ignore": true, "copy": true },
|
moel@348
|
167 |
t, o, i = 1, l = arguments.length;
|
moel@348
|
168 |
if (type(target) !== "object") {
|
moel@348
|
169 |
target = {};
|
moel@348
|
170 |
}
|
moel@348
|
171 |
for (; i < l; i++) {
|
moel@348
|
172 |
options = arguments[i];
|
moel@348
|
173 |
if (type(options) !== "object") {
|
moel@348
|
174 |
options = {};
|
moel@348
|
175 |
}
|
moel@348
|
176 |
for (name in options) {
|
moel@348
|
177 |
t = target[name]; o = options[name];
|
moel@348
|
178 |
if (name !== "constructor" && special[name] && type(o) !== "array") {
|
moel@348
|
179 |
if (type(o) !== "string") {
|
moel@348
|
180 |
throw new Error("ko.mapping.defaultOptions()." + name + " should be an array or string.");
|
moel@348
|
181 |
}
|
moel@348
|
182 |
o = [o];
|
moel@348
|
183 |
}
|
moel@348
|
184 |
switch (type(o)) {
|
moel@348
|
185 |
case "object": // Recurse
|
moel@348
|
186 |
t = type(t) === "object" ? t : {};
|
moel@348
|
187 |
target[name] = extendOptionsObject(t, o);
|
moel@348
|
188 |
break;
|
moel@348
|
189 |
case "array":
|
moel@348
|
190 |
t = type(t) === "array" ? t : [];
|
moel@348
|
191 |
target[name] = extendOptionsArray(t, o);
|
moel@348
|
192 |
break;
|
moel@348
|
193 |
default:
|
moel@348
|
194 |
target[name] = o;
|
moel@348
|
195 |
}
|
moel@348
|
196 |
}
|
moel@348
|
197 |
}
|
moel@348
|
198 |
return target;
|
moel@348
|
199 |
}
|
moel@348
|
200 |
|
moel@348
|
201 |
function mergeOptions() {
|
moel@348
|
202 |
var options = ko.utils.arrayPushAll([{}, defaultOptions], arguments); // Always use empty object as target to avoid changing default options
|
moel@348
|
203 |
options = extendOptionsObject.apply(this, options);
|
moel@348
|
204 |
return options;
|
moel@348
|
205 |
}
|
moel@348
|
206 |
|
moel@348
|
207 |
// When using a 'create' callback, we proxy the dependent observable so that it doesn't immediately evaluate on creation.
|
moel@348
|
208 |
// The reason is that the dependent observables in the user-specified callback may contain references to properties that have not been mapped yet.
|
moel@348
|
209 |
function withProxyDependentObservable(dependentObservables, callback) {
|
moel@348
|
210 |
var localDO = ko.dependentObservable;
|
moel@348
|
211 |
ko.dependentObservable = function (read, owner, options) {
|
moel@348
|
212 |
options = options || {};
|
moel@348
|
213 |
|
moel@348
|
214 |
if (read && typeof read == "object") { // mirrors condition in knockout implementation of DO's
|
moel@348
|
215 |
options = read;
|
moel@348
|
216 |
}
|
moel@348
|
217 |
|
moel@348
|
218 |
var realDeferEvaluation = options.deferEvaluation;
|
moel@348
|
219 |
|
moel@348
|
220 |
var isRemoved = false;
|
moel@348
|
221 |
|
moel@348
|
222 |
// We wrap the original dependent observable so that we can remove it from the 'dependentObservables' list we need to evaluate after mapping has
|
moel@348
|
223 |
// completed if the user already evaluated the DO themselves in the meantime.
|
moel@348
|
224 |
var wrap = function (DO) {
|
moel@348
|
225 |
var wrapped = realKoDependentObservable({
|
moel@348
|
226 |
read: function () {
|
moel@348
|
227 |
if (!isRemoved) {
|
moel@348
|
228 |
ko.utils.arrayRemoveItem(dependentObservables, DO);
|
moel@348
|
229 |
isRemoved = true;
|
moel@348
|
230 |
}
|
moel@348
|
231 |
return DO.apply(DO, arguments);
|
moel@348
|
232 |
},
|
moel@348
|
233 |
write: function (val) {
|
moel@348
|
234 |
return DO(val);
|
moel@348
|
235 |
},
|
moel@348
|
236 |
deferEvaluation: true
|
moel@348
|
237 |
});
|
moel@348
|
238 |
if(DEBUG) wrapped._wrapper = true;
|
moel@348
|
239 |
return wrapped;
|
moel@348
|
240 |
};
|
moel@348
|
241 |
|
moel@348
|
242 |
options.deferEvaluation = true; // will either set for just options, or both read/options.
|
moel@348
|
243 |
var realDependentObservable = new realKoDependentObservable(read, owner, options);
|
moel@348
|
244 |
|
moel@348
|
245 |
if (!realDeferEvaluation) {
|
moel@348
|
246 |
realDependentObservable = wrap(realDependentObservable);
|
moel@348
|
247 |
dependentObservables.push(realDependentObservable);
|
moel@348
|
248 |
}
|
moel@348
|
249 |
|
moel@348
|
250 |
return realDependentObservable;
|
moel@348
|
251 |
}
|
moel@348
|
252 |
ko.dependentObservable.fn = realKoDependentObservable.fn;
|
moel@348
|
253 |
ko.computed = ko.dependentObservable;
|
moel@348
|
254 |
var result = callback();
|
moel@348
|
255 |
ko.dependentObservable = localDO;
|
moel@348
|
256 |
ko.computed = ko.dependentObservable;
|
moel@348
|
257 |
return result;
|
moel@348
|
258 |
}
|
moel@348
|
259 |
|
moel@348
|
260 |
function updateViewModel(mappedRootObject, rootObject, options, parentName, parent, parentPropertyName) {
|
moel@348
|
261 |
var isArray = ko.utils.unwrapObservable(rootObject) instanceof Array;
|
moel@348
|
262 |
|
moel@348
|
263 |
// If nested object was already mapped previously, take the options from it
|
moel@348
|
264 |
if (parentName !== undefined && exports.isMapped(mappedRootObject)) {
|
moel@348
|
265 |
options = ko.utils.unwrapObservable(mappedRootObject)[mappingProperty];
|
moel@348
|
266 |
parentName = "";
|
moel@348
|
267 |
parentPropertyName = "";
|
moel@348
|
268 |
}
|
moel@348
|
269 |
|
moel@348
|
270 |
parentName = parentName || "";
|
moel@348
|
271 |
parentPropertyName = parentPropertyName || "";
|
moel@348
|
272 |
|
moel@348
|
273 |
var callbackParams = {
|
moel@348
|
274 |
data: rootObject,
|
moel@348
|
275 |
parent: parent
|
moel@348
|
276 |
};
|
moel@348
|
277 |
|
moel@348
|
278 |
var getCallback = function (name) {
|
moel@348
|
279 |
var callback;
|
moel@348
|
280 |
if (parentName === "") {
|
moel@348
|
281 |
callback = options[name];
|
moel@348
|
282 |
} else if (callback = options[parentName]) {
|
moel@348
|
283 |
callback = callback[name]
|
moel@348
|
284 |
}
|
moel@348
|
285 |
return callback;
|
moel@348
|
286 |
};
|
moel@348
|
287 |
|
moel@348
|
288 |
var hasCreateCallback = function () {
|
moel@348
|
289 |
return getCallback("create") instanceof Function;
|
moel@348
|
290 |
};
|
moel@348
|
291 |
|
moel@348
|
292 |
var createCallback = function (data) {
|
moel@348
|
293 |
return withProxyDependentObservable(dependentObservables, function () {
|
moel@348
|
294 |
return getCallback("create")({
|
moel@348
|
295 |
data: data || callbackParams.data,
|
moel@348
|
296 |
parent: callbackParams.parent
|
moel@348
|
297 |
});
|
moel@348
|
298 |
});
|
moel@348
|
299 |
};
|
moel@348
|
300 |
|
moel@348
|
301 |
var hasUpdateCallback = function () {
|
moel@348
|
302 |
return getCallback("update") instanceof Function;
|
moel@348
|
303 |
};
|
moel@348
|
304 |
|
moel@348
|
305 |
var updateCallback = function (obj, data) {
|
moel@348
|
306 |
var params = {
|
moel@348
|
307 |
data: data || callbackParams.data,
|
moel@348
|
308 |
parent: callbackParams.parent,
|
moel@348
|
309 |
target: ko.utils.unwrapObservable(obj)
|
moel@348
|
310 |
};
|
moel@348
|
311 |
|
moel@348
|
312 |
if (ko.isWriteableObservable(obj)) {
|
moel@348
|
313 |
params.observable = obj;
|
moel@348
|
314 |
}
|
moel@348
|
315 |
|
moel@348
|
316 |
return getCallback("update")(params);
|
moel@348
|
317 |
}
|
moel@348
|
318 |
|
moel@348
|
319 |
var alreadyMapped = visitedObjects.get(rootObject);
|
moel@348
|
320 |
if (alreadyMapped) {
|
moel@348
|
321 |
return alreadyMapped;
|
moel@348
|
322 |
}
|
moel@348
|
323 |
|
moel@348
|
324 |
if (!isArray) {
|
moel@348
|
325 |
// For atomic types, do a direct update on the observable
|
moel@348
|
326 |
if (!canHaveProperties(rootObject)) {
|
moel@348
|
327 |
switch (exports.getType(rootObject)) {
|
moel@348
|
328 |
case "function":
|
moel@348
|
329 |
if (hasUpdateCallback()) {
|
moel@348
|
330 |
if (ko.isWriteableObservable(rootObject)) {
|
moel@348
|
331 |
rootObject(updateCallback(rootObject));
|
moel@348
|
332 |
mappedRootObject = rootObject;
|
moel@348
|
333 |
} else {
|
moel@348
|
334 |
mappedRootObject = updateCallback(rootObject);
|
moel@348
|
335 |
}
|
moel@348
|
336 |
} else {
|
moel@348
|
337 |
mappedRootObject = rootObject;
|
moel@348
|
338 |
}
|
moel@348
|
339 |
break;
|
moel@348
|
340 |
default:
|
moel@348
|
341 |
if (ko.isWriteableObservable(mappedRootObject)) {
|
moel@348
|
342 |
if (hasUpdateCallback()) {
|
moel@348
|
343 |
mappedRootObject(updateCallback(mappedRootObject));
|
moel@348
|
344 |
} else {
|
moel@348
|
345 |
mappedRootObject(ko.utils.unwrapObservable(rootObject));
|
moel@348
|
346 |
}
|
moel@348
|
347 |
} else {
|
moel@348
|
348 |
if (hasCreateCallback()) {
|
moel@348
|
349 |
mappedRootObject = createCallback();
|
moel@348
|
350 |
} else {
|
moel@348
|
351 |
mappedRootObject = ko.observable(ko.utils.unwrapObservable(rootObject));
|
moel@348
|
352 |
}
|
moel@348
|
353 |
|
moel@348
|
354 |
if (hasUpdateCallback()) {
|
moel@348
|
355 |
mappedRootObject(updateCallback(mappedRootObject));
|
moel@348
|
356 |
}
|
moel@348
|
357 |
}
|
moel@348
|
358 |
break;
|
moel@348
|
359 |
}
|
moel@348
|
360 |
|
moel@348
|
361 |
} else {
|
moel@348
|
362 |
mappedRootObject = ko.utils.unwrapObservable(mappedRootObject);
|
moel@348
|
363 |
if (!mappedRootObject) {
|
moel@348
|
364 |
if (hasCreateCallback()) {
|
moel@348
|
365 |
var result = createCallback();
|
moel@348
|
366 |
|
moel@348
|
367 |
if (hasUpdateCallback()) {
|
moel@348
|
368 |
result = updateCallback(result);
|
moel@348
|
369 |
}
|
moel@348
|
370 |
|
moel@348
|
371 |
return result;
|
moel@348
|
372 |
} else {
|
moel@348
|
373 |
if (hasUpdateCallback()) {
|
moel@348
|
374 |
return updateCallback(result);
|
moel@348
|
375 |
}
|
moel@348
|
376 |
|
moel@348
|
377 |
mappedRootObject = {};
|
moel@348
|
378 |
}
|
moel@348
|
379 |
}
|
moel@348
|
380 |
|
moel@348
|
381 |
if (hasUpdateCallback()) {
|
moel@348
|
382 |
mappedRootObject = updateCallback(mappedRootObject);
|
moel@348
|
383 |
}
|
moel@348
|
384 |
|
moel@348
|
385 |
visitedObjects.save(rootObject, mappedRootObject);
|
moel@348
|
386 |
|
moel@348
|
387 |
// For non-atomic types, visit all properties and update recursively
|
moel@348
|
388 |
visitPropertiesOrArrayEntries(rootObject, function (indexer) {
|
moel@348
|
389 |
var fullPropertyName = getPropertyName(parentPropertyName, rootObject, indexer);
|
moel@348
|
390 |
|
moel@348
|
391 |
if (ko.utils.arrayIndexOf(options.ignore, fullPropertyName) != -1) {
|
moel@348
|
392 |
return;
|
moel@348
|
393 |
}
|
moel@348
|
394 |
|
moel@348
|
395 |
if (ko.utils.arrayIndexOf(options.copy, fullPropertyName) != -1) {
|
moel@348
|
396 |
mappedRootObject[indexer] = rootObject[indexer];
|
moel@348
|
397 |
return;
|
moel@348
|
398 |
}
|
moel@348
|
399 |
|
moel@348
|
400 |
// In case we are adding an already mapped property, fill it with the previously mapped property value to prevent recursion.
|
moel@348
|
401 |
// If this is a property that was generated by fromJS, we should use the options specified there
|
moel@348
|
402 |
var prevMappedProperty = visitedObjects.get(rootObject[indexer]);
|
moel@348
|
403 |
var value = prevMappedProperty || updateViewModel(mappedRootObject[indexer], rootObject[indexer], options, indexer, mappedRootObject, fullPropertyName);
|
moel@348
|
404 |
|
moel@348
|
405 |
if (ko.isWriteableObservable(mappedRootObject[indexer])) {
|
moel@348
|
406 |
mappedRootObject[indexer](ko.utils.unwrapObservable(value));
|
moel@348
|
407 |
} else {
|
moel@348
|
408 |
mappedRootObject[indexer] = value;
|
moel@348
|
409 |
}
|
moel@348
|
410 |
|
moel@348
|
411 |
options.mappedProperties[fullPropertyName] = true;
|
moel@348
|
412 |
});
|
moel@348
|
413 |
}
|
moel@348
|
414 |
} else {
|
moel@348
|
415 |
var changes = [];
|
moel@348
|
416 |
|
moel@348
|
417 |
var hasKeyCallback = getCallback("key") instanceof Function;
|
moel@348
|
418 |
var keyCallback = hasKeyCallback ? getCallback("key") : function (x) {
|
moel@348
|
419 |
return x;
|
moel@348
|
420 |
};
|
moel@348
|
421 |
if (!ko.isObservable(mappedRootObject)) {
|
moel@348
|
422 |
// When creating the new observable array, also add a bunch of utility functions that take the 'key' of the array items into account.
|
moel@348
|
423 |
mappedRootObject = ko.observableArray([]);
|
moel@348
|
424 |
|
moel@348
|
425 |
mappedRootObject.mappedRemove = function (valueOrPredicate) {
|
moel@348
|
426 |
var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) {
|
moel@348
|
427 |
return value === keyCallback(valueOrPredicate);
|
moel@348
|
428 |
};
|
moel@348
|
429 |
return mappedRootObject.remove(function (item) {
|
moel@348
|
430 |
return predicate(keyCallback(item));
|
moel@348
|
431 |
});
|
moel@348
|
432 |
}
|
moel@348
|
433 |
|
moel@348
|
434 |
mappedRootObject.mappedRemoveAll = function (arrayOfValues) {
|
moel@348
|
435 |
var arrayOfKeys = filterArrayByKey(arrayOfValues, keyCallback);
|
moel@348
|
436 |
return mappedRootObject.remove(function (item) {
|
moel@348
|
437 |
return ko.utils.arrayIndexOf(arrayOfKeys, keyCallback(item)) != -1;
|
moel@348
|
438 |
});
|
moel@348
|
439 |
}
|
moel@348
|
440 |
|
moel@348
|
441 |
mappedRootObject.mappedDestroy = function (valueOrPredicate) {
|
moel@348
|
442 |
var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) {
|
moel@348
|
443 |
return value === keyCallback(valueOrPredicate);
|
moel@348
|
444 |
};
|
moel@348
|
445 |
return mappedRootObject.destroy(function (item) {
|
moel@348
|
446 |
return predicate(keyCallback(item));
|
moel@348
|
447 |
});
|
moel@348
|
448 |
}
|
moel@348
|
449 |
|
moel@348
|
450 |
mappedRootObject.mappedDestroyAll = function (arrayOfValues) {
|
moel@348
|
451 |
var arrayOfKeys = filterArrayByKey(arrayOfValues, keyCallback);
|
moel@348
|
452 |
return mappedRootObject.destroy(function (item) {
|
moel@348
|
453 |
return ko.utils.arrayIndexOf(arrayOfKeys, keyCallback(item)) != -1;
|
moel@348
|
454 |
});
|
moel@348
|
455 |
}
|
moel@348
|
456 |
|
moel@348
|
457 |
mappedRootObject.mappedIndexOf = function (item) {
|
moel@348
|
458 |
var keys = filterArrayByKey(mappedRootObject(), keyCallback);
|
moel@348
|
459 |
var key = keyCallback(item);
|
moel@348
|
460 |
return ko.utils.arrayIndexOf(keys, key);
|
moel@348
|
461 |
}
|
moel@348
|
462 |
|
moel@348
|
463 |
mappedRootObject.mappedCreate = function (value) {
|
moel@348
|
464 |
if (mappedRootObject.mappedIndexOf(value) !== -1) {
|
moel@348
|
465 |
throw new Error("There already is an object with the key that you specified.");
|
moel@348
|
466 |
}
|
moel@348
|
467 |
|
moel@348
|
468 |
var item = hasCreateCallback() ? createCallback(value) : value;
|
moel@348
|
469 |
if (hasUpdateCallback()) {
|
moel@348
|
470 |
var newValue = updateCallback(item, value);
|
moel@348
|
471 |
if (ko.isWriteableObservable(item)) {
|
moel@348
|
472 |
item(newValue);
|
moel@348
|
473 |
} else {
|
moel@348
|
474 |
item = newValue;
|
moel@348
|
475 |
}
|
moel@348
|
476 |
}
|
moel@348
|
477 |
mappedRootObject.push(item);
|
moel@348
|
478 |
return item;
|
moel@348
|
479 |
}
|
moel@348
|
480 |
}
|
moel@348
|
481 |
|
moel@348
|
482 |
var currentArrayKeys = filterArrayByKey(ko.utils.unwrapObservable(mappedRootObject), keyCallback).sort();
|
moel@348
|
483 |
var newArrayKeys = filterArrayByKey(rootObject, keyCallback);
|
moel@348
|
484 |
if (hasKeyCallback) newArrayKeys.sort();
|
moel@348
|
485 |
var editScript = ko.utils.compareArrays(currentArrayKeys, newArrayKeys);
|
moel@348
|
486 |
|
moel@348
|
487 |
var ignoreIndexOf = {};
|
moel@348
|
488 |
|
moel@348
|
489 |
var newContents = [];
|
moel@348
|
490 |
for (var i = 0, j = editScript.length; i < j; i++) {
|
moel@348
|
491 |
var key = editScript[i];
|
moel@348
|
492 |
var mappedItem;
|
moel@348
|
493 |
var fullPropertyName = getPropertyName(parentPropertyName, rootObject, i);
|
moel@348
|
494 |
switch (key.status) {
|
moel@348
|
495 |
case "added":
|
moel@348
|
496 |
var item = getItemByKey(ko.utils.unwrapObservable(rootObject), key.value, keyCallback);
|
moel@348
|
497 |
mappedItem = updateViewModel(undefined, item, options, parentName, mappedRootObject, fullPropertyName);
|
moel@348
|
498 |
if(!hasCreateCallback()) {
|
moel@348
|
499 |
mappedItem = ko.utils.unwrapObservable(mappedItem);
|
moel@348
|
500 |
}
|
moel@348
|
501 |
|
moel@348
|
502 |
var index = ignorableIndexOf(ko.utils.unwrapObservable(rootObject), item, ignoreIndexOf);
|
moel@348
|
503 |
newContents[index] = mappedItem;
|
moel@348
|
504 |
ignoreIndexOf[index] = true;
|
moel@348
|
505 |
break;
|
moel@348
|
506 |
case "retained":
|
moel@348
|
507 |
var item = getItemByKey(ko.utils.unwrapObservable(rootObject), key.value, keyCallback);
|
moel@348
|
508 |
mappedItem = getItemByKey(mappedRootObject, key.value, keyCallback);
|
moel@348
|
509 |
updateViewModel(mappedItem, item, options, parentName, mappedRootObject, fullPropertyName);
|
moel@348
|
510 |
|
moel@348
|
511 |
var index = ignorableIndexOf(ko.utils.unwrapObservable(rootObject), item, ignoreIndexOf);
|
moel@348
|
512 |
newContents[index] = mappedItem;
|
moel@348
|
513 |
ignoreIndexOf[index] = true;
|
moel@348
|
514 |
break;
|
moel@348
|
515 |
case "deleted":
|
moel@348
|
516 |
mappedItem = getItemByKey(mappedRootObject, key.value, keyCallback);
|
moel@348
|
517 |
break;
|
moel@348
|
518 |
}
|
moel@348
|
519 |
|
moel@348
|
520 |
changes.push({
|
moel@348
|
521 |
event: key.status,
|
moel@348
|
522 |
item: mappedItem
|
moel@348
|
523 |
});
|
moel@348
|
524 |
}
|
moel@348
|
525 |
|
moel@348
|
526 |
mappedRootObject(newContents);
|
moel@348
|
527 |
|
moel@348
|
528 |
var arrayChangedCallback = getCallback("arrayChanged");
|
moel@348
|
529 |
if (arrayChangedCallback instanceof Function) {
|
moel@348
|
530 |
ko.utils.arrayForEach(changes, function (change) {
|
moel@348
|
531 |
arrayChangedCallback(change.event, change.item);
|
moel@348
|
532 |
});
|
moel@348
|
533 |
}
|
moel@348
|
534 |
}
|
moel@348
|
535 |
|
moel@348
|
536 |
return mappedRootObject;
|
moel@348
|
537 |
}
|
moel@348
|
538 |
|
moel@348
|
539 |
function ignorableIndexOf(array, item, ignoreIndices) {
|
moel@348
|
540 |
for (var i = 0, j = array.length; i < j; i++) {
|
moel@348
|
541 |
if (ignoreIndices[i] === true) continue;
|
moel@348
|
542 |
if (array[i] === item) return i;
|
moel@348
|
543 |
}
|
moel@348
|
544 |
return null;
|
moel@348
|
545 |
}
|
moel@348
|
546 |
|
moel@348
|
547 |
function mapKey(item, callback) {
|
moel@348
|
548 |
var mappedItem;
|
moel@348
|
549 |
if (callback) mappedItem = callback(item);
|
moel@348
|
550 |
if (exports.getType(mappedItem) === "undefined") mappedItem = item;
|
moel@348
|
551 |
|
moel@348
|
552 |
return ko.utils.unwrapObservable(mappedItem);
|
moel@348
|
553 |
}
|
moel@348
|
554 |
|
moel@348
|
555 |
function getItemByKey(array, key, callback) {
|
moel@348
|
556 |
var filtered = ko.utils.arrayFilter(ko.utils.unwrapObservable(array), function (item) {
|
moel@348
|
557 |
return mapKey(item, callback) === key;
|
moel@348
|
558 |
});
|
moel@348
|
559 |
|
moel@348
|
560 |
if (filtered.length == 0) throw new Error("When calling ko.update*, the key '" + key + "' was not found!");
|
moel@348
|
561 |
if ((filtered.length > 1) && (canHaveProperties(filtered[0]))) throw new Error("When calling ko.update*, the key '" + key + "' was not unique!");
|
moel@348
|
562 |
|
moel@348
|
563 |
return filtered[0];
|
moel@348
|
564 |
}
|
moel@348
|
565 |
|
moel@348
|
566 |
function filterArrayByKey(array, callback) {
|
moel@348
|
567 |
return ko.utils.arrayMap(ko.utils.unwrapObservable(array), function (item) {
|
moel@348
|
568 |
if (callback) {
|
moel@348
|
569 |
return mapKey(item, callback);
|
moel@348
|
570 |
} else {
|
moel@348
|
571 |
return item;
|
moel@348
|
572 |
}
|
moel@348
|
573 |
});
|
moel@348
|
574 |
}
|
moel@348
|
575 |
|
moel@348
|
576 |
function visitPropertiesOrArrayEntries(rootObject, visitorCallback) {
|
moel@348
|
577 |
if (rootObject instanceof Array) {
|
moel@348
|
578 |
for (var i = 0; i < rootObject.length; i++)
|
moel@348
|
579 |
visitorCallback(i);
|
moel@348
|
580 |
} else {
|
moel@348
|
581 |
for (var propertyName in rootObject)
|
moel@348
|
582 |
visitorCallback(propertyName);
|
moel@348
|
583 |
}
|
moel@348
|
584 |
};
|
moel@348
|
585 |
|
moel@348
|
586 |
function canHaveProperties(object) {
|
moel@348
|
587 |
var type = exports.getType(object);
|
moel@348
|
588 |
return (type === "object" || type === "array") && (object !== null) && (type !== "undefined");
|
moel@348
|
589 |
}
|
moel@348
|
590 |
|
moel@348
|
591 |
// Based on the parentName, this creates a fully classified name of a property
|
moel@348
|
592 |
|
moel@348
|
593 |
function getPropertyName(parentName, parent, indexer) {
|
moel@348
|
594 |
var propertyName = parentName || "";
|
moel@348
|
595 |
if (parent instanceof Array) {
|
moel@348
|
596 |
if (parentName) {
|
moel@348
|
597 |
propertyName += "[" + indexer + "]";
|
moel@348
|
598 |
}
|
moel@348
|
599 |
} else {
|
moel@348
|
600 |
if (parentName) {
|
moel@348
|
601 |
propertyName += ".";
|
moel@348
|
602 |
}
|
moel@348
|
603 |
propertyName += indexer;
|
moel@348
|
604 |
}
|
moel@348
|
605 |
return propertyName;
|
moel@348
|
606 |
}
|
moel@348
|
607 |
|
moel@348
|
608 |
function visitModel(rootObject, callback, options, parentName, fullParentName) {
|
moel@348
|
609 |
// If nested object was already mapped previously, take the options from it
|
moel@348
|
610 |
if (parentName !== undefined && exports.isMapped(rootObject)) {
|
moel@348
|
611 |
//options = ko.utils.unwrapObservable(rootObject)[mappingProperty];
|
moel@348
|
612 |
options = mergeOptions(ko.utils.unwrapObservable(rootObject)[mappingProperty], options);
|
moel@348
|
613 |
parentName = "";
|
moel@348
|
614 |
}
|
moel@348
|
615 |
|
moel@348
|
616 |
if (parentName === undefined) { // the first call
|
moel@348
|
617 |
visitedObjects = new objectLookup();
|
moel@348
|
618 |
}
|
moel@348
|
619 |
|
moel@348
|
620 |
parentName = parentName || "";
|
moel@348
|
621 |
|
moel@348
|
622 |
var mappedRootObject;
|
moel@348
|
623 |
var unwrappedRootObject = ko.utils.unwrapObservable(rootObject);
|
moel@348
|
624 |
if (!canHaveProperties(unwrappedRootObject)) {
|
moel@348
|
625 |
return callback(rootObject, fullParentName);
|
moel@348
|
626 |
} else {
|
moel@348
|
627 |
// Only do a callback, but ignore the results
|
moel@348
|
628 |
callback(rootObject, fullParentName);
|
moel@348
|
629 |
mappedRootObject = unwrappedRootObject instanceof Array ? [] : {};
|
moel@348
|
630 |
}
|
moel@348
|
631 |
|
moel@348
|
632 |
visitedObjects.save(rootObject, mappedRootObject);
|
moel@348
|
633 |
|
moel@348
|
634 |
var origFullParentName = fullParentName;
|
moel@348
|
635 |
visitPropertiesOrArrayEntries(unwrappedRootObject, function (indexer) {
|
moel@348
|
636 |
if (options.ignore && ko.utils.arrayIndexOf(options.ignore, indexer) != -1) return;
|
moel@348
|
637 |
|
moel@348
|
638 |
var propertyValue = unwrappedRootObject[indexer];
|
moel@348
|
639 |
var fullPropertyName = getPropertyName(parentName, unwrappedRootObject, indexer);
|
moel@348
|
640 |
|
moel@348
|
641 |
// If we don't want to explicitly copy the unmapped property...
|
moel@348
|
642 |
if (ko.utils.arrayIndexOf(options.copy, indexer) === -1) {
|
moel@348
|
643 |
// ...find out if it's a property we want to explicitly include
|
moel@348
|
644 |
if (ko.utils.arrayIndexOf(options.include, indexer) === -1) {
|
moel@348
|
645 |
// Options contains all the properties that were part of the original object.
|
moel@348
|
646 |
// If a property does not exist, and it is not because it is part of an array (e.g. "myProp[3]"), then it should not be unmapped.
|
moel@348
|
647 |
if (options.mappedProperties && !options.mappedProperties[fullPropertyName] && !(unwrappedRootObject instanceof Array)) {
|
moel@348
|
648 |
return;
|
moel@348
|
649 |
}
|
moel@348
|
650 |
}
|
moel@348
|
651 |
}
|
moel@348
|
652 |
|
moel@348
|
653 |
fullParentName = getPropertyName(origFullParentName, unwrappedRootObject, indexer);
|
moel@348
|
654 |
|
moel@348
|
655 |
var propertyType = exports.getType(ko.utils.unwrapObservable(propertyValue));
|
moel@348
|
656 |
switch (propertyType) {
|
moel@348
|
657 |
case "object":
|
moel@348
|
658 |
case "array":
|
moel@348
|
659 |
case "undefined":
|
moel@348
|
660 |
var previouslyMappedValue = visitedObjects.get(propertyValue);
|
moel@348
|
661 |
mappedRootObject[indexer] = (exports.getType(previouslyMappedValue) !== "undefined") ? previouslyMappedValue : visitModel(propertyValue, callback, options, fullPropertyName, fullParentName);
|
moel@348
|
662 |
break;
|
moel@348
|
663 |
default:
|
moel@348
|
664 |
mappedRootObject[indexer] = callback(propertyValue, fullParentName);
|
moel@348
|
665 |
}
|
moel@348
|
666 |
});
|
moel@348
|
667 |
|
moel@348
|
668 |
return mappedRootObject;
|
moel@348
|
669 |
}
|
moel@348
|
670 |
|
moel@348
|
671 |
function objectLookup() {
|
moel@348
|
672 |
var keys = [];
|
moel@348
|
673 |
var values = [];
|
moel@348
|
674 |
this.save = function (key, value) {
|
moel@348
|
675 |
var existingIndex = ko.utils.arrayIndexOf(keys, key);
|
moel@348
|
676 |
if (existingIndex >= 0) values[existingIndex] = value;
|
moel@348
|
677 |
else {
|
moel@348
|
678 |
keys.push(key);
|
moel@348
|
679 |
values.push(value);
|
moel@348
|
680 |
}
|
moel@348
|
681 |
};
|
moel@348
|
682 |
this.get = function (key) {
|
moel@348
|
683 |
var existingIndex = ko.utils.arrayIndexOf(keys, key);
|
moel@348
|
684 |
return (existingIndex >= 0) ? values[existingIndex] : undefined;
|
moel@348
|
685 |
};
|
moel@348
|
686 |
};
|
moel@348
|
687 |
})); |