/***************************************************************************** * Open MCT Web, Copyright (c) 2014-2015, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * * Open MCT Web is licensed under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0. * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * * Open MCT Web includes source code licensed under additional open source * licenses. See the Open Source Licenses file (LICENSES.md) included with * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ /*global define,Promise*/ /** * Module defining MutationCapability. Created by vwoeltje on 11/12/14. */ define( [], function () { "use strict"; var TOPIC_PREFIX = "mutation:"; // Utility function to overwrite a destination object // with the contents of a source object. function copyValues(destination, source) { // First, remove all previously-existing keys Object.keys(destination).forEach(function (k) { delete destination[k]; }); // Second, write all new keys Object.keys(source).forEach(function (k) { destination[k] = source[k]; }); } // Utility function to cast to a promise, without waiting // for nextTick if a value is non-promise-like. function fastPromise(value) { return (value || {}).then ? value : { then: function (callback) { return fastPromise(callback(value)); } }; } /** * The `mutation` capability allows a domain object's model to be * modified. Wrapping such modifications in calls made through * this capability allows these changes to be tracked (e.g. to * ensure that a domain object's `modified` timestamp is kept * up-to-date.) * * Usage: * * ``` * domainObject.useCapability("mutation", function (model) { * // make changes to model here... * }); * ``` * * @param {DomainObject} domainObject the domain object * which will expose this capability * @constructor */ function MutationCapability(topic, now, domainObject) { var t = topic(TOPIC_PREFIX + domainObject.getId()); function mutate(mutator, timestamp) { // Get the object's model and clone it, so the // mutator function has a temporary copy to work with. var model = domainObject.getModel(), clone = JSON.parse(JSON.stringify(model)), useTimestamp = arguments.length > 1; // Function to handle copying values to the actual function handleMutation(mutationResult) { // If mutation result was undefined, just use // the clone; this allows the mutator to omit return // values and just change the model directly. var result = mutationResult || clone; // Allow mutators to change their mind by // returning false. if (mutationResult !== false) { // Copy values if result was a different object // (either our clone or some other new thing) if (model !== result) { copyValues(model, result); } model.modified = useTimestamp ? timestamp : now(); t.notify(model); } // Report the result of the mutation return mutationResult !== false; } // Invoke the provided mutator, then make changes to // the underlying model (if applicable.) return fastPromise(mutator(clone)).then(handleMutation); } function listen(listener) { return t.listen(listener); } return { /** * Alias of `mutate`, used to support useCapability. */ invoke: mutate, /** * Modify the domain object's model, using a provided * function. This function will receive a copy of the * domain object's model as an argument; behavior * varies depending on that function's return value: * * * If no value (or undefined) is returned by the mutator, * the state of the model object delivered as the mutator's * argument will become the domain object's new model. * This is useful for writing code that modifies the model * directly. * * If a plain object is returned, that object will be used * as the domain object's new model. * * If boolean `false` is returned, the mutation will be * cancelled. * * If a promise is returned, its resolved value will be * handled as one of the above. * * * @param {function} mutator the function which will make * changes to the domain object's model. * @param {number} [timestamp] timestamp to record for * this mutation (otherwise, system time will be * used) * @returns {Promise.} a promise for the result * of the mutation; true if changes were made. */ mutate: mutate, /** * Listen for mutations of this domain object's model. * The provided listener will be invoked with the domain * object's new model after any changes. To stop listening, * invoke the function returned by this method. * @param {Function} listener function to call on mutation * @returns {Function} a function to stop listening */ listen: listen }; } return MutationCapability; } );