diff --git a/src/rootPages/Designer/editors/EditorManager.js b/src/rootPages/Designer/editors/EditorManager.js index ccc29e69..9b6c1f57 100644 --- a/src/rootPages/Designer/editors/EditorManager.js +++ b/src/rootPages/Designer/editors/EditorManager.js @@ -6,7 +6,7 @@ */ import _ABViewDefault from "./views/_ABViewDefault"; -export default function (AB) { +export default function(AB) { const Editors = []; // {array} // All the ABField Component Inerfaces available. @@ -34,7 +34,6 @@ export default function (AB) { require("./views/ABViewLabel"), require("./views/ABViewLayout"), require("./views/ABViewMenu"), - require("./views/ABViewOrgChart"), require("./views/ABViewPage"), require("./views/ABViewPDFImporter"), require("./views/ABViewPivot"), @@ -59,7 +58,7 @@ export default function (AB) { * A filter for limiting which editor you want. * @return [{ClassUI(Editor1)}, {ClassUI(Editor2)}, ...] */ - editors: function (f = () => true) { + editors: function(f = () => true) { return Editors.filter(f); }, }; diff --git a/src/rootPages/Designer/editors/views/ABViewOrgChart.js b/src/rootPages/Designer/editors/views/ABViewOrgChart.js deleted file mode 100644 index 8ffb0774..00000000 --- a/src/rootPages/Designer/editors/views/ABViewOrgChart.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * ABViewOrgChartEditor - * The widget that displays the UI Editor Component on the screen - * when designing the UI. - */ -var myClass = null; -// {singleton} -// we will want to call this factory fn() repeatedly in our imports, -// but we only want to define 1 Class reference. - -import FABViewDefault from "./_ABViewDefault"; - -export default function (AB) { - if (!myClass) { - const ABViewDefault = FABViewDefault(AB); - // var L = UIClass.L(); - // var L = ABViewContainer.L(); - - myClass = class ABViewOrgChartEditor extends ABViewDefault { - static get key() { - return "orgchart"; - } - - constructor(view, base = "interface_editor_viewOrgChart") { - // base: {string} unique base id reference - - super(view, base); - - // this.component = this.view.component(); - } - - ui() { - let _ui = super.ui(); - return _ui; - } - - init(AB) { - this.AB = AB; - return super.init(AB); - } - - detatch() { - this.component?.detatch?.(); - } - - onShow() { - this.component?.onShow?.(); - } - }; - } - - return myClass; -} diff --git a/src/rootPages/Designer/properties/PropertyManager.js b/src/rootPages/Designer/properties/PropertyManager.js index 2ecaf3ed..296684c9 100644 --- a/src/rootPages/Designer/properties/PropertyManager.js +++ b/src/rootPages/Designer/properties/PropertyManager.js @@ -8,7 +8,7 @@ import ABView from "./views/ABView"; var PropertyMgr = null; -export default function (AB) { +export default function(AB) { if (!PropertyMgr) { var Fields = []; // {array} @@ -110,7 +110,6 @@ export default function (AB) { require("./views/ABViewLayout"), require("./views/ABViewList"), require("./views/ABViewMenu"), - require("./views/ABViewOrgChart"), require("./views/ABViewPage"), require("./views/ABViewPDFImporter"), require("./views/ABViewPivot"), @@ -161,19 +160,19 @@ export default function (AB) { * A filter for limiting which fields you want. * @return [{ClassUI(Field1)}, {ClassUI(Field2)}, ...] */ - fields: function (f = () => true) { + fields: function(f = () => true) { return Fields.filter(f); }, - processElements: function (f = () => true) { + processElements: function(f = () => true) { return Processes.filter(f); }, - views: function (v = () => true) { + views: function(v = () => true) { return Views.filter(v); }, - mobileViews: function (v = () => true) { + mobileViews: function(v = () => true) { return MobileViews.filter(v); }, }; diff --git a/src/rootPages/Designer/properties/dataFields/ABFieldConnect.js b/src/rootPages/Designer/properties/dataFields/ABFieldConnect.js index c6ac0f7d..c981e262 100644 --- a/src/rootPages/Designer/properties/dataFields/ABFieldConnect.js +++ b/src/rootPages/Designer/properties/dataFields/ABFieldConnect.js @@ -31,6 +31,12 @@ export default function (AB) { indexField: "", indexField2: "", + netsuite_one: "", + netsuiteOneLabel: "", + netsuiteOneColumn: "", + + netsuite_many: "", + connectDataPopup: "", }); } @@ -237,6 +243,37 @@ export default function (AB) { }, }, }, + { + view: "layout", + id: ids.netsuite_one, + hidden: true, + cols: [ + { + id: ids.netsuiteOneLabel, + view: "label", + label: L(" [Select object]'s field"), + width: 300, + }, + { + id: ids.netsuiteOneColumn, + name: "netsuiteOneColumn", + disallowEdit: true, + view: "richselect", + // value: FC.defaultValues().linkViaType, + // width: 200, + fillspace: true, + options: [], + on: { + // onChange: (newV, oldV) => { + // this.selectLinkViaType(newV, oldV); + // }, + onAfterRender: function () { + ABField.CYPRESS_REF(this); + }, + }, + }, + ], + }, ]); } @@ -295,6 +332,7 @@ export default function (AB) { $fieldLink2.refresh(); this.updateCustomIndex(); + this.checkNetsuiteObjects(); } show() { @@ -387,13 +425,19 @@ export default function (AB) { const options = []; // if an ABApplication is set then load in the related objects const application = this.CurrentApplication; + + // if this is a Netsuite Object, just gather other Netsuite Objs + let objFilter = () => true; + if (this.CurrentObject.isNetsuite) { + objFilter = (o) => o.isNetsuite; + } if (application) { - application.objectsIncluded().forEach((o) => { + application.objectsIncluded(objFilter).forEach((o) => { options.push({ id: o.id, value: o.label }); }); } else { // else load in all the ABObjects - this.AB.objects().forEach((o) => { + this.AB.objects(objFilter).forEach((o) => { options.push({ id: o.id, value: o.label }); }); } @@ -449,6 +493,7 @@ export default function (AB) { $field.refresh(); this.updateCustomIndex(); + this.checkNetsuiteObjects(); } selectObjectTo(newValue, oldValue) { @@ -474,6 +519,8 @@ export default function (AB) { $$(ids.link2).show(); this.updateCustomIndex(); + + this.checkNetsuiteObjects(); } updateCustomIndex() { @@ -578,6 +625,62 @@ export default function (AB) { this.checkCustomFK(); } + + async checkNetsuiteObjects() { + let ids = this.ids; + if (!this.CurrentObject.isNetsuite) { + $$(ids.netsuite_one)?.hide(); + $$(ids.netsuite_many)?.hide(); + return; + } + + let linkType = $$(ids.linkType).getValue(); + let linkViaType = $$(ids.linkViaType).getValue(); + + if (linkType == "one") { + await this.updateNetsuiteOneUI(this.CurrentObject); + $$(ids.netsuite_one)?.show(); + $$(ids.netsuite_many)?.hide(); + } else if (linkViaType == "one") { + // fill label and Drop list with object in Droplist + let objID = $$(ids.linkObject).getValue(); + let connObj = this.AB.objectByID(objID); + if (!connObj) return; + await this.updateNetsuiteOneUI(connObj); + $$(ids.netsuite_one)?.show(); + $$(ids.netsuite_many)?.hide(); + } else { + // this is many:many + $$(ids.netsuite_one)?.hide(); + $$(ids.netsuite_many)?.show(); + } + } + + async updateNetsuiteOneUI(object) { + let ids = this.ids; + + // fill label and Drop list with Current Object + $$(ids.netsuiteOneLabel).setValue( + L(" {0}'s field", [object.label]) + ); + + let result = await this.AB.Network.get({ + url: `/netsuite/table/${object.tableName}`, + params: { + credentials: JSON.stringify(object.credentials), + }, + }); + let fields = result.filter((r) => r.type == "object"); + let options = fields.map((f) => { + return { + id: f.column, + value: f.column, + }; + }); + + $$(ids.netsuiteOneColumn).define("options", options); + $$(ids.netsuiteOneColumn).refresh(); + } } return ABFieldConnectProperty; diff --git a/src/rootPages/Designer/properties/views/ABViewOrgChart.js b/src/rootPages/Designer/properties/views/ABViewOrgChart.js deleted file mode 100644 index 5146e430..00000000 --- a/src/rootPages/Designer/properties/views/ABViewOrgChart.js +++ /dev/null @@ -1,359 +0,0 @@ -/* - * ABViewChart - * A Property manager for our ABViewChart definitions - */ - -import FABView from "./ABView"; - -export default function (AB) { - const BASE_ID = "properties_abview_org_chart"; - - const ABView = FABView(AB); - const uiConfig = AB.Config.uiSettings(); - const L = ABView.L(); - - class ABViewOrgChartProperty extends ABView { - constructor() { - super(BASE_ID, { - datacollectionID: "", - fields: "", - direction: "", - depth: "", - color: "", - pan: "", - zoom: "", - height: "", - export: "", - exportFilename: "", - }); - - this.AB = AB; - } - - static get key() { - return "orgchart"; - } - - ui() { - const ids = this.ids; - - return super.ui([ - { - id: ids.datacollectionID, - name: "datacollectionID", - view: "richselect", - label: L("Data Collection"), - labelWidth: uiConfig.labelWidthLarge, - options: [], - on: { - onChange: (value) => { - this.CurrentView.settings.datacollectionID = value; - this.populateSubValueFieldOptions( - this.CurrentView?.datacollection?.datasource - ); - this.onChange(); - }, - }, - }, - { - cols: [ - { - view: "label", - label: "Fields", - width: uiConfig.labelWidthLarge, - }, - { - id: ids.fields, - name: "fields", - view: "tree", - template: - "{common.icon()} {common.checkbox()} #value#", - select: false, - height: 200, - data: [], - on: { - onItemCheck: () => { - const fieldValues = $$(this.ids.fields).getChecked(); - this.refreshValueFieldOptions(fieldValues); - this.onChange(); - }, - }, - }, - ], - }, - { - id: ids.direction, - name: "direction", - view: "richselect", - label: L("Direction"), - labelWidth: uiConfig.labelWidthLarge, - options: [ - { id: "t2b", value: L("Top to Bottom") }, - { id: "b2t", value: L("Bottom to Top") }, - { id: "l2r", value: L("Left to Right") }, - { id: "r2l", value: L("Right to Left") }, - ], - on: { - onChange: () => { - this.onChange(); - }, - }, - }, - { - id: ids.depth, - name: "depth", - hidden: true, // NOTE: use choose Connect Fields option - view: "counter", - label: L("Depth"), - labelWidth: uiConfig.labelWidthLarge, - value: 0, - on: { - onChange: () => { - this.onChange(); - }, - }, - }, - { - id: ids.color, - name: "color", - view: "colorpicker", - label: L("Color"), - labelWidth: uiConfig.labelWidthLarge, - on: { - onChange: () => { - this.onChange(); - }, - }, - }, - { - hidden: true, // NOTE: does not support - id: ids.pan, - name: "pan", - view: "checkbox", - label: L("Pan"), - labelWidth: uiConfig.labelWidthLarge, - value: 0, - on: { - onChange: () => { - this.onChange(); - }, - }, - }, - { - hidden: true, // NOTE: does not support - id: ids.zoom, - name: "zoom", - view: "checkbox", - label: L("Zoom"), - labelWidth: uiConfig.labelWidthLarge, - value: 0, - on: { - onChange: () => { - this.onChange(); - }, - }, - }, - { - id: ids.height, - view: "counter", - name: "height", - label: L("Height"), - labelWidth: uiConfig.labelWidthLarge, - on: { - onChange: () => { - this.onChange(); - }, - }, - }, - { - hidden: true, // NOTE: does not support - view: "fieldset", - label: L("Export"), - body: { - view: "layout", - borderless: true, - rows: [ - { - id: ids.export, - name: "export", - view: "checkbox", - label: L("Is Exportable"), - labelWidth: uiConfig.labelWidthLarge, - value: 0, - on: { - onChange: () => { - this.onChange(); - }, - }, - }, - { - id: ids.exportFilename, - view: "text", - name: "exportFilename", - label: L("File name"), - placeholder: L("Enter file name"), - labelWidth: uiConfig.labelWidthLarge, - }, - ], - }, - }, - ]); - } - - async init(AB) { - this.AB = AB; - - await super.init(AB); - - webix.extend($$(this.ids.component), webix.ProgressBar); - } - - populate(view) { - super.populate(view); - - const ids = this.ids; - const $component = $$(ids.component); - const defaultValues = this.defaultValues(); - const values = Object.assign( - $component.getValues(), - Object.assign(defaultValues, view.settings) - ); - - const $fieldList = $$(ids.fields); - $fieldList.clearAll(); - - this.populateDatacollection(values.datacollectionId); - // this.populateDescriptionFieldOptions(values.columnDescription); - - const fieldValues = (view.settings?.fields ?? "").split(","); - this.refreshValueFieldOptions(fieldValues); - - $component.setValues(values); - } - - populateDatacollection(datacollectionId) { - const $dataCollection = $$(this.ids.datacollectionID); - - // Pull data collections to options - const dcOptions = this.CurrentView.application - .datacollectionsIncluded() - .map((d) => { - return { id: d.id, value: d.label }; - }); - $dataCollection.define("options", dcOptions); - $dataCollection.define("value", datacollectionId); - $dataCollection.refresh(); - } - - refreshValueFieldOptions(fieldValues = []) { - const ids = this.ids; - const view = this.CurrentView; - const $fieldList = $$(ids.fields); - - $fieldList.clearAll(); - - // Populate 1:M fields option of the root object - this.populateSubValueFieldOptions(view.datacollection?.datasource); - - // Populate sub 1:M fields option of each fields - fieldValues.forEach((fId) => { - if (!fId) return; - - const $fieldItem = $fieldList.getItem(fId); - if ($fieldItem) { - const abField = $fieldItem.field; - this.populateSubValueFieldOptions(abField.datasourceLink, fId); - } - }); - - // Set check items - $fieldList.blockEvent(); - fieldValues.forEach((fId) => { - if ($fieldList.exists(fId)) $fieldList.checkItem(fId); - }); - $fieldList.unblockEvent(); - } - - populateSubValueFieldOptions(object, parentFieldId) { - const view = this.CurrentView; - const $fields = $$(this.ids.fields); - - view.getValueFields(object).forEach((f, index) => { - if ($fields.exists(f.id)) return; - $fields.add( - { - id: f.id, - value: f.label, - field: f, - }, - index, - parentFieldId - ); - }); - - $fields.openAll(); - } - - // populateDescriptionFieldOptions(fieldId) { - // const valueField = this.CurrentView.valueField(); - // const $columnDescription = $$(this.ids.columnDescription); - - // const connectFieldOpts = - // valueField?.datasourceLink - // ?.fields?.((f) => f.key != "connectObject") - // .map?.((f) => { - // return { - // id: f.id, - // value: f.label, - // }; - // }) ?? []; - // $columnDescription.define("options", connectFieldOpts); - // $columnDescription.define("value", fieldId); - // $columnDescription.refresh(); - // } - - defaultValues() { - const ViewClass = this.ViewClass(); - - let values = null; - - if (ViewClass) { - values = ViewClass.defaultValues(); - } - - return values; - } - - /** - * @method values - * return the values for this form. - * @return {obj} - */ - values() { - const values = super.values(); - const ids = this.ids; - const $component = $$(ids.component); - - values.settings = Object.assign( - $component.getValues(), - values.settings - ); - - // Retrive the values of your properties from Webix and store them in the view - values.settings.fields = $$(ids.fields).getChecked().join(","); - - return values; - } - - /** - * @method FieldClass() - * A method to return the proper ABViewXXX Definition. - * NOTE: Must be overwritten by the Child Class - */ - ViewClass() { - return super._ViewClass("orgchart"); - } - } - - return ABViewOrgChartProperty; -} diff --git a/src/rootPages/Designer/ui_work_object_list_newObject.js b/src/rootPages/Designer/ui_work_object_list_newObject.js index 16a6cc11..94b870fa 100644 --- a/src/rootPages/Designer/ui_work_object_list_newObject.js +++ b/src/rootPages/Designer/ui_work_object_list_newObject.js @@ -22,6 +22,7 @@ import UIBlankObject from "./ui_work_object_list_newObject_blank"; import UICsvObject from "./ui_work_object_list_newObject_csv"; import UIApiObject from "./ui_work_object_list_newObject_api"; import UIImportObject from "./ui_work_object_list_newObject_import"; +import UINetsuiteObject from "./ui_work_object_list_newObject_netsuite"; // const ABImportExternal = require("./ab_work_object_list_newObject_external"); export default function (AB) { const UIClass = UI_Class(AB); @@ -42,6 +43,7 @@ export default function (AB) { this.CsvTab = UICsvObject(AB); this.ApiTab = UIApiObject(AB); this.ImportTab = UIImportObject(AB); + this.NetsuiteTab = UINetsuiteObject(AB); /* this.ExternalTab = new ABImportExternal(AB); */ @@ -86,10 +88,11 @@ export default function (AB) { view: "tabview", id: this.ids.tab, cells: [ - this.BlankTab.ui() /*, this.ImportTab.ui(), this.ExternalTab.ui() */, + this.BlankTab.ui() /* this.ExternalTab.ui() */, this.CsvTab.ui(), this.ApiTab.ui(), this.ImportTab.ui(), + this.NetsuiteTab.ui(), ], tabbar: { on: { @@ -129,7 +132,9 @@ export default function (AB) { "BlankTab", "CsvTab", "ApiTab", - "ImportTab" /*, "ExternalTab"*/, + "ImportTab", + "NetsuiteTab", + /*, "ExternalTab"*/ ].forEach((k) => { allInits.push(this[k].init(AB)); this[k].on("cancel", () => { @@ -161,6 +166,7 @@ export default function (AB) { this.CsvTab.applicationLoad(application); this.ApiTab.applicationLoad(application); this.ImportTab.applicationLoad(application); + this.NetsuiteTab.applicationLoad(application); } /** @@ -285,6 +291,9 @@ export default function (AB) { case this.ExternalTab?.ids.form: this.ExternalTab?.onShow?.(this.CurrentApplicationID); break; + case this.NetsuiteTab?.ids.form: + this.NetsuiteTab?.onShow?.(this.CurrentApplicationID); + break; } } } diff --git a/src/rootPages/Designer/ui_work_object_list_newObject_api_read_response.js b/src/rootPages/Designer/ui_work_object_list_newObject_api_read_response.js index 2d0275d0..147f9e85 100644 --- a/src/rootPages/Designer/ui_work_object_list_newObject_api_read_response.js +++ b/src/rootPages/Designer/ui_work_object_list_newObject_api_read_response.js @@ -79,7 +79,7 @@ export default function (AB) { label: L("Data Key"), placeholder: "data.example", bottomLabel: L( - "* JSON key containing the relevant data from the resonse object. Can be left blank to use the root level data." + "* JSON key containing the relevant data from the response object. Can be left blank to use the root level data." ), suggest: [], on: { @@ -261,12 +261,12 @@ export default function (AB) { _addFieldItem(key, type) { const uiItem = this._fieldItem(key, type); - $$(this.ids.fields).addView(uiItem); + $$(this.ids.connections).addView(uiItem); } _clearFieldItems() { - const $fields = $$(this.ids.fields); - AB.Webix.ui([], $fields); + const $connections = $$(this.ids.connections); + AB.Webix.ui([], $connections); } _populateDataKeys() { diff --git a/src/rootPages/Designer/ui_work_object_list_newObject_netsuite.js b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite.js new file mode 100644 index 00000000..32018fb8 --- /dev/null +++ b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite.js @@ -0,0 +1,484 @@ +/* + * ui_work_object_list_newObject_netsuite + * + * Display the form for creating a new ABObject that connects to a Netsuite + * instance. + */ +import UI_Class from "./ui_class"; +import UI_Credentials from "./ui_work_object_list_newObject_netsuite_credentials"; +import UI_Tables from "./ui_work_object_list_newObject_netsuite_tables"; +import UI_Fields from "./ui_work_object_list_newObject_netsuite_fields"; +import UI_Connections from "./ui_work_object_list_newObject_netsuite_connections"; +import UI_FieldTest from "./ui_work_object_list_newObject_netsuite_dataTest"; + +export default function (AB) { + const UIClass = UI_Class(AB); + const L = UIClass.L(); + + class UI_Work_Object_List_NewObject_Netsuite extends UIClass { + constructor() { + super("ui_work_object_list_newObject_netsuite", { + // component: base, + + form: "", + buttonSave: "", + buttonCancel: "", + }); + + this.UI_Credentials = UI_Credentials(AB); + this.UI_Tables = UI_Tables(AB); + this.UI_Fields = UI_Fields(AB); + this.UI_FieldTest = UI_FieldTest(AB); + this.UI_Connections = UI_Connections(AB); + } + + ui() { + // Our webix UI definition: + return { + id: this.ids.component, + header: L("Netsuite"), + body: { + view: "form", + id: this.ids.form, + width: 820, + height: 650, + rules: { + // TODO: + // name: inputValidator.rules.validateObjectName + }, + elements: [ + { + rows: [ + { + view: "text", + label: L("Name"), + name: "name", + required: true, + placeholder: L("Object name"), + labelWidth: 70, + }, + { + view: "checkbox", + label: L("Read Only"), + name: "readonly", + value: 0, + // disabled: true, + }, + ], + }, + { + view: "tabview", + cells: [ + this.UI_Credentials.ui(), + this.UI_Tables.ui(), + this.UI_Fields.ui(), + this.UI_Connections.ui(), + this.UI_FieldTest.ui(), + ], + }, + { fillspace: true }, + { + cols: [ + { fillspace: true }, + { + view: "button", + id: this.ids.buttonCancel, + value: L("Cancel"), + css: "ab-cancel-button", + autowidth: true, + click: () => { + this.cancel(); + }, + }, + { + view: "button", + id: this.ids.buttonSave, + css: "webix_primary", + value: L("Save"), + autowidth: true, + type: "form", + click: () => { + return this.save(); + }, + disabled: true, + }, + ], + }, + ], + }, + }; + } + + async init(AB) { + this.AB = AB; + + this.$form = $$(this.ids.form); + AB.Webix.extend(this.$form, webix.ProgressBar); + + this.UI_Credentials.init(AB); + this.UI_Tables.init(AB); + this.UI_Fields.init(AB); + this.UI_Connections.init(AB); + this.UI_FieldTest.init(AB); + + this.UI_Credentials.show(); + this.UI_Tables.disable(); + this.UI_Fields.disable(); + this.UI_Connections.disable(); + this.UI_FieldTest.disable(); + + // "verified" is triggered on the credentials tab once we verify + // the entered data is successful. + this.UI_Credentials.on("verified", () => { + this.UI_Tables.enable(); + let creds = this.UI_Credentials.credentials(); + this.UI_Tables.setCredentials(creds); + this.UI_Fields.setCredentials(creds); + this.UI_FieldTest.setCredentials(creds); + this.UI_Connections.setCredentials(creds); + this.UI_Tables.show(); + }); + + this.UI_Credentials.on("notverified", () => { + this.UI_Tables.disable(); + }); + + this.UI_Tables.on("tables", (tables) => { + this.UI_Connections.setAllTables(tables); + }); + + this.UI_Tables.on("table.selected", (table) => { + this.UI_Fields.enable(); + this.UI_Fields.loadFields(table); + this.UI_Fields.show(); + + this.UI_Connections.setTable(table); + this.UI_FieldTest.setTable(table); + }); + + this.UI_Fields.on("connections", (list) => { + this.UI_Connections.loadConnections(list); + this.UI_Connections.enable(); + }); + + this.UI_Fields.on("fields.ready", (config) => { + this.UI_FieldTest.enable(); + this.UI_FieldTest.loadConfig(config); + }); + + this.UI_FieldTest.on("data.verfied", () => { + $$(this.ids.buttonSave).enable(); + }); + + // "save.error" is triggered by the ui_work_object_list_newObject + // if there was an error saving the values from our form. + this.on("save.error", (err) => { + this.onError(err); + }); + + // "save.successful" is triggered by the ui_work_object_list_newObject + // if the values we provided were successfully saved. + this.on("save.successful", async (obj) => { + this.onSuccess(); + + // try { + // await obj.fetchData(); + + // webix.message({ + // type: "success", + // text: L("Successfully fetching data."), + // }); + // } catch (err) { + // webix.message({ + // type: "error", + // text: L("Error fetching data."), + // }); + // this.AB.notify.developer(err, { + // context: "ABObjectAPI.fetchData()", + // object: obj.toObj(), + // }); + // } + }); + + // init() routines are always considered async so: + return Promise.resolve(); + } + + cancel() { + this.formClear(); + this.emit("cancel"); + } + + formClear() { + this.$form.clearValidation(); + this.$form.clear(); + + this.UI_Credentials.formClear(); + this.UI_Tables.formClear(); + this.UI_Fields.formClear(); + this.UI_Connections.formClear(); + this.UI_FieldTest.formClear(); + + $$(this.ids.buttonSave).disable(); + } + + /** + * @method onError() + * Our Error handler when the data we provided our parent + * ui_work_object_list_newObject object had an error saving + * the values. + * @param {Error|ABValidation|other} err + * The error information returned. This can be several + * different types of objects: + * - A javascript Error() object + * - An ABValidation object returned from our .isValid() + * method + * - An error response from our API call. + */ + onError(err) { + if (err) { + console.error(err); + let message = L("the entered data is invalid"); + // if this was our Validation() object: + if (err.updateForm) { + err.updateForm(this.$form); + } else { + if (err.code && err.data) { + message = err.data?.sqlMessage ?? message; + } else { + message = err?.message ?? message; + } + } + + const values = this.$form.getValues(); + webix.alert({ + title: L("Error creating Object: {0}", [values.name]), + ok: L("fix it"), + text: message, + type: "alert-error", + }); + } + // get notified if there was an error saving. + $$(this.ids.buttonSave).enable(); + } + + /** + * @method onSuccess() + * Our success handler when the data we provided our parent + * ui_work_object_list_newObject successfully saved the values. + */ + onSuccess() { + this.formClear(); + $$(this.ids.buttonSave).enable(); + } + + /** + * @function save + * + * verify the current info is ok, package it, and return it to be + * added to the application.createModel() method. + */ + async save() { + this.busy(); + + const Form = this.$form; + + Form.clearValidation(); + + // if it doesn't pass the basic form validation, return: + if (!Form.validate()) { + this.ready(); + return false; + } + + let values = Form.getValues(); + + values.credentials = this.UI_Credentials.getValues(); + values.tableName = this.UI_Tables.getValues(); + + let allFields = this.UI_Fields.getValues(); + + // Pick out our special columns: pk, created_at, updated_at + let pkField = allFields.find((f) => f.pk); + if (!pkField) { + webix.alert({ + title: L("Error creating Object: {0}", [values.name]), + ok: L("fix it"), + text: L("No primary key specified."), + type: "alert-error", + }); + return; + } + values.primaryColumnName = pkField.column; + + values.columnRef = { created_at: null, updated_at: null }; + + ["created_at", "updated_at"].forEach((field) => { + let foundField = allFields.find((f) => f[field]); + if (foundField) { + values.columnRef[field] = foundField.column; + } + }); + + // Create a new Object + const object = AB.objectNew( + Object.assign({ isNetsuite: true }, values) + ); + + try { + // Add fields + + for (const f of allFields) { + let def = { + name: f.title, + label: f.title, + columnName: f.column, + key: f.abType, + }; + if (f.default) { + def.settings = {}; + def.settings.default = f.default; + } + const field = AB.fieldNew(def, object); + await field.save(true); + + // values.fieldIDs.push(field.id); + } + // values.id = object.id; + } catch (err) { + console.error(err); + } + + let allConnectFields = this.UI_Connections.getValues(); + for (var i = 0; i < allConnectFields.length; i++) { + let f = allConnectFields[i]; + /* f = + { + "thisField": "_this_object_", + "thatObject": "b7c7cca2-b919-4a90-b199-650a7a4693c1", + "thatObjectField": "custrecord_whq_teams_strategy_strtgy_cod", + "linkType": "many:one" + } + */ + + let linkObject = this.AB.objectByID(f.thatObject); + if (!linkObject) continue; + + let linkType = f.linkType; + let parts = linkType.split(":"); + let link = parts[0]; + let linkVia = parts[1]; + + let thisField = { + key: "connectObject", + // columnName: f.thisField, + label: linkObject.label, + settings: { + showIcon: "1", + + linkObject: linkObject.id, + linkType: link, + linkViaType: linkVia, + isCustomFK: 0, + indexField: "", + indexField2: "", + isSource: 0, + width: 100, + }, + }; + + let linkField = this.AB.cloneDeep(thisField); + // linkField.columnName = f.thatObjectField; + linkField.label = object.label || object.name; + linkField.settings.linkObject = object.id; + linkField.settings.linkType = linkVia; + linkField.settings.linkViaType = link; + + switch (linkType) { + case "one:one": + if (f.whichSource == "_this_") { + thisField.settings.isSource = 1; + } else { + linkField.settings.isSource = 1; + } + thisField.columnName = f.sourceField; + linkField.columnName = f.sourceField; + break; + + case "one:many": + case "many:one": + thisField.columnName = f.thatField; + linkField.columnName = f.thatField; + break; + + case "many:many": + thisField.settings.joinTable = f.joinTable; + linkField.settings.joinTable = f.joinTable; + + thisField.settings.joinTableReference = f.thisObjReference; + linkField.settings.joinTableReference = f.thatObjReference; + + thisField.settings.joinTablePK = f.joinTablePK; + linkField.settings.joinTablePK = f.joinTablePK; + + thisField.settings.joinTableEntity = f.joinTableEntity; + linkField.settings.joinTableEntity = f.joinTableEntity; + + if (f.joinActiveField != "_none_") { + thisField.settings.joinActiveField = f.joinActiveField; + thisField.settings.joinActiveValue = f.joinActiveValue; + thisField.settings.joinInActiveValue = f.joinInActiveValue; + + linkField.settings.joinActiveField = f.joinActiveField; + linkField.settings.joinActiveValue = f.joinActiveValue; + linkField.settings.joinInActiveValue = f.joinInActiveValue; + } + break; + } + + // create an initial LinkColumn + let fieldLink = linkObject.fieldNew(linkField); + await fieldLink.save(true); // should get an .id now + + // make sure I can reference field => linkColumn + thisField.settings.linkColumn = fieldLink.id; + let field = object.fieldNew(thisField); + await field.save(); + + // now update reference linkColumn => field + fieldLink.settings.linkColumn = field.id; + await fieldLink.save(); + } + + this.emit("save", object.toObj()); + + this.ready(); + } + + /** + * @function show() + * + * Show this component. + */ + show() { + $$(this.ids.component)?.show(); + } + + busy() { + const $form = $$(this.ids.form); + const $saveButton = $$(this.ids.buttonSave); + + $form.showProgress({ type: "icon" }); + $saveButton.disable(); + } + + ready() { + const $form = $$(this.ids.form); + const $saveButton = $$(this.ids.buttonSave); + + $form.hideProgress(); + $saveButton.enable(); + } + } + return new UI_Work_Object_List_NewObject_Netsuite(); +} diff --git a/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_connections.js b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_connections.js new file mode 100644 index 00000000..bdae1a56 --- /dev/null +++ b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_connections.js @@ -0,0 +1,907 @@ +/* + * ui_work_object_list_newObject_netsuite_connections + * + * Display the tab/form for selecting which of the available conections we + * want to create for this table. + */ +import UI_Class from "./ui_class"; + +export default function (AB) { + const UIClass = UI_Class(AB); + const L = UIClass.L(); + const uiConfig = AB.Config.uiSettings(); + + class UI_Work_Object_List_NewObject_Netsuite_Connections extends UIClass { + constructor() { + super("ui_work_object_list_newObject_netsuite_connections", { + // component: base, + + form: "", + + // fieldSelector: "", + connections: "", + displayConnections: "", + displayNoConnections: "", + + fieldGrid: "", + buttonVerify: "", + buttonLookup: "", + tableName: "", + }); + + this.allTables = []; + // [ { id, name }, ... ] + // A list of all the available tables. This is used for identifying the + // join tables in many:many connections. + // We get this list from the Tables interface tab. + + this.credentials = {}; + // { CRED_KEY : CRED_VAL } + // The entered credential references necessary to perform our Netsuite + // operations. + + this.connectionList = null; + // {array} + // Holds an array of connection descriptions + + this.connections = null; + // {array} + // Holds the array of chosen/verified connections + } + + ui() { + // Our webix UI definition: + return { + id: this.ids.component, + header: L("Connections"), + body: { + view: "form", + id: this.ids.form, + width: 800, + height: 450, + rules: { + // TODO: + // name: inputValidator.rules.validateObjectName + }, + elements: [ + { + view: "layout", + padding: 10, + rows: [ + { + id: this.ids.tableName, + label: L("Selected Table: {0}", [this.table]), + view: "label", + height: 40, + }, + ], + }, + + // Field Selector + { + view: "multiview", + animate: false, + borderless: true, + rows: [ + { + id: this.ids.displayNoConnections, + rows: [ + { + maxHeight: uiConfig.xxxLargeSpacer, + hidden: uiConfig.hideMobile, + }, + { + view: "label", + align: "center", + height: 200, + label: "
", + }, + { + // id: ids.error_msg, + view: "label", + align: "center", + label: L( + "You have no other Netwuite Objects imported" + ), + }, + { + // id: ids.error_msg, + view: "label", + align: "center", + label: L( + "Continue creating this object now, then create the connections on the other objects you import." + ), + }, + { + maxHeight: uiConfig.xxxLargeSpacer, + hidden: uiConfig.hideMobile, + }, + ], + }, + { + id: this.ids.displayConnections, + rows: [ + { + // id: ids.tabFields, + view: "layout", + padding: 10, + rows: [ + { + cols: [ + { + label: L("Connections"), + view: "label", + }, + { + icon: "wxi-plus", + view: "icon", + width: 38, + click: () => { + this._addConnection(); + }, + }, + ], + }, + { + view: "scrollview", + scroll: "y", + borderless: true, + padding: 0, + margin: 0, + body: { + id: this.ids.connections, + view: "layout", + padding: 0, + margin: 0, + rows: [], + }, + }, + ], + }, + ], + }, + ], + }, + ], + }, + }; + } + + async init(AB) { + this.AB = AB; + + this.$form = $$(this.ids.form); + + AB.Webix.extend(this.$form, webix.ProgressBar); + + // this.$fieldSelector = $$(this.ids.fieldSelector); + // if (this.$fieldSelector) + // AB.Webix.extend(this.$fieldSelector, webix.ProgressBar); + + // init() routines are always considered async so: + return Promise.resolve(); + } + + disable() { + $$(this.ids.form).disable(); + } + + enable() { + $$(this.ids.form).enable(); + } + + formClear() { + this.$form.clearValidation(); + this.$form.clear(); + this.disable(); + } + + getValues() { + return []; // TODO: + } + + setTable(table) { + this.table = table; + $$(this.ids.tableName).setValue( + `${this.table}` + ); + } + + loadConnections(allConnections) { + this.connectionList = allConnections; + // refresh more often than on init(); + this.listNetsuiteObjects = this.AB.objects((o) => o.isNetsuite); + if (this.listNetsuiteObjects.length == 0) { + $$(this.ids.displayNoConnections)?.show(); + } else { + $$(this.ids.displayConnections)?.show(); + } + } + + _fieldItem(key, type) { + const self = this; + const fieldTypes = this.AB.Class.ABFieldManager.allFields(); + const fieldKeys = ["string", "LongText", "number", "date", "boolean"]; + + const linkTypes = ["one:one", "one:many", "many:one", "many:many"]; + const linkOptions = linkTypes.map((l) => { + return { id: l, value: l }; + }); + linkOptions.unshift({ id: "_choose", value: L("choose link type") }); + + // For the Base Object, let's include all fields that are clearly + // objects. + let fieldOptions = this.connectionList.map((conn) => { + return { + id: conn.column, + value: conn.column, + }; + }); + + let thisObjectFields = fieldOptions; + let thatObjectFields = []; + + let listOtherObjects = this.listNetsuiteObjects.map((nObj) => { + return { + id: nObj.id, + value: nObj.label, + }; + }); + listOtherObjects.unshift({ id: "_choose", value: L("Choose Object") }); + + return { + view: "form", + elements: [ + { + cols: [ + // object and type + { + rows: [ + { + placeholder: L("Existing Netsuite Object"), + options: listOtherObjects, + view: "select", + name: "thatObject", + label: L("To:"), + // value: type, + on: { + onChange: async function ( + newVal, + oldVal, + config + ) { + let connObj = self.AB.objectByID(newVal); + if (connObj) { + let result = await self.AB.Network.get({ + url: `/netsuite/table/${connObj.tableName}`, + params: { + credentials: JSON.stringify( + self.credentials + ), + }, + }); + let fields = result.filter( + (r) => r.type == "object" + ); + let options = fields.map((f) => { + return { + id: f.column, + value: f.column, + }; + }); + + // include a "_that_object_" incase this is a one:xxx + // connection. + // options.unshift({ + // id: "_that_object_", + // value: L("That Object"), + // }); + + thatObjectFields = options; + /* + let $linkColumn = + this.getParentView().getChildViews()[1]; + + $linkColumn.define("options", options); + $linkColumn.refresh(); + */ + let $rowsFieldsets = this.getParentView() + .getParentView() + .getChildViews()[1]; + + // update one:one ThatObject: + let whichOptions = $rowsFieldsets + .getChildViews()[0] + .getChildViews()[0] + .getChildViews()[1]; + let newOptions = [ + { id: "_choose", value: L("Choose") }, + { + id: "_this_", + value: L("This Object"), + }, + ]; + newOptions.push({ + id: connObj.id, + value: connObj.label, + }); + whichOptions.define( + "options", + newOptions + ); + whichOptions.refresh(); + } + }, + }, + }, + { + placeholder: "Link Type", + options: linkOptions, + view: "select", + name: "linkType", + label: L("link type"), + on: { + onChange: async function ( + newVal, + oldVal, + config + ) { + let $toObj = + this.getParentView().getChildViews()[0]; + let $linkColumn = + this.getParentView().getChildViews()[1]; + + let objID = $toObj.getValue(); + let Obj = self.AB.objectByID(objID); + + let linkVal = $linkColumn.getValue(); + let links = linkVal.split(":"); + let messageA = self.message( + L("This object"), + links[0], + Obj.label + ); + + let messageB = self.message( + Obj.label, + links[1], + L("This object") + ); + + if (newVal == "_choose") { + messageA = messageB = ""; + } + + let $linkTextA = + this.getParentView().getChildViews()[2]; + let $linkTextB = + this.getParentView().getChildViews()[3]; + + $linkTextA.define("label", messageA); + $linkTextA.refresh(); + + $linkTextB.define("label", messageB); + $linkTextB.refresh(); + + let $rowsFieldsets = this.getParentView() + .getParentView() + .getChildViews()[1]; + + let $thatFieldOptions; + + switch (linkVal) { + case "one:one": + $rowsFieldsets + .getChildViews()[0] + .show(); + break; + + case "one:many": + // This Object's fields must be in field picker: + $thatFieldOptions = $rowsFieldsets + .getChildViews()[1] + .getChildViews()[0] + .getChildViews()[1]; + $thatFieldOptions.define( + "options", + thisObjectFields + ); + $thatFieldOptions.refresh(); + $rowsFieldsets + .getChildViews()[1] + .show(); + break; + + case "many:one": + // This Object's fields must be in field picker: + $thatFieldOptions = $rowsFieldsets + .getChildViews()[1] + .getChildViews()[0] + .getChildViews()[1]; + $thatFieldOptions.define( + "options", + thatObjectFields + ); + $thatFieldOptions.refresh(); + $rowsFieldsets + .getChildViews()[1] + .show(); + break; + + case "many:many": + $rowsFieldsets + .getChildViews()[2] + .show(); + break; + } + }, + }, + // value: type, + }, + { + // this to that + // id: ids.fieldLink2, + view: "label", + // width: 200, + }, + { + // that to this + view: "label", + // width: 200, + }, + ], + }, + { + rows: [ + { + view: "fieldset", + label: L("one to one"), + hidden: true, + body: { + rows: [ + { + view: "label", + label: L( + "which object holds the connection value?" + ), + }, + { + view: "select", + options: [ + { + id: "_choose", + value: L("Choose Object"), + }, + { + id: "_this_", + value: L("This Object"), + }, + { + id: "_that_", + value: L("That Object"), + }, + ], + name: "whichSource", + on: { + onChange: async function ( + newVal, + oldVal, + config + ) { + if (newVal == "_choose") return; + + let $fieldPicker = + this.getParentView().getChildViews()[2]; + + if (newVal == "_this_") { + $fieldPicker.define( + "options", + thisObjectFields + ); + } else { + $fieldPicker.define( + "options", + thatObjectFields + ); + } + $fieldPicker.refresh(); + $fieldPicker.show(); + }, + }, + }, + { + view: "select", + label: L("which field"), + name: "sourceField", + options: [], + hidden: true, + }, + ], + }, + }, + { + view: "fieldset", + label: L("one:X"), + hidden: true, + body: { + rows: [ + { + view: "label", + label: L( + "which field defines the connection?" + ), + }, + { + view: "select", + // label: L("which field"), + name: "thatField", + options: [], + // hidden: false, + }, + ], + }, + }, + { + view: "fieldset", + label: L("many:many"), + hidden: true, + body: { + rows: [ + { + view: "label", + label: L( + "which table is the join table?" + ), + }, + { + view: "combo", + name: "joinTable", + options: { + filter: (item, value) => { + return ( + item.value + .toLowerCase() + .indexOf( + value.toLowerCase() + ) > -1 + ); + }, + body: { + // template: "#value#", + data: this.allTables, + }, + }, + on: { + onChange: async function ( + newVal, + oldVal, + config + ) { + let result = + await self.AB.Network.get({ + url: `/netsuite/table/${newVal}`, + params: { + credentials: + JSON.stringify( + self.credentials + ), + }, + }); + // let fields = result.filter( + // (r) => r.type == "object" + // ); + let options = result.map((f) => { + return { + id: f.column, + value: f.column, + }; + }); + + let $thisObjRef = + this.getParentView().getChildViews()[2]; + $thisObjRef.define( + "options", + options + ); + $thisObjRef.refresh(); + $thisObjRef.show(); + + let $thatObjRef = + this.getParentView().getChildViews()[3]; + $thatObjRef.define( + "options", + options + ); + $thatObjRef.refresh(); + $thatObjRef.show(); + + let $objectPK = + this.getParentView().getChildViews()[4]; + $objectPK.define( + "options", + options + ); + + let pkField = result.find( + (r) => r.title == "Internal ID" + ); + if (pkField) { + $objectPK.setValue( + pkField.column + ); + } + $objectPK.refresh(); + $objectPK.show(); + + let $entityField = + this.getParentView().getChildViews()[5]; + $entityField.define( + "options", + options + ); + + let fieldEntity = result.find( + (r) => { + if (!r.column) return false; + + return ( + r.column.indexOf( + "entity" + ) > -1 + ); + } + ); + if (fieldEntity) { + $entityField.setValue( + fieldEntity.column + ); + } + $entityField.refresh(); + $entityField.show(); + + let fOptions = + self.AB.cloneDeep(options); + fOptions.unshift({ + id: "_none_", + value: "", + }); + let $activeField = + this.getParentView().getChildViews()[6]; + $activeField.define( + "options", + fOptions + ); + $activeField.refresh(); + $activeField.show(); + }, + }, + }, + + { + view: "select", + label: L("This Object's reference"), + labelPosition: "top", + options: [], + name: "thisObjReference", + hidden: true, + }, + { + view: "select", + label: L("That Object's reference"), + labelPosition: "top", + options: [], + name: "thatObjReference", + hidden: true, + }, + { + view: "select", + label: L("Join Table Primary Key:"), + labelPosition: "top", + options: [], + name: "joinTablePK", + hidden: true, + }, + { + view: "select", + label: L( + "Which field holds the Entity:" + ), + labelPosition: "top", + options: [], + name: "joinTableEntity", + hidden: true, + }, + { + view: "select", + label: L("Join Table isActive Field:"), + labelPosition: "top", + options: [], + name: "joinActiveField", + hidden: true, + on: { + onChange: async function ( + newVal, + oldVal, + config + ) { + if (newVal != "_none_") { + // show the active/inactive value + let siblings = + this.getParentView().getChildViews(); + siblings[ + siblings.length - 2 + ].show(); + siblings[ + siblings.length - 1 + ].show(); + } + }, + }, + }, + { + view: "text", + label: L("Active Value"), + name: "joinActiveValue", + hidden: true, + value: "", + }, + { + view: "text", + label: L("InActive Value"), + name: "joinInActiveValue", + hidden: true, + value: "", + }, + ], + }, + }, + ], + }, + { + // Delete Column + rows: [ + {}, + { + icon: "wxi-trash", + view: "icon", + width: 38, + click: function () { + const $item = this.getParentView() + .getParentView() + .getParentView(); + $$(self.ids.connections).removeView($item); + }, + }, + {}, + ], + // delete Row Icon + }, + ], + }, + ], + }; + } + + _addConnection(key, type) { + const uiItem = this._fieldItem(key, type); + $$(this.ids.connections).addView(uiItem); + } + + _clearFieldItems() { + const $connections = $$(this.ids.connections); + AB.Webix.ui([], $connections); + } + + message(a, link, b) { + let msg; + if (link == "many") { + msg = L("{0} has many {1} entities", [a, b]); + } else { + msg = L("{0} has one {1} entity", [a, b]); + } + + return msg; + } + + /** + * @method onError() + * Our Error handler when the data we provided our parent + * ui_work_object_list_newObject object had an error saving + * the values. + * @param {Error|ABValidation|other} err + * The error information returned. This can be several + * different types of objects: + * - A javascript Error() object + * - An ABValidation object returned from our .isValid() + * method + * - An error response from our API call. + */ + onError(err) { + if (err) { + console.error(err); + let message = L("the entered data is invalid"); + // if this was our Validation() object: + if (err.updateForm) { + err.updateForm(this.$form); + } else { + if (err.code && err.data) { + message = err.data?.sqlMessage ?? message; + } else { + message = err?.message ?? message; + } + } + + const values = this.$form.getValues(); + webix.alert({ + title: L("Error creating Object: {0}", [values.name]), + ok: L("fix it"), + text: message, + type: "alert-error", + }); + } + // get notified if there was an error saving. + $$(this.ids.buttonVerify).enable(); + } + + /** + * @method onSuccess() + * Our success handler when the data we provided our parent + * ui_work_object_list_newObject successfully saved the values. + */ + onSuccess() { + this.formClear(); + $$(this.ids.buttonVerify).enable(); + } + + /** + * @function show() + * + * Show this component. + */ + show() { + $$(this.ids.component)?.show(); + } + + busy() { + const $verifyButton = $$(this.ids.buttonVerify); + + // this.$fieldSelector.showProgress({ type: "icon" }); + $verifyButton.disable(); + } + + ready() { + const $verifyButton = $$(this.ids.buttonVerify); + + // this.$fieldSelector.hideProgress(); + $verifyButton.enable(); + } + + setCredentials(creds) { + this.credentials = creds; + } + + setAllTables(tables) { + this.allTables = this.AB.cloneDeep(tables); + this.allTables.unshift({ id: "_choose", value: L("Choose") }); + } + + getValues() { + let values = []; + $$(this.ids.connections) + .getChildViews() + .forEach(($row) => { + values.push($row.getValues()); + }); + return values; + } + + // verify() { + // this.emit("fields.ready", { + // credentials: this.credentials, + // table: this.table, + // fieldList: this.fieldList, + // }); + // } + } + return new UI_Work_Object_List_NewObject_Netsuite_Connections(); +} diff --git a/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_credentials.js b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_credentials.js new file mode 100644 index 00000000..e4fbf138 --- /dev/null +++ b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_credentials.js @@ -0,0 +1,317 @@ +/* + * ui_work_object_list_newObject_netsuite_credentials + * + * Display the tab/form for entering the Netsuite credentials for our + * connection. + */ +import UI_Class from "./ui_class"; + +const KeysCredentials = [ + "NETSUITE_CONSUMER_KEY", + "NETSUITE_CONSUMER_SECRET", + "NETSUITE_TOKEN_KEY", + "NETSUITE_TOKEN_SECRET", +]; +const KeysAPI = [ + "NETSUITE_REALM", + "NETSUITE_BASE_URL", + "NETSUITE_QUERY_BASE_URL", +]; + +const KeysALL = KeysCredentials.concat(KeysAPI); + +export default function (AB) { + const UIClass = UI_Class(AB); + const L = UIClass.L(); + + class UI_Work_Object_List_NewObject_Netsuite_Credentials extends UIClass { + constructor() { + super("ui_work_object_list_newObject_netsuite_credentials", { + // component: base, + + form: "", + buttonVerify: "", + labelVerified: "", + }); + + this.entries = {}; + } + + ui() { + // Our webix UI definition: + let ui = { + id: this.ids.component, + header: L("Credentials"), + body: { + view: "form", + id: this.ids.form, + width: 820, + height: 700, + rules: { + // TODO: + // name: inputValidator.rules.validateObjectName + }, + elements: [ + { + rows: [], + }, + { + cols: [ + { fillspace: true }, + // { + // view: "button", + // id: this.ids.buttonCancel, + // value: L("Cancel"), + // css: "ab-cancel-button", + // autowidth: true, + // click: () => { + // this.cancel(); + // }, + // }, + { + view: "button", + id: this.ids.buttonVerify, + css: "webix_primary", + value: L("Verify"), + autowidth: true, + type: "form", + click: () => { + return this.verify(); + }, + }, + ], + }, + { + cols: [ + { fillspace: true }, + { + id: this.ids.labelVerified, + view: "label", + label: L( + "All parameters are valid. Continue on to select a Table to work with." + ), + hidden: true, + }, + ], + }, + ], + }, + }; + + let rows = ui.body.elements[0].rows; + let fsOauth = { + view: "fieldset", + label: L("Netsuite OAuth 1.0 Credentials"), + body: { + rows: [], + }, + }; + + let EnvInput = (k) => { + return { + cols: [ + { + id: k, + view: "text", + label: k, + name: k, + required: true, + placeholder: `ENV:${k}`, + value: `ENV:${k}`, + labelWidth: 230, + on: { + onChange: (nV, oV) => { + this.envVerify(k, nV); + }, + }, + }, + { + id: `${k}_verified`, + view: "label", + width: 20, + label: '', + hidden: true, + }, + ], + }; + }; + + KeysCredentials.forEach((k) => { + fsOauth.body.rows.push(EnvInput(k)); + }); + rows.push(fsOauth); + rows.push({ height: 15 }); + + let fsAPI = { + view: "fieldset", + label: L("Netsuite API Config"), + body: { + rows: [], + }, + }; + + KeysAPI.forEach((k) => { + fsAPI.body.rows.push(EnvInput(k)); + }); + rows.push(fsAPI); + + return ui; + } + + async init(AB) { + this.AB = AB; + + this.$form = $$(this.ids.form); + AB.Webix.extend(this.$form, webix.ProgressBar); + + // init() routines are always considered async so: + return Promise.resolve(); + } + + formClear() { + this.$form.clearValidation(); + this.$form.clear(); + + KeysALL.forEach((k) => { + $$(k).setValue(`ENV:${k}`); + }); + } + + getValues() { + return this.credentials(); + } + + /** + * @method onError() + * Our Error handler when the data we provided our parent + * ui_work_object_list_newObject object had an error saving + * the values. + * @param {Error|ABValidation|other} err + * The error information returned. This can be several + * different types of objects: + * - A javascript Error() object + * - An ABValidation object returned from our .isValid() + * method + * - An error response from our API call. + */ + onError(err) { + if (err) { + console.error(err); + let message = L("the entered data is invalid"); + // if this was our Validation() object: + if (err.updateForm) { + err.updateForm(this.$form); + } else { + if (err.code && err.data) { + message = err.data?.sqlMessage ?? message; + } else { + message = err?.message ?? message; + } + } + + const values = this.$form.getValues(); + webix.alert({ + title: L("Error creating Object: {0}", [values.name]), + ok: L("fix it"), + text: message, + type: "alert-error", + }); + } + // get notified if there was an error saving. + $$(this.ids.buttonVerify).enable(); + } + + /** + * @method onSuccess() + * Our success handler when the data we provided our parent + * ui_work_object_list_newObject successfully saved the values. + */ + onSuccess() { + this.formClear(); + $$(this.ids.buttonVerify).enable(); + } + + /** + * @function show() + * + * Show this component. + */ + show() { + $$(this.ids.component)?.show(); + console.log("SHOW, Baby! SHOW!"); + } + + busy() { + const $form = $$(this.ids.form); + const $saveButton = $$(this.ids.buttonVerify); + + $form.showProgress({ type: "icon" }); + $saveButton.disable(); + } + + credentials() { + let creds = {}; + KeysALL.forEach((k) => { + let $input = $$(k); + if ($input) { + creds[k] = $input.getValue(); + } + }); + + return creds; + } + + envVerify(k, nV) { + let envKey = nV.replace("ENV:", ""); + return this.AB.Network.get({ + url: `/env/verify/${envKey}`, + }) + .then((result) => { + console.log(result); + if (result.status == "success") { + $$(`${k}_verified`).show(); + this.entries[k] = true; + } else { + $$(`${k}_verified`).hide(); + this.entries[k] = false; + } + }) + .catch((err) => { + console.error(err); + $$(`${k}_verified`).hide(); + this.entries[k] = false; + }); + } + + ready() { + const $form = $$(this.ids.form); + const $saveButton = $$(this.ids.buttonVerify); + + $form.hideProgress(); + $saveButton.enable(); + } + + async verify() { + let isVerified = true; + let AllVerifies = []; + KeysALL.forEach((k) => { + let nV = $$(k).getValue(); + AllVerifies.push(this.envVerify(k, nV)); + }); + await Promise.all(AllVerifies); + + KeysALL.forEach((k) => { + isVerified = isVerified && this.entries[k]; + }); + + if (isVerified) { + this.emit("verified"); + $$(this.ids.labelVerified)?.show(); + } else { + this.emit("notverified"); + $$(this.ids.labelVerified)?.hide(); + } + } + } + return new UI_Work_Object_List_NewObject_Netsuite_Credentials(); +} diff --git a/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_dataTest.js b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_dataTest.js new file mode 100644 index 00000000..a258d31f --- /dev/null +++ b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_dataTest.js @@ -0,0 +1,390 @@ +/* + * ui_work_object_list_newObject_netsuite_dataTest + * + * Display the tab/form to review what the current Definitions look like + * working with the data from Netsuite. + */ +import UI_Class from "./ui_class"; + +export default function (AB) { + const UIClass = UI_Class(AB); + const L = UIClass.L(); + + class UI_Work_Object_List_NewObject_Netsuite_DataTest extends UIClass { + constructor() { + super("ui_work_object_list_newObject_netsuite_dataTest", { + // component: base, + form: "", + network: "", + dataView: "", + + buttonVerify: "", + tableName: "", + }); + + this.credentials = {}; + // { CRED_KEY : CRED_VAL } + // The entered credential references necessary to perform our Netsuite + // operations. + + this.fieldKeys = [ + "string", + "LongText", + "number", + "date", + "boolean", + "json", + "list", + ]; + // {array} of types of ABFields we can translate into. + + this.fieldList = null; + // {array} + // Holds an array of field descriptions + + this.table = null; + // {string} + // name of the table we are working with + } + + ui() { + // Our webix UI definition: + return { + id: this.ids.component, + header: L("Data Verification"), + body: { + view: "form", + id: this.ids.form, + width: 800, + height: 400, + rules: { + // TODO: + // name: inputValidator.rules.validateObjectName + }, + elements: [ + // Field Selector + { + view: "layout", + padding: 10, + rows: [ + { + cols: [ + { + id: this.ids.tableName, + label: L("Selected Table: {0}", [this.table]), + view: "label", + height: 40, + }, + {}, + ], + }, + { + view: "multiview", + // keepViews: true, + cells: [ + // Select Table indicator + { + id: this.ids.network, + rows: [ + {}, + { + view: "label", + align: "center", + height: 200, + label: "
", + }, + { + view: "label", + align: "center", + label: L( + "Gathering data from Netsuite." + ), + }, + {}, + ], + }, + { + id: this.ids.dataView, + rows: [ + {}, + { + view: "label", + label: "Waiting for response", + }, + {}, + ], + // hidden: true, + }, + ], + }, + + // { + // id: this.ids.fieldGrid, + // view: "datatable", + // resizeColumn: true, + // height: 300, + // columns: [ + // { + // id: "title", + // header: L("title"), + // editor: "text", + // }, + // { id: "column", header: L("column") }, + + // { id: "nullable", header: L("nullable") }, + // { id: "readOnly", header: L("read only") }, + // { + // id: "pk", + // header: L("is primary key"), + // template: "{common.radio()}", + // }, + // // { + // // id: "description", + // // header: L("description"), + // // fillspace: true, + // // }, + // { + // id: "abType", + // header: L("AB Field Type"), + // editor: "select", + // options: [" "].concat(this.fieldKeys), + // }, + // { + // id: "delme", + // header: "", + // template: "{common.trashIcon()}", + // }, + // ], + // editable: true, + // scroll: "y", + // onClick: { + // "wxi-trash": (e, id) => { + // debugger; + // $$(this.ids.fieldGrid).remove(id); + // }, + // }, + // }, + ], + }, + + { + cols: [ + { fillspace: true }, + // { + // view: "button", + // id: this.ids.buttonCancel, + // value: L("Cancel"), + // css: "ab-cancel-button", + // autowidth: true, + // click: () => { + // this.cancel(); + // }, + // }, + { + view: "button", + id: this.ids.buttonVerify, + css: "webix_primary", + value: L("Verify"), + autowidth: true, + type: "form", + click: () => { + return this.verify(); + }, + }, + ], + }, + ], + }, + }; + } + + async init(AB) { + this.AB = AB; + + this.$form = $$(this.ids.form); + AB.Webix.extend(this.$form, webix.ProgressBar); + + // init() routines are always considered async so: + return Promise.resolve(); + } + + disable() { + $$(this.ids.form).disable(); + } + + enable() { + $$(this.ids.form).enable(); + } + + formClear() { + this.$form.clearValidation(); + this.$form.clear(); + + // reset the data view to blank + let table = { + id: this.ids.dataView, + rows: [ + {}, + { + view: "label", + label: "Waiting for response", + }, + {}, + ], + // hidden: true, + }; + webix.ui(table, $$(this.ids.dataView)); + this.disable(); + } + setTableName() { + $$(this.ids.tableName).setValue( + `${this.table}` + ); + } + + setTable(table) { + this.table = table; + this.setTableName(); + + // this is called when a table name has been selected. + // but we need to be disabled until they have verified the + // fields. + this.formClear(); + } + + async loadConfig(config) { + this.credentials = config.credentials; + this.setTable(config.table); + this.fieldList = config.fieldList; + + $$(this.ids.network).show(); + this.busy(); + + let result = await this.AB.Network.get({ + url: `/netsuite/dataVerify/${this.table}`, + params: { + credentials: JSON.stringify(this.credentials), + }, + }); + + this.data = result; + // this.ids.dataView, + + // convert all the json types to strings for display: + this.fieldList + .filter((f) => f.abType == "json") + .forEach((f) => { + this.data.forEach((d) => { + try { + d[f.column] = JSON.stringify(d[f.column]); + } catch (e) { + console.log(e); + } + }); + }); + + this.showTable(); + this.enable(); + this.ready(); + } + + showTable() { + let table = { + id: this.ids.dataView, + view: "datatable", + columns: this.fieldList.map((f) => { + return { + id: f.column, + header: f.title, + }; + }), + data: this.data, + }; + + webix.ui(table, $$(this.ids.dataView)); + $$(this.ids.dataView).show(); + } + + /** + * @method onError() + * Our Error handler when the data we provided our parent + * ui_work_object_list_newObject object had an error saving + * the values. + * @param {Error|ABValidation|other} err + * The error information returned. This can be several + * different types of objects: + * - A javascript Error() object + * - An ABValidation object returned from our .isValid() + * method + * - An error response from our API call. + */ + // onError(err) { + // if (err) { + // console.error(err); + // let message = L("the entered data is invalid"); + // // if this was our Validation() object: + // if (err.updateForm) { + // err.updateForm(this.$form); + // } else { + // if (err.code && err.data) { + // message = err.data?.sqlMessage ?? message; + // } else { + // message = err?.message ?? message; + // } + // } + + // const values = this.$form.getValues(); + // webix.alert({ + // title: L("Error creating Object: {0}", [values.name]), + // ok: L("fix it"), + // text: message, + // type: "alert-error", + // }); + // } + // // get notified if there was an error saving. + // $$(this.ids.buttonVerify).enable(); + // } + + /** + * @method onSuccess() + * Our success handler when the data we provided our parent + * ui_work_object_list_newObject successfully saved the values. + */ + // onSuccess() { + // this.formClear(); + // $$(this.ids.buttonVerify).enable(); + // } + + verify() { + this.emit("data.verfied"); + } + + /** + * @function show() + * + * Show this component. + */ + show() { + $$(this.ids.component)?.show(); + } + + busy() { + const $verifyButton = $$(this.ids.buttonVerify); + + this.$form.showProgress({ type: "icon" }); + $verifyButton.disable(); + } + + ready() { + const $verifyButton = $$(this.ids.buttonVerify); + + this.$form.hideProgress(); + $verifyButton.enable(); + } + + setCredentials(creds) { + this.credentials = creds; + } + } + return new UI_Work_Object_List_NewObject_Netsuite_DataTest(); +} diff --git a/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_fields.js b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_fields.js new file mode 100644 index 00000000..08caf392 --- /dev/null +++ b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_fields.js @@ -0,0 +1,419 @@ +/* + * ui_work_object_list_newObject_netsuite_fields + * + * Display the tab/form for selecting which of the available tables we are + * working with. + */ +import UI_Class from "./ui_class"; + +export default function (AB) { + const UIClass = UI_Class(AB); + const L = UIClass.L(); + + class UI_Work_Object_List_NewObject_Netsuite_Fields extends UIClass { + constructor() { + super("ui_work_object_list_newObject_netsuite_fields", { + // component: base, + + form: "", + + tableName: "", + // tableList: "", + fieldSelector: "", + // fields: "", + fieldGrid: "", + buttonVerify: "", + // buttonLookup: "", + }); + + this.credentials = {}; + // { CRED_KEY : CRED_VAL } + // The entered credential references necessary to perform our Netsuite + // operations. + + this.fieldKeys = [ + "string", + "LongText", + "number", + "date", + "datetime", + "boolean", + "json", + "list", + // "connectObject", + ]; + // {array} of types of ABFields we can translate into. + + this.fieldList = null; + // {array} + // Holds an array of field descriptions + + this.fields = null; + // {array} + // Holds the array of chosen/verified fields + } + + ui() { + // Our webix UI definition: + return { + id: this.ids.component, + header: L("Fields"), + body: { + view: "form", + id: this.ids.form, + width: 800, + height: 450, + rules: { + // TODO: + // name: inputValidator.rules.validateObjectName + }, + elements: [ + // Field Selector + { + id: this.ids.fieldSelector, + view: "layout", + padding: 10, + rows: [ + { + rows: [ + { + id: this.ids.tableName, + label: L("Selected Table: {0}", [this.table]), + view: "label", + height: 40, + }, + {}, + ], + }, + // { + // view: "scrollview", + // scroll: "y", + // borderless: true, + // padding: 0, + // margin: 0, + // body: { + // id: this.ids.fields, + // view: "layout", + // padding: 0, + // margin: 0, + // rows: [], + // }, + // }, + { + id: this.ids.fieldGrid, + view: "datatable", + resizeColumn: true, + height: 300, + columns: [ + { + id: "title", + header: L("title"), + editor: "text", + }, + { id: "column", header: L("column") }, + + { id: "nullable", header: L("nullable") }, + { id: "readOnly", header: L("read only") }, + { + id: "default", + header: L("Default Value"), + editor: "text", + }, + { + id: "pk", + header: L("is primary key"), + template: "{common.radio()}", + }, + { + id: "created_at", + header: L("Created At"), + template: "{common.radio()}", + }, + { + id: "updated_at", + header: L("Updated At"), + template: "{common.radio()}", + }, + // { + // id: "description", + // header: L("description"), + // fillspace: true, + // }, + { + id: "abType", + header: L("AB Field Type"), + editor: "select", + options: [" "].concat(this.fieldKeys), + on: { + onChange: (newValue, oldValue) => { + debugger; + }, + }, + }, + { + id: "delme", + header: "", + template: "{common.trashIcon()}", + }, + ], + editable: true, + scroll: "xy", + onClick: { + "wxi-trash": (e, id) => { + $$(this.ids.fieldGrid).remove(id); + this.fields = this.fields.filter( + (f) => f.id != id.row + ); + }, + }, + }, + { + cols: [ + { fillspace: true }, + // { + // view: "button", + // id: this.ids.buttonCancel, + // value: L("Cancel"), + // css: "ab-cancel-button", + // autowidth: true, + // click: () => { + // this.cancel(); + // }, + // }, + { + view: "button", + id: this.ids.buttonVerify, + css: "webix_primary", + value: L("Verify"), + autowidth: true, + type: "form", + click: () => { + return this.verify(); + }, + }, + ], + }, + ], + }, + ], + }, + }; + } + + async init(AB) { + this.AB = AB; + + this.$form = $$(this.ids.form); + + this.$fieldSelector = $$(this.ids.fieldSelector); + AB.Webix.extend(this.$form, webix.ProgressBar); + AB.Webix.extend(this.$fieldSelector, webix.ProgressBar); + + // init() routines are always considered async so: + return Promise.resolve(); + } + + disable() { + $$(this.ids.form).disable(); + } + + enable() { + $$(this.ids.form).enable(); + } + + formClear() { + this.$form.clearValidation(); + this.$form.clear(); + + $$(this.ids.fieldGrid)?.clearAll(); + this.disable(); + $$(this.ids.buttonVerify).disable(); + } + + addABType(f) { + switch (f.type) { + case "array": + // this is most likely a MANY:x connection. + // Q:what do we default this to? + f.abType = "json"; + break; + + case "object": + // this is most likely a ONE:X[ONE,MANY] connection. + // // lets scan the properties of the dest obj, + // // find a property with title = "Internal Identifier" + // // and make this ABType == that property.type + + // if (f.properties) { + // Object.keys(f.properties).forEach((k) => { + // if (f.properties[k].title == "Internal Identifier") { + // f.abType = f.properties[k].type; + // } + // }); + // } + // // default to "string" if an Internal Identifier isn't found. + // if (!f.abType) { + // f.abType = "string"; + // } + f.abType = "connectObject"; + break; + + case "boolean": + f.abType = "boolean"; + break; + + default: + f.abType = "string"; + } + + // just in case: + // lets see if this looks like a date field instead + + if (f.abType == "string") { + let lcTitle = f.title?.toLowerCase(); + if (lcTitle) { + let indxDate = lcTitle.indexOf("date") > -1; + let indxDay = lcTitle.indexOf("day") > -1; + if (indxDate || indxDay) { + f.abType = "date"; + } + } + + if (f.format == "date-time") { + f.abType = "datetime"; + } + } + + // Seems like the PKs have title == "Internal ID" + if (f.title == "Internal ID") { + f.pk = true; + } + } + + getValues() { + return this.fields; + } + + setTableName() { + $$(this.ids.tableName).setValue( + `${this.table}` + ); + } + async loadFields(table) { + $$(this.ids.fieldGrid)?.clearAll(); + this.table = table; + $$(this.ids.fieldSelector).show(); + this.busy(); + this.setTableName(); + + let result = await this.AB.Network.get({ + url: `/netsuite/table/${table}`, + params: { credentials: JSON.stringify(this.credentials) }, + }); + + this.fieldList = result; + (result || []).forEach((f) => { + this.addABType(f); + }); + + // ok, in this pane, we are just looking at the base fields + // leave the connections to the next pane: + this.fields = result.filter((r) => r.type != "object"); + + // let our other pane know about it's connections + this.emit( + "connections", + result.filter((r) => r.type == "object") + ); + + $$(this.ids.fieldGrid).parse(this.fields); + this.ready(); + } + + /** + * @method onError() + * Our Error handler when the data we provided our parent + * ui_work_object_list_newObject object had an error saving + * the values. + * @param {Error|ABValidation|other} err + * The error information returned. This can be several + * different types of objects: + * - A javascript Error() object + * - An ABValidation object returned from our .isValid() + * method + * - An error response from our API call. + */ + onError(err) { + if (err) { + console.error(err); + let message = L("the entered data is invalid"); + // if this was our Validation() object: + if (err.updateForm) { + err.updateForm(this.$form); + } else { + if (err.code && err.data) { + message = err.data?.sqlMessage ?? message; + } else { + message = err?.message ?? message; + } + } + + const values = this.$form.getValues(); + webix.alert({ + title: L("Error creating Object: {0}", [values.name]), + ok: L("fix it"), + text: message, + type: "alert-error", + }); + } + // get notified if there was an error saving. + $$(this.ids.buttonVerify).enable(); + } + + /** + * @method onSuccess() + * Our success handler when the data we provided our parent + * ui_work_object_list_newObject successfully saved the values. + */ + onSuccess() { + this.formClear(); + $$(this.ids.buttonVerify).enable(); + } + + /** + * @function show() + * + * Show this component. + */ + show() { + $$(this.ids.component)?.show(); + } + + busy() { + const $verifyButton = $$(this.ids.buttonVerify); + + this.$fieldSelector.showProgress({ type: "icon" }); + $verifyButton.disable(); + } + + ready() { + const $verifyButton = $$(this.ids.buttonVerify); + + this.$fieldSelector.hideProgress(); + $verifyButton.enable(); + } + + setCredentials(creds) { + this.credentials = creds; + } + + verify() { + this.emit("fields.ready", { + credentials: this.credentials, + table: this.table, + fieldList: this.fields, + }); + } + } + return new UI_Work_Object_List_NewObject_Netsuite_Fields(); +} diff --git a/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_tables.js b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_tables.js new file mode 100644 index 00000000..e3a62781 --- /dev/null +++ b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_tables.js @@ -0,0 +1,375 @@ +/* + * ui_work_object_list_newObject_netsuite_tables + * + * Display the tab/form for selecting which of the available tables we are + * working with. + */ +import UI_Class from "./ui_class"; + +export default function (AB) { + const UIClass = UI_Class(AB); + const L = UIClass.L(); + + class UI_Work_Object_List_NewObject_Netsuite_Tables extends UIClass { + constructor() { + super("ui_work_object_list_newObject_netsuite_tables", { + // component: base, + + form: "", + + searchText: "", + tableList: "", + // fieldSelector: "", + fields: "", + buttonVerify: "", + buttonLookup: "", + }); + + this.credentials = {}; + // { CRED_KEY : CRED_VAL } + // The entered credential references necessary to perform our Netsuite + // operations. + + this.lastSelectedTable = null; + // {string} + // the table name of the last selected table. + } + + ui() { + // Our webix UI definition: + return { + id: this.ids.component, + header: L("Tables"), + body: { + view: "form", + id: this.ids.form, + width: 800, + height: 700, + rules: { + // TODO: + // name: inputValidator.rules.validateObjectName + }, + elements: [ + { + cols: [ + // The Table Selector Column + { + rows: [ + { + cols: [ + { + view: "label", + align: "left", + label: L( + "Use the provided credentials to request a list of tables to work with." + ), + }, + { + view: "button", + id: this.ids.buttonLookup, + value: L("Load Catalog"), + // css: "ab-cancel-button", + autowidth: true, + click: () => { + this.loadCatalog(); + }, + }, + ], + }, + { + id: this.ids.searchText, + view: "search", + icon: "fa fa-search", + label: L("Search"), + labelWidth: 80, + placeholder: L("tablename"), + height: 35, + keyPressTimeout: 100, + on: { + onAfterRender() { + AB.ClassUI.CYPRESS_REF(this); + }, + onTimedKeyPress: () => { + let searchText = $$(this.ids.searchText) + .getValue() + .toLowerCase(); + + this.$list.filter(function (item) { + return ( + !item.value || + item.value + .toLowerCase() + .indexOf(searchText) > -1 + ); + }); + }, + }, + }, + { + id: this.ids.tableList, + view: "list", + select: 1, + height: 400, + on: { + onItemClick: (id, e) => { + if (id != this.lastSelectedTable) { + this.lastSelectedTable = id; + this.emit("table.selected", id); + } + }, + }, + }, + ], + }, + + // Select Table indicator + { + rows: [ + {}, + { + view: "label", + align: "center", + height: 200, + label: "
", + }, + { + view: "label", + align: "center", + label: L("Select an table to work with."), + }, + {}, + ], + }, + ], + }, + // { + // cols: [ + // { fillspace: true }, + // // { + // // view: "button", + // // id: this.ids.buttonCancel, + // // value: L("Cancel"), + // // css: "ab-cancel-button", + // // autowidth: true, + // // click: () => { + // // this.cancel(); + // // }, + // // }, + // { + // view: "button", + // id: this.ids.buttonVerify, + // css: "webix_primary", + // value: L("Verify"), + // autowidth: true, + // type: "form", + // click: () => { + // return this.verify(); + // }, + // }, + // ], + // }, + ], + }, + }; + } + + async init(AB) { + this.AB = AB; + + this.$form = $$(this.ids.form); + this.$list = $$(this.ids.tableList); + // this.$fieldSelector = $$(this.ids.fieldSelector); + AB.Webix.extend(this.$form, webix.ProgressBar); + AB.Webix.extend(this.$list, webix.ProgressBar); + // AB.Webix.extend(this.$fieldSelector, webix.ProgressBar); + + // init() routines are always considered async so: + return Promise.resolve(); + } + + disable() { + $$(this.ids.form).disable(); + } + + enable() { + $$(this.ids.form).enable(); + } + + formClear() { + this.$form.clearValidation(); + this.$form.clear(); + + $$(this.ids.searchText).setValue(""); + this.$list.filter(() => true); + this.lastSelectedTable = null; + } + getValues() { + return this.lastSelectedTable; + } + + async loadCatalog() { + this.busy(); + let result = await this.AB.Network.get({ + url: "/netsuite/metadata", + params: { credentials: JSON.stringify(this.credentials) }, + }); + + let data = []; + result.forEach((r) => { + data.push({ id: r, value: r }); + }); + let $table = $$(this.ids.tableList); + $table.clearAll(); + $table.parse(data); + + // console.error(data); + this.emit("tables", data); + } + + _fieldItem(def) { + const self = this; + const fieldTypes = this.AB.Class.ABFieldManager.allFields(); + const fieldKeys = ["string", "LongText", "number", "date", "boolean"]; + + let key = def.column || def.title; + let type = def.type; + + return { + cols: [ + { + view: "text", + value: key, + placeholder: "key", + }, + { + placeholder: "Type", + options: fieldKeys.map((fKey) => { + return { + id: fKey, + value: fieldTypes + .filter((f) => f.defaults().key == fKey)[0] + ?.defaults().menuName, + }; + }), + view: "select", + value: type, + }, + { + icon: "wxi-trash", + view: "icon", + width: 38, + click: function () { + const $item = this.getParentView(); + $$(self.ids.fields).removeView($item); + }, + }, + ], + }; + } + + async loadFields(table) { + // $$(this.ids.fieldSelector).show(); + this.busyFields(); + + let result = await this.AB.Network.get({ + url: `/netsuite/table/${table}`, + params: { credentials: JSON.stringify(this.credentials) }, + }); + + this.fieldList = result; + (result || []).forEach((f) => { + const uiItem = this._fieldItem(f); + $$(this.ids.fields).addView(uiItem); + }); + this.readyFields(); + } + + /** + * @method onError() + * Our Error handler when the data we provided our parent + * ui_work_object_list_newObject object had an error saving + * the values. + * @param {Error|ABValidation|other} err + * The error information returned. This can be several + * different types of objects: + * - A javascript Error() object + * - An ABValidation object returned from our .isValid() + * method + * - An error response from our API call. + */ + onError(err) { + if (err) { + console.error(err); + let message = L("the entered data is invalid"); + // if this was our Validation() object: + if (err.updateForm) { + err.updateForm(this.$form); + } else { + if (err.code && err.data) { + message = err.data?.sqlMessage ?? message; + } else { + message = err?.message ?? message; + } + } + + const values = this.$form.getValues(); + webix.alert({ + title: L("Error creating Object: {0}", [values.name]), + ok: L("fix it"), + text: message, + type: "alert-error", + }); + } + // get notified if there was an error saving. + $$(this.ids.buttonVerify).enable(); + } + + /** + * @method onSuccess() + * Our success handler when the data we provided our parent + * ui_work_object_list_newObject successfully saved the values. + */ + onSuccess() { + this.formClear(); + $$(this.ids.buttonVerify).enable(); + } + + /** + * @function show() + * + * Show this component. + */ + show() { + $$(this.ids.component)?.show(); + } + + busy() { + const $list = $$(this.ids.tableList); + // const $verifyButton = $$(this.ids.buttonVerify); + + $list.showProgress({ type: "icon" }); + // $verifyButton.disable(); + } + + // busyFields() { + // this.$fieldSelector.showProgress({ type: "icon" }); + // } + + ready() { + const $form = $$(this.ids.form); + // const $verifyButton = $$(this.ids.buttonVerify); + + $form.hideProgress(); + // $verifyButton.enable(); + } + + // readyFields() { + // this.$fieldSelector.hideProgress(); + // } + + setCredentials(creds) { + this.credentials = creds; + } + } + return new UI_Work_Object_List_NewObject_Netsuite_Tables(); +} diff --git a/src/rootPages/Designer/ui_work_object_workspace.js b/src/rootPages/Designer/ui_work_object_workspace.js index 94527dd0..9513c00c 100644 --- a/src/rootPages/Designer/ui_work_object_workspace.js +++ b/src/rootPages/Designer/ui_work_object_workspace.js @@ -179,15 +179,18 @@ export default function (AB, ibase, init_settings) { this.callbackHeaderEditorMenu(action, field, node); }); - if (!this.isReadOnly) { - this.PopupDefineLabelComponent = new FPopupDefineLabel( - AB, - `${base}_defineLabel` - ); - this.PopupDefineLabelComponent.on("changed", () => { - this.callbackDefineLabel(); - }); - } + // NOTE: label information is only concerning how we display the info + // from this Object. It doesn't impact the data. So we can go ahead + // and allow users to modify the Label data. + // if (!this.isReadOnly) { + this.PopupDefineLabelComponent = new FPopupDefineLabel( + AB, + `${base}_defineLabel` + ); + this.PopupDefineLabelComponent.on("changed", () => { + this.callbackDefineLabel(); + }); + // } this.PopupFilterDataTableComponent = new FPopupFilterDataTable( AB, @@ -437,7 +440,7 @@ export default function (AB, ibase, init_settings) { icon: "fa fa-crosshairs", css: "webix_transparent", type: "icon", - hidden: this.isReadOnly, + // hidden: this.isReadOnly, click: function () { _logic.toolbarDefineLabel(this.$view); }, @@ -707,9 +710,9 @@ export default function (AB, ibase, init_settings) { allInits.push(this.PopupHeaderEditMenu.init(AB)); - if (!this.isReadOnly) { - allInits.push(this.PopupDefineLabelComponent.init(AB)); - } + // if (!this.isReadOnly) { + allInits.push(this.PopupDefineLabelComponent.init(AB)); + // } allInits.push(this.PopupFilterDataTableComponent.init(AB)); this.PopupFilterDataTableComponent.on("save", (...params) => { @@ -1391,8 +1394,8 @@ export default function (AB, ibase, init_settings) { if (!this.isReadOnly) { this.PopupNewDataFieldComponent.objectLoad(object); - this.PopupDefineLabelComponent.objectLoad(object); } + this.PopupDefineLabelComponent.objectLoad(object); this.PopupFilterDataTableComponent.objectLoad(object); this.PopupFrozenColumnsComponent.objectLoad(object); @@ -1782,7 +1785,7 @@ export default function (AB, ibase, init_settings) { const ids = this.ids; const $buttonAddField = $$(ids.buttonAddField), $spaceAddField = $$(ids.spaceAddField), - $buttonLabel = $$(ids.buttonLabel), + // $buttonLabel = $$(ids.buttonLabel), $spaceLabel = $$(ids.spaceLabel), $buttonImport = $$(ids.buttonImport), $spaceImport = $$(ids.spaceImport), @@ -1792,7 +1795,7 @@ export default function (AB, ibase, init_settings) { if (this._isReadOnly) { $buttonAddField?.hide(); $spaceAddField?.hide(); - $buttonLabel?.hide(); + // $buttonLabel?.hide(); $spaceLabel?.hide(); $buttonImport?.hide(); $spaceImport?.hide(); @@ -1801,7 +1804,7 @@ export default function (AB, ibase, init_settings) { } else { $buttonAddField?.show(); $spaceAddField?.show(); - $buttonLabel?.show(); + // $buttonLabel?.show(); $spaceLabel?.show(); $buttonImport?.show(); $spaceImport?.show(); diff --git a/src/rootPages/Designer/ui_work_object_workspace_popupNewDataField.js b/src/rootPages/Designer/ui_work_object_workspace_popupNewDataField.js index 46a42248..ec83732e 100644 --- a/src/rootPages/Designer/ui_work_object_workspace_popupNewDataField.js +++ b/src/rootPages/Designer/ui_work_object_workspace_popupNewDataField.js @@ -410,6 +410,12 @@ export default function (AB, ibase) { let linkCol; + if (this.CurrentObject.isNetsuite) { + if (vals.settings?.netsuiteOneColumn) { + vals.columnName = vals.settings.netsuiteOneColumn; + } + } + // if this is an ADD operation, (_editField will be undefined) if (!this._editField) { // get a new instance of a field: @@ -420,7 +426,9 @@ export default function (AB, ibase) { field.columnName === "" ) { this.AB.Webix.message({ - text: "The column name can't contain special characters.", + text: L( + "The column name can't contain special characters." + ), type: "error", }); @@ -577,7 +585,10 @@ export default function (AB, ibase) { await field.save(); // when add new link fields, then run create migrate fields here - if (!this._editField) { + if ( + !this._editField && + !this.CurrentObject.isNetsuite + ) { await field.migrateCreate(); await linkCol.migrateCreate(); }