From f87e924ce5f0fe9a9f3b59f99311820ba47f9e05 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Fri, 13 Oct 2017 18:33:26 +0200 Subject: [PATCH] Add `setAttributes`, `getAttributes`, `setClass` and `addClass` to Component --- src/dom_components/model/Component.js | 70 +++++++++++++++++- src/dom_components/model/ComponentText.js | 6 +- src/dom_components/view/ComponentTextView.js | 9 ++- src/domain_abstract/model/Styleable.js | 10 ++- src/selector_manager/index.js | 29 ++++++-- test/specs/dom_components/model/Component.js | 74 ++++++++++++++++++-- test/specs/selector_manager/index.js | 31 ++++++++ 7 files changed, 210 insertions(+), 19 deletions(-) diff --git a/src/dom_components/model/Component.js b/src/dom_components/model/Component.js index 0f6a4620c..e4befc1b3 100644 --- a/src/dom_components/model/Component.js +++ b/src/dom_components/model/Component.js @@ -69,7 +69,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({ icon: '', // Component related style - style: {}, + style: '', // Key-value object of the component's attributes attributes: '', @@ -98,7 +98,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({ }, initialize(props = {}, opt = {}) { - const em = opt.sm || {}; + const em = opt.sm || opt.em || {}; // Check void elements if(opt && opt.config && @@ -108,6 +108,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({ this.opt = opt; this.sm = em; + this.em = em; this.config = props; this.set('attributes', this.get('attributes') || {}); this.listenTo(this, 'change:script', this.scriptUpdated); @@ -132,6 +133,71 @@ module.exports = Backbone.Model.extend(Styleable).extend({ this.init(); }, + + /** + * Update attributes of the model + * @param {Object} attrs Key value attributes + * @example + * model.setAttributes({id: 'test', 'data-key': 'value'}); + */ + setAttributes(attrs) { + attrs = { ...attrs }; + + // Handle classes + const classes = attrs.class; + classes && this.setClass(classes); + delete attrs.class; + + // Handle style + const style = attrs.style; + style && this.setStyle(style); + delete attrs.style; + + this.set('attributes', attrs); + }, + + + /** + * Return attributes + * @return {Object} + */ + getAttributes() { + return this.get('attributes'); + }, + + + /** + * Add classes + * @param {Array|string} classes Array or string of classes + * @return {Array} Array of added selectors + * @example + * model.addClass('class1'); + * model.addClass('class1 class2'); + * model.addClass(['class1', 'class2']); + * // -> [SelectorObject, ...] + */ + addClass(classes) { + const added = this.em.get('SelectorManager').addClass(classes); + return this.get('classes').add(added); + }, + + + /** + * Set classes (resets current collection) + * @param {Array|string} classes Array or string of classes + * @return {Array} Array of added selectors + * @example + * model.setClass('class1'); + * model.setClass('class1 class2'); + * model.setClass(['class1', 'class2']); + * // -> [SelectorObject, ...] + */ + setClass(classes) { + this.get('classes').reset(); + return this.addClass(classes); + }, + + initClasses() { const classes = this.normalizeClasses(this.get('classes') || this.config.classes || []); this.set('classes', new Selectors(classes)); diff --git a/src/dom_components/model/ComponentText.js b/src/dom_components/model/ComponentText.js index 07d0373ee..58867f0fd 100644 --- a/src/dom_components/model/ComponentText.js +++ b/src/dom_components/model/ComponentText.js @@ -1,11 +1,11 @@ -var Component = require('./Component'); +const Component = require('./Component'); module.exports = Component.extend({ - defaults: _.extend({}, Component.prototype.defaults, { + defaults: { ...Component.prototype.defaults, type: 'text', droppable: false, editable: true, - }), + }, }); diff --git a/src/dom_components/view/ComponentTextView.js b/src/dom_components/view/ComponentTextView.js index d2152b7bf..cd88a26e0 100644 --- a/src/dom_components/view/ComponentTextView.js +++ b/src/dom_components/view/ComponentTextView.js @@ -23,14 +23,13 @@ module.exports = ComponentView.extend({ * @private * */ enableEditing() { - if (this.rteEnabled) { + const rte = this.rte; + + if (this.rteEnabled || !this.model.get('editable')) { return; } - const editable = this.model.get('editable'); - const rte = this.rte; - - if (rte && editable) { + if (rte) { try { this.activeRte = rte.enable(this, this.activeRte); } catch (err) { diff --git a/src/domain_abstract/model/Styleable.js b/src/domain_abstract/model/Styleable.js index f981630c0..6b411599f 100644 --- a/src/domain_abstract/model/Styleable.js +++ b/src/domain_abstract/model/Styleable.js @@ -1,3 +1,7 @@ +import { isString } from 'underscore'; +import ParserHtml from 'parser/model/ParserHtml'; + +const parseStyle = ParserHtml().parseStyle; export default { /** @@ -20,10 +24,14 @@ export default { /** * Set new style object - * @param {Object} prop + * @param {Object|string} prop * @param {Object} opts */ setStyle(prop = {}, opts = {}) { + if (isString(prop)) { + prop = parseStyle(prop); + } + this.set('style', Object.assign({}, prop), opts); }, diff --git a/src/selector_manager/index.js b/src/selector_manager/index.js index 815527a55..40069df50 100644 --- a/src/selector_manager/index.js +++ b/src/selector_manager/index.js @@ -1,8 +1,4 @@ /** - * * [add](#add) - * * [get](#get) - * * [getAll](#getall) - * * Selectors in GrapesJS are used in CSS Composer inside Rules and in Components as classes. To get better this concept let's take * a look at this code: * @@ -52,6 +48,9 @@ * statesLabel: '- Selecte State -', * } */ + +import { isString } from 'underscore' + module.exports = config => { var c = config || {}, defaults = require('./config/config'), @@ -139,6 +138,28 @@ module.exports = config => { return selector; }, + /** + * Add class selectors + * @param {Array|string} classes Array or string of classes + * @return {Array} Array of added selectors + * @example + * sm.addClass('class1'); + * sm.addClass('class1 class2'); + * sm.addClass(['class1', 'class2']); + * // -> [SelectorObject, ...] + */ + addClass(classes) { + const added = []; + + if (isString(classes)) { + classes = classes.trim().split(' '); + } + + classes.forEach(name => added.push(selectors.add({name}))) + return added; + }, + + /** * Get the selector by its name * @param {String} name Selector name diff --git a/test/specs/dom_components/model/Component.js b/test/specs/dom_components/model/Component.js index 10412401a..8aed7c6cd 100644 --- a/test/specs/dom_components/model/Component.js +++ b/test/specs/dom_components/model/Component.js @@ -6,18 +6,22 @@ const ComponentLink = require('dom_components/model/ComponentLink'); const ComponentMap = require('dom_components/model/ComponentMap'); const ComponentVideo = require('dom_components/model/ComponentVideo'); const Components = require('dom_components/model/Components'); +const Selector = require('selector_manager/model/Selector'); +const Editor = require('editor/model/Editor'); const $ = Backbone.$; module.exports = { run() { - var obj; - var dcomp; - var compOpts; + let obj; + let dcomp; + let compOpts; + let em; describe('Component', () => { beforeEach(() => { - obj = new Component(); + em = new Editor({}); + obj = new Component({}, {em}); dcomp = new DomComponents(); compOpts = { componentTypes: dcomp.componentTypes, @@ -116,6 +120,68 @@ module.exports = { expect(obj).toEqual({tagName: 'span'}); }); + it('setClass single class string', () => { + obj.setClass('class1'); + const result = obj.get('classes').models; + expect(result.length).toEqual(1); + expect(result[0] instanceof Selector).toEqual(true); + expect(result[0].get('name')).toEqual('class1'); + }); + + it('setClass multiple class string', () => { + obj.setClass('class1 class2'); + const result = obj.get('classes').models; + expect(result.length).toEqual(2); + }); + + it('setClass single class array', () => { + obj.setClass(['class1']); + const result = obj.get('classes').models; + expect(result.length).toEqual(1); + }); + + it('setClass multiple class array', () => { + obj.setClass(['class1', 'class2']); + const result = obj.get('classes').models; + expect(result.length).toEqual(2); + }); + + it('addClass multiple array', () => { + obj.addClass(['class1', 'class2']); + const result = obj.get('classes').models; + expect(result.length).toEqual(2); + }); + + it('addClass avoid same name classes', () => { + obj.addClass(['class1', 'class2']); + obj.addClass(['class1', 'class3']); + const result = obj.get('classes').models; + expect(result.length).toEqual(3); + }); + + it('setAttributes', () => { + obj.setAttributes({ + id: 'test', + 'data-test': 'value', + class: 'class1 class2', + style: 'color: white; background: #fff' + }); + expect(obj.getAttributes()).toEqual({ + id: 'test', + 'data-test': 'value', + }); + expect(obj.get('classes').length).toEqual(2); + expect(obj.getStyle()).toEqual({ + color: 'white', + background: '#fff', + }); + }); + + it('setAttributes overwrites correctly', () => { + obj.setAttributes({id: 'test', 'data-test': 'value'}); + obj.setAttributes({'data-test': 'value2'}); + expect(obj.getAttributes()).toEqual({'data-test': 'value2'}); + }); }); describe('Image Component', () => { diff --git a/test/specs/selector_manager/index.js b/test/specs/selector_manager/index.js index f3897620a..5a8255b83 100644 --- a/test/specs/selector_manager/index.js +++ b/test/specs/selector_manager/index.js @@ -1,4 +1,5 @@ var SelectorManager = require('selector_manager'); +var Selector = require('selector_manager/model/Selector'); var Models = require('./model/SelectorModels'); var ClassTagView = require('./view/ClassTagView'); var ClassTagsView = require('./view/ClassTagsView'); @@ -75,6 +76,36 @@ describe('SelectorManager', () => { expect(obj.get('test')).toEqual(undefined); }); + it('addClass single class string', () => { + const result = obj.addClass('class1'); + expect(result.length).toEqual(1); + expect(result[0] instanceof Selector).toEqual(true); + expect(result[0].get('name')).toEqual('class1'); + }); + + it('addClass multiple class string', () => { + const result = obj.addClass('class1 class2'); + expect(result.length).toEqual(2); + expect(obj.getAll().length).toEqual(2); + }); + + it('addClass single class array', () => { + const result = obj.addClass(['class1']); + expect(result.length).toEqual(1); + }); + + it('addClass multiple class array', () => { + const result = obj.addClass(['class1', 'class2']); + expect(result.length).toEqual(2); + }); + + it('addClass Avoid same name classes', () => { + obj.addClass('class1'); + const result = obj.addClass('class1'); + expect(obj.getAll().length).toEqual(1); + expect(result.length).toEqual(1); + }); + }); Models.run();