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.
     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 }));