diff --git a/src/block_manager/config/config.js b/src/block_manager/config/config.js new file mode 100644 index 000000000..f0085db79 --- /dev/null +++ b/src/block_manager/config/config.js @@ -0,0 +1,7 @@ +define(function () { + return { + + 'blocks': [], + + }; +}); \ No newline at end of file diff --git a/src/block_manager/main.js b/src/block_manager/main.js new file mode 100644 index 000000000..c76129323 --- /dev/null +++ b/src/block_manager/main.js @@ -0,0 +1,105 @@ +/** + * * [add](#add) + * * [get](#get) + * * [getAll](#getall) + * * [render](#render) + * + * Block manager helps managing various, draggable, pieces of content that could be reused inside templates. + * + * Before using methods you should get first the module from the editor instance, in this way: + * + * ```js + * var blockManager = editor.BlockManager; + * ``` + * + * @module BlockManager + * @param {Object} config Configurations + * @param {Array} [config.blocks=[]] Default blocks + * @example + * ... + * blockManager: { + * blocks: [ + * {id:'h1-block' label: 'Heading', content:'

...

'}, + * ... + * ], + * } + * ... + */ +define(function(require) { + + return function(config) { + var c = config || {}, + defaults = require('./config/config'), + Blocks = require('./model/Blocks'), + BlocksView = require('./view/BlocksView'); + + for (var name in defaults) { + if (!(name in c)) + c[name] = defaults[name]; + } + + var blocks = new Blocks(c.blocks); + var view = new BlocksView({ collection: blocks }, c); + + return { + + /** + * Add new block to the collection. + * @param {string} id Block id + * @param {Object} opts Options + * @param {string} opts.label Name of the block + * @param {string} opts.content HTML content + * @param {Object} [opts.attributes={}] Block attributes + * @return {Block} Added block + * @example + * blockManager.add('h1-block', { + * label: 'Heading', + * content: '

Put your title here

', + * attributes: { + * title: 'Insert h1 block' + * } + * }); + */ + add: function(id, opts){ + var obj = opts || {}; + obj.id = id; + return blocks.add(obj); + }, + + /** + * Return block by id + * @param {string} id Block id + * @example + * var block = blockManager.get('h1-block'); + * console.log(JSON.stringify(block)); + * // {label: 'Heading', content: '

Put your ...', ...} + */ + get: function(id){ + return blocks.get(id); + }, + + /** + * Return all blocks + * @return {Collection} + * @example + * var blocks = blockManager.getAll(); + * console.log(JSON.stringify(blocks)); + * // [{label: 'Heading', content: '

Put your ...'}, ...] + */ + getAll: function(){ + return blocks; + }, + + /** + * Render blocks + * @return {HTMLElement} + */ + render: function(){ + return view.render().el; + }, + + }; + + }; + +}); \ No newline at end of file diff --git a/src/block_manager/model/Block.js b/src/block_manager/model/Block.js new file mode 100644 index 000000000..8eebc16ec --- /dev/null +++ b/src/block_manager/model/Block.js @@ -0,0 +1,13 @@ +define(['backbone'], + function(Backbone){ + + return Backbone.Model.extend({ + + defaults :{ + label: '', + content: '', + attributes: {}, + }, + + }); + }); diff --git a/src/block_manager/model/Blocks.js b/src/block_manager/model/Blocks.js new file mode 100644 index 000000000..f2715857b --- /dev/null +++ b/src/block_manager/model/Blocks.js @@ -0,0 +1,9 @@ +define(['backbone','./Block'], + function (Backbone, Block) { + + return Backbone.Collection.extend({ + + model: Block, + + }); +}); diff --git a/src/block_manager/view/BlockView.js b/src/block_manager/view/BlockView.js new file mode 100644 index 000000000..9b945ebc8 --- /dev/null +++ b/src/block_manager/view/BlockView.js @@ -0,0 +1,17 @@ +define(['backbone'], +function(Backbone) { + + return Backbone.View.extend({ + + initialize: function(o) { + this.config = o.config || {}; + this.listenTo(this.model, 'destroy', this.remove); + }, + + render: function() { + this.el.innerHTML = this.model.get('label'); + return this; + }, + + }); +}); \ No newline at end of file diff --git a/src/block_manager/view/BlocksView.js b/src/block_manager/view/BlocksView.js new file mode 100644 index 000000000..7462b3704 --- /dev/null +++ b/src/block_manager/view/BlocksView.js @@ -0,0 +1,56 @@ +define(['backbone', './BlockView'], +function(Backbone, BlockView) { + + return Backbone.View.extend({ + + initialize: function(opts, config) { + this.config = config || {}; + this.ppfx = this.config.pStylePrefix || ''; + this.listenTo(this.collection, 'add', this.addTo); + }, + + /** + * Add new model to the collection + * @param {Model} model + * @private + * */ + addTo: function(model){ + this.add(model); + }, + + /** + * Render new model inside the view + * @param {Model} model + * @param {Object} fragment Fragment collection + * */ + add: function(model, fragment){ + var frag = fragment || null; + var view = new BlockView({ + model: model, + attributes: model.get('attributes'), + }, this.config); + var rendered = view.render().el; + + if(frag) + frag.appendChild(rendered); + else + this.$el.append(rendered); + }, + + + + render: function() { + var frag = document.createDocumentFragment(); + this.$el.empty(); + + this.collection.each(function(model){ + this.add(model, frag); + }, this); + + this.$el.append(frag); + this.$el.addClass(this.ppfx + 'blocks-c'); + return this; + }, + + }); +}); \ No newline at end of file diff --git a/src/commands/main.js b/src/commands/main.js index 7c58cf9a2..c398fe796 100644 --- a/src/commands/main.js +++ b/src/commands/main.js @@ -80,6 +80,7 @@ define(function(require) { defaultCommands['sw-visibility'] = require('./view/SwitchVisibility'); defaultCommands['open-layers'] = require('./view/OpenLayers'); defaultCommands['open-sm'] = require('./view/OpenStyleManager'); + defaultCommands['open-blocks'] = require('./view/OpenBlocks'); //this.defaultCommands['resize-comp'] = require('./view/ResizeComponent'); if(config.em) diff --git a/src/commands/view/OpenBlocks.js b/src/commands/view/OpenBlocks.js new file mode 100644 index 000000000..cbf22c766 --- /dev/null +++ b/src/commands/view/OpenBlocks.js @@ -0,0 +1,27 @@ +define(function() { + + return { + + run: function(editor, sender) { + var config = editor.Config; + var pfx = config.stylePrefix; + var bm = editor.BlockManager; + if(!this.blocks){ + this.blocks = bm.render(); + var panels = editor.Panels; + if(!panels.getPanel('views-container')) + panelC = panels.addPanel({id: 'views-container'}); + else + panelC = panels.getPanel('views-container'); + panelC.set('appendContent', this.blocks).trigger('change:appendContent'); + } + + this.blocks.style.display = 'block'; + }, + + stop: function() { + if(this.blocks) + this.blocks.style.display = 'none'; + } + }; + }); \ No newline at end of file diff --git a/src/config/require-config.js b/src/config/require-config.js index 53f894737..c663cf3d3 100644 --- a/src/config/require-config.js +++ b/src/config/require-config.js @@ -36,6 +36,7 @@ require.config({ { name: 'GrapesJS', location: 'grapesjs', }, { name: 'Editor', location: 'editor', }, { name: 'AssetManager', location: 'asset_manager', }, + { name: 'BlockManager', location: 'block_manager', }, { name: 'StyleManager', location: 'style_manager', }, { name: 'ClassManager', location: 'class_manager', }, { name: 'DeviceManager', location: 'device_manager', }, diff --git a/src/demo.js b/src/demo.js index fa3b8aa63..4670173a4 100644 --- a/src/demo.js +++ b/src/demo.js @@ -65,6 +65,7 @@ panels: { id : 'selcomp', className : 'fa fa fa-mouse-pointer', command : 'select-comp', + active: 1, },{ id : 'create', className : 'fa fa-plus-square-o icon-add-comp', @@ -410,6 +411,8 @@ panels: { ); + window.grapesEditor = editor; + }); }); diff --git a/src/device_manager/main.js b/src/device_manager/main.js index fe9cfb90a..c3b117b00 100644 --- a/src/device_manager/main.js +++ b/src/device_manager/main.js @@ -50,7 +50,6 @@ define(function(require) { * @param {Object} opts Custom options * @return {Device} Added device * @example - * // In case of strings, would be interpreted as images * deviceManager.add('Tablet', '900px'); */ add: function(name, width, opts){ diff --git a/src/editor/config/config.js b/src/editor/config/config.js index 310d4fc9d..f0f7864b2 100644 --- a/src/editor/config/config.js +++ b/src/editor/config/config.js @@ -38,7 +38,7 @@ define(function () { storageType: 'local', // The css that could only be seen (for instance, inside the code viewer) - protectedCss: 'body{margin:0;}', + protectedCss: 'body{margin:0;height:100%}#wrapper{height:100%}', //Configurations for Asset Manager assetManager : {}, @@ -96,6 +96,23 @@ define(function () { }], }, + //Configurations for Block Manager + blockManager: { + 'blocks': [{ + id: 'b1', + label: 'Block1', + content: '

Block 1

', + },{ + id: 'b2', + label: 'Block2', + content: '

Block 2

', + },{ + id: 'b3', + label: 'Block3', + content: '

Block 3

', + }], + }, + // If true render a select of available devices showDevices: 1, diff --git a/src/editor/main.js b/src/editor/main.js index f2ec10e73..46202c0f3 100644 --- a/src/editor/main.js +++ b/src/editor/main.js @@ -95,6 +95,11 @@ define(function (require){ */ AssetManager: em.get('AssetManager'), + /** + * @property {BlockManager} + */ + BlockManager: em.get('BlockManager'), + /** * @property {ClassManager} */ diff --git a/src/editor/model/Editor.js b/src/editor/model/Editor.js index 14425b2a8..ce1b8bc09 100644 --- a/src/editor/model/Editor.js +++ b/src/editor/model/Editor.js @@ -3,6 +3,7 @@ define([ 'backboneUndo', 'keymaster', 'AssetManager', + 'BlockManager', 'StorageManager', 'DeviceManager', 'ModalDialog', @@ -22,6 +23,7 @@ define([ UndoManager, Keymaster, AssetManager, + BlockManager, StorageManager, DeviceManager, ModalDialog, @@ -63,6 +65,7 @@ define([ this.initClassManager(); this.initModal(); this.initAssetManager(); + this.initBlockManager(); this.initCodeManager(); this.initCommands(); this.initPanels(); @@ -87,6 +90,17 @@ define([ this.set('Editor', editor); }, + /** + * Initialize block manager + * @private + * */ + initBlockManager: function(){ + var cfg = this.config.blockManager; + cfg.em = this; + cfg.pStylePrefix = this.config.stylePrefix; + this.set('BlockManager', new BlockManager(cfg)); + }, + /** * Initialize device manager * @private diff --git a/src/panels/main.js b/src/panels/main.js index 10547bd62..d4777a874 100644 --- a/src/panels/main.js +++ b/src/panels/main.js @@ -52,6 +52,7 @@ define(function(require) { defaults = require('./config/config'), Panel = require('./model/Panel'), Panels = require('./model/Panels'), + PanelView = require('./view/PanelView'), PanelsView = require('./view/PanelsView'); // Set default options diff --git a/styles/css/main.css b/styles/css/main.css index 9b758e30c..8b94714a8 100644 --- a/styles/css/main.css +++ b/styles/css/main.css @@ -2846,7 +2846,7 @@ ol.example li.placeholder:before { z-index: 4; } .wte-pn-panel#wte-pn-views-container { height: 100%; - padding: 52px 0 0; + padding: 42px 0 0; right: 0; width: 15%; overflow: auto; diff --git a/styles/scss/main.scss b/styles/scss/main.scss index 597216d64..88aef5b82 100644 --- a/styles/scss/main.scss +++ b/styles/scss/main.scss @@ -353,7 +353,7 @@ $leftWidth: 15%; &##{$pn-prefix}views-container{ height: 100%; - padding: 52px 0 0; + padding: 42px 0 0; right: 0; width: $leftWidth; overflow: auto; diff --git a/test/runner/main.js b/test/runner/main.js index 8e9f0882d..9ced103b1 100644 --- a/test/runner/main.js +++ b/test/runner/main.js @@ -21,6 +21,7 @@ require(['../src/config/require-config.js', 'config/config.js'], function() { 'specs/style_manager/main.js', 'specs/storage_manager/main.js', 'specs/plugin_manager/main.js', + 'specs/block_manager/main.js', 'specs/parser/main.js', 'specs/grapesjs/main.js', 'specs/utils/main.js' diff --git a/test/specs/block_manager/main.js b/test/specs/block_manager/main.js new file mode 100644 index 000000000..20d207e08 --- /dev/null +++ b/test/specs/block_manager/main.js @@ -0,0 +1,76 @@ +var modulePath = './../../../test/specs/block_manager'; + +define([ 'BlockManager', + modulePath + '/view/BlocksView', + ], + function(BlockManager, BlocksView) { + + describe('BlockManager', function() { + + describe('Main', function() { + + var obj; + var idTest; + var optsTest; + + beforeEach(function () { + idTest = 'h1-block'; + optsTest = { + label: 'Heading', + content: '

Test

' + }; + obj = new BlockManager(); + }); + + afterEach(function () { + delete obj; + }); + + it('Object exists', function() { + obj.should.be.exist; + }); + + it('No blocks inside', function() { + var coll = obj.getAll(); + coll.length.should.equal(0); + }); + + it('Add new block', function() { + var model = obj.add(idTest, optsTest); + obj.getAll().length.should.equal(1); + }); + + it('Added block has correct data', function() { + var model = obj.add(idTest, optsTest); + model.get('label').should.equal(optsTest.label); + model.get('content').should.equal(optsTest.content); + }); + + it('Add block with attributes', function() { + optsTest.attributes = {'class':'test'}; + var model = obj.add(idTest, optsTest); + model.get('attributes').class.should.equal('test'); + }); + + it('The id of the block is unique', function() { + var model = obj.add(idTest, optsTest); + var model2 = obj.add(idTest, {other: 'test'}); + model.should.deep.equal(model2); + }); + + it('Get block by id', function() { + var model = obj.add(idTest, optsTest); + var model2 = obj.get(idTest); + model.should.deep.equal(model2); + }); + + it('Render blocks', function() { + obj.render().should.be.ok; + }); + + }); + + BlocksView.run(); + + }); +}); \ No newline at end of file diff --git a/test/specs/block_manager/view/BlocksView.js b/test/specs/block_manager/view/BlocksView.js new file mode 100644 index 000000000..0aecb005f --- /dev/null +++ b/test/specs/block_manager/view/BlocksView.js @@ -0,0 +1,90 @@ +var path = 'BlockManager/view/'; +define([path + 'BlocksView', 'BlockManager/model/Blocks'], + function(BlocksView, Blocks) { + + return { + run : function(){ + describe('BlocksView', function() { + + var $fixtures; + var $fixture; + var model; + var view; + var editorModel; + var ppfx; + + before(function () { + $fixtures = $("#fixtures"); + $fixture = $('
'); + }); + + beforeEach(function () { + model = new Blocks([]); + view = new BlocksView({ collection: model }); + $fixture.empty().appendTo($fixtures); + $fixture.html(view.render().el); + }); + + afterEach(function () { + view.collection.reset(); + }); + + after(function () { + $fixture.remove(); + }); + + it("The container is not empty", function (){ + view.el.outerHTML.should.be.not.empty; + }); + + it("No children inside", function (){ + view.el.children.length.should.equal(0); + }); + + it("Render children on add", function (){ + model.add({}); + view.el.children.length.should.equal(1); + model.add([{},{}]); + view.el.children.length.should.equal(3); + }); + + it("Destroy children on remove", function (){ + model.add([{},{}]); + view.el.children.length.should.equal(2); + model.at(0).destroy(); + view.el.children.length.should.equal(1); + }); + + describe('With configs', function() { + + beforeEach(function () { + ppfx = 'pfx-t-'; + editorModel = new Backbone.Model(); + model = new Blocks([ + {name:'test1'}, + {name:'test2'} + ]); + view = new BlocksView({ + collection: model, + },{ + pStylePrefix: ppfx + }); + $fixture.empty().appendTo($fixtures); + $fixture.html(view.render().el); + }); + + it("Render children", function (){ + view.el.children.length.should.equal(2); + }); + + it("Render children", function (){ + view.el.getAttribute('class').should.equal(ppfx + 'blocks-c'); + }); + + }); + + }); + } + }; + +}); \ No newline at end of file