Files
piratepoet/assets/js/src/newsletter_editor/blocks/container.js
2019-05-23 07:55:14 -04:00

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;