diff --git a/platform/commonUI/edit/src/capabilities/EditableCompositionCapability.js b/platform/commonUI/edit/src/capabilities/EditableCompositionCapability.js new file mode 100644 index 0000000000..9fb364441d --- /dev/null +++ b/platform/commonUI/edit/src/capabilities/EditableCompositionCapability.js @@ -0,0 +1,36 @@ +/*global define*/ + + +define( + ['./EditableLookupCapability'], + function (EditableLookupCapability) { + 'use strict'; + + /** + * Wrapper for the "composition" capability; + * ensures that any domain objects reachable in Edit mode + * are also wrapped as EditableDomainObjects. + * + * Meant specifically for use by EditableDomainObject and the + * associated cache; the constructor signature is particular + * to a pattern used there and may contain unused arguments. + */ + return function EditableCompositionCapability( + contextCapability, + editableObject, + domainObject, + cache + ) { + // This is a "lookup" style capability (it looks up other + // domain objects), but we do not want to return the same + // specific value every time (composition may change) + return new EditableLookupCapability( + contextCapability, + editableObject, + domainObject, + cache, + false // Not idempotent + ); + }; + } +); \ No newline at end of file diff --git a/platform/commonUI/edit/src/capabilities/EditableContextCapability.js b/platform/commonUI/edit/src/capabilities/EditableContextCapability.js index f1b442f85a..b8658aa19a 100644 --- a/platform/commonUI/edit/src/capabilities/EditableContextCapability.js +++ b/platform/commonUI/edit/src/capabilities/EditableContextCapability.js @@ -2,12 +2,12 @@ define( - [], - function () { + ['./EditableLookupCapability'], + function (EditableLookupCapability) { 'use strict'; /** - * Wrapper for both "context" and "composition" capabilities; + * Wrapper for the "context" capability; * ensures that any domain objects reachable in Edit mode * are also wrapped as EditableDomainObjects. * @@ -21,55 +21,15 @@ define( domainObject, cache ) { - var capability = Object.create(contextCapability); - - // Check for domain object interface. If something has these - // three methods, we assume it's a domain object. - function isDomainObject(obj) { - return obj !== undefined && - typeof obj.getId === 'function' && - typeof obj.getModel === 'function' && - typeof obj.getCapability === 'function'; - } - - // Check an object returned by the wrapped capability; if it - // is a domain object, we want to make it editable and/or get - // it from the cache of editable domain objects. This will - // prevent changes made in edit mode from modifying the actual - // underlying domain object. - function makeEditableObject(obj) { - return isDomainObject(obj) ? - cache.getEditableObject(obj) : - obj; - } - - // Wrap a returned value (see above); if it's an array, wrap - // all elements. - function makeEditable(returnValue) { - return Array.isArray(returnValue) ? - returnValue.map(makeEditableObject) : - makeEditableObject(returnValue); - } - - // Wrap a returned value (see above); if it's a promise, wrap - // the resolved value. - function wrapResult(result) { - return result.then ? // promise-like - result.then(makeEditable) : - makeEditable(result); - } - - // Wrap all methods; return only editable domain objects. - Object.keys(contextCapability).forEach(function (k) { - capability[k] = function () { - return wrapResult(contextCapability[k].apply( - capability, - arguments - )); - }; - }); - - return capability; + // This is a "lookup" style capability (it looks up other + // domain objects), and it should be idempotent + return new EditableLookupCapability( + contextCapability, + editableObject, + domainObject, + cache, + true // Not idempotent + ); }; } ); \ No newline at end of file diff --git a/platform/commonUI/edit/src/capabilities/EditableLookupCapability.js b/platform/commonUI/edit/src/capabilities/EditableLookupCapability.js new file mode 100644 index 0000000000..b7636aa5dd --- /dev/null +++ b/platform/commonUI/edit/src/capabilities/EditableLookupCapability.js @@ -0,0 +1,97 @@ +/*global define*/ + + +define( + [], + function () { + 'use strict'; + + /** + * Wrapper for both "context" and "composition" capabilities; + * ensures that any domain objects reachable in Edit mode + * are also wrapped as EditableDomainObjects. + * + * Meant specifically for use by EditableDomainObject and the + * associated cache; the constructor signature is particular + * to a pattern used there and may contain unused arguments. + */ + return function EditableLookupCapability( + contextCapability, + editableObject, + domainObject, + cache, + idempotent + ) { + var capability = Object.create(contextCapability); + + // Check for domain object interface. If something has these + // three methods, we assume it's a domain object. + function isDomainObject(obj) { + return obj !== undefined && + typeof obj.getId === 'function' && + typeof obj.getModel === 'function' && + typeof obj.getCapability === 'function'; + } + + // Check an object returned by the wrapped capability; if it + // is a domain object, we want to make it editable and/or get + // it from the cache of editable domain objects. This will + // prevent changes made in edit mode from modifying the actual + // underlying domain object. + function makeEditableObject(obj) { + return isDomainObject(obj) ? + cache.getEditableObject(obj) : + obj; + } + + // Wrap a returned value (see above); if it's an array, wrap + // all elements. + function makeEditable(returnValue) { + return Array.isArray(returnValue) ? + returnValue.map(makeEditableObject) : + makeEditableObject(returnValue); + } + + // Wrap a returned value (see above); if it's a promise, wrap + // the resolved value. + function wrapResult(result) { + return result.then ? // promise-like + result.then(makeEditable) : + makeEditable(result); + } + + // Return a wrapped version of a function, which ensures + // all results are editable domain objects. + function wrapFunction(fn) { + return function () { + return wrapResult(contextCapability[fn].apply( + capability, + arguments + )); + }; + } + + // Wrap a method such that it only delegates once. + function oneTimeFunction(fn) { + return function () { + var result = wrapFunction(fn).apply(this, arguments); + capability[fn] = function () { + return result; + }; + return result; + }; + } + + // Wrap a method of this capability + function wrapMethod(fn) { + capability[fn] = + (idempotent ? oneTimeFunction : wrapFunction)(fn); + } + + // Wrap all methods; return only editable domain objects. + Object.keys(contextCapability).forEach(wrapFunction); + + return capability; + }; + } +); \ No newline at end of file diff --git a/platform/commonUI/edit/src/objects/EditableDomainObject.js b/platform/commonUI/edit/src/objects/EditableDomainObject.js index 14aa5435a0..8ec46fb3ab 100644 --- a/platform/commonUI/edit/src/objects/EditableDomainObject.js +++ b/platform/commonUI/edit/src/objects/EditableDomainObject.js @@ -53,9 +53,8 @@ define( // Constructor for EditableDomainObject, which adheres // to the same shared cache. - function EditableDomainObjectImpl(domainObject) { - var model = JSON.parse(JSON.stringify(domainObject.getModel())), - editableObject = Object.create(domainObject); + function EditableDomainObjectImpl(domainObject, model) { + var editableObject = Object.create(domainObject); // Only provide the cloned model. editableObject.getModel = function () { return model; }; diff --git a/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js b/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js index 0b7354dc84..6673e31998 100644 --- a/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js +++ b/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js @@ -15,7 +15,8 @@ * @module editor/object/editable-domain-object-cache */ define( - function () { + ["./EditableModelCache"], + function (EditableModelCache) { 'use strict'; /** @@ -32,7 +33,7 @@ define( * @memberof module:editor/object/editable-domain-object-cache */ function EditableDomainObjectCache(EditableDomainObject) { - var cache = {}, + var cache = new EditableModelCache(), dirty = {}; return { @@ -44,9 +45,10 @@ define( * @returns {DomainObject} the domain object in an editable form */ getEditableObject: function (domainObject) { - var id = domainObject.getId(); - return (cache[id] = - cache[id] || new EditableDomainObject(domainObject)); + return new EditableDomainObject( + domainObject, + cache.getCachedModel(domainObject) + ); }, /** * Mark an editable domain object (presumably already cached)