grapesjs-clot
Version:
Free and Open Source Web Builder Framework
258 lines (222 loc) • 8.23 kB
JavaScript
import { View } from 'common';
import { bindAll, isUndefined, debounce } from 'underscore';
import { isObject } from 'utils/mixins';
import { parse, stringify } from 'flatted';
import { myEditor } from '../..';
const clearProp = 'data-clear-style';
export default class Property extends View {
template() {
const { pfx, ppfx } = this;
return `
<div class="${pfx}label" data-sm-label></div>
<div class="${ppfx}fields" data-sm-fields></div>
`;
}
templateLabel(model) {
const { pfx, em } = this;
const { parent } = model;
const { icon = '', info = '' } = model.attributes;
const icons = em?.getConfig('icons');
const iconClose = icons?.close || '';
return `
<span class="${pfx}icon ${icon}" title="${info}">
${model.getLabel()}
</span>
${!parent ? `<div class="${pfx}clear" style="display: none" ${clearProp}>${iconClose}</div>` : ''}
`;
}
templateInput(model) {
return `
<div class="${this.ppfx}field">
<input placeholder="${model.getDefaultValue()}"/>
</div>
`;
}
initialize(o = {}) {
bindAll(this, '__change', '__updateStyle');
const config = o.config || {};
const { em } = config;
this.config = config;
this.em = em;
this.pfx = config.stylePrefix || '';
this.ppfx = config.pStylePrefix || '';
this.__destroyFn = this.destroy ? this.destroy.bind(this) : () => {};
const { model } = this;
model.view = this;
// Put a sligh delay on debounce in order to execute the update
// post styleManager.__upProps trigger.
this.onValueChange = debounce(this.onValueChange.bind(this), 10);
this.updateStatus = debounce(this.updateStatus.bind(this));
this.listenTo(model, 'destroy remove', this.remove);
this.listenTo(model, 'change:visible', this.updateVisibility);
this.listenTo(model, 'change:name change:className change:full', this.render);
this.listenTo(model, 'change:value', this.onValueChange);
this.listenTo(em, 'change:device', this.onValueChange);
const init = this.init && this.init.bind(this);
init && init();
}
remove() {
View.prototype.remove.apply(this, arguments);
['em', 'input', '$input', 'view'].forEach(i => (this[i] = null));
this.__destroyFn(this._getClbOpts());
}
/**
* Triggers when the status changes. The status indicates if the value of
* the proprerty is changed or inherited
* @private
*/
updateStatus() {
//console.log('style_manager/view/PropertyView.js => updateStatus start');
const { model, pfx, ppfx, config } = this;
const updatedCls = `${ppfx}four-color`;
const computedCls = `${ppfx}color-warn`;
const labelEl = this.$el.children(`.${pfx}label`);
const clearStyleEl = this.getClearEl();
const clearStyle = clearStyleEl ? clearStyleEl.style : {};
labelEl.removeClass(`${updatedCls} ${computedCls}`);
clearStyle.display = 'none';
if (model.hasValue({ noParent: true }) && config.highlightChanged) {
labelEl.addClass(updatedCls);
config.clearProperties && (clearStyle.display = '');
} else if (model.hasValue() && config.highlightComputed) {
labelEl.addClass(computedCls);
}
this.parent?.updateStatus();
//console.log('style_manager/view/PropertyView.js => updateStatus end');
}
/**
* Clear the property from the target
*/
clear(ev) {
ev && ev.stopPropagation();
this.model.clear();
}
/**
* Get clear element
* @return {HTMLElement}
*/
getClearEl() {
if (!this.clearEl) {
this.clearEl = this.el.querySelector(`[${clearProp}]`);
}
return this.clearEl;
}
/**
* Triggers when the value of element input/s is changed, so have to update
* the value of the model which will propogate those changes to the target
*/
inputValueChanged(ev) {
//console.log('PropertyView.js => inputValueChanged start');
ev && ev.stopPropagation();
// Skip the default update in case a custom emit method is defined
if (this.emit) return;
this.model.upValue(ev.target.value);
//console.log('PropertyView.js => inputValueChanged end');
}
onValueChange(m, val, opt = {}) {
//console.log('style_manager/view/PropertyView.js => onValueChange start');
this.setValue(this.model.getFullValue());
this.updateStatus();
//console.log('style_manager/view/PropertyView.js => onValueChange end');
}
/**
* Update the element input.
* Usually the value is a result of `model.getFullValue()`
* @param {String} value The value from the model
* */
setValue(value) {
//console.log('style_manager/view/PropertyView.js => setValue start');
const { model } = this;
const result = isUndefined(value) || value === '' ? model.getDefaultValue() : value;
if (this.update) return this.__update(result);
this.__setValueInput(result);
//console.log('style_manager/view/PropertyView.js => setValue end');
}
__setValueInput(value) {
//console.log('style_manager/view/PropertyView.js => __setValueInput start');
//console.trace();
const input = this.getInputEl();
input && (input.value = value);
//console.log('style_manager/view/PropertyView.js => __setValueInput end');
}
getInputEl() {
if (!this.input) {
this.input = this.el.querySelector('input');
}
return this.input;
}
updateVisibility() {
this.el.style.display = this.model.isVisible() ? '' : 'none';
}
clearCached() {
this.clearEl = null;
this.input = null;
this.$input = null;
}
__unset() {
const unset = this.unset && this.unset.bind(this);
unset && unset(this._getClbOpts());
}
__update(value) {
//console.log('style_manager/view/PropertyView.js => __update start');
//console.trace();
const update = this.update && this.update.bind(this);
update &&
update({
...this._getClbOpts(),
value,
});
//console.log('style_manager/view/PropertyView.js => __update end');
}
__change(...args) {
const emit = this.emit && this.emit.bind(this);
emit && emit(this._getClbOpts(), ...args);
}
__updateStyle(value, { complete, partial, ...opts } = {}) {
//console.log('style_manager/view/PropertyView.js => __updateStyle start');
const { model } = this;
const final = complete !== false && partial !== true;
if (isObject(value)) {
model.__upTargetsStyle(value, { avoidStore: !final });
} else {
model.upValue(value, { partial: !final });
}
//console.log('style_manager/view/PropertyView.js => __updateStyle end');
}
_getClbOpts() {
const { model, el, createdEl } = this;
return {
el,
createdEl,
property: model,
props: model.attributes,
change: this.__change,
updateStyle: this.__updateStyle,
};
}
render() {
this.clearCached();
const { pfx, model, el, $el } = this;
const name = model.getName();
const type = model.getType();
const cls = model.get('className') || '';
const className = `${pfx}property`;
// Support old integer classname
const clsType = type === 'number' ? `${pfx}${type} ${pfx}integer` : `${pfx}${type}`;
this.createdEl && this.__destroyFn(this._getClbOpts());
$el.empty().append(this.template(model));
$el.find('[data-sm-label]').append(this.templateLabel(model));
const create = this.create && this.create.bind(this);
this.createdEl = create && create(this._getClbOpts());
$el.find('[data-sm-fields]').append(this.createdEl || this.templateInput(model));
el.className = `${className} ${clsType} ${className}__${name} ${cls}`.trim();
el.className += model.isFull() ? ` ${className}--full` : '';
const onRender = this.onRender && this.onRender.bind(this);
onRender && onRender();
this.setValue(model.getValue());
}
}
Property.prototype.events = {
change: 'inputValueChanged',
[`click [${clearProp}]`]: 'clear',
};