grapesjs_codeapps
Version:
Free and Open Source Web Builder Framework/SC Modification
354 lines (311 loc) • 8.33 kB
JavaScript
import Backbone from 'backbone';
import { isArray, isEmpty } from 'underscore';
const Components = require('../model/Components');
const ComponentsView = require('./ComponentsView');
const Selectors = require('selector_manager/model/Selectors');
module.exports = Backbone.View.extend({
className() {
return this.getClasses();
},
tagName() {
return this.model.get('tagName');
},
initialize(opt = {}) {
const model = this.model;
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;
this.listenTo(model, 'change:style', this.updateStyle);
this.listenTo(model, 'change:attributes', this.renderAttributes);
this.listenTo(model, 'change:highlightable', this.updateHighlight);
this.listenTo(model, 'change:status', this.updateStatus);
this.listenTo(model, 'change:state', this.updateState);
this.listenTo(model, 'change:script', this.render);
this.listenTo(model, 'change:content', this.updateContent);
this.listenTo(model, 'change', this.handleChange);
this.listenTo(model, 'active', this.onActive);
$el.data('model', model);
model.view = this;
this.initClasses();
this.initComponents({ avoidRender: 1 });
this.init();
},
/**
* Initialize callback
*/
init() {},
/**
* Callback executed when the `active` event is triggered on component
*/
onActive() {},
initClasses() {
const { model } = this;
const event = 'change:classes';
const classes = model.get('classes');
if (classes instanceof Selectors) {
this.stopListening(model, event, this.initClasses);
this.listenTo(model, event, this.initClasses);
this.listenTo(classes, 'add remove change', this.updateClasses);
classes.length && this.importClasses();
}
},
initComponents(opts = {}) {
const { model, $el, childrenView } = this;
const event = 'change:components';
const comps = model.get('components');
const toListen = [model, event, this.initComponents];
if (comps instanceof Components) {
$el.data('collection', comps);
childrenView && childrenView.remove();
this.stopListening(...toListen);
!opts.avoidRender && this.renderChildren();
this.listenTo(...toListen);
}
},
/**
* Handle any property change
* @private
*/
handleChange() {
const model = this.model;
model.emitUpdate();
for (let prop in model.changed) {
model.emitUpdate(prop);
}
},
/**
* Import, if possible, classes inside main container
* @private
* */
importClasses() {
var clm = this.config.em.get('SelectorManager');
if (clm) {
this.model.get('classes').each(m => {
clm.add(m.get('name'));
});
}
},
/**
* Fires on state update. If the state is not empty will add a helper class
* @param {Event} e
* @private
* */
updateState(e) {
var cl = 'hc-state';
var state = this.model.get('state');
if (state) {
this.$el.addClass(cl);
} else {
this.$el.removeClass(cl);
}
},
/**
* Update item on status change
* @param {Event} e
* @private
* */
updateStatus(opts = {}) {
const em = this.em;
const el = this.el;
const status = this.model.get('status');
const pfx = this.pfx;
const ppfx = this.ppfx;
const selectedCls = `${pfx}selected`;
const selectedParentCls = `${selectedCls}-parent`;
const freezedCls = `${ppfx}freezed`;
const hoveredCls = `${ppfx}hovered`;
const toRemove = [selectedCls, selectedParentCls, freezedCls, hoveredCls];
this.$el.removeClass(toRemove.join(' '));
var actualCls = el.getAttribute('class') || '';
var cls = '';
switch (status) {
case 'selected':
cls = `${actualCls} ${selectedCls}`;
break;
case 'selected-parent':
cls = `${actualCls} ${selectedParentCls}`;
break;
case 'freezed':
cls = `${actualCls} ${freezedCls}`;
break;
case 'freezed-selected':
cls = `${actualCls} ${freezedCls} ${selectedCls}`;
break;
case 'hovered':
cls = !opts.avoidHover ? `${actualCls} ${hoveredCls}` : '';
break;
}
cls = cls.trim();
cls && el.setAttribute('class', cls);
},
/**
* Update highlight attribute
* @private
* */
updateHighlight() {
const hl = this.model.get('highlightable');
this.setAttribute('data-highlightable', hl ? 1 : '');
},
/**
* Update style attribute
* @private
* */
updateStyle() {
const em = this.em;
const model = this.model;
if (em && em.get('avoidInlineStyle')) {
this.el.id = model.getId();
const style = model.getStyle();
!isEmpty(style) && model.setStyle(style);
} else {
this.setAttribute('style', model.styleToString());
}
},
/**
* Update classe attribute
* @private
* */
updateClasses() {
const str = this.model
.get('classes')
.pluck('name')
.join(' ');
this.setAttribute('class', str);
// Regenerate status class
this.updateStatus();
},
/**
* Update single attribute
* @param {[type]} name [description]
* @param {[type]} value [description]
*/
setAttribute(name, value) {
const el = this.$el;
value ? el.attr(name, value) : el.removeAttr(name);
},
/**
* Get classes from attributes.
* This method is called before initialize
*
* @return {Array}|null
* @private
* */
getClasses() {
return this.model.getClasses().join(' ');
},
/**
* Update attributes
* @private
* */
updateAttributes() {
const model = this.model;
const defaultAttr = { 'data-gjs-type': model.get('type') || 'default' };
if (model.get('highlightable')) {
defaultAttr['data-highlightable'] = 1;
}
this.$el.attr({
...defaultAttr,
...model.getAttributes()
});
this.updateStyle();
},
/**
* Update component content
* @private
* */
updateContent() {
this.getChildrenContainer().innerHTML = this.model.get('content');
},
/**
* Prevent default helper
* @param {Event} e
* @private
*/
prevDef(e) {
e.preventDefault();
},
/**
* Render component's script
* @private
*/
updateScript() {
if (!this.model.get('script')) {
return;
}
var em = this.em;
if (em) {
var canvas = em.get('Canvas');
canvas.getCanvasView().updateScript(this);
}
},
/**
* Return children container
* Differently from a simple component where children container is the
* component itself
* <my-comp>
* <!--
* <child></child> ...
* -->
* </my-comp>
* You could have the children container more deeper
* <my-comp>
* <div></div>
* <div></div>
* <div>
* <div>
* <!--
* <child></child> ...
* -->
* </div>
* </div>
* </my-comp>
* @return HTMLElement
* @private
*/
getChildrenContainer() {
var container = this.el;
if (typeof this.getChildrenSelector == 'function') {
container = this.el.querySelector(this.getChildrenSelector());
} else if (typeof this.getTemplate == 'function') {
// Need to find deepest first child
}
return container;
},
/**
* Render children components
* @private
*/
renderChildren() {
this.updateContent();
const container = this.getChildrenContainer();
const view = new ComponentsView({
collection: this.model.get('components'),
config: this.config,
componentTypes: this.opts.componentTypes
});
view.render(container);
this.childrenView = view;
const childNodes = Array.prototype.slice.call(view.el.childNodes);
for (var i = 0, len = childNodes.length; i < len; i++) {
container.appendChild(childNodes.shift());
}
},
renderAttributes() {
this.updateAttributes();
this.updateClasses();
},
render() {
this.renderAttributes();
this.renderChildren();
this.updateScript();
this.onRender();
return this;
},
onRender() {}
});