jsharmony-cms
Version:
293 lines (244 loc) • 9.4 kB
JavaScript
/*
Copyright 2020 apHarmony
This file is part of jsHarmony.
jsHarmony is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
jsHarmony is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this package. If not, see <http://www.gnu.org/licenses/>.
*/
var _ = require('lodash');
var ComponentTemplate = require('./component/componentModel/componentTemplate');
var DataEditor_GridPreview = require('./component/editors/dataEditor_gridPreview');
var PropertyEditor_Form = require('./component/editors/propertyEditor_form');
var DataEditor_Form = require('./component/editors/dataEditor_form');
var DomSerializer = require('./component/utils/domSerializer');
var TemplateRenderer = require('./component/templateRenderer');
/** @typedef {import('./component/templateRenderer').RenderConfig} RenderConfig */
/**
* @callback BasicComponentController~beforeRender
* @param {RenderConfig} renderConfig
*/
/**
* @callback BasicComponentController~render
* @param {HTMLElement} element
* @param {Object} data - the component data
* @param {Object} properties - the component properties
* @param {Object} cms - the parent jsHarmonyCMSInstance
* @param {Object} component - the parent component
*/
/**
* @class
* @param {string} componentId - the globally unique component instance ID
* @param {(HTMLElement | JQuery)} element
* @param {Object} cms
* @param {Object} jsh
* @param {string} componentConfigId
*/
exports = module.exports = function(componentId, element, cms, jsh, componentConfigId) {
/** @type {JQuery} */
var $element = jsh.$(element);
/** @type {ComponentTemplate} */
var componentTemplate = new ComponentTemplate(cms.componentManager.componentTemplates[componentConfigId], jsh, cms);
/** @public @type {BasicComponentController~beforeRender} */
this.onBeforeRender = undefined;
/** @public @type {BasicComponentController~render} */
this.onRender = undefined;
/** @public @type {Object} */
this.template = undefined;
Object.defineProperty(this, 'template', { get: function() { return componentTemplate.getComponentConfig(); }});
/** @public @type {string} */
this.id = undefined;
Object.defineProperty(this, 'id', { get: function() { return componentId; }});
/** @public @type {Object} */
this.domSerializer = new DomSerializer(jsh);
/**
* Get the data from the element's serialized data attribute value.
* @private
* @return {Object}
*/
this.getData = function() {
return this.domSerializer.getAttr($element, 'data-component-data');
};
/**
* Get the properties from the element's serialized property attribute value
* and update from model definition.
* @private
* @return {Object}
*/
this.getProperties = function() {
var model = componentTemplate.getPropertiesModelTemplate_Form();
var properties = this.domSerializer.getAttr($element, 'data-component-properties');
return model.populateDataInstance(properties);
};
/**
* Setup the default properties object
* and save the object.
* @private
*/
this.initProperties = function() {
this.saveProperties(this.getProperties());
};
/**
* Check to see if the component is readonly.
* @private
* @returns {boolean} - true if the model is readonly.
*/
this.isReadOnly = function() {
return !!cms.readonly;
};
/**
* Open the data editor form.
* @public
*/
this.openDataEditor = function() {
var editorType = componentTemplate.getDataEditorType();
if (editorType === 'grid') {
throw new Error('Not Implemented');
} else if (editorType === 'grid_preview') {
this.openDataEditor_GridPreview();
} else if (editorType === 'form') {
this.openDataEditor_Form();
} else if (editorType != undefined) {
throw new Error('Unknown editor type "' + editorType + '"');
}
};
/**
* @private
* @param {object} modelInstance - the model instance to render (model will be mutated).
*/
this.openDataEditor_Form = function() {
var _this = this;
var dataEditor = new DataEditor_Form(componentTemplate, undefined, this.isReadOnly(), cms, jsh, _this);
var data = this.getData() || {};
dataEditor.open(data.item || {}, this.getProperties() || {}, function(updatedData) {
data.item = updatedData;
_this.saveData(data);
_this.render();
});
};
/**
* @private
*/
this.openDataEditor_GridPreview = function() {
var _this = this;
var dataEditor = new DataEditor_GridPreview(componentTemplate, cms, jsh, _this);
dataEditor.open(this.getData(), this.getProperties() || {}, function(updatedData) {
_this.saveData(updatedData);
_this.render();
});
};
/**
* Open the property editor form.
* @public
*/
this.openPropertiesEditor = function() {
var _this = this;
var propertyEditor = new PropertyEditor_Form(componentTemplate, cms, jsh);
propertyEditor.open(this.getProperties() || {}, function(data) {
_this.saveProperties(data);
_this.render();
});
};
this.openDefaultEditor = function(){
var _this = this;
var config = componentTemplate.getComponentConfig() || {};
var hasData = ((config.data || {}).fields || []).length > 0;
var hasProperties = ((config.properties || {}).fields || []).length > 0;
if(hasData) _this.openDataEditor();
else if(hasProperties) _this.openPropertiesEditor();
};
/**
* Render the component
* @public
*/
this.render = function(callback) {
if(!callback) callback = function(){};
var _this = this;
var config = componentTemplate.getComponentConfig() || {};
var template = (config.templates || {}).editor || '';
var data = _.extend({}, this.getData());
var props = this.getProperties();
var renderConfig = TemplateRenderer.createRenderConfig(template, data, props, cms);
if (_.isFunction(this.onBeforeRender)) this.onBeforeRender(renderConfig);
var rendered = TemplateRenderer.render(renderConfig, 'component', jsh, cms, config, { component_id: _this.id });
if(!rendered){
if(!template) rendered = '*** Component Rendering Error: Template Missing ***';
else rendered = '*** Component Rendering Error: Empty Result ***';
}
$element.empty().append(rendered);
$element.off('dblclick.cmsComponent').on('dblclick.cmsComponent', function(e){
_this.openDefaultEditor();
});
if (_.isFunction(this.onRender)) this.onRender($element[0], data, props, cms, this);
this.notifyUpdate($element[0], {
data: data,
properties: props,
});
setTimeout(function() {
jsh.async.each(
$element.find('[data-component]'),
function(el, el_cb) {
var $el = jsh.$(el);
if(!$el.hasClass('initialized')){
$el.addClass('initialized');
$el.attr('data-component-id', cms.componentManager.getNextComponentId());
}
cms.componentManager.renderContentComponent(el, undefined, el_cb);
},
callback
);
});
};
/**
* Call anytime the data is changed in the view (i.e.,
* by the user). This will update the component's data
* on the page.
* @private
* @param {(Object | undefined)} data
*/
this.saveData = function(data) {
this.domSerializer.setAttr($element, 'data-component-data', data);
};
/**
* Call anytime the properties are changed in the view (i.e.,
* by the user). This will save the properties for the components
* @private
* @param {(Object | undefined)} props
*/
this.saveProperties = function(props) {
this.domSerializer.setAttr($element, 'data-component-properties', props);
};
/**
* Trigger jsHarmonyCmsUpdate event on content editor container for external libraries
* @private
* @param {(Object | undefined)} props
*/
this.notifyUpdate = function(element, props) {
if(!element) element = $element[0];
if(!props) props = {};
//Get content area name from element
var contentAreaName = $element.closest('[cms-content-editor]').attr('cms-content-editor');
var componentId = this.id;
props.element = element;
props.componentId = componentId;
props.contentAreaName = contentAreaName;
jsh.XExt.trigger(cms.componentManager.onNotifyUpdate, props);
};
/**
* Get the data from the element's serialized data attribute value.
* @private
* @return {Object}
*/
this.getDialogClass = function() {
var rslt = (cms.componentManager.dialogClass||'');
rslt += ' '+($element.closest('[cms-content-editor]').attr('cms-dialog-class')||'');
return rslt.trim();
};
this.initProperties();
};