diff src/DML/MainVisBundle/Resources/assets/marionette/modules/ContextModule/ContextModule.01-Config.js @ 0:493bcb69166c

added public content
author Daniel Wolff
date Tue, 09 Feb 2016 20:54:02 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/DML/MainVisBundle/Resources/assets/marionette/modules/ContextModule/ContextModule.01-Config.js	Tue Feb 09 20:54:02 2016 +0100
@@ -0,0 +1,468 @@
+"use strict";
+
+App.module("ContextModule", function(ContextModule, App, Backbone, Marionette, $, _, Logger) {
+
+    // Define private variables
+    var logger = null;
+
+    ContextModule.addInitializer(function(options){
+
+        logger = Logger.get("ContextModule.Config");
+        logger.setLevel(Logger.WARN);
+
+        /**
+         * Config embraces common behaviour patterns of an object that can have pending changes –
+         * it consists of two attributes: parameters and plannedParameterUpdates. Both are simple
+         * Backbone objects (Models) with a list of scalar attributes.
+         *
+         * The ConfigObject itself is identified by its non-changeable clientId (getClientId())
+         * and can be serialized (dumped) and unserialized (restored).
+         *
+         * It is advised to listen to changes in the Config using "change", "change:parameters"
+         * and "change:plannedParameterUpdates" listener.
+         * It will be triggered each time when "parameters or "plannedParameterUpdates" attributes have been changed.
+         */
+        ContextModule.Config = Backbone.Model.extend({
+
+            // Client id prefix (cf123 instead of standard c123)
+            cidPrefix: "cf",
+
+            /**
+             * @memberOf App.ContextModule.Config
+             */
+            constructor: function(attributes, options) {
+                this._modificationPropagationEnabled = true;
+                this._parametersWereModified = false;
+                this._plannedParameterUpdatesWereModified = false;
+                this._cachedHashForTrimmedParameters = null;
+                this._cachedHashForParameters = null;
+                this._cachedHashForPlannedParameterUpdates = null;
+                this._cachedHashForPermanent = null;
+                this._cachedHashForTemp = null;
+                this._cachedHash = null;
+
+                var defaultParameters = (_.isSimpleObject(attributes) && _.isSimpleObject(attributes.parameters)) ? attributes.parameters : undefined;
+                var defaultPlannedParameterUpdates = (_.isSimpleObject(attributes) && _.isSimpleObject(attributes.plannedParameterUpdates)) ? attributes.plannedParameterUpdates : undefined;
+
+                var realAttributes = {};
+                realAttributes.parameters = new Backbone.Model(defaultParameters);
+                realAttributes.plannedParameterUpdates = new Backbone.Model(defaultPlannedParameterUpdates);
+
+                this.listenTo(realAttributes.parameters, "change", this._registerModificationOfParameters);
+                this.listenTo(realAttributes.plannedParameterUpdates, "change", this._registerModificationOfPlannedParameterUpdates);
+
+                Backbone.Model.apply(this, [realAttributes, options]);
+
+                if (attributes && attributes.clientId) {
+                    this.cid = attributes.clientId;
+                    _.markUniqueIdAsAlreadyUsed(attributes.clientId);
+                }
+            },
+
+            getClientId: function() {
+                return this.cid;
+            },
+
+            getDimension: function() {
+                return this.collection ? this.collection.dimension : undefined;
+            },
+            getConfigGridType: function() {
+                return this.collection ? this.collection.configGridType : undefined;
+            },
+
+            /**
+             * @memberOf App.ContextModule.Config
+             */
+            getParameterValue: function(parameterName) {
+                return this.attributes.parameters.attributes[parameterName];
+            },
+
+            /**
+             * @memberOf App.ContextModule.Config
+             */
+            getPlannedParameterValue: function(parameterName) {
+                var plannedParameterUpdatesAttributes = this.attributes.plannedParameterUpdates.attributes;
+                if (plannedParameterUpdatesAttributes.hasOwnProperty(parameterName)) {
+                    return plannedParameterUpdatesAttributes[parameterName];
+                } else {
+                    return this.attributes.parameters.attributes[parameterName];
+                }
+            },
+
+            /**
+             * @memberOf App.ContextModule.Config
+             *
+             * XXX clone parameter values?
+             */
+            getPlannedParameterValues: function(parameterName) {
+                var result = _.clone(this.attributes.parameters.attributes);
+                var plannedParameterUpdatesAttributes = this.attributes.plannedParameterUpdates.attributes;
+                for (var key in plannedParameterUpdatesAttributes) {
+                    if (plannedParameterUpdatesAttributes.hasOwnProperty(key)) {
+                        if (plannedParameterUpdatesAttributes[key] === undefined) {
+                            if (result.hasOwnProperty(key)) {
+                                delete result[key];
+                            }
+                        } else {
+                            result[key] = plannedParameterUpdatesAttributes[key];
+                        }
+                    }
+                }
+                return result;
+            },
+
+            /**
+             * @memberOf App.ContextModule.Config
+             */
+            isPlannedToUpdate: function(parameterName) {
+                return this.attributes.plannedParameterUpdates.attributes.hasOwnProperty(parameterName);
+                // var parameterValue = this.getParameterValue(parameterName);
+                // var plannedParameterValue = this.getPlannedParameterValue(parameterName);
+                // return (parameterValue !== plannedParameterValue);
+            },
+
+            /**
+             * @memberOf App.ContextModule.Config
+             */
+            hasPlannedParameterUpdates: function() {
+                return _.size(this.attributes.plannedParameterUpdates.attributes) > 0;
+            },
+
+            /**
+             * @memberOf App.ContextModule.Config
+             */
+            updateParameter: function(parameterName, parameterValue) {
+                if (!_.isString(parameterName)) {
+                    throw _.str.sprintf("Config::updateParameter called a non-string parameterName: %s", parameterName);
+                }
+                var prevModificationPropagationEnabled = this._modificationPropagationEnabled;
+                this._modificationPropagationEnabled = false;
+                this.attributes.plannedParameterUpdates.unset(parameterName);
+                if (typeof parameterValue !== "undefined") {
+                    this.attributes.parameters.set(parameterName, parameterValue);
+                } else {
+                    this.attributes.parameters.unset(parameterName);
+                }
+                if (prevModificationPropagationEnabled) {
+                    this._triggerModificationEventsIfNeeded();
+                    this._modificationPropagationEnabled = true;
+                }
+            },
+
+            /**
+             * @memberOf App.ContextModule.Config
+             */
+            planParameterUpdate: function(parameterName, parameterValue) {
+                if (!_.isString(parameterName)) {
+                    throw _.str.sprintf("Config::planParameterUpdate called a non-string parameterName: %s", parameterName);
+                }
+                var prevModificationPropagationEnabled = this._modificationPropagationEnabled;
+
+                this._modificationPropagationEnabled = false;
+                var plannedParameterUpdatesAttributes = this.attributes.plannedParameterUpdates.attributes;
+                var parametersAttributes = this.attributes.parameters.attributes;
+                if (parameterValue === parametersAttributes[parameterName]) {
+                    // special case: backbone won't fire a change event without this hack (due to how _.isEqual works)
+                    if (this.attributes.plannedParameterUpdates.attributes.hasOwnProperty(parameterName) && this.attributes.plannedParameterUpdates.attributes[parameterName] === undefined) {
+                        this.attributes.plannedParameterUpdates.set(parameterName, 42, {silent: true});
+                    }
+                    this.attributes.plannedParameterUpdates.unset(parameterName);
+                } else {
+                    // special case: backbone won't fire a change event without this hack (due to how _.isEqual works)
+                    if (parameterValue === undefined && this.attributes.parameters.attributes.hasOwnProperty(parameterName) && !this.attributes.plannedParameterUpdates.attributes.hasOwnProperty(parameterName)) {
+                        this.attributes.plannedParameterUpdates.set(parameterName, 42, {silent: true});
+                    }
+                    this.attributes.plannedParameterUpdates.set(parameterName, parameterValue);
+                }
+
+                if (prevModificationPropagationEnabled) {
+                    this._triggerModificationEventsIfNeeded();
+                    this._modificationPropagationEnabled = true;
+                }
+            },
+
+            /**
+             * @memberOf App.ContextModule.Config
+             */
+            cancelPlannedParameterUpdate: function(parameterName) {
+                if (!_.isString(parameterName)) {
+                    throw _.str.sprintf("Config::cancelPlannedParameterUpdate called a non-string parameterName: %s", parameterName);
+                }
+                var prevModificationPropagationEnabled = this._modificationPropagationEnabled;
+                this._modificationPropagationEnabled = false;
+
+                this.attributes.plannedParameterUpdates.unset(parameterName);
+                if (prevModificationPropagationEnabled) {
+                    this._triggerModificationEventsIfNeeded();
+                    this._modificationPropagationEnabled = true;
+                }
+            },
+
+            /**
+             * @memberOf App.ContextModule.Config
+             */
+            updateParameters: function(parameters) {
+                if (!_.isSimpleObject(parameters)) {
+                    throw _.str.sprintf("Config::updateParameters called a wrong argument: %s", parameters);
+                }
+                this._modificationPropagationEnabled = false;
+
+                for (var parameterName in parameters) {
+                    if (parameters.hasOwnProperty(parameterName)) {
+                        this.updateParameter(parameterName, parameters[parameterName]);
+                    }
+                }
+
+                this._triggerModificationEventsIfNeeded();
+                this._modificationPropagationEnabled = true;
+            },
+
+            /**
+             * @memberOf App.ContextModule.Config
+             */
+            planParameterUpdates: function(parameters) {
+                if (!_.isSimpleObject(parameters)) {
+                    throw _.str.sprintf("Config::planParameterUpdates called with a wrong argument: %s", parameters);
+                }
+                this._modificationPropagationEnabled = false;
+
+                for (var parameterName in parameters) {
+                    if (parameters.hasOwnProperty(parameterName)) {
+                        this.planParameterUpdate(parameterName, parameters[parameterName]);
+                    }
+                }
+
+                this._triggerModificationEventsIfNeeded();
+                this._modificationPropagationEnabled = true;
+            },
+
+            /**
+             * @memberOf App.ContextModule.Config
+             */
+            cancelPlannedParameterUpdates: function(parameterNames) {
+                if (_.isArray(parameterNames)) {
+                    this._modificationPropagationEnabled = false;
+
+                    for (var i = 0; i < parameterNames.length; i++) {
+                        this.cancelPlannedParameterUpdate(parameterNames[i]);
+                    }
+
+                    this._triggerModificationEventsIfNeeded();
+                    this._modificationPropagationEnabled = true;
+                } else if (!_.isUndefined(parameterNames)) {
+                    throw _.str.sprintf("Config::planParameterUpdates called a non-string parameters: %s", parameters);
+                } else {
+                    if (_.keys(this.attributes.plannedParameterUpdates.attributes).length) {
+                        this.attributes.plannedParameterUpdates.attributes.fix = 42;
+                    }
+                    this.attributes.plannedParameterUpdates.clear();
+                }
+            },
+
+            /**
+             * @memberOf App.ContextModule.Config
+             */
+            applyPlannedParameterUpdates: function() {
+
+                // Combine parameters with planned updates
+                var newParameters = _.extend(this.attributes.parameters.toJSON(), this.attributes.plannedParameterUpdates.toJSON());
+
+                // Remove "undefined" from the new parameters
+                for (var key in newParameters) {
+                    if (!newParameters.hasOwnProperty(key)) continue;
+
+                    if (typeof newParameters[key] === "undefined") {
+                        delete newParameters[key];
+                    }
+                }
+
+                // If there are any existing keys with "undefined" within plannedParameterUpdates,
+                // replace "undefined" with some value to make sure change:plannedParameterUpdates is triggered
+                // Another part of this hack is in "planParameterUpdate" method
+                var attributesInPlannedParameterUpdates = this.attributes.plannedParameterUpdates.attributes;
+                for (var key in attributesInPlannedParameterUpdates) {
+                    if (attributesInPlannedParameterUpdates[key] === undefined) {
+                        attributesInPlannedParameterUpdates[key] = 42;
+                        break;
+                    }
+                }
+
+                // Assign parameters and clear planned updates
+                this.unserialize({
+                    clientId: this.cid,
+                    parameters: newParameters,
+                    plannedParameterUpdates: {}
+                });
+            },
+
+            /**
+             * @memberOf App.ContextModule.Config
+             */
+            serialize: function() {
+                var result = {
+                        clientId: this.cid,
+                        parameters: this.attributes.parameters.toJSON(),
+                        plannedParameterUpdates: this.attributes.plannedParameterUpdates.toJSON()
+                };
+
+                return result;
+            },
+
+            /**
+             * @memberOf App.ContextModule.Config
+             */
+            unserialize: function(serializedAttributes) {
+                var fixedSerializedAttributes = serializedAttributes;
+                if (!_.isSimpleObject(serializedAttributes)) {
+                    logger.warn("Config::unserialize called for not an object: ", serializedAttributes);
+                    fixedSerializedAttributes = {};
+                }
+
+                if (this.cid != fixedSerializedAttributes.clientId && !_.isUndefined(fixedSerializedAttributes.clientId)) {
+                    throw _.str.sprintf("Parameter bag client id (%s) is not equal to the client id of the serialized object (%s).", this.cid, fixedSerializedAttributes.clientId);
+                }
+
+                this._parametersWereModified = false;
+                this._plannedParameterUpdatesWereModified = false;
+                this._modificationPropagationEnabled = false;
+
+                var fixedSerializedPlannedParameterUpdates = fixedSerializedAttributes.plannedParameterUpdates;
+                if (!_.isSimpleObject(fixedSerializedPlannedParameterUpdates)) {
+                    if (_.isSimpleObject(serializedAttributes)) {
+                        logger.warn("Config::unserialize called for object with faulty plannedParameterUpdates: ", fixedSerializedPlannedParameterUpdates);
+                    }
+                    fixedSerializedPlannedParameterUpdates = {};
+                }
+                if (!_.isEqual(this.attributes.plannedParameterUpdates.attributes, fixedSerializedPlannedParameterUpdates)) {
+                    this.attributes.plannedParameterUpdates
+                        .set("fix", 42, {silent: true})
+                        .clear()
+                        .set(fixedSerializedPlannedParameterUpdates);
+                }
+
+                var fixedSerializedParameters = fixedSerializedAttributes.parameters;
+                if (!_.isSimpleObject(fixedSerializedParameters)) {
+                    if (_.isSimpleObject(serializedAttributes)) {
+                        logger.warn("Config::unserialize called for object with faulty parameters: ", fixedSerializedParameters);
+                    }
+                    fixedSerializedParameters = {};
+                }
+                if (!_.isEqual(this.attributes.parameters.toJSON(), fixedSerializedParameters)) {
+                    this.attributes.parameters
+                        .clear()
+                        .set(fixedSerializedParameters);
+                }
+
+                this._triggerModificationEventsIfNeeded();
+                this._modificationPropagationEnabled = true;
+            },
+
+            clone: function() {
+                var serializedAttributes = this.serialize();
+                delete serializedAttributes.clientId;
+                return new ContextModule.Config(serializedAttributes);
+            },
+
+            getHashForParameters: function() {
+                if (this._cachedHashForParameters === null) {
+                    this._cachedHashForParameters = JSON.stringify(this.attributes.parameters.attributes);
+                }
+                return this._cachedHashForParameters;
+            },
+
+            getHashForTrimmedParameters: function() {
+                if (this._cachedHashForTrimmedParameters === null) {
+                    var attributesToHash = _.clone(this.attributes.parameters.attributes);
+                    for (var key in attributesToHash){
+                        if (attributesToHash.hasOwnProperty(key) && _.isString(attributesToHash[key])) {
+                            attributesToHash[key] = _.str.trim(attributesToHash[key]);
+                        }
+                    }
+                    this._cachedHashForTrimmedParameters = JSON.stringify(attributesToHash);
+                }
+                return this._cachedHashForTrimmedParameters;
+            },
+
+            getHashForPlannedParameterUpdates: function() {
+                if (this._cachedHashForPlannedParameterUpdates === null) {
+                    var attributes = this.attributes.plannedParameterUpdates.attributes;
+                    this._cachedHashForPlannedParameterUpdates = JSON.stringify(attributes);
+                    // special treatment of undefined is needed here
+                    // see http://stackoverflow.com/questions/26540706/json-stringify-removes-hash-keys-with-undefined-values
+                    for (var key in attributes) {
+                        if (attributes.hasOwnProperty(key) && attributes[key] === undefined) {
+                            this._cachedHashForPlannedParameterUpdates += key + "|";
+                        }
+                    }
+                }
+                return this._cachedHashForPlannedParameterUpdates;
+            },
+
+            getHashForPermanent: function() {
+                if (this._cachedHashForPermanent === null) {
+                    this._cachedHashForPermanent = this.getHashForParameters() + this.getHashForPlannedParameterUpdates();
+                }
+                return this._cachedHashForPermanent;
+            },
+
+            getHashForTemp: function() {
+                if (this._cachedHashForTemp === null) {
+                    this._cachedHashForTemp = "";
+                }
+                return this._cachedHashForTemp;
+            },
+
+            getHash: function() {
+                if (!this._cachedHash) {
+                    this._cachedHash = this.getHashForPermanent() + this.getHashForTemp();
+                }
+                return this._cachedHash;
+            },
+
+            _registerModificationOfParameters: function() {
+                this._cachedHashForParameters = null;
+                this._cachedHashForTrimmedParameters = null;
+                this._cachedHashForPermanent = null;
+                this._cachedHash = null;
+
+                this._parametersWereModified = true;
+                if (this._modificationPropagationEnabled) {
+                    this._triggerModificationEventsIfNeeded();
+                };
+            },
+
+            _registerModificationOfPlannedParameterUpdates: function() {
+                this._cachedHashForPlannedParameterUpdates = null;
+                this._cachedHashForPermanent = null;
+                this._cachedHash = null;
+
+                this._plannedParameterUpdatesWereModified = true;
+                if (this._modificationPropagationEnabled) {
+                    this._triggerModificationEventsIfNeeded();
+                };
+            },
+
+            _triggerModificationEventsIfNeeded: function() {
+                this.changed = [true];
+                if (this._parametersWereModified) {
+                    this.trigger("change:parameters");
+                }
+                if (this._plannedParameterUpdatesWereModified) {
+                    this.trigger("change:plannedParameterUpdates");
+                }
+                if (this._tempParametersWereModified) {
+                    this.trigger("change:tempParameters");
+                }
+
+                if (this._parametersWereModified || this._plannedParameterUpdatesWereModified) {
+                    this.trigger("change:parametersOrPlannedParameterUpdates");
+                    this.trigger("change");
+                }
+                this._parametersWereModified = false;
+                this._plannedParameterUpdatesWereModified = false;
+                this._tempParametersWereModified = false;
+                this.changed = null;
+            },
+        });
+    });
+}, Logger);