jsharmony-cms
Version:
296 lines (250 loc) • 11.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 Cloner = require('../utils/cloner');
var FieldModel = require('./fieldModel');
/** @typedef {DataModelTemplate_GridPreview} DataModelTemplate_GridPreview */
/**
* @class
* @classdesc Normalizes the model for use in the grid preview
* editor. Creates a model template that can then be used to generate
* unique instances of the model for editing.
* @param {import('./componentTemplate').ComponentTemplate} componentTemplate
* @param {object} dataModel - the raw data model from the component config
*/
function DataModelTemplate_GridPreview(componentTemplate, dataModel) {
/** @private @type {Object} */
this._jsh = componentTemplate._jsh;
/** @private @type {Object} */
this._cms = componentTemplate._cms;
/** @private @type {Object} */
this._componentTemplate = componentTemplate;
/** @private @type {string} */
this._componentTemplateId = componentTemplate.getTemplateId();
/** @private @type {string} */
this._idFieldName = '';
/** @private @type {object} */
this._modelTemplate = {};
/** @private @type {string} */
this._rawOriginalJs = '';
/** @private @type {string} */
this._rowTemplate = '';
/** @private @type {string} */
this._sequenceFieldName = '';
this.buildTemplate(componentTemplate, dataModel);
}
/**
* @private
* @param {import('./componentTemplate').ComponentTemplate} componentTemplate
* @param {object} dataModel - the raw data model from the component config
*/
DataModelTemplate_GridPreview.prototype.buildTemplate = function(componentTemplate, dataModel) {
var modelConfig = Cloner.deepClone(dataModel || {});
if (modelConfig.layout !== 'grid_preview') return undefined;
if(modelConfig.js && _.isString(modelConfig.js) && modelConfig.js.trim()) modelConfig.js = '(function(){ var cms = '+this._cms._instance+';' + modelConfig.js + ' })();';
this._rawOriginalJs = '\r\n' + (modelConfig.js || '') + '\r\n';
var popup = _.isArray(modelConfig.popup) ? modelConfig.popup : [];
var fields = modelConfig.fields || [];
this._idFieldName = this.ensureIdField(fields);
this._sequenceFieldName = this.ensureSequenceField(fields);
_.forEach(fields, function(field) {
field.control = 'hidden';
});
fields.push({
name: 'component_preview', control: 'label', caption: '', unbound: true, controlstyle: 'vertical-align:baseline;display:block;min-height:1px;',
value: '<div tabindex="0" data-component-template="gridRow"></div>',
ongetvalue: 'return;'
});
var componentConfig = this._componentTemplate && this._componentTemplate._componentConfig;
var model = {};
this._modelTemplate = model;
model.title = 'Edit ' + componentTemplate.getCaptions()[0];
model.popup = [ _.isNumber(popup[0]) ? popup[0] : 400, _.isNumber(popup[1]) ? popup[1] : 200 ];
model.fields = fields;
model.layout = 'grid';
model.unbound = true;
model.newrowposition = 'last';
model.commitlevel= 'page';
model.hide_system_buttons = ['export', 'search', 'save', 'add'];
model.sort = [];
model.buttons = [
{link: 'js:_this.close()', icon: 'ok', actions: 'BIU', text: 'Done' },
{link: 'js:_this.addItem()', icon: 'add', actions: 'I', text: 'Add', class: 'jsharmony_cms_component_dataGridEditor_insert' },
];
model.getapi = 'return _this.getDataApi(xmodel, apitype)';
model.onrowbind = '_this.onRowBind(xmodel,jobj,datarow);';
model.oncommit = '_this.onCommit(xmodel, rowid, callback);';
model.ejs = '';
model.sort = { [this._sequenceFieldName]: 'asc' };
model.oninit = [
"jsh.$root('.xform'+xmodel.class).before('<div class=\"dataGridEditor_instructions\"><span style=\"font-size:1.3em;position:relative;top:1px;margin-right:2px;margin-left:4px;\">🛈</span> Add, edit, and re-order items using the icons <span class=\"dataGridEditor_instructions_doubleClick\">:: Double-click to edit</span></div>');",
].join(' ');
model.rowclass = "<%=xejs.iif(rowid==0,'first')%>";
model.tableclass = ((model.tableclass||'')+' '+(componentConfig&&componentConfig.options&&componentConfig.options.component_preview_size=='collapse'?'jsharmony_cms_component_preview_collapse':'jsharmony_cms_component_preview_expand')).trim();
this._jsh.XPage.ParseModelDefinition(model, null, null, { ignoreErrors: true });
//--------------------------------------------------
// Get templates
//--------------------------------------------------
var templateHtml = '<div>' + modelConfig.ejs + '</div>';
var rowTemplate = '';
var selRowPreview = (modelConfig.templates || {}).gridRowPreview;
if(selRowPreview){
//If gridRowPreview is set, extract the template from the model.ejs file
var rowPreview = this._jsh.$(templateHtml).find(selRowPreview);
if (rowPreview.length > 1) throw new Error('Row template must contain a single root element. Found ' + rowPreview.length + ' elements');
rowTemplate = rowPreview ? rowPreview.html() : undefined;
}
else {
//If templates are not used, return the entire model.ejs as the template
rowTemplate = templateHtml;
}
this._rowTemplate = rowTemplate;
return model;
};
/**
* Ensure that an ID field exists.
* If no ID field exists then one will be added.
* An error will be thrown if more than one ID field exists.
* @private
* @param {Object[]} fields
* @returns {string} the name of the ID field
*/
DataModelTemplate_GridPreview.prototype.ensureIdField = function(fields) {
var idFields = _.filter(fields, function(field) { return field.key; });
if (idFields.length > 1) throw new Error('Expected a single ID field. Found ' + idFields.length);
if (idFields.length < 1) {
var idField = { name: '_jsh_auto_id', type: 'varchar', control: 'hidden', key: true, caption: '', isAutoAddedField: true };
idFields = [idField];
fields.push(idField);
}
return idFields[0].name;
};
/**
* Ensure that a sequence field exists.
* If no sequence field exists then one will be added.
* @private
* @param {Object[]} fields
* @returns {string} the name of the sequence field
*/
DataModelTemplate_GridPreview.prototype.ensureSequenceField = function(fields) {
var seqFieldName = 'sequence'; //This is by convention!!!
var hasSeqField = _.some(fields, function(field) { return field.name === seqFieldName; });
if (!hasSeqField) {
var idField = { name: seqFieldName, type: 'int', control: 'hidden', caption: '', isAutoAddedField: true };
fields.push(idField);
}
return seqFieldName;
};
/**
* Get the name of the field used for the data item ID.
* @public
* @returns {string}
*/
DataModelTemplate_GridPreview.prototype.getIdFieldName = function() {
return this._idFieldName;
};
/**
* @public
*/
DataModelTemplate_GridPreview.prototype.getModelInstance = function() {
var model = Cloner.deepClone(this._modelTemplate);
model.id = DataModelTemplate_GridPreview.getNextInstanceId(this._componentTemplate);
//model.js is stringified and executed in the context of the model
/* globals modelid, jsh */
model.js = function() {
var gridApi = new jsh.XAPI.Grid.Static(modelid);
var formApi = new jsh.XAPI.Form.Static(modelid);
return {
getDataApi: function(xmodel, apiType) {
if (apiType === 'grid') return gridApi;
else if (apiType === 'form') return formApi;
}
};
};
return model;
};
/**
* Return the raw model JavaScript.
* @public
* @returns {Object}
*/
DataModelTemplate_GridPreview.prototype.getModelJs = function() {
return this._rawOriginalJs;
};
/**
* Get a unique ID for the model instance
* @private
* @returns {string}
*/
DataModelTemplate_GridPreview.getNextInstanceId = function(componentTemplate) {
if (DataModelTemplate_GridPreview._id == undefined) DataModelTemplate_GridPreview._id = 0;
var id = DataModelTemplate_GridPreview._id++;
return 'DataModel_GridPreview_' + componentTemplate.getClassName() + '_' + id;
};
/**
* Get the EJS string used to render the row item preview
* @public
* @returns {string}
*/
DataModelTemplate_GridPreview.prototype.getRowTemplate = function() {
return this._rowTemplate || '';
};
/**
* Create a pristine copy of the data.
* This will remove extraneous properties (that don't exist in the model)
* and do data conversions. It will also add missing fields.
* The returned value will match the field model exactly.
*
* NOTE: this does not set default values! If the value is not set in
* dataInstance then the property will be set to undefined.
*
* @public
* @param {Object} dataInstance - the existing field values.
* @param {Object} isAutoAddedField - if true then the added fields (e.g., ID, sequence) will be removed.
* @returns {Object} a copy of the dataInstance with type conversions done and extraneous
* properties removed.
*/
DataModelTemplate_GridPreview.prototype.makePristineCopy = function(dataInstance, removeAutoAddedFields) {
var fields = removeAutoAddedFields ? _.filter(this._modelTemplate.fields, function(field) { return !field.isAutoAddedField; }) : this._modelTemplate.fields;
return FieldModel.makePristineCopy(dataInstance, fields);
};
/**
* Iterates through the fieldModels
* to look for fields with "type" property. If a field has the type property
* then the field will be added to the new data instance object.
*
* Setting the field follows specific rules
* 1. If the data instance does not contain the property key
* then the property is set to either undefined or the default value.
* 2. If the data instance contains the property and the property value is
* defined then it is left as-is.
* 3. If the data instance contains the property and the property value is
* null/undefined then the property is overridden if there is a default AND
* it is a required field. If it is not required then the value is left as
* null/undefined (which allows the user to clear default values that are
* not required fields).
*
* This will also correctly convert values as needed.
*
* This mutates the dataInstance.
* @public
* @param {Object} dataInstance - the data instance. Each property corresponds
* to a field in the field array. This object will be mutated.
* @returns {Object} the new or mutated data
*/
DataModelTemplate_GridPreview.prototype.populateDataInstance = function(dataInstance) {
return FieldModel.populateDataInstance(dataInstance, this._modelTemplate.fields || []);
};
exports = module.exports = DataModelTemplate_GridPreview;