diff --git a/src/editor/main.js b/src/editor/main.js index b6a754639..ea8821a33 100644 --- a/src/editor/main.js +++ b/src/editor/main.js @@ -30,14 +30,15 @@ * ``` * * **Available events** - * #component:add - Triggered when a new component is added to the editor, the model is passed as an argument to the callback - * #component:update - Triggered when a component is, generally, updated (moved, styled, etc.) - * #component:styleUpdate - Triggered when the style of the component is updated - * #component:update:{propertyName} - Listen any property change - * #canvasScroll - Triggered when the canvas is scrolled - * #run:{commandName} - Triggered when some command is called to run (eg. editor.runCommand('preview')) - * #stop:{commandName} - Triggered when some command is called to stop (eg. editor.stopCommand('preview')) - * #load - When the editor is loaded + * component:add - Triggered when a new component is added to the editor, the model is passed as an argument to the callback + * component:update - Triggered when a component is, generally, updated (moved, styled, etc.) + * component:update:{propertyName} - Listen any property change + * component:styleUpdate - Triggered when the style of the component is updated + * component:styleUpdate:{propertyName} - Listen for a specific style property change + * canvasScroll - Triggered when the canvas is scrolled + * run:{commandName} - Triggered when some command is called to run (eg. editor.runCommand('preview')) + * stop:{commandName} - Triggered when some command is called to stop (eg. editor.stopCommand('preview')) + * load - When the editor is loaded * * @module Editor * @param {Object} config Configurations diff --git a/src/style_manager/config/config.js b/src/style_manager/config/config.js index 95aa96aa4..0e8f6b7ec 100644 --- a/src/style_manager/config/config.js +++ b/src/style_manager/config/config.js @@ -1,13 +1,17 @@ define(function () { - return { + return { - stylePrefix: 'sm-', + stylePrefix: 'sm-', - sectors: [], + sectors: [], - // Text to show in case no element selected - textNoElement: 'Select an element before using Style Manager', - - }; + // Text to show in case no element selected + textNoElement: 'Select an element before using Style Manager', + + // Hide the property in case it's not stylable for the + // selected component (each component has 'stylable' property) + hideNotStylable: true, + + }; }); diff --git a/src/style_manager/view/PropertyView.js b/src/style_manager/view/PropertyView.js index 93bc0820d..9fb8a0a70 100644 --- a/src/style_manager/view/PropertyView.js +++ b/src/style_manager/view/PropertyView.js @@ -1,308 +1,320 @@ define(['backbone', 'text!./../templates/propertyLabel.html', 'text!./../templates/propertyInput.html'], - function (Backbone, propertyLabel, propertyTemplate) { - /** - * @class PropertyView - * */ - return Backbone.View.extend({ - - template: _.template(propertyTemplate), - templateLabel: _.template(propertyLabel), - - events: {'change': 'valueUpdated'}, - - initialize: function(o) { - this.config = o.config || {}; - this.pfx = this.config.stylePrefix || ''; - this.ppfx = this.config.pStylePrefix || ''; - this.target = o.target || {}; - this.propTarget = o.propTarget || {}; - this.onChange = o.onChange || {}; - this.onInputRender = o.onInputRender || {}; - this.customValue = o.customValue || {}; - this.defaultValue = this.model.get('defaults'); - this.property = this.model.get('property'); - this.input = this.$input = null; - this.className = this.pfx + 'property'; - this.inputHolderId = '#' + this.pfx + 'input-holder'; - - if(!this.model.get('value')) - this.model.set('value', this.model.get('defaults')); - - this.listenTo(this.model, 'destroy remove', this.remove); - this.listenTo( this.propTarget, 'update', this.targetUpdated); - this.listenTo( this.model ,'change:value', this.valueChanged); - this.listenTo( this.model ,'targetUpdated', this.targetUpdated); - }, - - /** - * Returns selected target which should have 'style' property - * @return {Model|null} - */ - getTarget: function(){ - if(this.selectedComponent) - return this.selectedComponent; - return this.propTarget ? this.propTarget.model : null; - }, - - /** - * Fired when the input value is updated - */ - valueUpdated: function(){ - if(this.$input) - this.model.set('value', this.getInputValue()); - }, - - /** - * Fired when the target is updated - * */ - targetUpdated: function(){ - this.selectedComponent = this.propTarget.model; - this.helperComponent = this.propTarget.helper; - if(this.getTarget()){ - if(!this.sameValue()){ - this.renderInputRequest(); - } - } - }, - - /** - * Checks if the value from selected component is the - * same of the value of the model - * - * @return {Boolean} - * */ - sameValue: function(){ - return this.getComponentValue() == (this.model.get('value') + this.model.get('unit')); - }, - - - /** - * Get the value from the selected component of this property - * - * @return {String} - * */ - getComponentValue: function(){ - var target = this.getTarget(); - - if(!target) - return; - - var targetProp = target.get('style')[this.property]; - if(targetProp) - this.componentValue = targetProp; - else - this.componentValue = this.defaultValue + (this.unit || ''); // todo model - - // Check if wrap inside function is required - if(this.model.get('functionName')){ - var v = this.fetchFromFunction(this.componentValue); - if(v) - this.componentValue = v; - } - - // This allow to ovveride the normal flow of selecting component value, - // useful in composite properties - if(this.customValue && typeof this.customValue === "function"){ - var index = this.model.collection.indexOf(this.model); - var t = this.customValue(this, index); - if(t) - this.componentValue = t; - } - - return this.componentValue; - }, - - /** - * Fetch the string from function type value - * @param {String} v Function type value - * - * @return {String} - * */ - fetchFromFunction: function(v){ - return v.substring(v.indexOf("(") + 1, v.lastIndexOf(")")); - }, - - /** - * Returns value from inputs - * @return {string} - */ - getValueForTarget: function(){ - return this.model.getValue(); - }, - - /** - * Returns value from input - * @return {string} - */ - getInputValue: function(){ - return this.$input ? this.$input.val() : ''; - }, - - /** - * Property was changed, so I need to update the component too - * @param {Object} e Events - * @param {Mixed} val Value - * @param {Object} opt Options - * */ - valueChanged: function(e, val, opt) { - var mVal = this.getValueForTarget(); - - if(this.$input) - this.setValue(mVal); - - if(!this.getTarget()) - return; - - // Check if component is allowed to be styled - if(!this.isTargetStylable()) - return; - - value = this.getValueForTarget(); - - var func = this.model.get('functionName'); - if(func) - value = func + '(' + value + ')'; - - var target = this.getTarget(); - var onChange = this.onChange; - - if(onChange && typeof onChange === "function"){ - onChange(target, this, opt); - }else - this.updateTargetStyle(value, null, opt); + function (Backbone, propertyLabel, propertyTemplate) { + /** + * @class PropertyView + * */ + return Backbone.View.extend({ + + template: _.template(propertyTemplate), + templateLabel: _.template(propertyLabel), + + events: {'change': 'valueUpdated'}, + + initialize: function(o) { + this.config = o.config || {}; + this.pfx = this.config.stylePrefix || ''; + this.ppfx = this.config.pStylePrefix || ''; + this.target = o.target || {}; + this.propTarget = o.propTarget || {}; + this.onChange = o.onChange || {}; + this.onInputRender = o.onInputRender || {}; + this.customValue = o.customValue || {}; + this.defaultValue = this.model.get('defaults'); + this.property = this.model.get('property'); + this.input = this.$input = null; + this.className = this.pfx + 'property'; + this.inputHolderId = '#' + this.pfx + 'input-holder'; + + if(!this.model.get('value')) + this.model.set('value', this.model.get('defaults')); + + this.listenTo(this.model, 'destroy remove', this.remove); + this.listenTo( this.propTarget, 'update', this.targetUpdated); + this.listenTo( this.model ,'change:value', this.valueChanged); + this.listenTo( this.model ,'targetUpdated', this.targetUpdated); + }, + + /** + * Returns selected target which should have 'style' property + * @return {Model|null} + */ + getTarget: function(){ + if(this.selectedComponent) + return this.selectedComponent; + return this.propTarget ? this.propTarget.model : null; + }, + + /** + * Fired when the input value is updated + */ + valueUpdated: function(){ + if(this.$input) + this.model.set('value', this.getInputValue()); + }, + + /** + * Fired when the target is updated + * */ + targetUpdated: function(){ + this.selectedComponent = this.propTarget.model; + this.helperComponent = this.propTarget.helper; + if(this.getTarget()){ + if(!this.sameValue()){ + this.renderInputRequest(); + } + } + }, + + /** + * Checks if the value from selected component is the + * same of the value of the model + * + * @return {Boolean} + * */ + sameValue: function(){ + return this.getComponentValue() == (this.model.get('value') + this.model.get('unit')); + }, + + + /** + * Get the value from the selected component of this property + * + * @return {String} + * */ + getComponentValue: function(){ + var target = this.getTarget(); + + if(!target) + return; + + var targetProp = target.get('style')[this.property]; + if(targetProp) + this.componentValue = targetProp; + else + this.componentValue = this.defaultValue + (this.unit || ''); // todo model + + // Check if wrap inside function is required + if(this.model.get('functionName')){ + var v = this.fetchFromFunction(this.componentValue); + if(v) + this.componentValue = v; + } + + // This allow to ovveride the normal flow of selecting component value, + // useful in composite properties + if(this.customValue && typeof this.customValue === "function"){ + var index = this.model.collection.indexOf(this.model); + var t = this.customValue(this, index); + if(t) + this.componentValue = t; + } + return this.componentValue; + }, + + /** + * Fetch the string from function type value + * @param {String} v Function type value + * + * @return {String} + * */ + fetchFromFunction: function(v){ + return v.substring(v.indexOf("(") + 1, v.lastIndexOf(")")); + }, + + /** + * Returns value from inputs + * @return {string} + */ + getValueForTarget: function(){ + return this.model.getValue(); + }, + + /** + * Returns value from input + * @return {string} + */ + getInputValue: function(){ + return this.$input ? this.$input.val() : ''; + }, + + /** + * Property was changed, so I need to update the component too + * @param {Object} e Events + * @param {Mixed} val Value + * @param {Object} opt Options + * */ + valueChanged: function(e, val, opt) { + var mVal = this.getValueForTarget(); var em = this.config.em; + var model = this.model; + + if(this.$input) + this.setValue(mVal); + + if(!this.getTarget()) + return; + + // Check if component is allowed to be styled + var hideNoStyle = this.config.hideNotStylable; + if (!this.isTargetStylable()) { + if (hideNoStyle) { + this.hide(); + } + return; + } else { + this.show(); + } + + value = this.getValueForTarget(); + + var func = model.get('functionName'); + if(func) + value = func + '(' + value + ')'; + + var target = this.getTarget(); + var onChange = this.onChange; + + if(onChange && typeof onChange === "function"){ + onChange(target, this, opt); + }else + this.updateTargetStyle(value, null, opt); + if(em){ - var model = this.model; em.trigger('component:update', model); em.trigger('component:styleUpdate', model); em.trigger('component:styleUpdate:' + model.get('property'), model); } - }, - - /** - * Update target style - * @param {string} propertyValue - * @param {string} propertyName - * @param {Object} opts - */ - updateTargetStyle: function(propertyValue, propertyName, opts){ - var propName = propertyName || this.property; - var value = propertyValue || ''; - var avSt = opts ? opts.avoidStore : 0; - var target = this.getTarget(); - var targetStyle = _.clone(target.get('style')); - - if(value) - targetStyle[propName] = value; - else - delete targetStyle[propName]; - - target.set('style', targetStyle, { avoidStore : avSt}); - - if(this.helperComponent) - this.helperComponent.set('style', targetStyle, { avoidStore : avSt}); - }, - - /** - * Check if target is stylable with this property - * @return {Boolean} - */ - isTargetStylable: function(){ - var stylable = this.getTarget().get('stylable'); - // Stylable could also be an array indicating with which property - // the target could be styled - if(stylable instanceof Array) - stylable = _.indexOf(stylable, this.property) >= 0; - return stylable; - }, - - /** - * Set value to the input - * @param {String} value - * @param {Boolean} force - * */ - setValue: function(value, force){ - var f = force === 0 ? 0 : 1; - var def = this.model.get('defaults'); - var v = this.model.get('value') || def; - if(value || f){ - v = value; - } - if(this.$input) - this.$input.val(v); - this.model.set({value: v}, {silent: true}); - }, - - /** - * Render label - * */ - renderLabel: function(){ - this.$el.html( this.templateLabel({ - pfx : this.pfx, - ppfx : this.ppfx, - icon : this.model.get('icon'), - info : this.model.get('info'), - label : this.model.get('name'), - })); - }, - - /** - * Render field property - * */ - renderField : function() { - this.renderTemplate(); - this.renderInput(); - delete this.componentValue; - }, - - /** - * Render loaded template - * */ - renderTemplate: function(){ - this.$el.append( this.template({ - pfx : this.pfx, - ppfx : this.ppfx, - icon : this.model.get('icon'), - info : this.model.get('info'), - label : this.model.get('name'), - })); - }, - - /** - * Renders input, to override - * */ - renderInput: function(){ - if(!this.$input){ - this.$input = $('', { - placeholder: this.model.get('defaults'), - type: 'text' - }); - this.$el.find(this.inputHolderId).html(this.$input); - } - this.setValue(this.componentValue, 0); - }, - - /** - * Request to render input of the property - * */ - renderInputRequest: function(){ - this.renderInput(); - }, - - /** - * Clean input - * */ - cleanValue: function(){ - this.setValue(''); - }, - - render : function(){ - this.renderLabel(); - this.renderField(); - this.$el.attr('class', this.className); - return this; - }, - - }); + }, + + /** + * Update target style + * @param {string} propertyValue + * @param {string} propertyName + * @param {Object} opts + */ + updateTargetStyle: function(propertyValue, propertyName, opts){ + var propName = propertyName || this.property; + var value = propertyValue || ''; + var avSt = opts ? opts.avoidStore : 0; + var target = this.getTarget(); + var targetStyle = _.clone(target.get('style')); + + if(value) + targetStyle[propName] = value; + else + delete targetStyle[propName]; + + target.set('style', targetStyle, { avoidStore : avSt}); + + if(this.helperComponent) + this.helperComponent.set('style', targetStyle, { avoidStore : avSt}); + }, + + /** + * Check if target is stylable with this property + * @return {Boolean} + */ + isTargetStylable: function(){ + var stylable = this.getTarget().get('stylable'); + // Stylable could also be an array indicating with which property + // the target could be styled + if(stylable instanceof Array) + stylable = _.indexOf(stylable, this.property) >= 0; + return stylable; + }, + + /** + * Set value to the input + * @param {String} value + * @param {Boolean} force + * */ + setValue: function(value, force){ + var f = force === 0 ? 0 : 1; + var def = this.model.get('defaults'); + var v = this.model.get('value') || def; + if(value || f){ + v = value; + } + if(this.$input) + this.$input.val(v); + this.model.set({value: v}, {silent: true}); + }, + + show: function () { + this.el.style.display = 'block'; + }, + + hide: function () { + this.el.style.display = 'none'; + }, + + renderLabel: function(){ + this.$el.html( this.templateLabel({ + pfx : this.pfx, + ppfx : this.ppfx, + icon : this.model.get('icon'), + info : this.model.get('info'), + label : this.model.get('name'), + })); + }, + + /** + * Render field property + * */ + renderField : function() { + this.renderTemplate(); + this.renderInput(); + delete this.componentValue; + }, + + /** + * Render loaded template + * */ + renderTemplate: function(){ + this.$el.append( this.template({ + pfx : this.pfx, + ppfx : this.ppfx, + icon : this.model.get('icon'), + info : this.model.get('info'), + label : this.model.get('name'), + })); + }, + + /** + * Renders input, to override + * */ + renderInput: function(){ + if(!this.$input){ + this.$input = $('', { + placeholder: this.model.get('defaults'), + type: 'text' + }); + this.$el.find(this.inputHolderId).html(this.$input); + } + this.setValue(this.componentValue, 0); + }, + + /** + * Request to render input of the property + * */ + renderInputRequest: function(){ + this.renderInput(); + }, + + /** + * Clean input + * */ + cleanValue: function(){ + this.setValue(''); + }, + + render : function(){ + this.renderLabel(); + this.renderField(); + this.$el.attr('class', this.className); + return this; + }, + + }); });