diff --git a/src/plugins/index.js b/src/plugins/index.js index 02b8afe..687dac3 100644 --- a/src/plugins/index.js +++ b/src/plugins/index.js @@ -1,3 +1,5 @@ +import viewFormProperties from "./web_view_form/FNAbviewform.js"; +import viewFormEditor from "./web_view_form/FNAbviewformEditor.js"; import viewCarouselProperties from "./web_view_carousel/FNAbviewcarousel.js"; import viewCarouselEditor from "./web_view_carousel/FNAbviewcarouselEditor.js"; import viewCommentProperties from "./web_view_comment/FNAbviewcomment.js"; @@ -38,6 +40,8 @@ const AllPlugins = [ viewTabEditor, viewTextProperties, viewTextEditor, + viewFormProperties, + viewFormEditor, ]; export default { diff --git a/src/plugins/web_view_form/FNAbviewform.js b/src/plugins/web_view_form/FNAbviewform.js new file mode 100644 index 0000000..9f21dc7 --- /dev/null +++ b/src/plugins/web_view_form/FNAbviewform.js @@ -0,0 +1,783 @@ +export default function FNAbviewformProperties({ + AB, + ABViewPropertiesPlugin, + // ABUIPlugin, +}) { + const FABViewContainer = + require("../../rootPages/Designer/properties/views/ABViewContainer").default; + const FABViewRuleListFormRecordRules = + require("../../rootPages/Designer/properties/rules/ABViewRuleListFormRecordRules").default; + const FABViewRuleListFormSubmitRules = + require("../../rootPages/Designer/properties/rules/ABViewRuleListFormSubmitRules").default; + + const ABViewContainer = FABViewContainer(AB); + const uiConfig = AB.Config.uiSettings(); + const L = ABViewContainer.L(); + + var ABViewFormPropertyComponentDefaults = {}; + + const base = "properties_abview_form"; + + const PopupRecordRule = FABViewRuleListFormRecordRules( + AB, + `${base}_popupRecordRule` + ); + + const PopupSubmitRule = FABViewRuleListFormSubmitRules( + AB, + `${base}_popupSubmitRule` + ); + + + +return class ABAbviewformProperties extends ABViewPropertiesPlugin { + +static getPluginKey() { + return this.key; + } + +static getPluginType() { + return "properties-view"; + // properties-view : will display in the properties panel of the ABDesigner + } + + + + + constructor(b = null, id = null) { + b = b || base; + id = Object.assign(id || {}, { + // Put our ids here + datacollection: "", + fields: "", + showLabel: "", + labelPosition: "", + labelWidth: "", + height: "", + clearOnLoad: "", + clearOnSave: "", + + buttonSubmitRules: "", + buttonRecordRules: "", + }); + super(b, id); + + this.AB = AB; + ABViewFormPropertyComponentDefaults = + this.AB.ClassManager.viewClass("form").defaultValues(); + } + + static get key() { + return "form"; + } + + ui(elements = null) { + let ids = this.ids; + + elements = elements || []; + + return super.ui( + [ + { + id: ids.datacollection, + view: "richselect", + name: "datacollection", + label: L("Datacollection"), + labelWidth: uiConfig.labelWidthLarge, + skipAutoSave: true, + on: { + onChange: (newId, oldId) => { + this.selectSource(newId, oldId); + }, + }, + }, + + { + view: "fieldset", + label: L("Form Fields:"), + labelWidth: uiConfig.labelWidthLarge, + body: { + type: "clean", + padding: 10, + rows: [ + { + id: ids.fields, + view: "list", + name: "fields", + + select: false, + minHeight: 200, + template: (...params) => { + return this.listTemplate(...params); + }, + type: { + markCheckbox: function (item) { + return ( + "" + ); + }, + }, + onClick: { + check: (...params) => { + return this.check(...params); + }, + }, + }, + ], + }, + }, + { + id: ids.showLabel, + name: "showLabel", + view: "checkbox", + label: L("Display Label"), + labelWidth: uiConfig.labelWidthLarge, + click: () => { + this.onChange(); + }, + }, + { + id: ids.labelPosition, + view: "richselect", + name: "labelPosition", + + label: L("Label Position"), + labelWidth: uiConfig.labelWidthLarge, + options: [ + { + id: "left", + value: L("Left"), + }, + { + id: "top", + value: L("Top"), + }, + ], + on: { + onChange: () => { + this.onChange(); + }, + }, + }, + { + id: ids.labelWidth, + view: "counter", + name: "labelWidth", + + label: L("Label Width"), + labelWidth: uiConfig.labelWidthLarge, + on: { + onChange: () => { + this.onChange(); + }, + }, + }, + { + id: ids.height, + view: "counter", + name: "height", + label: L("Height"), + labelWidth: uiConfig.labelWidthLarge, + on: { + onChange: () => { + this.onChange(); + }, + }, + }, + { + id: ids.clearOnLoad, + view: "checkbox", + name: "clearOnLoad", + + label: L("Clear on load"), + labelWidth: uiConfig.labelWidthLarge, + on: { + onChange: () => { + this.onChange(); + }, + }, + }, + { + id: ids.clearOnSave, + view: "checkbox", + name: "clearOnSave", + label: L("Clear on save"), + labelWidth: uiConfig.labelWidthLarge, + on: { + onChange: () => { + this.onChange(); + }, + }, + }, + { + view: "fieldset", + label: L("Rules:"), + labelWidth: uiConfig.labelWidthLarge, + body: { + type: "clean", + padding: 10, + rows: [ + { + cols: [ + { + view: "label", + label: L("Submit Rules:"), + width: uiConfig.labelWidthLarge, + }, + { + id: ids.buttonSubmitRules, + view: "button", + css: "webix_primary", + name: "buttonSubmitRules", + label: L("Settings"), + icon: "fa fa-gear", + type: "icon", + badge: 0, + click: () => { + this.submitRuleShow(); + }, + }, + ], + }, + // { + // cols: [ + // { + // view: "label", + // label: L("Display Rules:"), + // width: uiConfig.labelWidthLarge, + // }, + // { + // view: "button", + // name: "buttonDisplayRules", + // css: "webix_primary", + // label: L("Settings"), + // icon: "fa fa-gear", + // type: "icon", + // badge: 0, + // click: () => { + // this.displayRuleShow(); + // }, + // }, + // ], + // }, + { + cols: [ + { + view: "label", + label: L("Record Rules:"), + width: uiConfig.labelWidthLarge, + }, + { + id: ids.buttonRecordRules, + view: "button", + name: "buttonRecordRules", + css: "webix_primary", + label: L("Settings"), + icon: "fa fa-gear", + type: "icon", + badge: 0, + click: () => { + this.recordRuleShow(); + }, + }, + ], + }, + ], + }, + }, + ].concat(elements) + ); + } + + async init(AB) { + super.init(AB); + + webix.extend($$(this.ids.component), webix.ProgressBar); + + let allInits = []; + allInits.push(PopupRecordRule.init(AB)); + PopupRecordRule.on("save", () => { + this.onChange(); + this.populateBadgeNumber(); + }); + allInits.push(PopupSubmitRule.init(AB)); + PopupSubmitRule.on("save", (/* settings */) => { + this.onChange(); + this.populateBadgeNumber(); + }); + + return Promise.all(allInits); + } + + populate(view) { + super.populate(view); + let ids = this.ids; + if (!view) return; + + let formCom = view.parentFormComponent(); + let datacollectionId = formCom.settings.dataviewID + ? formCom.settings.dataviewID + : null; + var SourceSelector = $$(ids.datacollection); + + // Pull data collections to options + var dcOptions = view.application + .datacollectionsIncluded() + .filter((dc) => { + const obj = dc.datasource; + return ( + dc.sourceType == "object" && + !obj?.isImported && + !obj?.isReadOnly + ); + }) + .map((d) => { + let entry = { id: d.id, value: d.label }; + if (d.sourceType == "query") { + entry.icon = "fa fa-filter"; + } else { + entry.icon = "fa fa-database"; + } + return entry; + }); + SourceSelector.define("options", dcOptions); + SourceSelector.define("value", datacollectionId); + SourceSelector.refresh(); + + this.propertyUpdateFieldOptions(datacollectionId); + + // update properties when a field component is deleted + view.views().forEach((v) => { + if (v.isFormField) + v.once("destroyed", () => this.populate(view)); + }); + + SourceSelector.enable(); + $$(ids.showLabel).setValue(view.settings.showLabel); + $$(ids.labelPosition).setValue( + view.settings.labelPosition || + ABViewFormPropertyComponentDefaults.labelPosition + ); + $$(ids.labelWidth).setValue( + view.settings.labelWidth || + ABViewFormPropertyComponentDefaults.labelWidth + ); + $$(ids.height).setValue( + view.settings.height || ABViewFormPropertyComponentDefaults.height + ); + $$(ids.clearOnLoad).setValue( + view.settings.clearOnLoad || + ABViewFormPropertyComponentDefaults.clearOnLoad + ); + $$(ids.clearOnSave).setValue( + view.settings.clearOnSave || + ABViewFormPropertyComponentDefaults.clearOnSave + ); + + // NOTE: load the object and view BEFORE the .fromSettings(); + PopupRecordRule.objectLoad(this.CurrentObject); + PopupRecordRule.viewLoad(view); + PopupRecordRule.fromSettings(view.settings.recordRules || []); + + PopupSubmitRule.objectLoad(this.CurrentObject); + PopupSubmitRule.viewLoad(view); + PopupSubmitRule.fromSettings(view.settings.submitRules || []); + + // this.propertyUpdateRules(ids, view, datacollectionId); + this.populateBadgeNumber(); + + // when a change is made in the properties the popups need to reflect the change + this.updateEventIds = this.updateEventIds || {}; // { viewId: boolean, ..., viewIdn: boolean } + if (!this.updateEventIds[view.id]) { + this.updateEventIds[view.id] = true; + + view.addListener("properties.updated", () => { + this.populateBadgeNumber(); + }); + } + } + + defaultValues() { + let values = {}; + var ViewClass = this.ViewClass(); + if (ViewClass) { + values = ViewClass.defaultValues(); + } + return values; + } + + /** + * @method values + * return the values for this form. + * @return {obj} + */ + values() { + let ids = this.ids; + let vals = super.values(); + + vals.settings = vals.settings || {}; + + vals.settings.dataviewID = $$(ids.datacollection).getValue(); + vals.settings.showLabel = $$(ids.showLabel).getValue(); + vals.settings.labelPosition = + $$(ids.labelPosition).getValue() || + ABViewFormPropertyComponentDefaults.labelPosition; + vals.settings.labelWidth = + $$(ids.labelWidth).getValue() || + ABViewFormPropertyComponentDefaults.labelWidth; + vals.settings.height = $$(ids.height).getValue(); + vals.settings.clearOnLoad = $$(ids.clearOnLoad).getValue(); + vals.settings.clearOnSave = $$(ids.clearOnSave).getValue(); + + vals.settings.recordRules = PopupRecordRule.toSettings(); + vals.settings.submitRules = PopupSubmitRule.toSettings(); + return vals; + } + + /** + * @method FieldClass() + * A method to return the proper ABViewXXX Definition. + * NOTE: Must be overwritten by the Child Class + */ + ViewClass() { + return super._ViewClass("form"); + } + + // + // + // + + busy() { + $$(this.ids.component)?.showProgress?.({ type: "icon" }); + } + + check(e, fieldId) { + if (this._isBusy) return; + this._isBusy = true; + this.busy(); + const ids = this.ids; + let currView = this.CurrentView; + let formView = currView.parentFormComponent(); + + // update UI list + let item = $$(ids.fields).getItem(fieldId); + item.selected = item.selected ? 0 : 1; + $$(ids.fields).updateItem(fieldId, item); + + let doneFn = () => { + this._isBusy = false; + formView + .refreshDefaultButton(ids) + .save() + .then(() => { + // refresh UI + currView.emit("properties.updated", currView); + this.onChange(); + this.ready(); + }); + + // // trigger a save() + // this.propertyEditorSave(ids, currView); + }; + + // add a field to the form + if (item.selected) { + // Check for duplication + let exists = formView + .fieldComponents() + .find((c) => c.settings.fieldId == fieldId); + if (exists) { + this._isBusy = false; + this.ready(); + return; + } + + let fieldView = formView.addFieldToForm(item); + if (fieldView) { + fieldView.save().then(() => { + fieldView.once("destroyed", () => this.populate(currView)); + formView.viewInsert(fieldView).then(() => { + doneFn(); + }); + }); + } else { + this._isBusy = false; + this.ready(); + } + } + // remove field in the form + else { + let fieldView = formView + .fieldComponents() + .filter((c) => c.settings.fieldId == fieldId)[0]; + if (fieldView) { + // let remainingViews = formView.views(c => c.settings.fieldId != fieldId); + // formView._views = remainingViews; + + fieldView.destroy(); + formView.viewRemove(fieldView).then(() => { + doneFn(); + }); + } else { + this._isBusy = false; + this.ready(); + } + } + } + + populateBadgeNumber(/* */) { + const ids = this.ids; + + let view = this.CurrentView; + + if (!view) return; + + if (view.settings.submitRules) { + $$(ids.buttonSubmitRules).define( + "badge", + view.settings.submitRules.length || null + ); + } else { + $$(ids.buttonSubmitRules).define("badge", null); + } + $$(ids.buttonSubmitRules).refresh(); + + // if (view.settings.displayRules) { + // $$(ids.buttonDisplayRules).define( + // "badge", + // view.settings.displayRules.length || null + // ); + // $$(ids.buttonDisplayRules).refresh(); + // } else { + // $$(ids.buttonDisplayRules).define("badge", null); + // $$(ids.buttonDisplayRules).refresh(); + // } + + if (view.settings.recordRules) { + $$(ids.buttonRecordRules).define( + "badge", + view.settings.recordRules.length || null + ); + } else { + $$(ids.buttonRecordRules).define("badge", null); + } + $$(ids.buttonRecordRules).refresh(); + } + + ready() { + $$(this.ids.component)?.hideProgress?.(); + } + + recordRuleShow() { + PopupRecordRule.fromSettings(this.CurrentView.settings.recordRules); + PopupRecordRule.show(); + } + + submitRuleShow() { + PopupSubmitRule.fromSettings(this.CurrentView.settings.submitRules); + PopupSubmitRule.show(); + } + + listTemplate(field, common) { + let currView = this.CurrentView; + + // disable in form + var fieldComponent = field.formComponent(); + if (fieldComponent == null) + return ` ${field.label}
Disable
`; + + var componentKey = fieldComponent.common().key; + var formComponent = currView.application.viewAll( + (v) => v.common().key == componentKey + )[0]; + + return `${common.markCheckbox(field)} ${ + field.label + }
${ + formComponent ? L(formComponent.common().labelKey) : "" + }
`; + } + + /** + * @method propertyUpdateFieldOptions + * Populate fields of object to select list in property + * + * @param {ABViewForm} view - the current component + * @param {string} dcId - id of ABDatacollection + */ + propertyUpdateFieldOptions(dcId) { + const ids = this.ids; + var formComponent = this.CurrentView.parentFormComponent(); + var existsFields = formComponent.fieldComponents(); + var datacollection = this.AB.datacollectionByID(dcId); + var object = datacollection ? datacollection.datasource : null; + + // Pull field list + var fieldOptions = []; + if (object != null) { + fieldOptions = object.fields().map((f) => { + f.selected = + existsFields.filter((com) => { + return f.id == com.settings.fieldId; + }).length > 0; + + return f; + }); + + this.objectLoad(object); + } + + $$(ids.fields).clearAll(); + $$(ids.fields).parse(fieldOptions); + } + + refreshDefaultButton() { + const ids = this.ids; + const ABViewFormButton = + this.AB.ClassManager.viewClass("button"); + + // If default button is not exists, then skip this + let defaultButton = this.views( + (v) => v instanceof ABViewFormButton && v.settings.isDefault + )[0]; + + // Add a default button + if (defaultButton == null) { + defaultButton = ABViewFormButton.newInstance( + this.application, + this + ); + defaultButton.settings.isDefault = true; + } + // Remove default button from array, then we will add it to be the last item later (.push) + else { + this._views = this.views( + (v) => !(v instanceof ABViewFormButton) && !v.settings.isDefault + ); + } + + // Calculate position Y of the default button + let yList = this.views().map((v) => (v.position.y || 0) + 1); + yList.push(this._views.length || 0); + yList.push($$(ids.fields).length || 0); + let posY = Math.max(...yList); + + // Update to be the last item + defaultButton.position.y = posY; + + // Keep the default button is always the last item of array + this._views.push(defaultButton); + + return defaultButton; + } + + selectSource(dcId /*, oldDcId */) { + // TODO : warning message + const ids = this.ids; + this.busy(); + + let currView = this.CurrentView; + let formView = currView.parentFormComponent(); + + currView.settings.dataviewID = dcId; + + // clear sub views + var viewsToRemove = currView._views; + currView._views = []; + + return ( + Promise.resolve() + .then(() => { + var allRemoves = []; + viewsToRemove.forEach((v) => { + allRemoves.push(v.destroy()); + }); + return Promise.all(allRemoves); + }) + // .then(() => { + // // remove all old components + // let destroyTasks = []; + // if (oldDcId != null) { + // let oldComps = formView.views(); + // oldComps.forEach(child => destroyTasks.push(() => child.destroy())); + // } + + // return destroyTasks.reduce((promiseChain, currTask) => { + // return promiseChain.then(currTask); + // }, Promise.resolve([])); + // }) + .then(() => { + // refresh UI + // formView.emit('properties.updated', currView); + + // Update field options in property + this.propertyUpdateFieldOptions(dcId); + + // add all fields to editor by default + if (currView._views.length > 0) return Promise.resolve(); + + let saveTasks = []; + let fields = $$(ids.fields).find({}); + fields.reverse(); + fields.forEach((f, index) => { + if (!f.selected) { + let yPosition = fields.length - index - 1; + + // Add new form field + let newFieldView = currView.addFieldToForm( + f, + yPosition + ); + if (newFieldView) { + newFieldView.once("destroyed", () => + this.populate(currView) + ); + + // // Call save API + saveTasks.push(newFieldView.save()); + } + + // update item to UI list + f.selected = 1; + $$(ids.fields).updateItem(f.id, f); + } + }); + + let defaultButton = formView.refreshDefaultButton(ids); + if (defaultButton) saveTasks.push(defaultButton.save()); + + return Promise.all(saveTasks); + }) + // Saving + .then(() => { + //// NOTE: the way the .addFieldToForm() works, it will prevent + //// the typical field.save() -> triggering the form.save() on a + //// new Field. So once all our field.saves() are finished, we + //// need to perform a form.save() to persist the changes. + return currView.save(); + }) + // Finally + .then(() => { + // refresh UI + formView.emit("properties.updated", currView); + + // Update field options in property + // this.propertyUpdateRules(ids, currView, dcId); + + this.ready(); + this.onChange(); + return Promise.resolve(); + }) + ); + } + } + + + + +} + diff --git a/src/plugins/web_view_form/FNAbviewformEditor.js b/src/plugins/web_view_form/FNAbviewformEditor.js new file mode 100644 index 0000000..688bfcf --- /dev/null +++ b/src/plugins/web_view_form/FNAbviewformEditor.js @@ -0,0 +1,61 @@ +export default function FNAbviewformEditor({ AB, ABViewEditorPlugin }) { + const FABViewContainer = + require("../../rootPages/Designer/editors/views/ABViewContainer").default; + + const ABViewContainer = FABViewContainer(AB); + // var L = UIClass.L(); + // var L = ABViewContainer.L(); + + return class ABAbviewformEditor extends ABViewContainer { + + static getPluginKey() { + return this.key; + } + + /** + * @method getPluginType + * return the plugin type for this editor. + * plugin types are how our ClassManager knows how to store + * the plugin. + * @return {string} plugin type + */ + static getPluginType() { + return "editor-view"; + // editor-view : will display in the editor panel of the ABDesigner + } + + + + + static get key() { + return "form"; + } + + constructor(view, base = "interface_editor_viewform") { + // base: {string} unique base id reference + + super(view, base); + + // this.component = this.view.component(); + } + + ui() { + let _ui = super.ui(); + _ui.rows[0].cellHeight = 75; + return _ui; + } + + init(AB) { + this.AB = AB; + return super.init(AB); + } + + detatch() { + this.component?.detatch?.(); + } + + onShow() { + this.component?.onShow?.(); + } + }; +} diff --git a/src/rootPages/Designer/editors/EditorManager.js b/src/rootPages/Designer/editors/EditorManager.js index c81903b..ec62b0e 100644 --- a/src/rootPages/Designer/editors/EditorManager.js +++ b/src/rootPages/Designer/editors/EditorManager.js @@ -27,7 +27,7 @@ export default function (AB) { require("./views/ABViewDataview"), require("./views/ABViewDetail"), require("./views/ABViewDocxBuilder"), - require("./views/ABViewForm"), + // require("./views/ABViewForm"), require("./views/ABViewFormUrl"), require("./views/ABViewGantt"), require("./views/ABViewGrid"), diff --git a/src/rootPages/Designer/properties/PropertyManager.js b/src/rootPages/Designer/properties/PropertyManager.js index 2a0bee7..6bb78ff 100644 --- a/src/rootPages/Designer/properties/PropertyManager.js +++ b/src/rootPages/Designer/properties/PropertyManager.js @@ -92,7 +92,7 @@ export default function (AB) { require("./views/ABViewDetailText"), require("./views/ABViewDetailTree"), require("./views/ABViewDocxBuilder"), - require("./views/ABViewForm"), + // require("./views/ABViewForm"), require("./views/ABViewFormButton"), require("./views/ABViewFormCheckbox"), require("./views/ABViewFormConnect"), diff --git a/src/rootPages/Designer/ui_work_interface_workspace_editor_layout.js b/src/rootPages/Designer/ui_work_interface_workspace_editor_layout.js index 94586ed..0f797d7 100644 --- a/src/rootPages/Designer/ui_work_interface_workspace_editor_layout.js +++ b/src/rootPages/Designer/ui_work_interface_workspace_editor_layout.js @@ -287,7 +287,8 @@ export default function (AB) { } if (idDashboard) { const $dashboard = $$(idDashboard); - if ($dashboard) $dashboard.clearAll(); + if ($dashboard && typeof $dashboard.clearAll === "function") + $dashboard.clearAll(); } // add the editorUI if it is not already added