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