From: Olivier Lamy Date: Sun, 1 Jan 2012 00:46:08 +0000 (+0000) Subject: add knockout mapping plugin X-Git-Tag: archiva-1.4-M3~1660 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=307cb89d0d705301ef7e49182162b1d9d7cda918;p=archiva.git add knockout mapping plugin git-svn-id: https://svn.apache.org/repos/asf/archiva/trunk@1226204 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/archiva-modules/archiva-web/archiva-webapp-js/src/main/webapp/js/knockout.mapping-latest.debug.js b/archiva-modules/archiva-web/archiva-webapp-js/src/main/webapp/js/knockout.mapping-latest.debug.js new file mode 100644 index 000000000..3e87629e0 --- /dev/null +++ b/archiva-modules/archiva-web/archiva-webapp-js/src/main/webapp/js/knockout.mapping-latest.debug.js @@ -0,0 +1,678 @@ +// Knockout Mapping plugin v2.0.3 +// (c) 2011 Steven Sanderson, Roy Jacobs - http://knockoutjs.com/ +// License: Ms-Pl (http://www.opensource.org/licenses/ms-pl.html) + +// Google Closure Compiler helpers (used only to make the minified file smaller) +ko.exportSymbol = function (publicPath, object) { + var tokens = publicPath.split("."); + var target = window; + for (var i = 0; i < tokens.length - 1; i++) + target = target[tokens[i]]; + target[tokens[tokens.length - 1]] = object; +}; +ko.exportProperty = function (owner, publicName, object) { + owner[publicName] = object; +}; + +(function () { + ko.mapping = {}; + + var mappingProperty = "__ko_mapping__"; + var realKoDependentObservable = ko.dependentObservable; + var mappingNesting = 0; + var dependentObservables; + var visitedObjects; + + var _defaultOptions = { + include: ["_destroy"], + ignore: [], + copy: [] + }; + var defaultOptions = _defaultOptions; + + function extendObject(destination, source) { + for (var key in source) { + if (source.hasOwnProperty(key) && source[key]) { + if (key && destination[key] && !(destination[key] instanceof Array)) { + extendObject(destination[key], source[key]); + } else { + destination[key] = source[key]; + } + } + } + } + + function merge(obj1, obj2) { + var merged = {}; + extendObject(merged, obj1); + extendObject(merged, obj2); + + return merged; + } + + ko.mapping.isMapped = function (viewModel) { + var unwrapped = ko.utils.unwrapObservable(viewModel); + return unwrapped && unwrapped[mappingProperty]; + } + + ko.mapping.fromJS = function (jsObject /*, inputOptions, target*/ ) { + if (arguments.length == 0) throw new Error("When calling ko.fromJS, pass the object you want to convert."); + + // When mapping is completed, even with an exception, reset the nesting level + window.setTimeout(function () { + mappingNesting = 0; + }, 0); + + if (!mappingNesting++) { + dependentObservables = []; + visitedObjects = new objectLookup(); + } + + var options; + var target; + + if (arguments.length == 2) { + if (arguments[1][mappingProperty]) { + target = arguments[1]; + } else { + options = arguments[1]; + } + } + if (arguments.length == 3) { + options = arguments[1]; + target = arguments[2]; + } + + if (target) { + options = merge(options, target[mappingProperty]); + } + options = fillOptions(options); + + var result = updateViewModel(target, jsObject, options); + if (target) { + result = target; + } + + // Evaluate any dependent observables that were proxied. + // Do this in a timeout to defer execution. Basically, any user code that explicitly looks up the DO will perform the first evaluation. Otherwise, + // it will be done by this code. + if (!--mappingNesting) { + window.setTimeout(function () { + ko.utils.arrayForEach(dependentObservables, function (DO) { + if (DO) DO(); + }); + }, 0); + } + + // Save any new mapping options in the view model, so that updateFromJS can use them later. + result[mappingProperty] = merge(result[mappingProperty], options); + + return result; + }; + + ko.mapping.fromJSON = function (jsonString /*, options, target*/ ) { + var parsed = ko.utils.parseJson(jsonString); + arguments[0] = parsed; + return ko.mapping.fromJS.apply(this, arguments); + }; + + ko.mapping.updateFromJS = function (viewModel) { + throw new Error("ko.mapping.updateFromJS, use ko.mapping.fromJS instead. Please note that the order of parameters is different!"); + }; + + ko.mapping.updateFromJSON = function (viewModel) { + throw new Error("ko.mapping.updateFromJSON, use ko.mapping.fromJSON instead. Please note that the order of parameters is different!"); + }; + + ko.mapping.toJS = function (rootObject, options) { + if (!defaultOptions) ko.mapping.resetDefaultOptions(); + + if (arguments.length == 0) throw new Error("When calling ko.mapping.toJS, pass the object you want to convert."); + if (!(defaultOptions.ignore instanceof Array)) throw new Error("ko.mapping.defaultOptions().ignore should be an array."); + if (!(defaultOptions.include instanceof Array)) throw new Error("ko.mapping.defaultOptions().include should be an array."); + if (!(defaultOptions.copy instanceof Array)) throw new Error("ko.mapping.defaultOptions().copy should be an array."); + + // Merge in the options used in fromJS + options = fillOptions(options, rootObject[mappingProperty]); + + // We just unwrap everything at every level in the object graph + return ko.mapping.visitModel(rootObject, function (x) { + return ko.utils.unwrapObservable(x) + }, options); + }; + + ko.mapping.toJSON = function (rootObject, options) { + var plainJavaScriptObject = ko.mapping.toJS(rootObject, options); + return ko.utils.stringifyJson(plainJavaScriptObject); + }; + + ko.mapping.defaultOptions = function () { + if (arguments.length > 0) { + defaultOptions = arguments[0]; + } else { + return defaultOptions; + } + }; + + ko.mapping.resetDefaultOptions = function () { + defaultOptions = { + include: _defaultOptions.include.slice(0), + ignore: _defaultOptions.ignore.slice(0), + copy: _defaultOptions.copy.slice(0) + }; + }; + + function getType(x) { + if ((x) && (typeof (x) === "object") && (x.constructor == (new Date).constructor)) return "date"; + return typeof x; + } + + function fillOptions(options, otherOptions) { + options = options || {}; + + // Is there only a root-level mapping present? + if ((options.create instanceof Function) || (options.update instanceof Function) || (options.key instanceof Function) || (options.arrayChanged instanceof Function)) { + options = { + "": options + }; + } + + if (otherOptions) { + options.ignore = mergeArrays(otherOptions.ignore, options.ignore); + options.include = mergeArrays(otherOptions.include, options.include); + options.copy = mergeArrays(otherOptions.copy, options.copy); + } + options.ignore = mergeArrays(options.ignore, defaultOptions.ignore); + options.include = mergeArrays(options.include, defaultOptions.include); + options.copy = mergeArrays(options.copy, defaultOptions.copy); + + options.mappedProperties = options.mappedProperties || {}; + return options; + } + + function mergeArrays(a, b) { + if (!(a instanceof Array)) { + if (getType(a) === "undefined") a = []; + else a = [a]; + } + if (!(b instanceof Array)) { + if (getType(b) === "undefined") b = []; + else b = [b]; + } + return a.concat(b); + } + + // When using a 'create' callback, we proxy the dependent observable so that it doesn't immediately evaluate on creation. + // The reason is that the dependent observables in the user-specified callback may contain references to properties that have not been mapped yet. + function withProxyDependentObservable(dependentObservables, callback) { + var localDO = ko.dependentObservable; + ko.dependentObservable = function (read, owner, options) { + options = options || {}; + + var realDeferEvaluation = options.deferEvaluation; + + if (read && typeof read == "object") { // mirrors condition in knockout implementation of DO's + options = read; + } + + var isRemoved = false; + + // We wrap the original dependent observable so that we can remove it from the 'dependentObservables' list we need to evaluate after mapping has + // completed if the user already evaluated the DO themselves in the meantime. + var wrap = function (DO) { + var wrapped = realKoDependentObservable({ + read: function () { + if (!isRemoved) { + ko.utils.arrayRemoveItem(dependentObservables, DO); + isRemoved = true; + } + return DO.apply(DO, arguments); + }, + write: function (val) { + return DO(val); + }, + deferEvaluation: true + }); + wrapped.__ko_proto__ = realKoDependentObservable; + return wrapped; + }; + + options.deferEvaluation = true; // will either set for just options, or both read/options. + var realDependentObservable = new realKoDependentObservable(read, owner, options); + realDependentObservable.__ko_proto__ = realKoDependentObservable; + + if (!realDeferEvaluation) { + dependentObservables.push(realDependentObservable); + realDependentObservable = wrap(realDependentObservable); + } + + return realDependentObservable; + } + ko.computed = ko.dependentObservable; + var result = callback(); + ko.dependentObservable = localDO; + ko.computed = ko.dependentObservable; + return result; + } + + function updateViewModel(mappedRootObject, rootObject, options, parentName, parent, parentPropertyName) { + var isArray = ko.utils.unwrapObservable(rootObject) instanceof Array; + + parentPropertyName = parentPropertyName || ""; + + // If this object was already mapped previously, take the options from there and merge them with our existing ones. + if (ko.mapping.isMapped(mappedRootObject)) { + var previousMapping = ko.utils.unwrapObservable(mappedRootObject)[mappingProperty]; + options = merge(previousMapping, options); + } + + var callbackParams = { + data: rootObject, + parent: parent + }; + + var hasCreateCallback = function () { + return options[parentName] && options[parentName].create instanceof Function; + }; + + var createCallback = function (data) { + return withProxyDependentObservable(dependentObservables, function () { + return options[parentName].create({ + data: data || callbackParams.data, + parent: callbackParams.parent + }); + }); + }; + + var hasUpdateCallback = function () { + return options[parentName] && options[parentName].update instanceof Function; + }; + + var updateCallback = function (obj, data) { + var params = { + data: data || callbackParams.data, + parent: callbackParams.parent, + target: ko.utils.unwrapObservable(obj) + }; + + if (ko.isWriteableObservable(obj)) { + params.observable = obj; + } + + return options[parentName].update(params); + } + + var alreadyMapped = visitedObjects.get(rootObject); + if (alreadyMapped) { + return alreadyMapped; + } + + parentName = parentName || ""; + + if (!isArray) { + // For atomic types, do a direct update on the observable + if (!canHaveProperties(rootObject)) { + switch (getType(rootObject)) { + case "function": + if (hasUpdateCallback()) { + if (ko.isWriteableObservable(rootObject)) { + rootObject(updateCallback(rootObject)); + mappedRootObject = rootObject; + } else { + mappedRootObject = updateCallback(rootObject); + } + } else { + mappedRootObject = rootObject; + } + break; + default: + if (ko.isWriteableObservable(mappedRootObject)) { + if (hasUpdateCallback()) { + mappedRootObject(updateCallback(mappedRootObject)); + } else { + mappedRootObject(ko.utils.unwrapObservable(rootObject)); + } + } else { + if (hasCreateCallback()) { + mappedRootObject = createCallback(); + } else { + mappedRootObject = ko.observable(ko.utils.unwrapObservable(rootObject)); + } + + if (hasUpdateCallback()) { + mappedRootObject(updateCallback(mappedRootObject)); + } + } + break; + } + + } else { + mappedRootObject = ko.utils.unwrapObservable(mappedRootObject); + if (!mappedRootObject) { + if (hasCreateCallback()) { + var result = createCallback(); + + if (hasUpdateCallback()) { + result = updateCallback(result); + } + + return result; + } else { + if (hasUpdateCallback()) { + return updateCallback(result); + } + + mappedRootObject = {}; + } + } + + if (hasUpdateCallback()) { + mappedRootObject = updateCallback(mappedRootObject); + } + + visitedObjects.save(rootObject, mappedRootObject); + + // For non-atomic types, visit all properties and update recursively + visitPropertiesOrArrayEntries(rootObject, function (indexer) { + var fullPropertyName = parentPropertyName.length ? parentPropertyName + "." + indexer : indexer; + + if (ko.utils.arrayIndexOf(options.ignore, fullPropertyName) != -1) { + return; + } + + if (ko.utils.arrayIndexOf(options.copy, fullPropertyName) != -1) { + mappedRootObject[indexer] = rootObject[indexer]; + return; + } + + // In case we are adding an already mapped property, fill it with the previously mapped property value to prevent recursion. + // If this is a property that was generated by fromJS, we should use the options specified there + var prevMappedProperty = visitedObjects.get(rootObject[indexer]); + var value = prevMappedProperty || updateViewModel(mappedRootObject[indexer], rootObject[indexer], options, indexer, mappedRootObject, fullPropertyName); + + if (ko.isWriteableObservable(mappedRootObject[indexer])) { + mappedRootObject[indexer](ko.utils.unwrapObservable(value)); + } else { + mappedRootObject[indexer] = value; + } + + options.mappedProperties[fullPropertyName] = true; + }); + } + } else { + var changes = []; + + var hasKeyCallback = false; + var keyCallback = function (x) { + return x; + } + if (options[parentName] && options[parentName].key) { + keyCallback = options[parentName].key; + hasKeyCallback = true; + } + + if (!ko.isObservable(mappedRootObject)) { + // When creating the new observable array, also add a bunch of utility functions that take the 'key' of the array items into account. + mappedRootObject = ko.observableArray([]); + + mappedRootObject.mappedRemove = function (valueOrPredicate) { + var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { + return value === keyCallback(valueOrPredicate); + }; + return mappedRootObject.remove(function (item) { + return predicate(keyCallback(item)); + }); + } + + mappedRootObject.mappedRemoveAll = function (arrayOfValues) { + var arrayOfKeys = filterArrayByKey(arrayOfValues, keyCallback); + return mappedRootObject.remove(function (item) { + return ko.utils.arrayIndexOf(arrayOfKeys, keyCallback(item)) != -1; + }); + } + + mappedRootObject.mappedDestroy = function (valueOrPredicate) { + var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { + return value === keyCallback(valueOrPredicate); + }; + return mappedRootObject.destroy(function (item) { + return predicate(keyCallback(item)); + }); + } + + mappedRootObject.mappedDestroyAll = function (arrayOfValues) { + var arrayOfKeys = filterArrayByKey(arrayOfValues, keyCallback); + return mappedRootObject.destroy(function (item) { + return ko.utils.arrayIndexOf(arrayOfKeys, keyCallback(item)) != -1; + }); + } + + mappedRootObject.mappedIndexOf = function (item) { + var keys = filterArrayByKey(mappedRootObject(), keyCallback); + var key = keyCallback(item); + return ko.utils.arrayIndexOf(keys, key); + } + + mappedRootObject.mappedCreate = function (value) { + if (mappedRootObject.mappedIndexOf(value) !== -1) { + throw new Error("There already is an object with the key that you specified."); + } + + var item = hasCreateCallback() ? createCallback(value) : value; + if (hasUpdateCallback()) { + var newValue = updateCallback(item, value); + if (ko.isWriteableObservable(item)) { + item(newValue); + } else { + item = newValue; + } + } + mappedRootObject.push(item); + return item; + } + } + + var currentArrayKeys = filterArrayByKey(ko.utils.unwrapObservable(mappedRootObject), keyCallback).sort(); + var newArrayKeys = filterArrayByKey(rootObject, keyCallback); + if (hasKeyCallback) newArrayKeys.sort(); + var editScript = ko.utils.compareArrays(currentArrayKeys, newArrayKeys); + + var ignoreIndexOf = {}; + + var newContents = []; + for (var i = 0, j = editScript.length; i < j; i++) { + var key = editScript[i]; + var mappedItem; + var fullPropertyName = parentPropertyName + "[" + i + "]"; + switch (key.status) { + case "added": + var item = getItemByKey(ko.utils.unwrapObservable(rootObject), key.value, keyCallback); + mappedItem = ko.utils.unwrapObservable(updateViewModel(undefined, item, options, parentName, mappedRootObject, fullPropertyName)); + + var index = ignorableIndexOf(ko.utils.unwrapObservable(rootObject), item, ignoreIndexOf); + newContents[index] = mappedItem; + ignoreIndexOf[index] = true; + break; + case "retained": + var item = getItemByKey(ko.utils.unwrapObservable(rootObject), key.value, keyCallback); + mappedItem = getItemByKey(mappedRootObject, key.value, keyCallback); + updateViewModel(mappedItem, item, options, parentName, mappedRootObject, fullPropertyName); + + var index = ignorableIndexOf(ko.utils.unwrapObservable(rootObject), item, ignoreIndexOf); + newContents[index] = mappedItem; + ignoreIndexOf[index] = true; + break; + case "deleted": + mappedItem = getItemByKey(mappedRootObject, key.value, keyCallback); + break; + } + + changes.push({ + event: key.status, + item: mappedItem + }); + } + + mappedRootObject(newContents); + + if (options[parentName] && options[parentName].arrayChanged) { + ko.utils.arrayForEach(changes, function (change) { + options[parentName].arrayChanged(change.event, change.item); + }); + } + } + + return mappedRootObject; + } + + function ignorableIndexOf(array, item, ignoreIndices) { + for (var i = 0, j = array.length; i < j; i++) { + if (ignoreIndices[i] === true) continue; + if (array[i] === item) return i; + } + return null; + } + + function mapKey(item, callback) { + var mappedItem; + if (callback) mappedItem = callback(item); + if (getType(mappedItem) === "undefined") mappedItem = item; + + return ko.utils.unwrapObservable(mappedItem); + } + + function getItemByKey(array, key, callback) { + var filtered = ko.utils.arrayFilter(ko.utils.unwrapObservable(array), function (item) { + return mapKey(item, callback) === key; + }); + + if (filtered.length == 0) throw new Error("When calling ko.update*, the key '" + key + "' was not found!"); + if ((filtered.length > 1) && (canHaveProperties(filtered[0]))) throw new Error("When calling ko.update*, the key '" + key + "' was not unique!"); + + return filtered[0]; + } + + function filterArrayByKey(array, callback) { + return ko.utils.arrayMap(ko.utils.unwrapObservable(array), function (item) { + if (callback) { + return mapKey(item, callback); + } else { + return item; + } + }); + } + + function visitPropertiesOrArrayEntries(rootObject, visitorCallback) { + if (rootObject instanceof Array) { + for (var i = 0; i < rootObject.length; i++) + visitorCallback(i); + } else { + for (var propertyName in rootObject) + visitorCallback(propertyName); + } + }; + + function canHaveProperties(object) { + var type = getType(object); + return (type === "object") && (object !== null) && (type !== "undefined"); + } + + // Based on the parentName, this creates a fully classified name of a property + + function getPropertyName(parentName, parent, indexer) { + var propertyName = parentName || ""; + if (parent instanceof Array) { + if (parentName) { + propertyName += "[" + indexer + "]"; + } + } else { + if (parentName) { + propertyName += "."; + } + propertyName += indexer; + } + return propertyName; + } + + ko.mapping.visitModel = function (rootObject, callback, options) { + options = options || {}; + options.visitedObjects = options.visitedObjects || new objectLookup(); + + if (!options.parentName) { + options = fillOptions(options); + } + + var mappedRootObject; + var unwrappedRootObject = ko.utils.unwrapObservable(rootObject); + if (!canHaveProperties(unwrappedRootObject)) { + return callback(rootObject, options.parentName); + } else { + // Only do a callback, but ignore the results + callback(rootObject, options.parentName); + mappedRootObject = unwrappedRootObject instanceof Array ? [] : {}; + } + + options.visitedObjects.save(rootObject, mappedRootObject); + + var parentName = options.parentName; + visitPropertiesOrArrayEntries(unwrappedRootObject, function (indexer) { + if (options.ignore && ko.utils.arrayIndexOf(options.ignore, indexer) != -1) return; + + var propertyValue = unwrappedRootObject[indexer]; + options.parentName = getPropertyName(parentName, unwrappedRootObject, indexer); + + // If we don't want to explicitly copy the unmapped property... + if (ko.utils.arrayIndexOf(options.copy, indexer) === -1) { + // ...find out if it's a property we want to explicitly include + if (ko.utils.arrayIndexOf(options.include, indexer) === -1) { + // The mapped properties object contains all the properties that were part of the original object. + // 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. + if (unwrappedRootObject[mappingProperty] && unwrappedRootObject[mappingProperty].mappedProperties && !unwrappedRootObject[mappingProperty].mappedProperties[indexer] && !(unwrappedRootObject instanceof Array)) { + return; + } + } + } + + var outputProperty; + switch (getType(ko.utils.unwrapObservable(propertyValue))) { + case "object": + case "undefined": + var previouslyMappedValue = options.visitedObjects.get(propertyValue); + mappedRootObject[indexer] = (getType(previouslyMappedValue) !== "undefined") ? previouslyMappedValue : ko.mapping.visitModel(propertyValue, callback, options); + break; + default: + mappedRootObject[indexer] = callback(propertyValue, options.parentName); + } + }); + + return mappedRootObject; + } + + function objectLookup() { + var keys = []; + var values = []; + this.save = function (key, value) { + var existingIndex = ko.utils.arrayIndexOf(keys, key); + if (existingIndex >= 0) values[existingIndex] = value; + else { + keys.push(key); + values.push(value); + } + }; + this.get = function (key) { + var existingIndex = ko.utils.arrayIndexOf(keys, key); + return (existingIndex >= 0) ? values[existingIndex] : undefined; + }; + }; + + ko.exportSymbol('ko.mapping', ko.mapping); + ko.exportSymbol('ko.mapping.fromJS', ko.mapping.fromJS); + ko.exportSymbol('ko.mapping.fromJSON', ko.mapping.fromJSON); + ko.exportSymbol('ko.mapping.isMapped', ko.mapping.isMapped); + ko.exportSymbol('ko.mapping.defaultOptions', ko.mapping.defaultOptions); + ko.exportSymbol('ko.mapping.toJS', ko.mapping.toJS); + ko.exportSymbol('ko.mapping.toJSON', ko.mapping.toJSON); + ko.exportSymbol('ko.mapping.updateFromJS', ko.mapping.updateFromJS); + ko.exportSymbol('ko.mapping.updateFromJSON', ko.mapping.updateFromJSON); + ko.exportSymbol('ko.mapping.visitModel', ko.mapping.visitModel); +})(); \ No newline at end of file