Resources/Web/js/knockout.mapping-latest.js
author moel.mich
Sun, 24 Jun 2012 16:16:46 +0000
changeset 354 e71b968c04f9
permissions -rw-r--r--
Fixed Issue 300.
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
}));