465 lines
15 KiB
JavaScript
465 lines
15 KiB
JavaScript
/* eslint-disable func-names */
|
|
/**
|
|
* Container content block.
|
|
* This is a special kind of block, as it can contain content blocks, as well
|
|
* as other containers.
|
|
*/
|
|
import Backbone from 'backbone';
|
|
import Marionette from 'backbone.marionette';
|
|
import _ from 'underscore';
|
|
import jQuery from 'jquery';
|
|
import App from 'newsletter_editor/App';
|
|
import BaseBlock from 'newsletter_editor/blocks/base';
|
|
|
|
var Module = {};
|
|
var base = BaseBlock;
|
|
var BlockCollection;
|
|
|
|
BlockCollection = Backbone.Collection.extend({
|
|
model: base.BlockModel,
|
|
initialize: function () {
|
|
this.on('add change remove', function () { App.getChannel().trigger('autoSave'); });
|
|
},
|
|
parse: function (response) {
|
|
return _.map(response, function (block) {
|
|
var Type = App.getBlockTypeModel(block.type);
|
|
// TODO: If type has no registered model, use a backup one
|
|
return new Type(block, { parse: true });
|
|
});
|
|
},
|
|
});
|
|
|
|
Module.ContainerBlockModel = base.BlockModel.extend({
|
|
relations: {
|
|
blocks: BlockCollection,
|
|
},
|
|
defaults: function () {
|
|
return this._getDefaults({
|
|
type: 'container',
|
|
columnLayout: false,
|
|
orientation: 'vertical',
|
|
image: {
|
|
src: null,
|
|
display: 'scale',
|
|
},
|
|
styles: {
|
|
block: {
|
|
backgroundColor: 'transparent',
|
|
},
|
|
},
|
|
blocks: new BlockCollection(),
|
|
}, App.getConfig().get('blockDefaults.container'));
|
|
},
|
|
_updateDefaults: function updateDefaults() {},
|
|
validate: function () {
|
|
// Recursively propagate validation checks to blocks in the tree
|
|
var invalidBlock = this.get('blocks').find(function (block) { return !block.isValid(); });
|
|
if (invalidBlock) {
|
|
return invalidBlock.validationError;
|
|
}
|
|
return undefined;
|
|
},
|
|
parse: function (response) {
|
|
// If container has any blocks - add them to a collection
|
|
if (response.type === 'container' && _.has(response, 'blocks') && response.blocks.constructor === Array) {
|
|
response.blocks = new BlockCollection(response.blocks, {
|
|
parse: true,
|
|
});
|
|
}
|
|
return response;
|
|
},
|
|
getChildren: function () {
|
|
var models = this.get('blocks').map(function (model) {
|
|
return [model, model.getChildren()];
|
|
});
|
|
|
|
return _.flatten(models);
|
|
},
|
|
});
|
|
|
|
Module.ContainerBlocksView = Marionette.CollectionView.extend({
|
|
className: 'mailpoet_container',
|
|
events: {
|
|
click: 'removeFocusFromAnyActiveElement',
|
|
},
|
|
childView: function (model) {
|
|
return App.getBlockTypeView(model.get('type'));
|
|
},
|
|
childViewOptions: function () {
|
|
var newRenderOptions = _.clone(this.renderOptions);
|
|
if (newRenderOptions.depth !== undefined) {
|
|
newRenderOptions.depth += 1;
|
|
}
|
|
return {
|
|
renderOptions: newRenderOptions,
|
|
};
|
|
},
|
|
emptyView: function () { return Module.ContainerBlockEmptyView; },
|
|
emptyViewOptions: function () { return { renderOptions: this.renderOptions }; },
|
|
initialize: function (options) {
|
|
this.renderOptions = options.renderOptions;
|
|
},
|
|
onChildviewResizeStart: function onChildviewResizeStart() {
|
|
this.triggerMethod('resizeStart');
|
|
},
|
|
onChildviewResizeStop: function onChildviewResizeStart(event) {
|
|
this.triggerMethod('resizeStop', event);
|
|
},
|
|
removeFocusFromAnyActiveElement: function removeFocusFromAnyActiveElement(event) {
|
|
var elementClass;
|
|
if (!event || !event.target) {
|
|
return;
|
|
}
|
|
elementClass = event.target.getAttribute('class');
|
|
if (!elementClass || elementClass.indexOf('mailpoet_container_horizontal') === -1) {
|
|
return;
|
|
}
|
|
document.activeElement.blur();
|
|
},
|
|
});
|
|
|
|
Module.ContainerBlockView = base.BlockView.extend({
|
|
regions: _.extend({}, base.BlockView.prototype.regions, {
|
|
blocks: {
|
|
el: '> .mailpoet_container',
|
|
replaceElement: true,
|
|
},
|
|
}),
|
|
className: 'mailpoet_block mailpoet_container_block mailpoet_droppable_block mailpoet_droppable_layout_block',
|
|
getTemplate: function () { return window.templates.containerBlock; },
|
|
events: _.extend({}, base.BlockView.prototype.events, {
|
|
'click .mailpoet_newsletter_layer_selector': 'toggleEditingLayer',
|
|
}),
|
|
ui: {
|
|
tools: '> .mailpoet_tools',
|
|
},
|
|
behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
|
|
ContainerDropZoneBehavior: {},
|
|
DraggableBehavior: {
|
|
cloneOriginal: true,
|
|
hideOriginal: true,
|
|
onDrop: function (options) {
|
|
// After a clone of model has been dropped, cleanup
|
|
// and destroy self
|
|
options.dragBehavior.view.model.destroy();
|
|
},
|
|
onDragSubstituteBy: function (behavior) {
|
|
var WidgetView;
|
|
var node;
|
|
// When block is being dragged, display the widget icon instead.
|
|
// This will create an instance of block's widget view and
|
|
// use it's rendered DOM element instead of the content block
|
|
if (_.isFunction(behavior.view.onDragSubstituteBy)) {
|
|
WidgetView = new (behavior.view.onDragSubstituteBy())();
|
|
WidgetView.render();
|
|
node = WidgetView.$el.get(0).cloneNode(true);
|
|
WidgetView.destroy();
|
|
return node;
|
|
}
|
|
return undefined;
|
|
},
|
|
testAttachToInstance: function (model, view) {
|
|
// Attach Draggable only to layout containers and disable it
|
|
// for root and column containers.
|
|
return view.renderOptions.depth === 1;
|
|
},
|
|
},
|
|
HighlightEditingBehavior: {},
|
|
}),
|
|
onDragSubstituteBy: function () {
|
|
// For two and three column layouts display their respective widgets,
|
|
// otherwise always default to one column layout widget
|
|
if (this.renderOptions.depth === 1) {
|
|
if (this.model.get('blocks').length === 3) return Module.ThreeColumnContainerWidgetView;
|
|
if (this.model.get('blocks').length === 2) return Module.TwoColumnContainerWidgetView;
|
|
}
|
|
return Module.OneColumnContainerWidgetView;
|
|
},
|
|
initialize: function (options) {
|
|
base.BlockView.prototype.initialize.apply(this, arguments);
|
|
|
|
this.renderOptions = _.defaults(options.renderOptions || {}, {});
|
|
},
|
|
onRender: function () {
|
|
var classIrregular = '';
|
|
var columnLayout;
|
|
this.toolsView = new Module.ContainerBlockToolsView({
|
|
model: this.model,
|
|
tools: {
|
|
settings: this.renderOptions.depth === 1,
|
|
delete: this.renderOptions.depth === 1,
|
|
duplicate: true,
|
|
move: this.renderOptions.depth === 1,
|
|
layerSelector: false,
|
|
},
|
|
});
|
|
this.showChildView('toolsRegion', this.toolsView);
|
|
this.showChildView('blocks', new Module.ContainerBlocksView({
|
|
collection: this.model.get('blocks'),
|
|
renderOptions: this.renderOptions,
|
|
}));
|
|
|
|
// TODO: Look for a better way to do this than here
|
|
// Sets child container orientation HTML class here,
|
|
// as child CollectionView won't have access to model
|
|
// and will overwrite existing region element instead
|
|
columnLayout = this.model.get('columnLayout');
|
|
if (typeof columnLayout === 'string') {
|
|
classIrregular = 'mailpoet_irregular_width_contents_container column_layout_' + columnLayout;
|
|
}
|
|
this.$('> .mailpoet_container').attr('class',
|
|
'mailpoet_container mailpoet_container_' + this.model.get('orientation') + ' ' + classIrregular);
|
|
},
|
|
addHighlight: function addHighlight() {
|
|
if (this.renderOptions.depth !== 1 || this.$el.hasClass('mailpoet_container_layer_active')) {
|
|
return;
|
|
}
|
|
this.$(this.ui.tools).addClass('mailpoet_display_tools');
|
|
this.$el.addClass('mailpoet_highlight');
|
|
this.toolsView.triggerMethod('showTools');
|
|
},
|
|
removeHighlight: function removeHighlight() {
|
|
if (this.renderOptions.depth !== 1 || this.$el.hasClass('mailpoet_container_layer_active')) {
|
|
return;
|
|
}
|
|
this.$(this.ui.tools).removeClass('mailpoet_display_tools');
|
|
this.$el.removeClass('mailpoet_highlight');
|
|
this.toolsView.triggerMethod('hideTools');
|
|
},
|
|
toggleEditingLayer: function (event) {
|
|
var that = this;
|
|
var $toggleButton = this.$('> .mailpoet_tools .mailpoet_newsletter_layer_selector');
|
|
var $overlay = jQuery('.mailpoet_layer_overlay');
|
|
var $container = this.$('> .mailpoet_container');
|
|
var disableContainerLayer = function () {
|
|
that.$el.removeClass('mailpoet_container_layer_active');
|
|
$toggleButton.removeClass('mailpoet_container_layer_active');
|
|
$container.removeClass('mailpoet_layer_highlight');
|
|
$overlay.hide();
|
|
$overlay.off('click');
|
|
};
|
|
var enableContainerLayer = function () {
|
|
that.$el.addClass('mailpoet_container_layer_active');
|
|
$toggleButton.addClass('mailpoet_container_layer_active');
|
|
$container.addClass('mailpoet_layer_highlight');
|
|
$overlay.click(disableContainerLayer);
|
|
$overlay.show();
|
|
};
|
|
if ($toggleButton.hasClass('mailpoet_container_layer_active')) {
|
|
disableContainerLayer();
|
|
} else {
|
|
enableContainerLayer();
|
|
}
|
|
event.stopPropagation();
|
|
},
|
|
});
|
|
|
|
Module.ContainerBlockEmptyView = Marionette.View.extend({
|
|
getTemplate: function () { return window.templates.containerEmpty; },
|
|
initialize: function (options) {
|
|
this.renderOptions = _.defaults(options.renderOptions || {}, {});
|
|
},
|
|
templateContext: function () {
|
|
return {
|
|
isRoot: this.renderOptions.depth === 0,
|
|
emptyContainerMessage: this.renderOptions.emptyContainerMessage || '',
|
|
};
|
|
},
|
|
});
|
|
|
|
Module.ContainerBlockToolsView = base.BlockToolsView.extend({
|
|
getSettingsView: function () { return Module.ContainerBlockSettingsView; },
|
|
});
|
|
|
|
Module.ContainerBlockSettingsView = base.BlockSettingsView.extend({
|
|
behaviors: _.extend({}, base.BlockSettingsView.prototype.behaviors, {
|
|
MediaManagerBehavior: {
|
|
onSelect: 'onImageSelect',
|
|
},
|
|
}),
|
|
getTemplate: function () { return window.templates.containerBlockSettings; },
|
|
events: function () {
|
|
return {
|
|
'change .mailpoet_field_container_background_color': _.partial(this.changeColorField, 'styles.block.backgroundColor'),
|
|
'click .mailpoet_done_editing': 'close',
|
|
'change .mailpoet_field_display_type': 'changeDisplayType',
|
|
};
|
|
},
|
|
initialize: function () {
|
|
base.BlockSettingsView.prototype.initialize.apply(this, arguments);
|
|
this.model.trigger('startEditing');
|
|
this._columnsSettingsView = new (Module.ContainerBlockColumnsSettingsView)({
|
|
collection: this.model.get('blocks'),
|
|
});
|
|
},
|
|
changeDisplayType: function (event) {
|
|
this.model.get('image').set('display', event.target.value);
|
|
this.model.trigger('change');
|
|
},
|
|
onImageSelect: function (image) {
|
|
this.model.set('image.src', image.src);
|
|
this.model.trigger('change');
|
|
this.render();
|
|
},
|
|
});
|
|
|
|
Module.ContainerBlockColumnsSettingsView = Marionette.CollectionView.extend({
|
|
childView: function () { return Module.ContainerBlockColumnSettingsView; },
|
|
childViewOptions: function (model, index) {
|
|
return {
|
|
columnIndex: index,
|
|
};
|
|
},
|
|
});
|
|
|
|
Module.ContainerBlockColumnSettingsView = Marionette.View.extend({
|
|
getTemplate: function () { return window.templates.containerBlockColumnSettings; },
|
|
initialize: function (options) {
|
|
this.columnNumber = (options.columnIndex || 0) + 1;
|
|
},
|
|
templateContext: function () {
|
|
return {
|
|
model: this.model.toJSON(),
|
|
columnNumber: this.columnNumber,
|
|
};
|
|
},
|
|
});
|
|
|
|
Module.OneColumnContainerWidgetView = base.WidgetView.extend({
|
|
className: base.WidgetView.prototype.className + ' mailpoet_droppable_layout_block',
|
|
getTemplate: function () { return window.templates.oneColumnLayoutInsertion; },
|
|
behaviors: {
|
|
DraggableBehavior: {
|
|
cloneOriginal: true,
|
|
drop: function () {
|
|
return new Module.ContainerBlockModel({
|
|
orientation: 'horizontal',
|
|
blocks: [
|
|
new Module.ContainerBlockModel(),
|
|
],
|
|
});
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
Module.TwoColumnContainerWidgetView = base.WidgetView.extend({
|
|
className: base.WidgetView.prototype.className + ' mailpoet_droppable_layout_block',
|
|
getTemplate: function () { return window.templates.twoColumnLayoutInsertion; },
|
|
behaviors: {
|
|
DraggableBehavior: {
|
|
cloneOriginal: true,
|
|
drop: function () {
|
|
return new Module.ContainerBlockModel({
|
|
orientation: 'horizontal',
|
|
blocks: [
|
|
new Module.ContainerBlockModel(),
|
|
new Module.ContainerBlockModel(),
|
|
],
|
|
});
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
Module.ThreeColumnContainerWidgetView = base.WidgetView.extend({
|
|
className: base.WidgetView.prototype.className + ' mailpoet_droppable_layout_block',
|
|
getTemplate: function () { return window.templates.threeColumnLayoutInsertion; },
|
|
behaviors: {
|
|
DraggableBehavior: {
|
|
cloneOriginal: true,
|
|
drop: function () {
|
|
return new Module.ContainerBlockModel({
|
|
orientation: 'horizontal',
|
|
blocks: [
|
|
new Module.ContainerBlockModel(),
|
|
new Module.ContainerBlockModel(),
|
|
new Module.ContainerBlockModel(),
|
|
],
|
|
});
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
Module.TwoColumn12ContainerWidgetView = base.WidgetView.extend({
|
|
className: base.WidgetView.prototype.className + ' mailpoet_droppable_layout_block',
|
|
getTemplate: function () { return window.templates.twoColumn12LayoutInsertion; },
|
|
behaviors: {
|
|
DraggableBehavior: {
|
|
cloneOriginal: true,
|
|
drop: function () {
|
|
var block = new Module.ContainerBlockModel({
|
|
orientation: 'horizontal',
|
|
blocks: [
|
|
new Module.ContainerBlockModel(),
|
|
new Module.ContainerBlockModel(),
|
|
],
|
|
});
|
|
block.set('columnLayout', '1_2');
|
|
return block;
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
Module.TwoColumn21ContainerWidgetView = base.WidgetView.extend({
|
|
className: base.WidgetView.prototype.className + ' mailpoet_droppable_layout_block',
|
|
getTemplate: function () { return window.templates.twoColumn21LayoutInsertion; },
|
|
behaviors: {
|
|
DraggableBehavior: {
|
|
cloneOriginal: true,
|
|
drop: function () {
|
|
var block = new Module.ContainerBlockModel({
|
|
orientation: 'horizontal',
|
|
blocks: [
|
|
new Module.ContainerBlockModel(),
|
|
new Module.ContainerBlockModel(),
|
|
],
|
|
});
|
|
block.set('columnLayout', '2_1');
|
|
return block;
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
App.on('before:start', function (BeforeStartApp) {
|
|
BeforeStartApp.registerBlockType('container', {
|
|
blockModel: Module.ContainerBlockModel,
|
|
blockView: Module.ContainerBlockView,
|
|
});
|
|
|
|
BeforeStartApp.registerLayoutWidget({
|
|
name: 'oneColumnLayout',
|
|
priority: 100,
|
|
widgetView: Module.OneColumnContainerWidgetView,
|
|
});
|
|
|
|
BeforeStartApp.registerLayoutWidget({
|
|
name: 'twoColumnLayout',
|
|
priority: 100,
|
|
widgetView: Module.TwoColumnContainerWidgetView,
|
|
});
|
|
|
|
BeforeStartApp.registerLayoutWidget({
|
|
name: 'threeColumnLayout',
|
|
priority: 100,
|
|
widgetView: Module.ThreeColumnContainerWidgetView,
|
|
});
|
|
|
|
BeforeStartApp.registerLayoutWidget({
|
|
name: 'twoColumn12Layout',
|
|
priority: 100,
|
|
widgetView: Module.TwoColumn12ContainerWidgetView,
|
|
});
|
|
|
|
BeforeStartApp.registerLayoutWidget({
|
|
name: 'twoColumn21Layout',
|
|
priority: 100,
|
|
widgetView: Module.TwoColumn21ContainerWidgetView,
|
|
});
|
|
});
|
|
|
|
export default Module;
|