UNPKG

jsharmony-cms

Version:
1,395 lines (1,184 loc) 854 kB
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ /* 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 DataModelTemplate_GridPreview = require('./dataModelTemplate_gridPreview'); var DataModelTemplate_FormPreview = require('./dataModelTemplate_formPreview'); var PropertiesModelTemplate_Form = require('./propertiesModelTemplate_form'); /** * @typedef {Object} MediaBrowserControlInfo * @property {string} dataFieldName * @property {string} titleFieldName * @property {('link' | 'media')} browserType */ /** @typedef {ComponentTemplate} ComponentTemplate */ /** * @class * @param {Object} componentConfig - the component configuration as defined by the component JSON. * @param {Object} jsh */ function ComponentTemplate(componentConfig, jsh, cms) { /** @private @type {Object} */ this._componentConfig = componentConfig; /** @private @type {Object} */ this._jsh = jsh; /** @private @type {Object} */ this._cms = cms; /** @private @type {DataModelTemplate_GridPreview} */ this._dataModelTemplate_GridPreview = undefined; /** @private @type {DataModelTemplate_FormPreview} */ this._dataModelTemplate_FormPreview = undefined; /** @private @type {PropertiesModelTemplate_Form} */ this._propertiesModelTemplate_Form = undefined; if (this._componentConfig.data) { this._componentConfig.data.fields = this.processBrowserFields(this._componentConfig.data.fields || []); this._dataModelTemplate_GridPreview = new DataModelTemplate_GridPreview(this, this._componentConfig.data); this._dataModelTemplate_FormPreview = new DataModelTemplate_FormPreview(this, this._componentConfig.data); } if (this._componentConfig.properties) { this._componentConfig.properties.fields = this.processBrowserFields(this._componentConfig.properties.fields || []); this._propertiesModelTemplate_Form = new PropertiesModelTemplate_Form(this, this._componentConfig.properties); } } /** * Get the component captions tuple as defined by the component JSON. * The first element is the title, the second element is the singular caption * (if exists), and the third element is the plural caption (if exists). * @public * @returns {[string, string, string]} */ ComponentTemplate.prototype.getCaptions = function() { var captions = [this._componentConfig.title]; if (_.isArray(this._componentConfig.caption)) { captions.push(this._componentConfig.caption[0]); captions.push(this._componentConfig.caption[1]); } else { captions.push(this._componentConfig.caption); captions.push(this._componentConfig.caption); } return captions; }; /** * Get the component configuration as defined by the component JSON. * @public * @returns {Object} */ ComponentTemplate.prototype.getComponentConfig = function() { return this._componentConfig; }; /** * Return the editor type * @public * @returns {('grid' | 'grid_preview' | 'form' | undefined)} */ ComponentTemplate.prototype.getDataEditorType = function() { if (this._componentConfig.data) { return this._componentConfig.data.layout; } return undefined; }; /** * @public * @returns {(DataModelTemplate_FormPreview | undefined)} */ ComponentTemplate.prototype.getDataModelTemplate_FormPreview = function() { return this._dataModelTemplate_FormPreview; }; /** * @public * @returns {(DataModelTemplate_GridPreview | undefined)} */ ComponentTemplate.prototype.getDataModelTemplate_GridPreview = function() { return this._dataModelTemplate_GridPreview; }; /** * @public * @returns {(PropertiesModelTemplate_Form | undefined)} */ ComponentTemplate.prototype.getPropertiesModelTemplate_Form = function() { return this._propertiesModelTemplate_Form; }; /** * Get the ID specified for the component configuration. * This is NOT an instance id. * @public * @returns {(string | undefined)} */ ComponentTemplate.prototype.getTemplateId = function() { return this._componentConfig.id; }; /** * Gets the base class name for this component * @public * @returns {(string | undefined)} */ ComponentTemplate.prototype.getClassName = function() { return this._componentConfig.className || this._jsh.XExt.escapeCSSClass(this._componentConfig.id, { nodash: true }); }; /** * @private * @param {object[]} fields * @returns {object[]} */ ComponentTemplate.prototype.processBrowserFields = function(fields) { var retVal = []; _.forEach(fields, function(field) { retVal.push(field); if (field.control !== 'link_browser' && field.control !== 'media_browser') { return; } var browserType = { link_browser: 'link', media_browser: 'media' }[field.control]; /** @type {MediaBrowserControlInfo} */ var info = { dataFieldName: field.name, titleFieldName: field.name + '_jsh_browserDataTitle', browserType: browserType, validate: field.validate, }; field.mediaBrowserControlInfo = info; field.name = info.titleFieldName; field.control = 'textbox'; field.controlclass = 'xtextbox_M'; field.type = 'varchar'; field.onchange = '(function() { var m = jsh.App[modelid]; if (m && m.onChangeBrowserTitleControl) m.onChangeBrowserTitleControl("' + info.dataFieldName + '"); })()'; delete field.validate; retVal.push({ name: field.name + '_browserButton', caption: '', control: 'button', value: 'Browse', nl: false, onclick: '(function() { var m = jsh.App[modelid]; if (m && m.openEditorBrowser) m.openEditorBrowser("' + info.dataFieldName + '"); })()' }); retVal.push({ name: field.name + '_resetButton', controlclass: 'secondary', controlstyle: 'margin-left:10px;', caption: '', control: 'button', value: 'Reset', nl: false, onclick: '(function() { var m = jsh.App[modelid]; if (m && m.resetEditorBrowser) m.resetEditorBrowser("' + info.dataFieldName + '"); })()' }); var coreField = { name: info.dataFieldName, caption: '', control: 'hidden', type: 'varchar' }; if(info.validate) coreField.validate = info.validate; if(field.caption) coreField.caption_ext = field.caption; retVal.push(coreField); }); return retVal; }; exports = module.exports = ComponentTemplate; },{"./dataModelTemplate_formPreview":2,"./dataModelTemplate_gridPreview":3,"./propertiesModelTemplate_form":5,"lodash":32}],2:[function(require,module,exports){ /* 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_FormPreview} DataModelTemplate_FormPreview */ /** * @class * @classdesc Normalizes the model for use in the form 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_FormPreview(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._itemTemplate = ''; /** @private @type {object} */ this._modelTemplate = {}; /** @private @type {string} */ this._rawOriginalJs = ''; this.buildTemplate(componentTemplate, dataModel); } /** * @private * @param {import('./componentTemplate').ComponentTemplate} componentTemplate * @param {object} dataModel - the raw data model from the component config */ DataModelTemplate_FormPreview.prototype.buildTemplate = function(componentTemplate, dataModel) { var modelConfig = Cloner.deepClone(dataModel || {}); if (modelConfig.layout !== 'grid_preview' && modelConfig.layout !== 'form') return undefined; var componentConfig = this._componentTemplate && this._componentTemplate._componentConfig; 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 || []; fields.unshift({ control:'html', value:'<div class="jsharmony_cms">', captionclass:'hidden'}); fields.push({ control:'html', value:'</div>', captionclass:'hidden'}); fields.push({ caption: '', control:'html', value:'<div class="jsharmony_cms_preview_editor jsharmony_cms_component_preview" data-id="previewWrapper"></div>', 'block':true, captionclass:'hidden' }); var model = _.extend({}, modelConfig); this._modelTemplate = model; model.title = modelConfig.title ? modelConfig.title : 'Edit ' + componentTemplate.getCaptions()[1]; model.popup = [ _.isNumber(popup[0]) ? popup[0] : 400, _.isNumber(popup[1]) ? popup[1] : 200 ]; model.fields = fields; model.layout = 'form'; model.unbound = true; model.onecolumn = true; model.formclass = ((model.formclass||'')+' '+(componentConfig&&componentConfig.options&&componentConfig.options.component_preview_size=='collapse'?'jsharmony_cms_component_preview_collapse':'jsharmony_cms_component_preview_expand')).trim(); model.ejs = ''; model.js = this._rawOriginalJs; this._jsh.XPage.ParseModelDefinition(model, null, null, { ignoreErrors: true }); _.forEach(fields, function(field) { if (field.type != undefined && field.mediaBrowserControlInfo == undefined) { field.onchange = '(function() { var m = jsh.App[modelid]; if (m && m.onChangeData) m.onChangeData(); })()'; } }); var templateHtml = '<div>' + modelConfig.ejs + '</div>'; var itemTemplate = ''; var selItemPreview = (modelConfig.templates || {}).itemPreview; if(selItemPreview){ //If itemPreview is set, extract the template from the model.ejs file var itemPreview = this._jsh.$(templateHtml).find(selItemPreview); if (itemPreview.length > 1) throw new Error('Item template must contain a single root element. Found ' + itemPreview.length + ' elements'); itemTemplate = itemPreview ? itemPreview.html() : undefined; } else { //If templates are not used, return the entire model.ejs as the template itemTemplate = templateHtml; } this._itemTemplate = itemTemplate; }; /** * Get the link browser field info (if exists) for the link field with * the given field name. * @public * @param {string} fieldName * @returns {(import('./componentTemplate').MediaBrowserControlInfo | undefined)} */ DataModelTemplate_FormPreview.prototype.getBrowserFieldInfo = function(fieldName) { var field = _.find(this._modelTemplate.fields, function(field) { return field.mediaBrowserControlInfo && field.mediaBrowserControlInfo.dataFieldName === fieldName; }); return field ? field.mediaBrowserControlInfo : undefined; }; /** * Get the link browser field infos * @public * @returns {import('./componentTemplate').MediaBrowserControlInfo[]]} */ DataModelTemplate_FormPreview.prototype.getBrowserFieldInfos = function() { var retVal = []; _.forEach(this._modelTemplate.fields, function(field) { if (field.mediaBrowserControlInfo) { retVal.push(field.mediaBrowserControlInfo); } }); return retVal; }; /** * Get the EJS string used to render the item preview * @public * @returns {string} */ DataModelTemplate_FormPreview.prototype.getItemTemplate = function() { return this._itemTemplate || ''; }; /** * @public */ DataModelTemplate_FormPreview.prototype.getModelInstance = function() { var model = Cloner.deepClone(this._modelTemplate); model.id = DataModelTemplate_FormPreview.getNextInstanceId(this._componentTemplate); return model; }; /** * Return the raw model JavaScript. * @public * @returns {Object} */ DataModelTemplate_FormPreview.prototype.getModelJs = function() { return this._rawOriginalJs; }; /** * Get a unique ID for the model instance * @private * @returns {string} */ DataModelTemplate_FormPreview.getNextInstanceId = function(componentTemplate) { if (DataModelTemplate_FormPreview._id == undefined) DataModelTemplate_FormPreview._id = 0; var id = DataModelTemplate_FormPreview._id++; return 'DataModel_FormPreview_' + componentTemplate.getClassName() + '_' + id; }; /** * 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. * @returns {Object} a copy of the dataInstance with type conversions done and extraneous * properties removed. */ DataModelTemplate_FormPreview.prototype.makePristineCopy = function(dataInstance) { return FieldModel.makePristineCopy(dataInstance, this._modelTemplate.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_FormPreview.prototype.populateDataInstance = function(dataInstance) { return FieldModel.populateDataInstance(dataInstance, this._modelTemplate.fields || []); }; exports = module.exports = DataModelTemplate_FormPreview; },{"../utils/cloner":18,"./fieldModel":4,"lodash":32}],3:[function(require,module,exports){ /* 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;\">&#x1f6c8;</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; },{"../utils/cloner":18,"./fieldModel":4,"lodash":32}],4:[function(require,module,exports){ /* 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 Convert = require('../utils/convert'); /** * @typedef {FieldModel} FieldModel */ /** * @class * @classdesc This class adds functionality for working with field models * defined in the JSH component JSON. */ function FieldModel() { } /** * JSH changes the types defined in the model. * This will convert the fields in dataInstance to the correct type * based on the fieldModel. * @public * @static * @param {Object} dataInstance - the data to operate on (will be mutated). * @param {Object[]} fields - the fields array from the model */ FieldModel.convertTypes = function(dataInstance, fields) { if (dataInstance == undefined) return; var numberTypeLut = { bigint: true, int: true, smallint: true, tinyint: true, decimal: true, float: true }; dataInstance = dataInstance || {}; _.forEach(fields || [], function(field) { var fieldName = field.name; var fieldType = field.type; if (fieldType == undefined) return; if (!(fieldName in dataInstance)) return; if (numberTypeLut[fieldType]) { dataInstance[fieldName] = Convert.toNumber(dataInstance[fieldName]); } }); }; /** * 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 * @static * @param {Object} dataInstance - the existing field values. * @param {Object[]} fields - the fields array from the model * @returns {Object} a copy of the dataInstance with type conversions done and extraneous * properties removed. */ FieldModel.makePristineCopy = function(dataInstance, fields) { var pristineCopy = {}; _.forEach(fields, function(field) { var fieldName = field.name; var fieldType = field.type; if (fieldType == undefined) return; pristineCopy[fieldName] = dataInstance[fieldName]; }); FieldModel.convertTypes(pristineCopy); return pristineCopy; }; /** * 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 * @static * @param {Object} dataInstance - the data instance. Each property corresponds * to a field in the fieldModels array. This object will not be mutated. * @param {Object[]} fields - the fields array from the model * @returns {Object} the new or mutated data */ FieldModel.populateDataInstance = function(dataInstance, fields) { dataInstance = dataInstance || {}; var fieldIndex = {}; _.forEach(fields || [], function(field) { var fieldName = field.name; var fieldType = field.type; if (fieldType == undefined) return; fieldIndex[fieldName] = field; // Must follow the rules to ensure // required fields are set to default values while also // allowing default fields to be cleared by the user if they are // not required fields. if (dataInstance[fieldName] != undefined) { return; } var isRequired = _.some((field.validate || []), function(a) { return a === 'Required'; }); var defaultValue= field.default; var propertyKeyExists = fieldName in dataInstance; if (propertyKeyExists && isRequired) { // The property has been set by the user // (since the key exists) but it is undefined/null // while being required. This means the undefined value needs // to be overridden with the default. dataInstance[fieldName] = defaultValue; } else if (!propertyKeyExists) { // The property has not been set by the user // (since the key does not exist) so must // default to the default value (even if undefined/null) dataInstance[fieldName] = defaultValue; } }); //Set _jsh_browserDataTitle to match field if blank for(var fieldName in fieldIndex){ var titleFieldName = fieldName + '_jsh_browserDataTitle'; if(fieldIndex[titleFieldName]){ if(dataInstance[fieldName]){ if(!dataInstance[titleFieldName]) dataInstance[titleFieldName] = dataInstance[fieldName]; } } } FieldModel.convertTypes(dataInstance); // Must return in case original instance was null/undefined return dataInstance; }; exports = module.exports = FieldModel; },{"../utils/convert":19,"lodash":32}],5:[function(require,module,exports){ /* 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 {PropertiesModelTemplate_Form} PropertiesModelTemplate_Form */ /** * @class * @classdesc Normalizes the model for use in the property form * 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} propertiesModel - the raw properties model from the component config */ function PropertiesModelTemplate_Form(componentTemplate, propertiesModel) { /** @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 {object} */ this._modelTemplate = {}; this.buildTemplate(componentTemplate, propertiesModel); } /** * @private * @param {import('./componentTemplate').ComponentTemplate} componentTemplate * @param {object} propertiesModel - the raw properties model from the component config */ PropertiesModelTemplate_Form.prototype.buildTemplate = function(componentTemplate, propertiesModel) { var modelConfig = Cloner.deepClone(propertiesModel || {}); var model = _.extend({}, modelConfig); if(modelConfig.fields && modelConfig.fields.length){ modelConfig.fields.unshift({ control:'html', value:'<div class="jsharmony_cms">',captionclass:'hidden'}); modelConfig.fields.push({ control:'html', value:'</div>',captionclass:'hidden'}); } this._modelTemplate = model; model.title = modelConfig.title ? modelConfig.title : 'Configure ' + componentTemplate.getCaptions()[0]; model.unbound = true; model.layout = 'form'; model.onecolumn = true; if(model.js && _.isString(model.js) && model.js.trim()) model.js = '(function(){ var cms = '+this._cms._instance+';' + model.js + ' })();'; this._jsh.XPage.ParseModelDefinition(model, null, null, { ignoreErrors: true }); }; /** * @public */ PropertiesModelTemplate_Form.prototype.getModelInstance = function() { var model = Cloner.deepClone(this._modelTemplate); model.id = PropertiesModelTemplate_Form.getNextInstanceId(this._componentTemplate); return model; }; /** * Get a unique ID for the model instance * @private * @returns {string} */ PropertiesModelTemplate_Form.getNextInstanceId = function(componentTemplate) { if (PropertiesModelTemplate_Form._id == undefined) PropertiesModelTemplate_Form._id = 0; var id = PropertiesModelTemplate_Form._id++; return 'PropertiesModel_Form_' + componentTemplate.getClassName() + '_' + id; }; /** * 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. * @returns {Object} a copy of the dataInstance with type conversions done and extraneous * properties removed. */ PropertiesModelTemplate_Form.prototype.makePristineCopy = function(dataInstance) { return FieldModel.makePristineCopy(dataInstance, this._modelTemplate.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 */ PropertiesModelTemplate_Form.prototype.populateDataInstance = function(dataInstance) { return FieldModel.populateDataInstance(dataInstance, this._modelTemplate.fields || []); }; exports = module.exports = PropertiesModelTemplate_Form; },{"../utils/cloner":18,"./fieldModel":4,"lodash":32}],6:[function(require,module,exports){ /* 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 DialogResizer = require('./dialogResizer'); var OverlayService = require('./overlayService'); /** * @typedef {Object} DialogConfig * @property {(boolean | undefined)} closeOnBackdropClick - Set true to close the dialog * when the background is clicked * @property {(string | undefined)} dialogId - set this to override the assigned unique ID for the dialog. * There is no need to set this. If it is set, it must be globally unique among ALL dialogs. * @property {(number | undefined)} maxHeight - set the max height (pixels) of the form if defined * @property {(number | undefined)} maxWidth - set the max width (pixels) of the form if defined * @property {(number | undefined)} minHeight - set the min height (pixels) of the form if defined * @property {(number | undefined)} minWidth - set the min width (pixels) of the form if defined * @property {(number | undefined)} width - set the width (pixels) of the form if defined * @property {(number | undefined)} height - set the height (pixels) of the form if defined * @property {(string | undefined)} cssClass - space delimited list of classes to add to the dialog element */ /** * Called when the dialog wants to accept/save the changes * @callback Dialog~acceptCallback * @param {Function} successFunc - Call this function if successfully accepted (e.g., no data errors; valid save). */ /** * Called when the dialog is first opened * @callback Dialog~beforeOpenCallback * @param {Object} xmodel - the JSH model instance * @param {Function} onComplete - Should be called by handler when complete */ /** * Called when the dialog wants to cancel/close without saving * @callback Dialog~cancelCallback * @param {Object} options * @returns {boolean} */ /** * Called when the dialog closes * @callback Dialog~closeCallback * @param {Object} options */ /** * Called when the dialog is first opened * @callback Dialog~openedCallback * @param {JQuery} dialogWrapper - the dialog wrapper element * @param {Object} xmodel - the JSH model instance * @param {Function} acceptFunc - Call this function to trigger accept logic * @param {Function} cancelFunc - Call this function to trigger cancel logic */ /** * @class * @param {Object} jsh * @param {Object} cms * @param {Object} model - the model that will be loaded into the virtual model * @param {DialogConfig} config - the dialog configuration */ function Dialog(jsh, cms, model, config) { this._jsh = jsh; this._cms = cms; this._model = model; this._id = config.dialogId ? config.dialogId : this.getNextId(); /** @type {DialogConfig} */ this._config = config || {}; this._$wrapper = this.makeDialog(this._id, this._config); this._destroyed = false; this.overlayService = new OverlayService(this); this._jsh.$(this._jsh.root).append(this._$wrapper); /** * @public * @type {Dialog~acceptCallback} */ this.onAccept = undefined; /** * @public * @type {Dialog~beforeOpenCallback} */ this.onBeforeOpen = undefined; /** * @public * @type {Dialog~cancelCallback} */ this.onCancel = undefined; /** * @public * @type {Dialog~closeCallback} */ this.onClose = undefined; /** * @public * @type {Dialog~openedCallback} */ this.onOpened = undefined; } /** * Used to keep track of dialog IDs to * ensure IDs are unique. * @type {Object.<string, boolean>} */ Dialog._idLookup = {}; /** * Call when dialog is closed. * Dialog is no longer usable after this is called. * @private */ Dialog.prototype.destroy = function() { this._$wrapper.remove(); if (this._$overlay) this._$overlay.remove(); delete Dialog._idLookup[this._id]; this._destroyed = true; }; /** * Get a CSS selector that can be used to find * the wrapper element. * @public * @returns {string} */ Dialog.prototype.getFormSelector = function() { return '.xdialogbox.' + this._id; }; /** * Get a globally unique (W.R.T this dialog class) * ID to be used for the current dialog instance * @private * @returns {string} */ Dialog.prototype.getNextId = function() { var id = undefined; do { id = 'jsharmony_component_dialog_uid_' + Math.random().toString().replace('.', ''); if (Dialog._idLookup[id]) { id = undefined; } } while (!id); Dialog._idLookup[id] = true; return id; }; /** * Get the scroll top position for the page. * @private * @param {JQuery} $wrapper * @returns {number} */ Dialog.prototype.getScrollTop = function($wrapper) { return $wrapper.scrollParent().scrollTop(); }; /** * @private */ Dialog.prototype.load = function(callback) { var _this = this; this._jsh.XPage.LoadVirtualModel(_this._jsh.$(_this.getFormSelector()), this._model, function(xmodel) { callback(xmodel); }); }; /** * Create the dialog elements and append to the body DOM. * @private * @param {string} id - the ID that uniquely identifies the dialog * @param {DialogConfig} config */ Dialog.prototype.makeDialog = function(id, config) { var $form = this._jsh.$('<div class="xdialogbox"></div>') .addClass(this._id) .attr('id', this._id) .addClass(config.cssClass || ''); if(config.maxWidth) $form.css('max-width', _.isNumber(config.maxWidth) ? config.maxWidth + 'px' : null); if(config.maxHeight) $form.css('max-height', _.isNumber(config.maxHeight) ? config.maxHeight + 'px' : null); if(config.minWidth) $form.css('min-width', _.isNumber(config.minWidth) ? config.minWidth + 'px' : null); if(config.minHeight) $form.css('min-height', _.isNumber(config.minHeight) ? config.minHeight + 'px' : null); if(config.height) $form.css('height', _.isNumber(config.height) ? config.height + 'px' : null); if(config.width) $form.css('width', _.isNumber(config.width) ? config.width + 'px' : null); var $wrapper = this._jsh.$('<div style="display: none;" class="xdialogbox-wrapper"></div>') .attr('id', id) .append($form); return $wrapper; }; /** * @public */ Dialog.prototype.open = function() { if (this._destroyed) { throw new Error('Dialog ' + this._id + ' has already been destroyed.'); } var _this = this; var formSelector = this.getFormSelector(); var oldActive = document.activeElement; this.load(function(xmodel) { var $wrapper = _this._jsh.$(formSelector); _this.registerLovs(xmodel); var lastScrollTop = 0; _this._jsh.XExt.execif(_this.onBeforeOpen, function(f){ _this.onBeforeOpen(xmodel, f); }, function(){ /** @type {DialogResizer} */ var dialogResizer = undefined; _this._jsh.XExt.CustomPrompt(formSelector, _this._jsh.$(formSelector), function(acceptFunc, cancelFunc) { //onInit _this.overlayService.pushDialog($wrapper); lastScrollTop = _this.getScrollTop($wrapper); dialogResizer = new DialogResizer($wrapper[0], _this._jsh); if (_.isFunction(_this.onOpened)) _this.onOpened($wrapper, xmodel, acceptFunc, cancelFunc); }, function(success) { //onAccept if (_.isFunction(_this.onAccept)) _this.onAccept(success); }, function(options) { //onCancel if (_.isFunction(_this.onCancel)) return _this.onCancel(options); return false; }, function() { //onClosed dialogResizer.closeDialog(); if(_.isFunction(_this.onClose)) _this.onClose(); _this.destroy(); _this.overlayService.popDialog(); }, { reuse: false, backgroundClose: _this._config.closeOnBackdropClick, restoreFocus: false, onClosing: function(cb){ if (oldActive) oldActive.focus(); _this.setScrollTop(lastScrollTop, $wrapper); return cb(); } } ); } ); }); }; /** * Register the LOVs defined in the model. * @private * @param {Object} xmodel */ Dialog.prototype.registerLovs = function(xmodel) { _.forEach(this._model.fields, function(field) { if (field.type == undefined || field.lov == undefined) return; var lovs = undefined; if (_.isArray(field.lov.values)) { lovs = field.lov.values; } else if (_.isObject(field.lov.values)) { lovs = _.map(_.toPairs(field.lov.values), function(kvp) { return { code_val: kvp[0], code_txt: kvp[1] }; }); } if (lovs) { xmodel.controller.setLOV(field.name, lovs); } }); }; /** * Set the scroll top position for the page