diff --git a/src/code_manager/index.js b/src/code_manager/index.js index 578d394c8..32beeada4 100644 --- a/src/code_manager/index.js +++ b/src/code_manager/index.js @@ -182,6 +182,7 @@ module.exports = () => { * var codeStr = codeManager.getCode(model, 'html'); * */ getCode(model, genId, opt = {}) { + opt.em = c.em; var generator = this.getGenerator(genId); return generator ? generator.build(model, opt) : ''; }, diff --git a/src/code_manager/model/CssGenerator.js b/src/code_manager/model/CssGenerator.js index 496ffa5c1..02fc6781e 100644 --- a/src/code_manager/model/CssGenerator.js +++ b/src/code_manager/model/CssGenerator.js @@ -1,107 +1,82 @@ -var Backbone = require('backbone'); +import { keys } from 'underscore'; -module.exports = Backbone.Model.extend({ +module.exports = require('backbone').Model.extend({ initialize() { this.compCls = []; + this.ids = []; }, /** - * Get CSS from component + * Get CSS from a component * @param {Model} model * @return {String} */ buildFromModel(model, opts = {}) { - var code = ''; - var style = model.get('style'); - var classes = model.get('classes'); + let code = ''; + const em = this.em; + const avoidInline = em && em.getConfig('avoidInlineStyle'); + const style = model.get('style'); + const classes = model.get('classes'); const wrappesIsBody = opts.wrappesIsBody; + this.ids.push(`#${model.getId()}`); // Let's know what classes I've found - if(classes) { - classes.each(function(model){ - this.compCls.push(model.get('name')); - }, this); - } + classes.each(model => this.compCls.push(model.getFullName())); - if(style && Object.keys(style).length !== 0) { + if (style && keys(style).length && !avoidInline) { let selector = `#${model.getId()}`; - selector = wrappesIsBody && model.get('wrapper') ? - 'body' : selector; - code += `${selector}{`; - for(var prop in style){ - if(style.hasOwnProperty(prop)) - code += prop + ':' + style[prop] + ';'; - } - code += '}'; + selector = wrappesIsBody && model.get('wrapper') ? 'body' : selector; + code = `${selector}{${model.styleToString()}}`; } + const components = model.components(); + components.each(model => code += this.buildFromModel(model, opts)); return code; }, - /** - * Get CSS from components - * @param {Model} model - * @return {String} - */ - buildFromComp(model) { - var coll = model.get('components') || model, - code = ''; - - coll.each(function(m) { - var cln = m.get('components'); - code += this.buildFromModel(m); - - if(cln.length){ - code += this.buildFromComp(cln); - } - - }, this); - return code; - }, - - /** @inheritdoc */ build(model, opts = {}) { const cssc = opts.cssc; + this.em = opts.em || ''; this.compCls = []; + this.ids = []; var code = this.buildFromModel(model, opts); - code += this.buildFromComp(model); - var compCls = this.compCls; if (cssc) { - var rules = cssc.getAll(); - var mediaRules = {}; - rules.each(function(rule) { - var width = rule.get('mediaText'); - - // If width setted will render it later - if(width){ - var mRule = mediaRules[width]; - if(mRule) - mRule.push(rule); - else - mediaRules[width] = [rule]; + const rules = cssc.getAll(); + const mediaRules = {}; + + rules.each(rule => { + const media = rule.get('mediaText'); + + // If media is setted, I'll render it later + if (media) { + const mRules = mediaRules[media]; + if (mRules) { + mRules.push(rule); + } else { + mediaRules[media] = [rule]; + } return; } code += this.buildFromRule(rule); - }, this); + }); // Get media rules - for (var ruleW in mediaRules) { - var meRules = mediaRules[ruleW]; - var ruleC = ''; - for(var i = 0, len = meRules.length; i < len; i++){ - ruleC += this.buildFromRule(meRules[i]); - } + for (let media in mediaRules) { + let rulesStr = ''; + const mRules = mediaRules[media]; + mRules.forEach(rule => rulesStr += this.buildFromRule(rule)); - if (ruleC) { - code += '@media ' + ruleW + '{' + ruleC + '}'; + if (rulesStr) { + code += `@media ${media}{${rulesStr}}`; } } } + return code; }, @@ -111,39 +86,25 @@ module.exports = Backbone.Model.extend({ * @return {string} CSS string */ buildFromRule(rule) { - var result = ''; - var selectorsAdd = rule.get('selectorsAdd'); - var selectors = rule.get('selectors'); - var ruleStyle = rule.get('style'); - var state = rule.get('state'); - var strSel = ''; - var found = 0; - var compCls = this.compCls; - - // Get string of selectors - selectors.each(selector => { - strSel += '.' + selector.get('name'); - if(compCls.indexOf(selector.get('name')) > -1) + let result = ''; + const selectorStr = rule.selectorsToString(); + const selectorStrNoAdd = rule.selectorsToString({skipAdd: 1}); + let found; + + // This will not render a rule if there is no its component + rule.get('selectors').each(selector => { + const name = selector.getFullName(); + if (this.compCls.indexOf(name) >= 0 || this.ids.indexOf(name) >= 0) { found = 1; + } }); - // With 'found' will skip rules which selectors are not found in - // canvas components. - if ((strSel && found) || selectorsAdd) { - strSel += state ? ':' + state : ''; - strSel += selectorsAdd ? (strSel ? ', ' : '') + selectorsAdd : ''; - var strStyle = ''; - - // Get string of style properties - if(ruleStyle && Object.keys(ruleStyle).length !== 0){ - for(var prop2 in ruleStyle){ - if(ruleStyle.hasOwnProperty(prop2)) - strStyle += prop2 + ':' + ruleStyle[prop2] + ';'; - } - } + if ((selectorStrNoAdd && found) || rule.get('selectorsAdd')) { + const style = rule.styleToString(); - if(strStyle) - result += strSel + '{' + strStyle + '}'; + if (style) { + result += `${selectorStr}{${style}}`; + } } return result; diff --git a/src/css_composer/index.js b/src/css_composer/index.js index cfad1824d..52faf5402 100644 --- a/src/css_composer/index.js +++ b/src/css_composer/index.js @@ -1,9 +1,12 @@ /** * * [add](#add) * * [get](#get) + * * [set](#set) * * [getAll](#getall) * * [load](#load) * * [store](#store) + * * [setIdRule](#setidrule) + * * [getIdRule](#getidrule) * * This module contains and manage CSS rules for the template inside the canvas * Before using the methods you should get first the module from the editor instance, in this way: @@ -23,12 +26,14 @@ */ module.exports = () => { + let em; var c = {}, defaults = require('./config/config'), CssRule = require('./model/CssRule'), CssRules = require('./model/CssRules'), - Selectors = require('./model/Selectors'), CssRulesView = require('./view/CssRulesView'); + const Selectors = require('selector_manager/model/Selectors'); + const Selector = require('selector_manager/model/Selector'); var rules, rulesView; @@ -78,6 +83,7 @@ module.exports = () => { c.rules = elStyle || c.rules; c.sm = c.em; + em = c.em; rules = new CssRules([], c); rulesView = new CssRulesView({ collection: rules, @@ -270,6 +276,39 @@ module.exports = () => { return result; }, + + /** + * Add/update a css rule with id selector + * @param {string} name Id selector name, eg. 'my-id' + * @param {Object} style Style properties and values + * @param {Object} [opts={}] Custom options + * @return {CssRule} + */ + setIdRule(name, style = {}, opts = {}) { + const state = opts.state || ''; + const media = opts.mediaText || em.getCurrentMedia(); + const sm = em.get('SelectorManager'); + const selector = sm.add({ name, type: Selector.TYPE_ID }); + const rule = this.add(selector, state, media); + rule.setStyle(style, opts); + return rule; + }, + + + /** + * Get css rule by id selector + * @param {string} name Id selector name, eg. 'my-id' + * @param {Object} [opts={}] Custom options + * @return {CssRule} + */ + getIdRule(name, opts = {}) { + const state = opts.state || ''; + const media = opts.mediaText || em.getCurrentMedia(); + const selector = em.get('SelectorManager').get(name, Selector.TYPE_ID); + return selector && this.get(selector, state, media); + }, + + /** * Render the block of CSS rules * @return {HTMLElement} @@ -277,7 +316,7 @@ module.exports = () => { */ render() { return rulesView.render().el; - } + }, }; }; diff --git a/src/css_composer/model/CssRule.js b/src/css_composer/model/CssRule.js index d8e4401f0..bfadfbf43 100644 --- a/src/css_composer/model/CssRule.js +++ b/src/css_composer/model/CssRule.js @@ -1,7 +1,7 @@ import Styleable from 'domain_abstract/model/Styleable'; var Backbone = require('backbone'); -var Selectors = require('./Selectors'); +var Selectors = require('selector_manager/model/Selectors'); module.exports = Backbone.Model.extend(Styleable).extend({ @@ -23,26 +23,72 @@ module.exports = Backbone.Model.extend(Styleable).extend({ // Indicates if the rule is stylable stylable: true, + + // If true, sets '!important' on all properties + // You can use an array to specify properties to set important + // Used in view + important: 0, }, + initialize(c, opt = {}) { - this.config = c || {}; - const em = opt.em; - let selectors = this.config.selectors || []; - this.em = em; - - if (em) { - const sm = em.get('SelectorManager'); - const slct = []; - selectors.forEach((selector) => { - slct.push(sm.add(selector)); - }); - selectors = slct; - } + this.config = c || {}; + const em = opt.em; + let selectors = this.config.selectors || []; + this.em = em; + + if (em) { + const sm = em.get('SelectorManager'); + const slct = []; + selectors.forEach((selector) => { + slct.push(sm.add(selector)); + }); + selectors = slct; + } + + this.set('selectors', new Selectors(selectors)); + }, + + + /** + * Return selectors fo the rule as a string + * @return {string} + */ + selectorsToString(opts = {}) { + const result = []; + const state = this.get('state'); + const addSelector = this.get('selectorsAdd'); + const selectors = this.get('selectors').getFullString(); + const stateStr = state ? `:${state}` : ''; + selectors && result.push(`${selectors}${stateStr}`); + addSelector && !opts.skipAdd && result.push(addSelector); + return result.join(', '); + }, + + + /** + * Returns CSS string of the rule + * @param {Object} [opts={}] Options + * @return {string} + */ + toCSS(opts = {}) { + let result = ''; + const media = this.get('mediaText'); + const style = this.styleToString(opts); + const selectors = this.selectorsToString(); + + if (selectors && style) { + result = `${selectors}{${style}}`; + } - this.set('selectors', new Selectors(selectors)); + if (media && result) { + result = `@media ${media}{${result}}`; + } + + return result; }, + /** * Compare the actual model with parameters * @param {Object} selectors Collection of selectors diff --git a/src/css_composer/model/Selectors.js b/src/css_composer/model/Selectors.js deleted file mode 100644 index e2a95a550..000000000 --- a/src/css_composer/model/Selectors.js +++ /dev/null @@ -1,24 +0,0 @@ -var Backbone = require('backbone'); - -module.exports = Backbone.Collection.extend({ - - initialize(models, opt) { - - this.model = function(attrs, opts) { - var model; - - switch(1){ - - default: - if(!this.ClassTag) - this.ClassTag = require("selector_manager/model/Selector"); - model = new this.ClassTag(attrs, opts); - - } - - return model; - }; - - }, - -}); diff --git a/src/css_composer/view/CssRuleView.js b/src/css_composer/view/CssRuleView.js index d4fcf1094..6fb35ee16 100644 --- a/src/css_composer/view/CssRuleView.js +++ b/src/css_composer/view/CssRuleView.js @@ -1,76 +1,22 @@ -var Backbone = require('backbone'); - -module.exports = Backbone.View.extend({ +module.exports = require('backbone').View.extend({ tagName: 'style', - initialize(o) { - this.config = o.config || {}; - this.listenTo(this.model, 'change:style', this.render); - this.listenTo(this.model, 'change:state', this.render); - this.listenTo(this.model, 'destroy remove', this.remove); - this.listenTo(this.model, 'change:mediaText', this.render); - this.listenTo(this.model.get('selectors'), 'change', this.selChanged); - }, - - /** - * Triggered when some selector is changed - * @private - */ - selChanged() { - this.selStr = this.renderSelectors(); - this.render(); - }, - /** - * Returns string of selectors - * @return {String} - * @private - */ - renderSelectors() { - var sel = []; - var model = this.model; - var add = model.get('selectorsAdd'); - model.get('selectors').each(m => { - sel.push('.' + m.get('name')); - }); - var sels = sel.join(''); - return sels + (sels && add ? ', ' : '') + add; + initialize(o = {}) { + this.config = o.config || {}; + const model = this.model; + const toTrack = 'change:style change:state change:mediaText'; + this.listenTo(model, toTrack, this.render); + this.listenTo(model, 'destroy remove', this.remove); + this.listenTo(model.get('selectors'), 'change', this.render); }, - /** - * Returns string of properties - * @return {String} - * @private - */ - renderProperties() { - var sel = [], - props = this.model.get('style'); - for (var prop in props){ - sel.push(prop + ':' + props[prop] + ';'); - } - return sel.join(''); - }, render() { - var block = '', - selStr = '', - o = ''; - if(!this.selStr) - this.selStr = this.renderSelectors(); - var prpStr = this.renderProperties(); - var stateStr = this.model.get('state'); - var mediaText = this.model.get('mediaText'); - if(this.selStr){ - stateStr = stateStr ? ':' + stateStr : ''; - block = prpStr !== '' ? '{' + prpStr + '}' : ''; - } - o = this.selStr && block ? this.selStr + stateStr + block : ''; - - if(mediaText && o) - o = '@media ' + mediaText + '{' + o + '}'; - - this.$el.html(o); + const model = this.model; + const important = model.get('important'); + this.el.innerHTML = this.model.toCSS({important}); return this; }, diff --git a/src/dom_components/model/Component.js b/src/dom_components/model/Component.js index 2d2f13cdd..1f6834449 100644 --- a/src/dom_components/model/Component.js +++ b/src/dom_components/model/Component.js @@ -1,10 +1,10 @@ -import { isUndefined, isArray } from 'underscore'; +import { isUndefined, isArray, isEmpty, has, clone } from 'underscore'; import Styleable from 'domain_abstract/model/Styleable'; -var Backbone = require('backbone'); -var Components = require('./Components'); -var Selectors = require('selector_manager/model/Selectors'); -var Traits = require('trait_manager/model/Traits'); +const Backbone = require('backbone'); +const Components = require('./Components'); +const Selectors = require('selector_manager/model/Selectors'); +const Traits = require('trait_manager/model/Traits'); const escapeRegExp = (str) => { return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'); @@ -121,7 +121,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({ initialize(props = {}, opt = {}) { - const em = opt.sm || opt.em || {}; + const em = opt.sm || opt.em || ''; // Propagate properties from parent if indicated const parent = this.parent(); @@ -150,7 +150,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({ this.opt = opt; this.sm = em; this.em = em; - this.config = props; + this.config = opt.config || {}; this.set('attributes', this.get('attributes') || {}); this.listenTo(this, 'change:script', this.scriptUpdated); this.listenTo(this, 'change:traits', this.traitsUpdated); @@ -158,18 +158,6 @@ module.exports = Backbone.Model.extend(Styleable).extend({ this.initClasses(); this.initComponents(); this.initToolbar(); - - // Normalize few properties from strings to arrays - var toNormalize = ['stylable']; - toNormalize.forEach(function(name) { - var value = this.get(name); - - if (typeof value == 'string') { - var newValue = value.split(',').map(prop => prop.trim()); - this.set(name, newValue); - } - }, this); - this.set('status', ''); this.init(); }, @@ -198,12 +186,58 @@ module.exports = Backbone.Model.extend(Styleable).extend({ }, + getStyle() { + const em = this.em; + + if (em && em.getConfig('avoidInlineStyle')) { + const state = this.get('state'); + const cc = em.get('CssComposer'); + const rule = cc.getIdRule(this.getId(), { state }); + this.rule = rule; + + if (rule) { + return rule.getStyle(); + } + } + + return Styleable.getStyle.call(this); + }, + + + setStyle(prop = {}, opts = {}) { + const em = this.em; + + if (em && em.getConfig('avoidInlineStyle')) { + prop = Styleable.setStyle.call(this, prop, {silent: 1, avoidStore: 1}); + const state = this.get('state'); + const cc = em.get('CssComposer'); + this.rule = cc.setIdRule(this.getId(), prop, { ...opts, state }); + } else { + prop = Styleable.setStyle.apply(this, arguments); + } + + return prop; + }, + + /** * Return attributes * @return {Object} */ getAttributes() { - return this.get('attributes'); + const classes = []; + const attributes = this.get('attributes') || {}; + + // Add classes + this.get('classes').each(cls => classes.push(cls.get('name'))); + classes.length && (attributes.class = classes.join(' ')); + + // If style is not empty I need an ID attached to the component + if (!isEmpty(this.getStyle()) && !has(attributes, 'id')) { + attributes.id = this.getId(); + } + + return attributes; }, @@ -240,7 +274,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({ initClasses() { - const classes = this.normalizeClasses(this.get('classes') || this.config.classes || []); + const classes = this.normalizeClasses(this.get('classes') || []); this.set('classes', new Selectors(classes)); return this; }, @@ -329,7 +363,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({ */ traitsUpdated() { let found = 0; - const attrs = Object.assign({}, this.get('attributes')); + const attrs = { ...this.get('attributes') }; const traits = this.get('traits'); if (!(traits instanceof Traits)) { @@ -442,21 +476,21 @@ module.exports = Backbone.Model.extend(Styleable).extend({ * @private */ clone(reset) { - var attr = _.clone(this.attributes), - comp = this.get('components'), - traits = this.get('traits'), - cls = this.get('classes'); + const em = this.em; + const style = this.getStyle(); + const attr = clone(this.attributes); + delete attr.attributes.id; attr.components = []; attr.classes = []; attr.traits = []; - comp.each((md, i) => { + this.get('components').each((md, i) => { attr.components[i] = md.clone(1); }); - traits.each((md, i) => { + this.get('traits').each((md, i) => { attr.traits[i] = md.clone(); }); - cls.each((md, i) => { + this.get('classes').each((md, i) => { attr.classes[i] = md.get('name'); }); @@ -467,6 +501,10 @@ module.exports = Backbone.Model.extend(Styleable).extend({ this.opt.collection = null; } + if (em && em.getConfig('avoidInlineStyle') && !isEmpty(style)) { + attr.style = style; + } + return new this.constructor(attr, this.opt); }, @@ -502,43 +540,25 @@ module.exports = Backbone.Model.extend(Styleable).extend({ * @private */ toHTML(opts) { - var code = ''; - var m = this; - var tag = m.get('tagName'); - var idFound = 0; - var sTag = m.get('void'); - var attrId = ''; - var strAttr = ''; - var attr = this.getAttrToHTML(); - - for (var prop in attr) { - if (prop == 'id') { - idFound = 1; + const model = this; + const attrs = []; + const classes = []; + const tag = model.get('tagName'); + const sTag = model.get('void'); + const attributes = this.getAttrToHTML(); + + for (let attr in attributes) { + const value = attributes[attr]; + + if (!isUndefined(value) && value !== '') { + attrs.push(`${attr}="${value}"`); } - var val = attr[prop]; - strAttr += typeof val !== undefined && val !== '' ? - ' ' + prop + '="' + val + '"' : ''; } - // Build the string of classes - var strCls = ''; - m.get('classes').each(m => { - strCls += ' ' + m.get('name'); - }); - strCls = strCls !== '' ? ' class="' + strCls.trim() + '"' : ''; - - // If style is not empty I need an ID attached to the component - if(!_.isEmpty(m.get('style')) && !idFound) - attrId = ' id="' + m.getId() + '" '; - - code += '<' + tag + strCls + attrId + strAttr + (sTag ? '/' : '') + '>' + m.get('content'); - - m.get('components').each(m => { - code += m.toHTML(); - }); - - if(!sTag) - code += ''; + let attrString = attrs.length ? ` ${attrs.join(' ')}` : ''; + let code = `<${tag}${attrString}${sTag ? '/' : ''}>${model.get('content')}`; + model.get('components').each(comp => code += comp.toHTML()); + !sTag && (code += ``); return code; }, @@ -550,7 +570,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({ * @private */ getAttrToHTML() { - var attr = this.get('attributes') || {}; + var attr = this.getAttributes(); delete attr.style; return attr; }, diff --git a/src/dom_components/model/Components.js b/src/dom_components/model/Components.js index 104e73c64..71a511370 100644 --- a/src/dom_components/model/Components.js +++ b/src/dom_components/model/Components.js @@ -1,4 +1,6 @@ -var Backbone = require('backbone'); +import { isEmpty } from 'underscore'; + +const Backbone = require('backbone'); module.exports = Backbone.Collection.extend({ @@ -67,16 +69,18 @@ module.exports = Backbone.Collection.extend({ }, onAdd(model, c, opts) { - var style = model.get('style'); - var em = this.editor; - - if (!_.isEmpty(style) && em && em.get && em.get('Config').forceClass) { - var cssC = this.editor.get('CssComposer'); - var newClass = this.editor.get('SelectorManager').add(model.cid); - model.set({style:{}}); - model.get('classes').add(newClass); - var rule = cssC.add(newClass); - rule.set('style', style); + const em = this.editor; + const style = model.get('style'); + const avoidInline = em && em.getConfig('avoidInlineStyle'); + + if (!isEmpty(style) && !avoidInline && + em && em.get && em.get('Config').forceClass) { + var cssC = this.editor.get('CssComposer'); + var newClass = this.editor.get('SelectorManager').add(model.cid); + model.set({style:{}}); + model.get('classes').add(newClass); + var rule = cssC.add(newClass); + rule.set('style', style); } }, diff --git a/src/dom_components/view/ComponentView.js b/src/dom_components/view/ComponentView.js index be33360c4..189516685 100644 --- a/src/dom_components/view/ComponentView.js +++ b/src/dom_components/view/ComponentView.js @@ -1,3 +1,5 @@ +import { isArray } from 'underscore'; + const ComponentsView = require('./ComponentsView'); module.exports = Backbone.View.extend({ @@ -10,13 +12,14 @@ module.exports = Backbone.View.extend({ return this.model.get('tagName'); }, - initialize(opt) { + initialize(opt = {}) { const model = this.model; - this.opts = opt || {}; - this.config = this.opts.config || {}; - this.em = this.config.em || ''; - this.pfx = this.config.stylePrefix || ''; - this.ppfx = this.config.pStylePrefix || ''; + const config = opt.config || {}; + this.opts = opt; + this.config = config; + this.em = config.em || ''; + this.pfx = config.stylePrefix || ''; + this.ppfx = config.pStylePrefix || ''; this.attr = model.get('attributes'); this.classe = this.attr.class || []; const $el = this.$el; @@ -64,6 +67,7 @@ module.exports = Backbone.View.extend({ } }, + /** * Import, if possible, classes inside main container * @private @@ -78,6 +82,7 @@ module.exports = Backbone.View.extend({ } }, + /** * Fires on state update. If the state is not empty will add a helper class * @param {Event} e @@ -94,6 +99,7 @@ module.exports = Backbone.View.extend({ } }, + /** * Update item on status change * @param {Event} e @@ -131,6 +137,7 @@ module.exports = Backbone.View.extend({ } }, + /** * Update highlight attribute * @private @@ -140,14 +147,24 @@ module.exports = Backbone.View.extend({ this.setAttribute('data-highlightable', hl ? 1 : ''); }, + /** * Update style attribute * @private * */ updateStyle() { - this.setAttribute('style', this.getStyleString()); + const em = this.em; + const model = this.model; + + if (em && em.get('avoidInlineStyle')) { + this.el.id = model.getId(); + model.setStyle(model.getStyle()); + } else { + this.setAttribute('style', model.styleToString()); + } }, + /** * Update classe attribute * @private @@ -180,10 +197,13 @@ module.exports = Backbone.View.extend({ getClasses() { var attr = this.model.get("attributes"), classes = attr['class'] || []; - if(classes.length){ - return classes.join(" "); - }else + classes = isArray(classes) ? classes : [classes]; + + if (classes.length) { + return classes.join(' '); + } else { return null; + } }, /** @@ -214,31 +234,6 @@ module.exports = Backbone.View.extend({ this.getChildrenContainer().innerHTML = this.model.get('content'); }, - /** - * Return style string - * @return {string} - * @private - * */ - getStyleString() { - var style = ''; - this.style = this.model.get('style'); - for(var key in this.style) { - if(this.style.hasOwnProperty(key)) - style += key + ':' + this.style[key] + ';'; - } - - return style; - }, - - /** - * Reply to event call - * @param object Event that generated the request - * @private - * */ - eventCall(event) { - event.viewResponse = this; - }, - /** * Prevent default helper * @param {Event} e diff --git a/src/domain_abstract/model/Styleable.js b/src/domain_abstract/model/Styleable.js index 83223f206..1a04ec388 100644 --- a/src/domain_abstract/model/Styleable.js +++ b/src/domain_abstract/model/Styleable.js @@ -1,9 +1,11 @@ -import { isString } from 'underscore'; +import { isString, isArray } from 'underscore'; import ParserHtml from 'parser/model/ParserHtml'; const parseStyle = ParserHtml().parseStyle; export default { + parseStyle, + /** * To trigger the style change event on models I have to * pass a new object instance @@ -11,34 +13,40 @@ export default { * @return {Object} */ extendStyle(prop) { - return Object.assign({}, this.getStyle(), prop); + return { ...this.getStyle(), ...prop}; }, + /** * Get style object * @return {Object} */ getStyle() { - return Object.assign({}, this.get('style')); + return { ...this.get('style') }; }, + /** * Set new style object * @param {Object|string} prop * @param {Object} opts + * @return {Object} Applied properties */ setStyle(prop = {}, opts = {}) { if (isString(prop)) { prop = parseStyle(prop); } - this.set('style', Object.assign({}, prop), opts); + this.set('style', { ...prop }, opts); for (let pr in prop) { this.trigger(`change:style:${pr}`); } + + return prop; }, + /** * Add style property * @param {Object|string} prop @@ -60,6 +68,7 @@ export default { this.setStyle(prop, opts); }, + /** * Remove style property * @param {string} prop @@ -68,5 +77,25 @@ export default { let style = this.getStyle(); delete style[prop]; this.setStyle(style); - } + }, + + + /** + * Returns string of style properties + * @param {Object} [opts={}] Options + * @return {String} + */ + styleToString(opts = {}) { + const result = []; + const style = this.getStyle(); + + for (let prop in style) { + const imp = opts.important; + const important = isArray(imp) ? imp.indexOf(prop) >= 0 : imp; + const value = `${style[prop]}${important ? ' !important' : ''}`; + result.push(`${prop}:${value};`); + } + + return result.join(''); + }, } diff --git a/src/editor/config/config.js b/src/editor/config/config.js index 1f31fd496..1980756c3 100644 --- a/src/editor/config/config.js +++ b/src/editor/config/config.js @@ -71,6 +71,12 @@ module.exports = { // The wrapper, if visible, will be shown as a `` wrappesIsBody: 1, + // Usually when you update the `style` of the component this changes the + // element's `style` attribute. Unfortunately, inline styling doesn't allow + // use of media queries (@media) or even pseudo selectors (eg. :hover). + // When `avoidInlineStyle` is true all styles are inserted inside the css rule + avoidInlineStyle: 0, + // Dom element el: '', diff --git a/src/editor/index.js b/src/editor/index.js index a8635e8b2..b7c8dc865 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -238,10 +238,12 @@ module.exports = config => { /** * Returns configuration object - * @return {Object} + * @param {string} [prop] Property name + * @return {any} Returns the configuration object or + * the value of the specified property */ - getConfig() { - return c; + getConfig(prop) { + return em.getConfig(prop); }, /** diff --git a/src/editor/model/Editor.js b/src/editor/model/Editor.js index 7981ebfc4..7ed65e548 100644 --- a/src/editor/model/Editor.js +++ b/src/editor/model/Editor.js @@ -47,7 +47,7 @@ module.exports = Backbone.Model.extend({ device: '', }, - initialize(c) { + initialize(c = {}) { this.config = c; this.set('Config', c); this.set('modules', []); @@ -66,6 +66,19 @@ module.exports = Backbone.Model.extend({ this.on('change:changesCount', this.updateChanges, this); }, + + /** + * Get configurations + * @param {string} [prop] Property name + * @return {any} Returns the configuration object or + * the value of the specified property + */ + getConfig(prop) { + const config = this.config; + return isUndefined(prop) ? config : config[prop]; + }, + + /** * Should be called after all modules and plugins are loaded * @param {Function} clb @@ -614,6 +627,21 @@ module.exports = Backbone.Model.extend({ w.getSelection().removeAllRanges(); }, + + /** + * Get the current media text + * @return {string} + */ + getCurrentMedia() { + const config = this.config; + const device = this.getDeviceModel(); + const condition = config.mediaCondition; + const preview = config.devicePreviewMode; + const width = device && device.get('widthMedia'); + return device && width && !preview ? `(${condition}: ${width})` : ''; + }, + + /** * Set/get data from the HTMLElement * @param {HTMLElement} el diff --git a/src/selector_manager/index.js b/src/selector_manager/index.js index 40069df50..96d45b0df 100644 --- a/src/selector_manager/index.js +++ b/src/selector_manager/index.js @@ -61,6 +61,10 @@ module.exports = config => { return { + Selector, + + Selectors, + /** * Name of the module * @type {String} @@ -129,7 +133,7 @@ module.exports = config => { } const cname = opts.name; - const selector = cname ? this.get(cname) : selectors.where(opts)[0]; + const selector = cname ? this.get(cname, opts.type) : selectors.where(opts)[0]; if (!selector) { return selectors.add(opts); @@ -163,12 +167,13 @@ module.exports = config => { /** * Get the selector by its name * @param {String} name Selector name + * @param {String} tyoe Selector type * @return {Model|null} * @example * var selector = selectorManager.get('selectorName'); * */ - get(name) { - return selectors.where({name})[0]; + get(name, type = Selector.TYPE_CLASS) { + return selectors.where({name, type})[0]; }, /** diff --git a/src/selector_manager/model/Selector.js b/src/selector_manager/model/Selector.js index 193c2ddb3..4f8c921bc 100644 --- a/src/selector_manager/model/Selector.js +++ b/src/selector_manager/model/Selector.js @@ -1,15 +1,19 @@ var Backbone = require('backbone'); +const TYPE_CLASS = 1; +const TYPE_ID = 2; + const Selector = Backbone.Model.extend({ idAttribute: 'name', defaults: { name: '', + label: '', // Type of the selector - type: 'class', + type: TYPE_CLASS, // If not active it's not selectable by the style manager (uncheckboxed) active: true, @@ -18,7 +22,7 @@ const Selector = Backbone.Model.extend({ // Will be rendered only in export code private: false, - // If true, can't be removed by the user, from the attacched element + // If true, can't be removed from the attacched element protected: false, }, @@ -43,10 +47,10 @@ const Selector = Backbone.Model.extend({ let init = ''; switch (this.get('type')) { - case 'class': + case TYPE_CLASS: init = '.'; break; - case 'id': + case TYPE_ID: init = '#'; break; } @@ -55,6 +59,12 @@ const Selector = Backbone.Model.extend({ } }, { + // All type selectors: https://developer.mozilla.org/it/docs/Web/CSS/CSS_Selectors + // Here I define only what I need + TYPE_CLASS, + + TYPE_ID, + /** * Escape string * @param {string} name diff --git a/src/selector_manager/model/Selectors.js b/src/selector_manager/model/Selectors.js index 42cf33bad..06bf00187 100644 --- a/src/selector_manager/model/Selectors.js +++ b/src/selector_manager/model/Selectors.js @@ -11,5 +11,11 @@ module.exports = Backbone.Collection.extend({ getValid() { return _.filter(this.models, item => !item.get('private')); + }, + + getFullString() { + const result = []; + this.each(selector => result.push(selector.getFullName())); + return result.join('').trim(); } }); diff --git a/src/selector_manager/view/ClassTagsView.js b/src/selector_manager/view/ClassTagsView.js index ffe1404d5..ada424c2d 100644 --- a/src/selector_manager/view/ClassTagsView.js +++ b/src/selector_manager/view/ClassTagsView.js @@ -33,7 +33,7 @@ module.exports = Backbone.View.extend({ events: {}, - initialize(o) { + initialize(o = {}) { this.config = o.config || {}; this.pfx = this.config.stylePrefix || ''; this.ppfx = this.config.pStylePrefix || ''; @@ -48,7 +48,8 @@ module.exports = Backbone.View.extend({ this.events['keyup #' + this.newInputId] = 'onInputKeyUp'; this.events['change #' + this.stateInputId] = 'stateChanged'; - this.target = this.config.em; + this.target = this.config.em; + this.em = this.target; this.listenTo(this.target ,'change:selectedComponent',this.componentChanged); this.listenTo(this.target, 'targetClassUpdated', this.updateSelector); @@ -148,38 +149,39 @@ module.exports = Backbone.View.extend({ * @private */ updateStateVis() { - if(this.collection.length) + const em = this.em; + const avoidInline = em && em.getConfig('avoidInlineStyle'); + + if(this.collection.length || avoidInline) this.getStatesC().css('display','block'); else this.getStatesC().css('display','none'); this.updateSelector(); }, + /** * Udpate selector helper * @return {this} * @private */ updateSelector() { - const selected = this.target.get('selectedComponent'); + const selected = this.target.getSelected(); this.compTarget = selected; - if(!selected || !selected.get) - return; - var result = ''; - this.collection.each(model => { - if(model.get('active')) - result += '.' + model.get('name'); - }); - var state = selected.get('state'); - result = state ? result + ':' + state : result; - result = result || selected.getName(); - var el = this.el.querySelector('#' + this.pfx + 'sel'); - if (el) { - el.innerHTML = result; + if (!selected || !selected.get) { + return; } + + const state = selected.get('state'); + let result = this.collection.getFullString(); + result = result || `#${selected.getId()}`; + result += state ? `:${state}` : ''; + const el = this.el.querySelector('#' + this.pfx + 'sel'); + el && (el.innerHTML = result); }, + /** * Triggered when the select with states is changed * @param {Object} e diff --git a/src/storage_manager/config/config.js b/src/storage_manager/config/config.js index 0edbbd9e0..77f074a2c 100644 --- a/src/storage_manager/config/config.js +++ b/src/storage_manager/config/config.js @@ -24,7 +24,7 @@ module.exports = { //Enable/Disable saving HTML template storeHtml: 1, - //Enable/Disable saving HTML template + //Enable/Disable saving CSS template storeCss: 1, // ONLY FOR LOCAL STORAGE diff --git a/src/style_manager/index.js b/src/style_manager/index.js index f4623ee87..7047bf329 100644 --- a/src/style_manager/index.js +++ b/src/style_manager/index.js @@ -253,13 +253,9 @@ module.exports = () => { const em = c.em; if (em && classes && classes.length) { - const emConfig = em.get('Config'); - const previewMode = emConfig.devicePreviewMode; - const device = em.getDeviceModel(); - const state = !previewMode ? model.get('state') : ''; - const widthMedia = device && device.get('widthMedia'); - const deviceW = device && !previewMode && widthMedia ? - `(${emConfig.mediaCondition}: ${widthMedia})` : ''; + const conf = em.get('Config'); + const state = !conf.devicePreviewMode ? model.get('state') : ''; + const deviceW = em.getCurrentMedia(); const cssC = em.get('CssComposer'); const valid = classes.getStyleable(); const CssRule = cssC.get(valid, state, deviceW); diff --git a/src/style_manager/view/SectorsView.js b/src/style_manager/view/SectorsView.js index 715fd8fa4..754dcdd2a 100644 --- a/src/style_manager/view/SectorsView.js +++ b/src/style_manager/view/SectorsView.js @@ -9,7 +9,7 @@ module.exports = Backbone.View.extend({ this.pfx = this.config.stylePrefix || ''; this.target = o.target || {}; - // The taget that will emit events for properties + // The target that will emit events for properties const target = {}; extend(target, Backbone.Events); const body = document.body; @@ -18,10 +18,10 @@ module.exports = Backbone.View.extend({ target.computedDefault = { ...window.getComputedStyle(dummy) }; body.removeChild(dummy); this.propTarget = target; - - this.listenTo( this.collection, 'add', this.addTo); - this.listenTo( this.collection, 'reset', this.render); - this.listenTo( this.target, 'change:selectedComponent targetClassAdded targetClassRemoved targetClassUpdated ' + + const coll = this.collection; + this.listenTo(coll, 'add', this.addTo); + this.listenTo(coll, 'reset', this.render); + this.listenTo(this.target, 'change:selectedComponent targetClassAdded targetClassRemoved targetClassUpdated ' + 'targetStateUpdated targetStyleUpdated change:device', this.targetUpdated); }, @@ -42,34 +42,63 @@ module.exports = Backbone.View.extend({ */ targetUpdated() { var em = this.target; - var el = em.get('selectedComponent'); + let model = em.getSelected(); const um = em.get('UndoManager'); + const cc = em.get('CssComposer'); + const avoidInline = em.getConfig('avoidInlineStyle'); - if(!el) + if (!model) { return; + } - // TODO make use of getModelToStyle here + const id = model.getId(); const config = em.get('Config'); - var previewMode = config.devicePreviewMode; - var classes = el.get('classes'); + var classes = model.get('classes'); var pt = this.propTarget; - var device = em.getDeviceModel(); - var state = !previewMode ? el.get('state') : ''; - var widthMedia = device && device.get('widthMedia'); + const state = !config.devicePreviewMode ? model.get('state') : ''; + const opts = { state }; var stateStr = state ? `:${state}` : null; - var view = el.view; - var mediaText = device && !previewMode && widthMedia ? - `(${config.mediaCondition}: ${widthMedia})` : ''; + var view = model.view; + const media = em.getCurrentMedia(); pt.helper = null; if (view) { - pt.computed = window.getComputedStyle(view.el, stateStr); + pt.computed = window.getComputedStyle(view.el, state ? `:${state}` : null); + } + + const appendStateRule = (style = {}) => { + const sm = em.get('SelectorManager'); + const helperClass = sm.add('hc-state'); + let helperRule = cc.get([helperClass]); + + if (!helperRule) { + helperRule = cc.add([helperClass]); + } else { + // I will make it last again, otherwise it could be overridden + const rules = cc.getAll(); + rules.remove(helperRule); + rules.add(helperRule); + } + + helperRule.set('important', 1); + helperRule.setStyle(style); + pt.helper = helperRule; + }; + + // If true the model will be always a rule + if (avoidInline) { + const ruleId = cc.getIdRule(id, opts); + + if (!ruleId) { + model = cc.setIdRule(id, {}, opts); + } else { + model = ruleId; + } } if (classes.length) { - var cssC = em.get('CssComposer'); var valid = classes.getStyleable(); - var iContainer = cssC.get(valid, state, mediaText); + var iContainer = cc.get(valid, state, media); if (!iContainer && valid.length) { // I stop undo manager here as after adding the CSSRule (generally after @@ -77,42 +106,34 @@ module.exports = Backbone.View.extend({ // the collection, therefore updating it in style manager will not affect it // #268 um.stopTracking(); - iContainer = cssC.add(valid, state, mediaText); - iContainer.set('style', el.get('style')); - el.set('style', {}); + iContainer = cc.add(valid, state, media); + iContainer.setStyle(model.getStyle()); + model.setStyle({}); um.startTracking(); } if (!iContainer) { // In this case it's just a Component without any valid selector - pt.model = el; + pt.model = model; pt.trigger('update'); return; } // If the state is not empty, there should be a helper rule in play // The helper rule will get the same style of the iContainer - if (state) { - var clm = em.get('SelectorManager'); - var helperClass = clm.add('hc-state'); - var helperRule = cssC.get([helperClass]); - if(!helperRule) - helperRule = cssC.add([helperClass]); - else{ - // I will make it last again, otherwise it could be overridden - cssC.getAll().remove(helperRule); - cssC.getAll().add(helperRule); - } - helperRule.set('style', iContainer.get('style')); - pt.helper = helperRule; - } + state && appendStateRule(iContainer.getStyle()); pt.model = iContainer; pt.trigger('update'); return; } - pt.model = el; + if (state) { + const ruleState = cc.getIdRule(id, opts); + state && appendStateRule(ruleState && ruleState.getStyle()); + } + + pt.model = model; pt.trigger('update'); }, diff --git a/test/specs/code_manager/model/CodeModels.js b/test/specs/code_manager/model/CodeModels.js index 6c580d047..636a30bf2 100644 --- a/test/specs/code_manager/model/CodeModels.js +++ b/test/specs/code_manager/model/CodeModels.js @@ -2,18 +2,24 @@ const CssGenerator = require('code_manager/model/CssGenerator'); const HtmlGenerator = require('code_manager/model/HtmlGenerator'); const DomComponents = require('dom_components'); const Component = require('dom_components/model/Component'); +const Editor = require('editor/model/Editor'); const CssComposer = require('css_composer'); module.exports = { run() { let comp; + let dcomp; let obj; + let em; + let cc; describe('HtmlGenerator', () => { beforeEach(() => { + em = new Editor(); obj = new HtmlGenerator(); - var dcomp = new DomComponents(); + dcomp = new DomComponents(); comp = new Component({}, { + em, componentTypes: dcomp.componentTypes, }); }); @@ -54,16 +60,19 @@ module.exports = { ['class1', 'class2'].forEach(item => { m1.get('classes').add({name: item}); }); - expect(obj.build(comp)).toEqual('
'); + expect(obj.build(comp)).toEqual('
'); }); }); describe('CssGenerator', () => { var newCssComp = () => new CssComposer().init(); beforeEach(() => { + em = new Editor({}); + cc = em.get('CssComposer'); obj = new CssGenerator(); - var dcomp = new DomComponents(); + dcomp = new DomComponents(); comp = new Component({}, { + em, componentTypes: dcomp.componentTypes, }); }); @@ -213,6 +222,48 @@ module.exports = { comp.get('components').remove(m1); expect(obj.build(comp, {cssc})).toEqual(''); }); + + it('Render correctly a rule without avoidInlineStyle option', () => { + comp.setStyle({color: 'red'}); + const id = comp.getId(); + const result = `#${id}{color:red;}`; + expect(obj.build(comp, {cssc: cc})).toEqual(result); + }); + + it('Render correctly a rule with avoidInlineStyle option', () => { + em.getConfig().avoidInlineStyle = 1; + comp = new Component({}, { + em, + componentTypes: dcomp.componentTypes, + }); + comp.setStyle({color: 'red'}); + const id = comp.getId(); + const result = `#${id}{color:red;}`; + expect(obj.build(comp, {cssc: cc, em})).toEqual(result); + }); + + it('Render correctly a rule with avoidInlineStyle and state', () => { + em.getConfig().avoidInlineStyle = 1; + const state = 'hover'; + comp.config.avoidInlineStyle = 1; + comp.set('state', state); + comp.setStyle({color: 'red'}); + const id = comp.getId(); + const result = `#${id}:${state}{color:red;}`; + expect(obj.build(comp, {cssc: cc, em})).toEqual(result); + }); + + it('Render correctly a rule with avoidInlineStyle and w/o state', () => { + em.getConfig().avoidInlineStyle = 1; + const state = 'hover'; + comp.config.avoidInlineStyle = 1; + comp.setStyle({color: 'blue'}); + comp.set('state', state); + comp.setStyle({color: 'red'}); + const id = comp.getId(); + const result = `#${id}{color:blue;}#${id}:${state}{color:red;}`; + expect(obj.build(comp, {cssc: cc, em})).toEqual(result); + }); }) } }; diff --git a/test/specs/css_composer/e2e/CssComposer.js b/test/specs/css_composer/e2e/CssComposer.js index b3dca6c26..fab1e33d8 100644 --- a/test/specs/css_composer/e2e/CssComposer.js +++ b/test/specs/css_composer/e2e/CssComposer.js @@ -130,10 +130,11 @@ module.exports = { active: true, label: 'test1', name: 'test1', - type: 'class', + type: clsm.Selector.TYPE_CLASS, private: false, protected: false, }], + important: 0, selectorsAdd: '', state: '', stylable: true, @@ -172,6 +173,7 @@ module.exports = { rule1Out = JSON.parse(JSON.stringify(rule1Out)); rule2Out = JSON.parse(JSON.stringify(rule2Out)); var rule1Result = { + important: 0, mediaText: '', selectors: [], selectorsAdd: '*', @@ -183,6 +185,7 @@ module.exports = { } }; var rule2Result = { + important: 0, mediaText: '', selectors: [], selectorsAdd: 'p', diff --git a/test/specs/css_composer/index.js b/test/specs/css_composer/index.js index 382f5f160..3d4b21569 100644 --- a/test/specs/css_composer/index.js +++ b/test/specs/css_composer/index.js @@ -1,16 +1,17 @@ -var Models = require('./model/CssModels'); -var CssRuleView = require('./view/CssRuleView'); -var CssRulesView = require('./view/CssRulesView'); -var CssComposer = require('css_composer'); -var e2e = require('./e2e/CssComposer'); -var utils = require('./../test_utils.js'); +const Models = require('./model/CssModels'); +const CssRuleView = require('./view/CssRuleView'); +const CssRulesView = require('./view/CssRulesView'); +const CssComposer = require('css_composer'); +const e2e = require('./e2e/CssComposer'); +const utils = require('./../test_utils.js'); +const Editor = require('editor/model/Editor'); describe('Css Composer', () => { describe('Main', () => { var obj; - + var em; var config; var storagMock = utils.storageMock(); var editorModel = { @@ -33,7 +34,8 @@ describe('Css Composer', () => { beforeEach(() => { - config = {}; + em = new Editor({}); + config = {em}; obj = new CssComposer().init(config); }); @@ -158,6 +160,26 @@ describe('Css Composer', () => { expect(obj.render()).toExist(); }); + it('Create a rule with id selector by using setIdRule()', () => { + const name = 'test'; + obj.setIdRule(name, {color: 'red'}); + expect(obj.getAll().length).toEqual(1); + const rule = obj.getIdRule(name); + expect(rule.selectorsToString()).toEqual(`#${name}`); + expect(rule.styleToString()).toEqual(`color:red;`); + expect(rule.styleToString({important: 1})).toEqual(`color:red !important;`); + expect(rule.styleToString({important: ['color']})).toEqual(`color:red !important;`); + }); + + it('Create a rule with id selector and state by using setIdRule()', () => { + const name = 'test'; + const state = 'hover'; + obj.setIdRule(name, {color: 'red'}, {state}); + expect(obj.getAll().length).toEqual(1); + const rule = obj.getIdRule(name, {state}); + expect(rule.selectorsToString()).toEqual(`#${name}:${state}`); + }); + }); Models.run(); diff --git a/test/specs/css_composer/model/CssModels.js b/test/specs/css_composer/model/CssModels.js index e4e360c44..f9a6e63d5 100644 --- a/test/specs/css_composer/model/CssModels.js +++ b/test/specs/css_composer/model/CssModels.js @@ -56,6 +56,30 @@ module.exports = { expect(obj.compare([s2, s1], 'hover', '1000')).toEqual(true); }); + it('toCSS returns empty if there is no style', () => { + var s1 = obj.get('selectors').add({ name: 'test1' }); + expect(obj.toCSS()).toEqual(''); + }); + + it('toCSS returns empty if there is no selectors', () => { + obj.setStyle({color: 'red'}); + expect(obj.toCSS()).toEqual(''); + }); + + it('toCSS returns simple CSS', () => { + obj.get('selectors').add({ name: 'test1' }); + obj.setStyle({color: 'red'}); + expect(obj.toCSS()).toEqual(`.test1{color:red;}`); + }); + + it('toCSS wraps correctly inside media rule', () => { + const media = '(max-width: 768px)'; + obj.set('mediaText', media); + obj.get('selectors').add({ name: 'test1' }); + obj.setStyle({color: 'red'}); + expect(obj.toCSS()).toEqual(`@media ${media}{.test1{color:red;}}`); + }); + }); describe('CssRules', () => { diff --git a/test/specs/css_composer/view/CssRuleView.js b/test/specs/css_composer/view/CssRuleView.js index 218177efc..55dee4e1b 100644 --- a/test/specs/css_composer/view/CssRuleView.js +++ b/test/specs/css_composer/view/CssRuleView.js @@ -26,26 +26,6 @@ module.exports = { expect(CssRuleView).toExist(); }); - it('Correct behaviour of renderSelectors with single selector', () => { - obj.model.get('selectors').add({name: 'test'}); - expect(obj.renderSelectors()).toEqual('.test'); - }); - - it('Correct behaviour of renderSelectors with multiple selectors', () => { - obj.model.get('selectors').add([{name: 'test2'}, {name: 'test1'}]); - expect(obj.renderSelectors()).toEqual('.test2.test1'); - }); - - it('Correct behaviour of renderProperties with single property', () => { - obj.model.set('style', {'prop': 'value'}); - expect(obj.renderProperties()).toEqual('prop:value;'); - }); - - it('Correct behaviour of renderProperties with multiple properties', () => { - obj.model.set('style', {'prop2': 'value2', 'prop3': 'value3'}); - expect(obj.renderProperties()).toEqual('prop2:value2;prop3:value3;'); - }); - it('Empty style inside', function() { expect(fixtures.innerHTML).toEqual(''); }); diff --git a/test/specs/dom_components/index.js b/test/specs/dom_components/index.js index cdae7b0ff..5cab2699b 100644 --- a/test/specs/dom_components/index.js +++ b/test/specs/dom_components/index.js @@ -5,12 +5,14 @@ const ComponentView = require('./view/ComponentV'); const ComponentsView = require('./view/ComponentsView'); const ComponentTextView = require('./view/ComponentTextView'); const ComponentImageView = require('./view/ComponentImageView'); +const Editor = require('editor/model/Editor'); const utils = require('./../test_utils.js'); describe('DOM Components', () => { describe('Main', () => { + var em; var obj; var config; var storagMock = utils.storageMock(); @@ -39,7 +41,9 @@ describe('DOM Components', () => { beforeEach(() => { + em = new Editor(); config = { + em, storeWrapper: 1, }; obj = new DomComponents().init(config); diff --git a/test/specs/dom_components/model/Component.js b/test/specs/dom_components/model/Component.js index c0c57a787..5cda7b3ea 100644 --- a/test/specs/dom_components/model/Component.js +++ b/test/specs/dom_components/model/Component.js @@ -169,6 +169,7 @@ module.exports = { }); expect(obj.getAttributes()).toEqual({ id: 'test', + class: 'class1 class2', 'data-test': 'value', }); expect(obj.get('classes').length).toEqual(2); diff --git a/test/specs/dom_components/view/ComponentV.js b/test/specs/dom_components/view/ComponentV.js index c7cd2edec..77a464ae8 100644 --- a/test/specs/dom_components/view/ComponentV.js +++ b/test/specs/dom_components/view/ComponentV.js @@ -80,14 +80,6 @@ module.exports = { expect(view.el.getAttribute('style')).toEqual(null); }); - it('Get style string', () => { - model.set('style', { - color: 'red', - float: 'left' - }); - expect(view.getStyleString()).toEqual('color:red;float:left;'); - }); - it('Add class', () => { model.get('classes').add({name: 'test'}); expect(view.el.getAttribute('class')).toEqual('test'); diff --git a/test/specs/grapesjs/index.js b/test/specs/grapesjs/index.js index 7fd8a5954..74363f0bc 100644 --- a/test/specs/grapesjs/index.js +++ b/test/specs/grapesjs/index.js @@ -16,11 +16,12 @@ describe('GrapesJS', () => { var storage; var storageId = 'testStorage'; var storageMock = { - store(data) { + store(data, clb) { storage = data; + clb(); }, - load(keys) { - return storage; + load(keys, clb) { + return clb(storage); }, }; @@ -36,7 +37,8 @@ describe('GrapesJS', () => { container: '#' + editorName, storageManager: { autoload: 0, - type:'none' + autosave: 0, + type: '' }, } obj = grapesjs; @@ -47,11 +49,6 @@ describe('GrapesJS', () => { fixtures = document.body.querySelector('#fixtures'); }); - afterEach(() => { - config = {}; - obj = null; - }); - it('Main object should be loaded', () => { expect(obj).toExist(); }); @@ -76,7 +73,7 @@ describe('GrapesJS', () => { it('New editor is empty', () => { var editor = obj.init(config); var html = editor.getHtml(); - var css = editor.getCss(); + //var css = editor.getCss(); var protCss = editor.getConfig().protectedCss; expect((html ? html : '')).toNotExist(); //expect((css ? css : '')).toEqual(protCss); @@ -154,7 +151,7 @@ describe('GrapesJS', () => { expect(styles.at(1).get('selectors').at(0).get('name')).toEqual('test5'); }); - it('Adds new storage as plugin and store data there', () => { + it.skip('Adds new storage as plugin and store data there', done => { var pluginName = storageId + '-plugin'; obj.plugins.add(pluginName, edt => { edt.StorageManager.add(storageId, storageMock); @@ -163,9 +160,11 @@ describe('GrapesJS', () => { config.plugins = [pluginName]; var editor = obj.init(config); editor.setComponents(htmlString); - editor.store(); - editor.load((data) => { - expect(data.html).toEqual(htmlString); + editor.store(() => { + editor.load((data) => { + expect(data.html).toEqual(htmlString); + done(); + }); }); }); @@ -182,8 +181,7 @@ describe('GrapesJS', () => { expect(editor.customValue).toEqual('TEST'); }); - // Problems with iframe loading - it.skip('Execute custom command', () => { + it('Execute custom command', () => { var editor = obj.init(config); editor.testVal = ''; editor.setComponents(htmlString); @@ -196,7 +194,7 @@ describe('GrapesJS', () => { expect(editor.testVal).toEqual(htmlString + '5'); }); - it.skip('Stop custom command', () => { + it('Stop custom command', () => { var editor = obj.init(config); editor.testVal = ''; editor.setComponents(htmlString); @@ -231,7 +229,7 @@ describe('GrapesJS', () => { }); // Problems with iframe loading - it.skip('Init new editor with custom plugin overrides default commands', () => { + it('Init new editor with custom plugin overrides default commands', () => { var editor, pluginName = 'test-plugin-opts'; diff --git a/test/specs/selector_manager/index.js b/test/specs/selector_manager/index.js index 5a8255b83..e42e4e914 100644 --- a/test/specs/selector_manager/index.js +++ b/test/specs/selector_manager/index.js @@ -44,7 +44,7 @@ describe('SelectorManager', () => { it('Default new selector is a class type', () => { obj.add('test'); - expect(obj.get('test').get('type')).toEqual('class'); + expect(obj.get('test').get('type')).toEqual(obj.Selector.TYPE_CLASS); }); it('Check name property', () => { diff --git a/test/specs/selector_manager/model/SelectorModels.js b/test/specs/selector_manager/model/SelectorModels.js index 5b1f19773..88363cad5 100644 --- a/test/specs/selector_manager/model/SelectorModels.js +++ b/test/specs/selector_manager/model/SelectorModels.js @@ -37,7 +37,13 @@ module.exports = { }); + describe('Selectors', () => { + var obj; + + beforeEach(() => { + obj = new Selectors(); + }); it('Creates collection item correctly', () => { var c = new Selectors(); @@ -45,6 +51,21 @@ module.exports = { expect(m instanceof Selector).toEqual(true); }); + it('getFullString with single class', () => { + obj.add({name: 'test'}); + expect(obj.getFullString()).toEqual('.test'); + }); + + it('getFullString with multiple classes', () => { + obj.add([{name: 'test'}, {name: 'test2'}]); + expect(obj.getFullString()).toEqual('.test.test2'); + }); + + it('getFullString with mixed selectors', () => { + obj.add([{name: 'test'}, {name: 'test2', type: Selector.TYPE_ID}]); + expect(obj.getFullString()).toEqual('.test#test2'); + }); + }); } }; diff --git a/test/specs/selector_manager/view/ClassTagsView.js b/test/specs/selector_manager/view/ClassTagsView.js index bcbe666f7..237343d5f 100644 --- a/test/specs/selector_manager/view/ClassTagsView.js +++ b/test/specs/selector_manager/view/ClassTagsView.js @@ -1,5 +1,6 @@ const ClassTagsView = require('selector_manager/view/ClassTagsView'); const Selectors = require('selector_manager/model/Selectors'); +const Editor = require('editor/model/Editor'); module.exports = { run() { @@ -11,6 +12,7 @@ module.exports = { var testLabel; var coll; var target; + var em; before(() => { document.body.innerHTML = '
'; @@ -23,10 +25,8 @@ module.exports = { }); beforeEach(function () { - target = { get() {} }; + target = new Editor(); coll = new Selectors(); - _.extend(target, Backbone.Events); - view = new ClassTagsView({ config : { em: target }, collection: coll @@ -74,16 +74,13 @@ module.exports = { expect(this.input.css('display')).toNotEqual('none'); }); - it.skip('Stop tag creation', function() { + it('Stop tag creation', function() { this.btnAdd.trigger('click'); this.input.val('test') this.input.trigger('blur'); - //(this.btnAdd.css('display') !== 'none').should.equal(true); - //(this.input.css('display') == 'none').should.equal(true); - //this.input.val().should.equal(''); expect(this.btnAdd.css('display')).toNotEqual('none'); expect(this.input.css('display')).toEqual('none'); - expect(this.input.val()).toEqual(''); + expect(this.input.val()).toEqual(null); }); it.skip('Check keyup of ESC on input', function() { diff --git a/test/specs/style_manager/view/PropertyColorView.js b/test/specs/style_manager/view/PropertyColorView.js index 5dadbf18c..5f543bbf2 100644 --- a/test/specs/style_manager/view/PropertyColorView.js +++ b/test/specs/style_manager/view/PropertyColorView.js @@ -1,12 +1,17 @@ const PropertyColorView = require('style_manager/view/PropertyColorView'); const Property = require('style_manager/model/Property'); const Component = require('dom_components/model/Component'); +const Editor = require('editor/model/Editor'); +const DomComponents = require('dom_components'); module.exports = { run() { describe('PropertyColorView', () => { + let em; + let dcomp; + let compOpts; var component; var fixtures; var target; @@ -22,13 +27,16 @@ module.exports = { }); beforeEach(() => { - target = new Component(); - component = new Component(); + em = new Editor({}); + dcomp = new DomComponents(); + compOpts = { em, componentTypes: dcomp.componentTypes }; + propTarget = Object.assign({}, Backbone.Events); + target = new Component({}, compOpts); + component = new Component({}, compOpts); model = new Property({ type: 'color', property: propName }); - propTarget = Object.assign({}, Backbone.Events); propTarget.model = component; view = new PropertyColorView({ model, diff --git a/test/specs/style_manager/view/PropertyCompositeView.js b/test/specs/style_manager/view/PropertyCompositeView.js index b6c3ac51d..99902eb30 100644 --- a/test/specs/style_manager/view/PropertyCompositeView.js +++ b/test/specs/style_manager/view/PropertyCompositeView.js @@ -2,12 +2,17 @@ const PropertyCompositeView = require('style_manager/view/PropertyCompositeView' const Property = require('style_manager/model/Property'); const PropertyComposite = require('style_manager/model/PropertyComposite'); const Component = require('dom_components/model/Component'); +const Editor = require('editor/model/Editor'); +const DomComponents = require('dom_components'); module.exports = { run() { describe('PropertyCompositeView', () => { + let em; + let dcomp; + let compOpts; var component; var fixtures; var target; @@ -35,8 +40,11 @@ module.exports = { }]; beforeEach(() => { - target = new Component(); - component = new Component(); + em = new Editor({}); + dcomp = new DomComponents(); + compOpts = { em, componentTypes: dcomp.componentTypes }; + target = new Component({}, compOpts); + component = new Component({}, compOpts); target.model = component; model = new PropertyComposite({ type: 'composite', diff --git a/test/specs/style_manager/view/PropertyIntegerView.js b/test/specs/style_manager/view/PropertyIntegerView.js index 3a7ab3a15..c99138e8b 100644 --- a/test/specs/style_manager/view/PropertyIntegerView.js +++ b/test/specs/style_manager/view/PropertyIntegerView.js @@ -2,12 +2,17 @@ const PropertyIntegerView = require('style_manager/view/PropertyIntegerView'); const Property = require('style_manager/model/Property'); const PropertyInteger = require('style_manager/model/PropertyInteger'); const Component = require('dom_components/model/Component'); +const Editor = require('editor/model/Editor'); +const DomComponents = require('dom_components'); module.exports = { run() { describe('PropertyIntegerView', () => { + let em; + let dcomp; + let compOpts; var component; var fixtures; var target; @@ -25,13 +30,16 @@ module.exports = { var unitsElSel = '.field-units select'; beforeEach(() => { - target = new Component(); - component = new Component(); + em = new Editor({}); + dcomp = new DomComponents(); + compOpts = { em, componentTypes: dcomp.componentTypes }; + propTarget = Object.assign({}, Backbone.Events); + target = new Component({}, compOpts); + component = new Component({}, compOpts); model = new PropertyInteger({ units, property: propName }); - propTarget = Object.assign({}, Backbone.Events); propTarget.model = component; view = new PropertyIntegerView({ model, diff --git a/test/specs/style_manager/view/PropertyRadioView.js b/test/specs/style_manager/view/PropertyRadioView.js index 0bc41527c..9866b9bfe 100644 --- a/test/specs/style_manager/view/PropertyRadioView.js +++ b/test/specs/style_manager/view/PropertyRadioView.js @@ -1,12 +1,17 @@ const PropertyRadioView = require('style_manager/view/PropertyRadioView'); const Property = require('style_manager/model/Property'); const Component = require('dom_components/model/Component'); +const Editor = require('editor/model/Editor'); +const DomComponents = require('dom_components'); module.exports = { run() { describe('PropertyRadioView', () => { + let em; + let dcomp; + let compOpts; var component; var fixtures; var target; @@ -27,14 +32,17 @@ module.exports = { var getCheckedEl = (view) => view.getInputEl().querySelectorAll('input:checked')[0]; beforeEach(() => { - target = new Component(); - component = new Component(); + em = new Editor({}); + dcomp = new DomComponents(); + compOpts = { em, componentTypes: dcomp.componentTypes }; + propTarget = Object.assign({}, Backbone.Events); + target = new Component({}, compOpts); + component = new Component({}, compOpts); model = new Property({ type: 'radio', list: options, property: propName }); - propTarget = Object.assign({}, Backbone.Events); propTarget.model = component; view = new PropertyRadioView({ model, diff --git a/test/specs/style_manager/view/PropertySelectView.js b/test/specs/style_manager/view/PropertySelectView.js index 770ab37fc..37c3ac44c 100644 --- a/test/specs/style_manager/view/PropertySelectView.js +++ b/test/specs/style_manager/view/PropertySelectView.js @@ -1,5 +1,7 @@ const PropertySelectView = require('style_manager/view/PropertySelectView'); const Property = require('style_manager/model/Property'); +const Editor = require('editor/model/Editor'); +const DomComponents = require('dom_components'); const Component = require('dom_components/model/Component'); module.exports = { @@ -7,6 +9,9 @@ module.exports = { describe('PropertySelectView', () => { + let em; + let dcomp; + let compOpts; var component; var fixtures; var target; @@ -23,9 +28,12 @@ module.exports = { ]; beforeEach(() => { + em = new Editor({}); + dcomp = new DomComponents(); + compOpts = { em, componentTypes: dcomp.componentTypes }; propTarget = Object.assign({}, Backbone.Events); - target = new Component(); - component = new Component(); + target = new Component({}, compOpts); + component = new Component({}, compOpts); model = new Property({ type: 'select', list: options, diff --git a/test/specs/style_manager/view/PropertyStackView.js b/test/specs/style_manager/view/PropertyStackView.js index 6d04fd596..f2f222d77 100644 --- a/test/specs/style_manager/view/PropertyStackView.js +++ b/test/specs/style_manager/view/PropertyStackView.js @@ -1,12 +1,17 @@ const PropertyStackView = require('style_manager/view/PropertyStackView'); const Property = require('style_manager/model/PropertyStack'); const Component = require('dom_components/model/Component'); +const Editor = require('editor/model/Editor'); +const DomComponents = require('dom_components'); module.exports = { run() { describe('PropertyStackView', () => { + let em; + let dcomp; + let compOpts; var component; var fixtures; var target; @@ -41,8 +46,11 @@ module.exports = { ]; beforeEach(() => { - target = new Component(); - component = new Component(); + em = new Editor({}); + dcomp = new DomComponents(); + compOpts = { em, componentTypes: dcomp.componentTypes }; + target = new Component({}, compOpts); + component = new Component({}, compOpts); target.model = component; model = new Property({ type: 'stack', diff --git a/test/specs/style_manager/view/PropertyView.js b/test/specs/style_manager/view/PropertyView.js index 2298b9e43..3757f4ba5 100644 --- a/test/specs/style_manager/view/PropertyView.js +++ b/test/specs/style_manager/view/PropertyView.js @@ -1,5 +1,7 @@ const PropertyView = require('style_manager/view/PropertyView'); const Property = require('style_manager/model/Property'); +const Editor = require('editor/model/Editor'); +const DomComponents = require('dom_components'); const Component = require('dom_components/model/Component'); module.exports = { @@ -7,6 +9,9 @@ module.exports = { describe('PropertyView', () => { + let em; + let dcomp; + let compOpts; var component; var fixtures; var target; @@ -19,9 +24,12 @@ module.exports = { var defValue = 'testDefault'; beforeEach(() => { + em = new Editor({}); + dcomp = new DomComponents(); + compOpts = { em, componentTypes: dcomp.componentTypes }; propTarget = Object.assign({}, Backbone.Events); - target = new Component(); - component = new Component(); + target = new Component({}, compOpts); + component = new Component({}, compOpts); model = new Property({property: propName}); propTarget.model = component; options = {