Mercurial > hg > dml-open-vis
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);