Resources/Web/js/knockout.mapping-latest.js
author moel.mich
Sun, 23 Sep 2012 18:37:43 +0000
changeset 380 573f1fff48b2
permissions -rw-r--r--
Fixed Issue 387. The new implementation does not try to start a ring 0 driver that already exists, but could not be opened. It tries to delete the driver and install it new. The driver is now stored temporarily in the application folder. The driver is not correctly removed on system shutdown.
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
}));