From d8f8fc2ab16fed117be32d67845d030daba41919 Mon Sep 17 00:00:00 2001 From: BenWirachot Date: Fri, 3 Jul 2026 09:52:43 +0700 Subject: [PATCH 1/6] Migrate detail view components to the plugin-based system architecture --- AppBuilder/core | 2 +- .../view_dataview/FNAbviewdataview.js | 24 +- .../included/view_detail/DetailComponents.js | 8 + .../included/view_detail/FNAbviewdetail.js | 121 +- .../view_detail/FNAbviewdetailCheckbox.js | 28 + .../view_detail/FNAbviewdetailConnect.js | 41 + .../view_detail/FNAbviewdetailCustom.js | 28 + .../view_detail/FNAbviewdetailImage.js | 28 + .../view_detail/FNAbviewdetailItem.js | 23 + .../view_detail/FNAbviewdetailSelectivity.js | 28 + .../view_detail/FNAbviewdetailText.js | 27 + .../view_detail/FNAbviewdetailTree.js | 27 + .../core/ABViewDetailCheckboxCore.js | 33 + .../core/ABViewDetailComponentCore.js | 63 + .../core/ABViewDetailConnectCore.js | 30 + .../view_detail/core/ABViewDetailCore.js | 111 + .../core/ABViewDetailCustomCore.js | 33 + .../view_detail/core/ABViewDetailImageCore.js | 60 + .../view_detail/core/ABViewDetailItemCore.js | 81 + .../core/ABViewDetailSelectivityCore.js | 49 + .../view_detail/core/ABViewDetailTextCore.js | 55 + .../view_detail/core/ABViewDetailTreeCore.js | 33 + .../FNAbviewdetailCheckboxComponent.js | 44 + .../viewComponent/FNAbviewdetailComponent.js | 217 ++ .../FNAbviewdetailConnectComponent.js | 50 + .../FNAbviewdetailCustomComponent.js | 77 + .../FNAbviewdetailImageComponent.js | 69 + .../FNAbviewdetailItemComponent.js | 150 + .../FNAbviewdetailSelectivityComponent.js | 86 + .../FNAbviewdetailTextComponent.js | 32 + .../FNAbviewdetailTreeComponent.js | 116 + AppBuilder/platform/views/ABViewCarousel.js | 80 - AppBuilder/platform/views/ABViewComment.js | 42 - AppBuilder/platform/views/ABViewDataSelect.js | 35 - AppBuilder/platform/views/ABViewDataview.js | 43 - AppBuilder/platform/views/ABViewDetail.js | 31 - .../platform/views/ABViewDetailCheckbox.js | 13 - .../platform/views/ABViewDetailConnect.js | 38 - .../platform/views/ABViewDetailCustom.js | 13 - .../platform/views/ABViewDetailImage.js | 13 - AppBuilder/platform/views/ABViewDetailItem.js | 17 - .../platform/views/ABViewDetailSelectivity.js | 15 - AppBuilder/platform/views/ABViewDetailText.js | 22 - AppBuilder/platform/views/ABViewDetailTree.js | 22 - AppBuilder/platform/views/ABViewGrid.js | 90 - AppBuilder/platform/views/ABViewImage.js | 31 - AppBuilder/platform/views/ABViewLayout.js | 21 - AppBuilder/platform/views/ABViewList.js | 29 - AppBuilder/platform/views/ABViewTab.js | 47 - AppBuilder/platform/views/ABViewText.js | 14 - .../viewComponent/ABViewCarouselComponent.js | 531 ---- .../viewComponent/ABViewCommentComponent.js | 310 --- .../ABViewDataSelectComponent.js | 54 - .../viewComponent/ABViewDataviewComponent.js | 391 --- .../ABViewDetailCheckboxComponent.js | 40 - .../viewComponent/ABViewDetailComponent.js | 228 -- .../ABViewDetailConnectComponent.js | 44 - .../ABViewDetailCustomComponent.js | 75 - .../ABViewDetailImageComponent.js | 66 - .../ABViewDetailItemComponent.js | 150 - .../ABViewDetailSelectivityComponent.js | 83 - .../ABViewDetailTextComponent.js | 31 - .../ABViewDetailTreeComponent.js | 118 - .../viewComponent/ABViewGridComponent.js | 2402 ----------------- .../viewComponent/ABViewImageComponent.js | 51 - .../viewComponent/ABViewLayoutComponent.js | 73 - .../viewComponent/ABViewListComponent.js | 52 - .../views/viewComponent/ABViewTabComponent.js | 577 ---- .../viewComponent/ABViewTextComponent.js | 70 - 69 files changed, 1688 insertions(+), 6048 deletions(-) create mode 100644 AppBuilder/platform/plugins/included/view_detail/DetailComponents.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCheckbox.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailConnect.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCustom.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailImage.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailItem.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailSelectivity.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailText.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailTree.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailCheckboxCore.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailComponentCore.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailConnectCore.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailCore.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailCustomCore.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailImageCore.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailItemCore.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailSelectivityCore.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailTextCore.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailTreeCore.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailCheckboxComponent.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailComponent.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailConnectComponent.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailCustomComponent.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailImageComponent.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailItemComponent.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailSelectivityComponent.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTextComponent.js create mode 100644 AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTreeComponent.js delete mode 100644 AppBuilder/platform/views/ABViewCarousel.js delete mode 100644 AppBuilder/platform/views/ABViewComment.js delete mode 100644 AppBuilder/platform/views/ABViewDataSelect.js delete mode 100644 AppBuilder/platform/views/ABViewDataview.js delete mode 100644 AppBuilder/platform/views/ABViewDetail.js delete mode 100644 AppBuilder/platform/views/ABViewDetailCheckbox.js delete mode 100644 AppBuilder/platform/views/ABViewDetailConnect.js delete mode 100644 AppBuilder/platform/views/ABViewDetailCustom.js delete mode 100644 AppBuilder/platform/views/ABViewDetailImage.js delete mode 100644 AppBuilder/platform/views/ABViewDetailItem.js delete mode 100644 AppBuilder/platform/views/ABViewDetailSelectivity.js delete mode 100644 AppBuilder/platform/views/ABViewDetailText.js delete mode 100644 AppBuilder/platform/views/ABViewDetailTree.js delete mode 100644 AppBuilder/platform/views/ABViewGrid.js delete mode 100644 AppBuilder/platform/views/ABViewImage.js delete mode 100644 AppBuilder/platform/views/ABViewLayout.js delete mode 100644 AppBuilder/platform/views/ABViewList.js delete mode 100644 AppBuilder/platform/views/ABViewTab.js delete mode 100644 AppBuilder/platform/views/ABViewText.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewCarouselComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewCommentComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewDataSelectComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewDataviewComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewDetailCheckboxComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewDetailComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewDetailConnectComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewDetailCustomComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewDetailImageComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewDetailItemComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewDetailSelectivityComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewDetailTextComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewDetailTreeComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewGridComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewImageComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewLayoutComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewListComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewTabComponent.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewTextComponent.js diff --git a/AppBuilder/core b/AppBuilder/core index eb9f5a32..03d42060 160000 --- a/AppBuilder/core +++ b/AppBuilder/core @@ -1 +1 @@ -Subproject commit eb9f5a32bde5871b68d312f2e38ee8610e43f7a6 +Subproject commit 03d4206039de2805ef40f971e0b5228eadcc212c diff --git a/AppBuilder/platform/plugins/included/view_dataview/FNAbviewdataview.js b/AppBuilder/platform/plugins/included/view_dataview/FNAbviewdataview.js index 0d423394..ec75954e 100644 --- a/AppBuilder/platform/plugins/included/view_dataview/FNAbviewdataview.js +++ b/AppBuilder/platform/plugins/included/view_dataview/FNAbviewdataview.js @@ -4,13 +4,15 @@ import FNABViewDetail from "../view_detail/FNAbviewdetail.js"; // FNAbviewdataview Web // A web side import for an ABView. // -export default function FNAbviewdataview({ - AB, - ABViewComponentPlugin, - ABViewContainer, - ABViewContainerComponent, - ABViewPropertyLinkPage, -}) { +export default function FNAbviewdataview(API) { + const { + AB, + ABViewComponentPlugin, + ABViewContainer, + ABViewContainerComponent, + ABViewPropertyLinkPage, + } = API; + const ABAbviewdataviewComponent = FNAbviewdataviewComponent({ AB, ABViewComponentPlugin, @@ -32,10 +34,10 @@ export default function FNAbviewdataview({ labelKey: "Data view(plugin)", // {string} the multilingual label key for the class label }; - const ABViewDetail = FNABViewDetail({ - ABViewContainer, - ABViewContainerComponent, - }); + const detailViews = FNABViewDetail(API); + const ABViewDetail = Array.isArray(detailViews) + ? detailViews.find((v) => v.common().key === "detail") + : detailViews; class ABViewDataviewCore extends ABViewDetail { /** diff --git a/AppBuilder/platform/plugins/included/view_detail/DetailComponents.js b/AppBuilder/platform/plugins/included/view_detail/DetailComponents.js new file mode 100644 index 00000000..38559357 --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/DetailComponents.js @@ -0,0 +1,8 @@ +export { default as FNAbviewdetailCheckbox } from "./FNAbviewdetailCheckbox.js"; +export { default as FNAbviewdetailConnect } from "./FNAbviewdetailConnect.js"; +export { default as FNAbviewdetailCustom } from "./FNAbviewdetailCustom.js"; +export { default as FNAbviewdetailImage } from "./FNAbviewdetailImage.js"; +export { default as FNAbviewdetailItem } from "./FNAbviewdetailItem.js"; +export { default as FNAbviewdetailSelectivity } from "./FNAbviewdetailSelectivity.js"; +export { default as FNAbviewdetailText } from "./FNAbviewdetailText.js"; +export { default as FNAbviewdetailTree } from "./FNAbviewdetailTree.js"; diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js index 6d5f6e16..7340a415 100644 --- a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js @@ -1,86 +1,55 @@ -import FNAbviewdetailComponent from "./FNAbviewdetailComponent.js"; - -// Detail view plugin: replaces the original ABViewDetail / ABViewDetailCore. -// All logic from both Core and platform is contained in this file. -export default function FNAbviewdetail({ - ABViewContainer, - ABViewContainerComponent, -}) { - const ABViewDetailComponent = FNAbviewdetailComponent({ +import * as dComponents from "./DetailComponents.js"; +import FNAbviewdetailComponent from "./viewComponent/FNAbviewdetailComponent.js"; +import FNAbviewdetailCore from "./core/ABViewDetailCore.js"; +import FNAbviewdetailItemCore from "./core/ABViewDetailItemCore.js"; + +export default function FNAbviewdetail(API) { + const { + ABViewComponentPlugin, + ABViewPlugin, + ABViewWidgetPlugin, + ABViewContainer, ABViewContainerComponent, - }); - - const ABViewDetailDefaults = { - key: "detail", - icon: "file-text-o", - labelKey: "Detail(plugin)", + ABViewPropertyAddPage, + AB, + } = API; + + let DetailAPI = { + AB, + ABViewComponentPlugin, + ABViewPlugin, + ABViewPropertyAddPage, + ABViewWidget: ABViewWidgetPlugin, }; - const ABViewDetailPropertyComponentDefaults = { - dataviewID: null, - showLabel: true, - labelPosition: "left", - labelWidth: 120, - height: 0, - }; + // 1. Initialize Base Item + const { FNAbviewdetailItem, ...otherDComponents } = dComponents; - return class ABViewDetailPlugin extends ABViewContainer { - /** - * @param {obj} values key=>value hash of ABView values - * @param {ABApplication} application the application object this view is under - * @param {ABView} parent the ABView this view is a child of. (can be null) - */ - constructor(values, application, parent, defaultValues) { - super( - values, - application, - parent, - defaultValues ?? ABViewDetailDefaults - ); - } + DetailAPI.ABViewDetailItem = FNAbviewdetailItem(DetailAPI); - static getPluginType() { - return "view"; - } + // Store ABViewDetailItem for 'instanceof' checks in other plugins + if (AB && AB.Class) { + AB.Class.ABViewDetailItem = DetailAPI.ABViewDetailItem; + } - static getPluginKey() { - return this.common().key; - } + DetailAPI.ABViewDetailItemComponent = + DetailAPI.ABViewDetailItem.ABViewDetailItemComponent; + // 2. Initialize Custom/Sub classes + const views = Object.values(otherDComponents).map((FNv) => FNv(DetailAPI)); - static common() { - return ABViewDetailDefaults; - } + // 3. Main Detail View Component & Class + const ABViewDetailComponent = FNAbviewdetailComponent( + ABViewContainerComponent + ); + const ABViewDetailCore = FNAbviewdetailCore(ABViewContainer); - static defaultValues() { - return ABViewDetailPropertyComponentDefaults; + const ABViewDetail = class ABViewDetail extends ABViewDetailCore { + static getPluginKey() { + return this.common().key; } - /** - * @method fromValues() - * Initialize this object with the given set of values. - * @param {obj} values - */ - fromValues(values) { - super.fromValues(values); - - this.settings.labelPosition = - this.settings.labelPosition || - ABViewDetailPropertyComponentDefaults.labelPosition; - - this.settings.showLabel = JSON.parse( - this.settings.showLabel != null - ? this.settings.showLabel - : ABViewDetailPropertyComponentDefaults.showLabel - ); - - this.settings.labelWidth = parseInt( - this.settings.labelWidth || - ABViewDetailPropertyComponentDefaults.labelWidth - ); - this.settings.height = parseInt( - this.settings.height ?? - ABViewDetailPropertyComponentDefaults.height - ); + static getPluginType() { + return "view"; } /** @@ -107,7 +76,7 @@ export default function FNAbviewdetail({ newView.settings.fieldId = field.id; newView.settings.labelWidth = this.settings.labelWidth || - ABViewDetailPropertyComponentDefaults.labelWidth; + this.constructor.defaultValues().labelWidth; newView.settings.alias = field.alias; newView.position.y = yPosition; @@ -135,4 +104,8 @@ export default function FNAbviewdetail({ } } }; + + views.push(ABViewDetail); + + return views; } diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCheckbox.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCheckbox.js new file mode 100644 index 00000000..b49ae6cd --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCheckbox.js @@ -0,0 +1,28 @@ +import FNAbviewdetailCheckboxComponent from "./viewComponent/FNAbviewdetailCheckboxComponent.js"; +import FNAbviewdetailCheckboxCoreFactory from "./core/ABViewDetailCheckboxCore.js"; + +export default function FNAbviewdetailCheckbox({ + ABViewComponentPlugin, + ABViewDetailItemComponent, + ABViewDetailItem, +}) { + const ABViewDetailCheckboxCore = + FNAbviewdetailCheckboxCoreFactory(ABViewDetailItem); + const ABViewDetailCheckboxComponent = FNAbviewdetailCheckboxComponent( + ABViewDetailItemComponent + ); + + return class ABViewDetailCheckbox extends ABViewDetailCheckboxCore { + static getPluginKey() { + return this.common().key; + } + + static getPluginType() { + return "view"; + } + + component() { + return new ABViewDetailCheckboxComponent(this); + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailConnect.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailConnect.js new file mode 100644 index 00000000..54c1779b --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailConnect.js @@ -0,0 +1,41 @@ +import FNAbviewdetailConnectComponent from "./viewComponent/FNAbviewdetailConnectComponent.js"; +import FNAbviewdetailConnectCoreFactory from "./core/ABViewDetailConnectCore.js"; + +export default function FNAbviewdetailConnect({ + ABViewComponentPlugin, + ABViewDetailItemComponent, + ABViewDetailItem, + ABViewPropertyAddPage, +}) { + const ABViewDetailConnectCore = + FNAbviewdetailConnectCoreFactory(ABViewDetailItem); + const ABViewDetailConnectComponent = FNAbviewdetailConnectComponent( + ABViewDetailItemComponent + ); + + return class ABViewDetailConnect extends ABViewDetailConnectCore { + static getPluginKey() { + return this.common().key; + } + + static getPluginType() { + return "view"; + } + + fromValues(values) { + super.fromValues(values); + this.addPageTool.fromSettings(this.settings); + } + + component() { + return new ABViewDetailConnectComponent(this); + } + + get addPageTool() { + if (this.__addPageTool == null) + this.__addPageTool = new ABViewPropertyAddPage(); + + return this.__addPageTool; + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCustom.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCustom.js new file mode 100644 index 00000000..76973264 --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCustom.js @@ -0,0 +1,28 @@ +import FNAbviewdetailCustomComponent from "./viewComponent/FNAbviewdetailCustomComponent.js"; +import FNAbviewdetailCustomCoreFactory from "./core/ABViewDetailCustomCore.js"; + +export default function FNAbviewdetailCustom({ + ABViewComponentPlugin, + ABViewDetailItemComponent, + ABViewDetailItem, +}) { + const ABViewDetailCustomCore = + FNAbviewdetailCustomCoreFactory(ABViewDetailItem); + const ABViewDetailCustomComponent = FNAbviewdetailCustomComponent( + ABViewDetailItemComponent + ); + + return class ABViewDetailCustom extends ABViewDetailCustomCore { + static getPluginKey() { + return this.common().key; + } + + static getPluginType() { + return "view"; + } + + component() { + return new ABViewDetailCustomComponent(this); + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailImage.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailImage.js new file mode 100644 index 00000000..957f92d7 --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailImage.js @@ -0,0 +1,28 @@ +import FNAbviewdetailImageComponent from "./viewComponent/FNAbviewdetailImageComponent.js"; +import FNAbviewdetailImageCoreFactory from "./core/ABViewDetailImageCore.js"; + +export default function FNAbviewdetailImage({ + ABViewComponentPlugin, + ABViewDetailItemComponent, + ABViewDetailItem, +}) { + const ABViewDetailImageCore = + FNAbviewdetailImageCoreFactory(ABViewDetailItem); + const ABViewDetailImageComponent = FNAbviewdetailImageComponent( + ABViewDetailItemComponent + ); + + return class ABViewDetailImage extends ABViewDetailImageCore { + static getPluginKey() { + return this.common().key; + } + + static getPluginType() { + return "view"; + } + + component() { + return new ABViewDetailImageComponent(this); + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailItem.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailItem.js new file mode 100644 index 00000000..25117da4 --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailItem.js @@ -0,0 +1,23 @@ +import FNAbviewdetailItemComponent from "./viewComponent/FNAbviewdetailItemComponent.js"; +import FNAbviewdetailItemCoreFactory from "./core/ABViewDetailItemCore.js"; + +export default function FNAbviewdetailItem({ + ABViewComponentPlugin, + ABViewWidget, +}) { + const ABViewDetailItemCore = FNAbviewdetailItemCoreFactory(ABViewWidget); + const ABViewDetailItemComponent = FNAbviewdetailItemComponent( + ABViewComponentPlugin + ); + + const ABViewDetailItem = class ABViewDetailItem extends ABViewDetailItemCore { + component() { + return new ABViewDetailItemComponent(this); + } + }; + + // Attach the component class so subclasses can access it + ABViewDetailItem.ABViewDetailItemComponent = ABViewDetailItemComponent; + + return ABViewDetailItem; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailSelectivity.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailSelectivity.js new file mode 100644 index 00000000..46cdc4f2 --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailSelectivity.js @@ -0,0 +1,28 @@ +import FNAbviewdetailSelectivityComponent from "./viewComponent/FNAbviewdetailSelectivityComponent.js"; +import FNAbviewdetailSelectivityCoreFactory from "./core/ABViewDetailSelectivityCore.js"; + +export default function FNAbviewdetailSelectivity({ + ABViewComponentPlugin, + ABViewDetailItemComponent, + ABViewDetailItem, +}) { + const ABViewDetailSelectivityCore = + FNAbviewdetailSelectivityCoreFactory(ABViewDetailItem); + const ABViewDetailSelectivityComponent = FNAbviewdetailSelectivityComponent( + ABViewDetailItemComponent + ); + + return class ABViewDetailSelectivity extends ABViewDetailSelectivityCore { + static getPluginKey() { + return this.common().key; + } + + static getPluginType() { + return "view"; + } + + component() { + return new ABViewDetailSelectivityComponent(this); + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailText.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailText.js new file mode 100644 index 00000000..fa3f770b --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailText.js @@ -0,0 +1,27 @@ +import FNAbviewdetailTextComponent from "./viewComponent/FNAbviewdetailTextComponent.js"; +import FNAbviewdetailTextCoreFactory from "./core/ABViewDetailTextCore.js"; + +export default function FNAbviewdetailText({ + ABViewComponentPlugin, + ABViewDetailItemComponent, + ABViewDetailItem, +}) { + const ABViewDetailTextCore = FNAbviewdetailTextCoreFactory(ABViewDetailItem); + const ABViewDetailTextComponent = FNAbviewdetailTextComponent( + ABViewDetailItemComponent + ); + + return class ABViewDetailText extends ABViewDetailTextCore { + static getPluginKey() { + return this.common().key; + } + + static getPluginType() { + return "view"; + } + + component() { + return new ABViewDetailTextComponent(this); + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailTree.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailTree.js new file mode 100644 index 00000000..3c693aea --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailTree.js @@ -0,0 +1,27 @@ +import FNAbviewdetailTreeComponent from "./viewComponent/FNAbviewdetailTreeComponent.js"; +import FNAbviewdetailTreeCoreFactory from "./core/ABViewDetailTreeCore.js"; + +export default function FNAbviewdetailTree({ + ABViewComponentPlugin, + ABViewDetailItemComponent, + ABViewDetailItem, +}) { + const ABViewDetailTreeCore = FNAbviewdetailTreeCoreFactory(ABViewDetailItem); + const ABViewDetailTreeComponent = FNAbviewdetailTreeComponent( + ABViewDetailItemComponent + ); + + return class ABViewDetailTree extends ABViewDetailTreeCore { + static getPluginKey() { + return this.common().key; + } + + static getPluginType() { + return "view"; + } + + component() { + return new ABViewDetailTreeComponent(this); + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailCheckboxCore.js b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailCheckboxCore.js new file mode 100644 index 00000000..507afbb6 --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailCheckboxCore.js @@ -0,0 +1,33 @@ +export default function (ABViewDetailItem) { + const ABViewDetailCheckboxPropertyComponentDefaults = {}; + + const ABViewDetailCheckboxDefaults = { + key: "detailcheckbox", // {string} unique key for this view + icon: "check-square-o", // {string} fa-[icon] reference for this view + labelKey: "ab.components.detail.checkbox", // {string} the multilingual label key for the class label + }; + + return class ABViewDetailCheckboxCore extends ABViewDetailItem { + /** + * @param {obj} values key=>value hash of ABView values + * @param {ABApplication} application the application object this view is under + * @param {ABView} parent the ABView this view is a child of. (can be null) + */ + constructor(values, application, parent, defaultValues) { + super( + values, + application, + parent, + defaultValues ?? ABViewDetailCheckboxDefaults + ); + } + + static common() { + return ABViewDetailCheckboxDefaults; + } + + static defaultValues() { + return ABViewDetailCheckboxPropertyComponentDefaults; + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailComponentCore.js b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailComponentCore.js new file mode 100644 index 00000000..5abe02cc --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailComponentCore.js @@ -0,0 +1,63 @@ +export default function (ABViewWidget) { + return class ABViewDetailComponentCore extends ABViewWidget { + // constructor(values, application, parent, defaultValues) { + // super(values, application, parent, defaultValues); + // } + + detailComponent() { + let detailView = null; + + let curr = this; + while ( + !curr.isRoot() && + curr.parent && + curr.key != "detail" && + curr.key != "dataview" + ) { + curr = curr.parent; + } + + if (curr.key == "detail" || curr.key == "dataview") { + detailView = curr; + } + + return detailView; + } + + field() { + let detailComponent = this.detailComponent(); + if (detailComponent == null) return null; + + let datacollection = detailComponent.datacollection; + if (datacollection == null) return null; + + let object = datacollection.datasource; + if (object == null) return null; + + let field = object.fields((v) => v.id == this.settings.fieldId)[0]; + + // set .alias to support queries that contains alias name + // [aliasName].[columnName] + if (field && this.settings.alias) { + field.alias = this.settings.alias; + } + + return field; + } + + getCurrentData() { + let detailCom = this.detailComponent(); + if (!detailCom) return null; + + let dv = detailCom.datacollection; + if (!dv) return null; + + let field = this.field(); + if (!field) return null; + + let currData = dv.getCursor(); + if (currData) return currData[field.columnName]; + else return null; + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailConnectCore.js b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailConnectCore.js new file mode 100644 index 00000000..cf22497e --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailConnectCore.js @@ -0,0 +1,30 @@ +export default function (ABViewDetailItem) { + const ABViewDetailPropertyComponentDefaults = { + formView: "", // id of form to add new data + }; + + const ABViewDefaults = { + key: "detailconnect", // {string} unique key for this view + icon: "list-ul", // {string} fa-[icon] reference for this view + labelKey: "ab.components.detail.connect", // {string} the multilingual label key for the class label + }; + + return class ABViewDetailConnectCore extends ABViewDetailItem { + /** + * @param {obj} values key=>value hash of ABView values + * @param {ABApplication} application the application object this view is under + * @param {ABView} parent the ABView this view is a child of. (can be null) + */ + constructor(values, application, parent, defaultValues) { + super(values, application, parent, defaultValues ?? ABViewDefaults); + } + + static common() { + return ABViewDefaults; + } + + static defaultValues() { + return ABViewDetailPropertyComponentDefaults; + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailCore.js b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailCore.js new file mode 100644 index 00000000..89ac2b4d --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailCore.js @@ -0,0 +1,111 @@ +export default function (ABViewContainer) { + const ABViewDetailDefaults = { + key: "detail", // {string} unique key for this view + icon: "file-text-o", // {string} fa-[icon] reference for this view + labelKey: "Detail", // {string} the multilingual label key for the class label + }; + + const ABViewDetailPropertyComponentDefaults = { + dataviewID: null, + showLabel: true, + labelPosition: "left", + labelWidth: 120, + height: 0, + }; + + return class ABViewDetailCore extends ABViewContainer { + /** + * @param {obj} values key=>value hash of ABView values + * @param {ABApplication} application the application object this view is under + * @param {ABView} parent the ABView this view is a child of. (can be null) + */ + constructor(values, application, parent, defaultValues) { + super( + values, + application, + parent, + defaultValues ?? ABViewDetailDefaults + ); + } + + static common() { + return ABViewDetailDefaults; + } + + static defaultValues() { + return ABViewDetailPropertyComponentDefaults; + } + + /** + * @method fromValues() + * + * initialze this object with the given set of values. + * @param {obj} values + */ + fromValues(values) { + super.fromValues(values); + + this.settings.labelPosition = + this.settings.labelPosition || + ABViewDetailPropertyComponentDefaults.labelPosition; + + // convert from "0" => true/false + this.settings.showLabel = JSON.parse( + this.settings.showLabel != null + ? this.settings.showLabel + : ABViewDetailPropertyComponentDefaults.showLabel + ); + + // convert from "0" => 0 + this.settings.labelWidth = parseInt( + this.settings.labelWidth || + ABViewDetailPropertyComponentDefaults.labelWidth + ); + this.settings.height = parseInt( + this.settings.height ?? ABViewDetailPropertyComponentDefaults.height + ); + } + + /** + * @method componentList + * return the list of components available on this view to display in the editor. + */ + componentList() { + let viewsToAllow = ["label", "text"], + allComponents = this.application.viewAll(); + + return allComponents.filter((c) => { + return viewsToAllow.indexOf(c.common().key) > -1; + }); + } + + addFieldToDetail(field, yPosition) { + if (field == null) return; + + let newView = field + .detailComponent() + .newInstance(this.application, this); + if (newView == null) return; + + // set settings to component + newView.settings = newView.settings ?? {}; + newView.settings.fieldId = field.id; + newView.settings.labelWidth = + this.settings.labelWidth || + ABViewDetailPropertyComponentDefaults.labelWidth; + + // keep alias to support Query that contains alias name + // [alias].[columnName] + newView.settings.alias = field.alias; + + // TODO : Default settings + + newView.position.y = yPosition; + + // add a new component + this._views.push(newView); + + return newView; + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailCustomCore.js b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailCustomCore.js new file mode 100644 index 00000000..317a1d90 --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailCustomCore.js @@ -0,0 +1,33 @@ +export default function (ABViewDetailItem) { + const ABViewDetailCustomPropertyComponentDefaults = {}; + + const ABViewDetailCustomDefaults = { + key: "detailcustom", // {string} unique key for this view + icon: "dot-circle-o", // {string} fa-[icon] reference for this view + labelKey: "ab.components.detail.custom", // {string} the multilingual label key for the class label + }; + + return class ABViewDetailCustomCore extends ABViewDetailItem { + /** + * @param {obj} values key=>value hash of ABView values + * @param {ABApplication} application the application object this view is under + * @param {ABView} parent the ABView this view is a child of. (can be null) + */ + constructor(values, application, parent, defaultValues) { + super( + values, + application, + parent, + defaultValues ?? ABViewDetailCustomDefaults + ); + } + + static common() { + return ABViewDetailCustomDefaults; + } + + static defaultValues() { + return ABViewDetailCustomPropertyComponentDefaults; + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailImageCore.js b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailImageCore.js new file mode 100644 index 00000000..f55a141b --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailImageCore.js @@ -0,0 +1,60 @@ +export default function (ABViewDetailItem) { + const ABViewDetailImagePropertyComponentDefaults = { + height: 80, + width: 120, + }; + + const ABViewDetailImageDefaults = { + key: "detailimage", // {string} unique key for this view + icon: "image", // {string} fa-[icon] reference for this view + labelKey: "ab.components.detail.image", // {string} the multilingual label key for the class label + }; + + return class ABViewDetailImageCore extends ABViewDetailItem { + /** + * @param {obj} values key=>value hash of ABView values + * @param {ABApplication} application the application object this view is under + * @param {ABView} parent the ABView this view is a child of. (can be null) + */ + constructor(values, application, parent, defaultValues) { + super( + values, + application, + parent, + defaultValues ?? ABViewDetailImageDefaults + ); + } + + static common() { + return ABViewDetailImageDefaults; + } + + static defaultValues() { + return ABViewDetailImagePropertyComponentDefaults; + } + + /// + /// Instance Methods + /// + + /** + * @method fromValues() + * + * initialze this object with the given set of values. + * @param {obj} values + */ + fromValues(values) { + super.fromValues(values); + + // convert from "0" => 0 + this.settings.height = parseInt( + this.settings.height || + ABViewDetailImagePropertyComponentDefaults.height + ); + this.settings.width = parseInt( + this.settings.width ?? + ABViewDetailImagePropertyComponentDefaults.width + ); + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailItemCore.js b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailItemCore.js new file mode 100644 index 00000000..15860128 --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailItemCore.js @@ -0,0 +1,81 @@ +export default function (ABViewWidget) { + return class ABViewDetailItemCore extends ABViewWidget { + // constructor(values, application, parent, defaultValues) { + // super(values, application, parent, defaultValues); + // } + + detailComponent() { + let detailView = null; + + let curr = this; + while ( + !curr.isRoot() && + curr.parent && + curr.key != "detail" && + curr.key != "dataview" + ) { + curr = curr.parent; + } + + if (curr.key == "detail" || curr.key == "dataview") { + detailView = curr; + } + + return detailView; + } + + field() { + let detailComponent = this.detailComponent(); + if (detailComponent == null) return null; + + let datacollection = detailComponent.datacollection; + if (datacollection == null) return null; + + let object = datacollection.datasource; + if (object == null) return null; + + let field = object.fields((v) => v.id == this.settings.fieldId)[0]; + + // set .alias to support queries that contains alias name + // [aliasName].[columnName] + if (field && this.settings.alias) { + field.alias = this.settings.alias; + } + + return field; + } + + getCurrentData() { + let detailCom = this.detailComponent(); + if (!detailCom) return null; + + let dv = detailCom.datacollection; + if (!dv) return null; + + let field = this.field(); + if (!field) return null; + + let currData = dv.getCursor(); + if (currData) return currData[field.columnName]; + else return null; + } + + /** + * @method componentList + * return the list of components available on this view to display in the editor. + */ + componentList() { + return []; + } + + /** + * @property datacollection + * return data source + * NOTE: this view doesn't track a DataCollection. + * @return {ABDataCollection} + */ + get datacollection() { + return null; + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailSelectivityCore.js b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailSelectivityCore.js new file mode 100644 index 00000000..8db2432e --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailSelectivityCore.js @@ -0,0 +1,49 @@ +export default function (ABViewDetailItem) { + const ABViewDetailPropertyComponentDefaults = { + height: 0, + }; + + const ABViewDefaults = { + key: "detailselectivity", // {string} unique key for this view + icon: "tasks", // {string} fa-[icon] reference for this view + labelKey: "ab.components.detail.selectivity", // {string} the multilingual label key for the class label + }; + + return class ABViewDetailSelectivityCore extends ABViewDetailItem { + /** + * @param {obj} values key=>value hash of ABView values + * @param {ABApplication} application the application object this view is under + * @param {ABView} parent the ABView this view is a child of. (can be null) + */ + constructor(values, application, parent, defaultValues) { + super(values, application, parent, defaultValues ?? ABViewDefaults); + } + + static common() { + return ABViewDefaults; + } + + static defaultValues() { + return ABViewDetailPropertyComponentDefaults; + } + + /// + /// Instance Methods + /// + + /** + * @method fromValues() + * + * initialze this object with the given set of values. + * @param {obj} values + */ + fromValues(values) { + super.fromValues(values); + + // convert from "0" => 0 + this.settings.height = parseInt( + this.settings.height ?? ABViewDetailPropertyComponentDefaults.height + ); + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailTextCore.js b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailTextCore.js new file mode 100644 index 00000000..9487f82e --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailTextCore.js @@ -0,0 +1,55 @@ +export default function (ABViewDetailItem) { + const ABViewDetailTextPropertyComponentDefaults = { + height: 0, + }; + + const ABViewDetailTextDefaults = { + key: "detailtext", // {string} unique key for this view + icon: "etsy", // {string} fa-[icon] reference for this view + labelKey: "ab.components.detail.text", // {string} the multilingual label key for the class label + }; + + return class ABViewDetailTextCore extends ABViewDetailItem { + /** + * @param {obj} values key=>value hash of ABView values + * @param {ABApplication} application the application object this view is under + * @param {ABView} parent the ABView this view is a child of. (can be null) + */ + constructor(values, application, parent, defaultValues) { + super( + values, + application, + parent, + defaultValues ?? ABViewDetailTextDefaults + ); + } + + static common() { + return ABViewDetailTextDefaults; + } + + static defaultValues() { + return ABViewDetailTextPropertyComponentDefaults; + } + + /// + /// Instance Methods + /// + + /** + * @method fromValues() + * + * initialze this object with the given set of values. + * @param {obj} values + */ + fromValues(values) { + super.fromValues(values); + + // convert from "0" => 0 + this.settings.height = parseInt( + this.settings.height || + ABViewDetailTextPropertyComponentDefaults.height + ); + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailTreeCore.js b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailTreeCore.js new file mode 100644 index 00000000..2f706057 --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/core/ABViewDetailTreeCore.js @@ -0,0 +1,33 @@ +export default function (ABViewDetailItem) { + const ABViewDetailPropertyComponentDefaults = {}; + + const ABViewDetailTreeDefaults = { + key: "detailtree", // {string} unique key for this view + icon: "sitemap", // {string} fa-[icon] reference for this view + labelKey: "ab.components.detail.tree", // {string} the multilingual label key for the class label + }; + + return class ABViewDetailTreeCore extends ABViewDetailItem { + /** + * @param {obj} values key=>value hash of ABView values + * @param {ABApplication} application the application object this view is under + * @param {ABView} parent the ABView this view is a child of. (can be null) + */ + constructor(values, application, parent, defaultValues) { + super( + values, + application, + parent, + defaultValues ?? ABViewDetailTreeDefaults + ); + } + + static common() { + return ABViewDetailTreeDefaults; + } + + static defaultValues() { + return ABViewDetailPropertyComponentDefaults; + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailCheckboxComponent.js b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailCheckboxComponent.js new file mode 100644 index 00000000..30f43955 --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailCheckboxComponent.js @@ -0,0 +1,44 @@ +export default function (ABViewDetailItemComponent) { + return class ABViewDetailCheckboxComponent extends ABViewDetailItemComponent { + constructor(baseView, idBase, ids) { + super(baseView, idBase || `ABViewDetailCheckbox_${baseView.id}`, ids); + } + + ui() { + const baseView = this.view; + const field = baseView.field(); + + return super.ui({ + on: { + //Add data-cy attribute for Cypress Testing + onAfterRender: () => { + const dataCy = `detail checkbox ${field?.columnName} ${ + field?.id + } ${ + baseView.parentDetailComponent()?.id ?? baseView.parent.id + }`; + + $$(this.ids.detailItem)?.$view.setAttribute( + "data-cy", + dataCy + ); + }, + }, + }); + } + + setValue(val) { + let checkbox = ""; + + // Check + if (val && JSON.parse(val)) + checkbox = + ''; + // Uncheck + else + checkbox = ''; + + super.setValue(checkbox); + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailComponent.js b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailComponent.js new file mode 100644 index 00000000..14255fed --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailComponent.js @@ -0,0 +1,217 @@ +export default function (ABViewContainerComponent) { + return class ABViewDetailComponent extends ABViewContainerComponent { + constructor(baseView, idBase, ids) { + super(baseView, idBase || `ABViewDetail_${baseView.id}`, ids); + this.idBase = idBase; + } + + ui() { + let _ui = super.ui(); + + // this wrapper allows the detail view to have a + // card appearance as well as enables the edit and + // details functions to work when clicked + return { + type: "form", + id: this.ids.component, + borderless: true, + rows: [ + { + body: _ui, + }, + ], + }; + } + + onShow() { + const baseView = this.view; + + try { + const dataCy = `Detail ${baseView.name?.split(".")[0]} ${ + baseView.id + }`; + + $$(this.ids.component)?.$view.setAttribute("data-cy", dataCy); + } catch (e) { + console.warn("Problem setting data-cy", e); + } + + // listen DC events + const dv = this.datacollection; + + if (dv) { + const currData = dv.getCursor(); + + if (currData) this.displayData(currData); + + ["changeCursor", "cursorStale", "collectionEmpty"].forEach( + (key) => { + this.eventAdd({ + emitter: dv, + eventName: key, + listener: (...p) => this.displayData(...p), + }); + } + ); + + this.eventAdd({ + emitter: dv, + eventName: "create", + listener: (createdRow) => { + const currCursor = dv.getCursor(); + + if (currCursor?.id === createdRow.id) + this.displayData(createdRow); + }, + }); + + this.eventAdd({ + emitter: dv, + eventName: "update", + listener: (updatedRow) => { + const currCursor = dv.getCursor(); + + if (currCursor?.id === updatedRow.id) + this.displayData(updatedRow); + }, + }); + } + + super.onShow(); + } + + displayData(rowData = {}) { + // make sure we have data to work with. If null is passed in + // then pull current cursor. + if (rowData == null) { + rowData = this.datacollection.getCursor(); + } + + const views = (this.view.views() || []).sort((a, b) => { + if (!a?.field?.() || !b?.field?.()) return 0; + + // NOTE: sort order of calculated fields. + // FORMULA field type should be calculated before CALCULATE field type + if (a.field().key === "formula" && b.field().key === "calculate") + return -1; + else if ( + a.field().key === "calculate" && + b.field().key === "formula" + ) + return 1; + + return 0; + }); + + views.forEach((f) => { + let val; + + if (f.field) { + const field = f.field(); + + if (!field) return; + + // get value of relation when field is a connect field + switch (field.key) { + case "connectObject": + val = field.pullRelationValues(rowData); + + break; + + case "list": + val = rowData?.[field.columnName]; + + if (!val) { + val = ""; + + break; + } + + if (field.settings.isMultiple === 0) { + let myVal = ""; + + field.settings.options.forEach((options) => { + if (options.id === val) myVal = options.text; + }); + + if (field.settings.hasColors) { + let myHex = "#66666"; + let hasCustomColor = ""; + + field.settings.options.forEach((h) => { + if (h.text === myVal) { + myHex = h.hex; + hasCustomColor = "hascustomcolor"; + } + }); + + myVal = `${myVal}`; + } + + val = myVal; + } else { + const items = []; + + let myVal = ""; + + val.forEach((value) => { + let hasCustomColor = ""; + let optionHex = ""; + + if (field.settings.hasColors && value.hex) { + hasCustomColor = "hascustomcolor"; + optionHex = `background: ${value.hex};`; + } + + field.settings.options.forEach((options) => { + if (options.id === value.id) myVal = options.text; + }); + items.push( + `${myVal}` + ); + }); + + val = items.join(""); + } + + break; + + case "user": + val = field.pullRelationValues(rowData); + + break; + + case "file": + val = rowData?.[field.columnName]; + + if (!val) { + val = ""; + + break; + } + + break; + + case "formula": + if (rowData) { + const needRecalculate = false; + + val = field.format(rowData, needRecalculate); + } + + break; + + default: + val = field.format(rowData); + } + } + + // set value to each components + const vComponent = f.component(this.idBase); + + vComponent?.setValue?.(val); + vComponent?.displayText?.(rowData); + }); + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailConnectComponent.js b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailConnectComponent.js new file mode 100644 index 00000000..a936c42d --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailConnectComponent.js @@ -0,0 +1,50 @@ +export default function (ABViewDetailItemComponent) { + return class ABViewDetailConnectComponent extends ABViewDetailItemComponent { + constructor(baseView, idBase, ids) { + super(baseView, idBase || `ABViewDetailConnect_${baseView.id}`, ids); + } + + ui() { + const baseView = this.view; + const settings = this.settings; + + return super.ui({ + on: { + //Add data-cy attribute for Cypress Testing + onAfterRender: () => { + const columnName = + baseView.field((fld) => fld.id === settings.fieldId) + ?.columnName ?? ""; + const dataCy = `detail connected ${columnName} ${ + settings.fieldId + } ${ + baseView.parentDetailComponent()?.id || baseView.parent.id + }`; + + $$(this.ids.detailItem)?.$view.setAttribute( + "data-cy", + dataCy + ); + }, + }, + }); + } + + setValue(val) { + const vals = []; + + if (Array.isArray(val)) + val.forEach((record) => { + vals.push( + `${record.text}` + ); + }); + else if (val) + vals.push( + `${val.text}` + ); + + super.setValue(vals.join("")); + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailCustomComponent.js b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailCustomComponent.js new file mode 100644 index 00000000..529848f7 --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailCustomComponent.js @@ -0,0 +1,77 @@ +export default function (ABViewDetailItemComponent) { + return class ABViewDetailCustomComponent extends ABViewDetailItemComponent { + constructor(baseView, idBase, ids) { + super(baseView, idBase || `ABViewDetailCustom_${baseView.id}`, ids); + } + + ui() { + const baseView = this.view; + const field = baseView.field(); + + let template = field ? field.columnHeader().template({}) : ""; + + return super.ui({ + minHeight: 45, + height: 60, + template, + on: { + //Add data-cy attribute for Cypress Testing + onAfterRender: () => { + const dataCy = `detail custom ${field?.columnName} ${ + field?.id + } ${ + baseView.parentDetailComponent()?.id || baseView.parent.id + }`; + + $$(this.ids.detailItem)?.$view.setAttribute( + "data-cy", + dataCy + ); + }, + }, + }); + } + + onShow() { + super.onShow(); + + const baseView = this.view; + const field = baseView.field(); + + if (!field) return; + + const $detailItem = $$(this.ids.detailItem); + + if (!$detailItem) return; + + const detailCom = baseView.detailComponent(), + rowData = detailCom.datacollection.getCursor() || {}, + node = $detailItem.$view; + + field.customDisplay(rowData, null, node, { + editable: false, + }); + // Hack: remove the extra webix_template class here, which adds padding so + // the item is not alligned with the others + node + .getElementsByClassName("webix_template")[1] + ?.removeAttribute("class"); + } + + setValue(val) { + const field = this.view.field(); + + if (!field) return; + + const $detailItem = $$(this.ids.detailItem); + + if (!$detailItem) return; + + const rowData = {}; + + rowData[field.columnName] = val; + + field.setValue($detailItem, rowData); + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailImageComponent.js b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailImageComponent.js new file mode 100644 index 00000000..0afcc25f --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailImageComponent.js @@ -0,0 +1,69 @@ +export default function (ABViewDetailItemComponent) { + return class ABViewDetailImageComponent extends ABViewDetailItemComponent { + constructor(baseView, idBase, ids) { + super(baseView, idBase || `ABViewDetailImage_${baseView.id}`, ids); + } + + ui() { + const baseView = this.view; + const field = baseView.field(); + const _ui = { + on: { + //Add data-cy attribute for Cypress Testing + onAfterRender: () => { + const dataCy = `detail image ${field?.columnName} ${ + field?.id + } ${ + baseView.parentDetailComponent()?.id || baseView.parent.id + }`; + + $$(this.ids.detailItem)?.$view.setAttribute( + "data-cy", + dataCy + ); + }, + }, + }; + const settings = this.settings; + + if (settings.height) _ui.height = settings.height; + + return super.ui(_ui); + } + + setValue(val) { + const field = this.view.field(); + + if (!field) { + super.setValue(""); + + return; + } + + const parsedImageUrl = val || field.settings.defaultImageUrl; + + if (!parsedImageUrl) { + super.setValue(""); + + return; + } + + const imageUrl = field.urlImage(parsedImageUrl); + const settings = this.settings; + const width = settings.width || field.settings.imageWidth || 200; + const height = settings.height + ? `${settings.height}px` + : field.settings.imageHeight + ? `${field.settings.imageHeight}px` + : "100%"; + const imageTemplate = [ + `
`, + `
`, + ``, + `
`, + ].join(""); + + super.setValue(imageTemplate); + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailItemComponent.js b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailItemComponent.js new file mode 100644 index 00000000..4da3280b --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailItemComponent.js @@ -0,0 +1,150 @@ +export default function (ABViewComponentPlugin) { + const SAFE_HTML_TAGS = [ + "abbr", + "acronym", + "b", + "blockquote", + "br", + "code", + "div", + "em", + "i", + "li", + "ol", + "p", + "span", + "strong", + "table", + "td", + "tr", + "ul", + "h1", + "h2", + "h3", + "h4", + "h5", + "abbr", + ]; + + return class ABViewDetailItemComponent extends ABViewComponentPlugin { + constructor(baseView, idBase, ids) { + super( + baseView, + idBase || `ABViewDetailItem_${baseView.id}`, + Object.assign( + { + detailItem: "", + detailItemLabel: "", + }, + ids + ) + ); + } + + ui(uiDetailItemComponent = {}) { + const baseView = this.view; + + // setup 'label' of the element + const settings = baseView.detailComponent()?.settings ?? {}; + const field = baseView.field(); + + const isLabelTop = settings.labelPosition == "top"; + + const group = []; + /** @const group will be used later as rows or cols depending on label position */ + if (settings.showLabel) { + const templateLabel = isLabelTop + ? "" + : ""; + + const labelUi = { + id: this.ids.detailItemLabel, + view: "template", + borderless: true, + height: 38, + template: templateLabel, + data: { label: field?.label ?? "" }, + }; + if (!isLabelTop) labelUi.width = settings.labelWidth + 24; // Add 24px to compensate for webix padding + group.push(labelUi); + } + + let height; + if (field?.settings?.useHeight === 1) + height = parseInt(field.settings.imageHeight) || height; + + const valueUi = Object.assign( + { + id: this.ids.detailItem, + view: "template", + borderless: true, + autowidth: true, + height, + isUsers: field?.key === "user", + template: isLabelTop + ? "
#display#
" + : "
#display#
", + data: { display: "" }, // show empty data in template + }, + uiDetailItemComponent + ); + // height = 0 behaves a bit differently then autoheight here. + if (!valueUi.height || valueUi.height == 0) { + delete valueUi.height; + valueUi.autoheight = true; + } + group.push(valueUi); + const itemUi = {}; + settings.labelPosition == "top" + ? (itemUi.rows = group) + : (itemUi.cols = group); + const _ui = super.ui([itemUi]); + + delete _ui.type; + + return _ui; + } + + setValue(val, detailId) { + const $detailItem = $$(detailId ?? this.ids.detailItem); + + if (!$detailItem) return; + + const field = this.view.field(); + + switch (field?.key) { + case "string": + case "LongText": { + const strVal = (val || "") + .toString() + // Sanitize all of HTML tags + .replace(/[<]/gm, "<") + // Allow safe HTML tags + .replace( + new RegExp( + `(<(/)?(${SAFE_HTML_TAGS.join("|")}))`, + "gm" + ), + "<$2$3" + ); + + $detailItem.setValues({ display: strVal }); + break; + } + case "json": { + let jsonVal = val; + + if (typeof val == "object") { + jsonVal = JSON.stringify(val, null, 2); + } + + $detailItem.setValues({ display: jsonVal }); + break; + } + default: + $detailItem.setValues({ display: val }); + break; + } + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailSelectivityComponent.js b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailSelectivityComponent.js new file mode 100644 index 00000000..91868fdc --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailSelectivityComponent.js @@ -0,0 +1,86 @@ +export default function (ABViewDetailItemComponent) { + return class ABViewDetailSelectivityComponent extends ABViewDetailItemComponent { + constructor(baseView, idBase, ids) { + super( + baseView, + idBase || `ABViewDetailSelectivityComponent_${baseView.id}`, + ids + ); + } + + ui() { + const baseView = this.view; + const settings = this.settings; + const field = baseView.field(); + const _ui = { + on: { + //Add data-cy attribute for Cypress Testing + onAfterRender: () => { + const dataCy = `detail selectivity ${field?.columnName} ${ + field?.id + } ${ + baseView.parentDetailComponent()?.id || baseView.parent.id + }`; + + $$(this.ids.detailItem)?.$view.setAttribute( + "data-cy", + dataCy + ); + }, + }, + }; + + if (settings.height) _ui.height = settings.height; + + return super.ui(_ui); + } + + async init(AB) { + await super.init(AB); + + // add div of selectivity to detail + this.setValue( + this.ids.detailItem, + `
` + ); + } + + getDomSelectivity() { + const elem = $$(this.ids.component); + + if (!elem) return; + + return elem.$view.getElementsByClassName("ab-detail-selectivity")[0]; + } + + setValue(val) { + // convert value to array + if (val && !(val instanceof Array)) val = [val]; + + setTimeout(() => { + // get selectivity dom + const domSelectivity = this.getDomSelectivity(); + const isUsers = this.ui().isUsers ?? false; + + // render selectivity to html dom + const selectivitySettings = { + multiple: true, + readOnly: true, + isUsers: isUsers, + }; + const field = this.view.field(); + + field.selectivityRender( + domSelectivity, + selectivitySettings, + // App + null, + {} + ); + + // set value to selectivity + field.selectivitySet(domSelectivity, val, /*App*/ null); + }, 50); + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTextComponent.js b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTextComponent.js new file mode 100644 index 00000000..5e27c2fe --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTextComponent.js @@ -0,0 +1,32 @@ +export default function (ABViewDetailItemComponent) { + return class ABViewDetailTextComponent extends ABViewDetailItemComponent { + constructor(baseView, idBase, ids) { + super(baseView, idBase || `ABViewDetailText_${baseView.id}`, ids); + } + + ui() { + const field = this.view.field(); + const _ui = { + css: "ab-text", + on: { + //Add data-cy attribute for Cypress Testing + onAfterRender: () => { + const dataCy = `detail text ${field?.columnName} ${ + field?.id + } ${ + this.view.parentDetailComponent()?.id || + this.view.parent.id + }`; + + $$(this.ids.component)?.$view.setAttribute("data-cy", dataCy); + }, + }, + }; + const settings = this.settings; + + if (settings.height) _ui.height = settings.height; + + return super.ui(_ui); + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTreeComponent.js b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTreeComponent.js new file mode 100644 index 00000000..a053a678 --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTreeComponent.js @@ -0,0 +1,116 @@ +export default function (ABViewDetailItemComponent) { + return class ABViewDetailTreeComponent extends ABViewDetailItemComponent { + constructor(baseView, idBase, ids) { + super(baseView, idBase || `ABViewDetailTree_${baseView.id}`, ids); + } + + get className() { + return "ab-detail-tree"; + } + + async init(AB) { + await super.init(AB); + + // add div of tree to detail + this.setValue(`
`); + } + + getDomTree() { + const $detailItem = $$(this.ids.detailItem); + + if (!$detailItem) return; + + return $detailItem.$view.getElementsByClassName(this.className)[0]; + } + + setValue(val) { + // convert value to array + let vals = []; + + if (Array.isArray(val)) { + vals = val; + } else if (val) { + // if it is the initial html string, then just set it and return + if (typeof val == "string" && val.indexOf(this.className) > -1) { + super.setValue(val); + return; + } + + try { + const parsed = JSON.parse(val); + + if (Array.isArray(parsed)) { + vals = parsed; + } else { + vals.push(parsed); + } + } catch (e) { + if (typeof val == "string") + vals = val.split(",").filter((v) => v !== ""); + else vals.push(val); + } + + // Normalize all entries to IDs + vals = vals.map((v) => + v && typeof v === "object" && v.id ? v.id : v + ); + } + + setTimeout(() => { + // get tree dom + const domTree = this.getDomTree(); + + if (!domTree) return false; + + const field = this.view.field(); + const branches = []; + + let selectOptions = this.AB.cloneDeep(field.settings.options); + + selectOptions = new this.AB.Webix.TreeCollection({ + data: selectOptions, + }); + + selectOptions.data.each(function (obj) { + if (vals.some((v) => v == obj.id)) { + let html = ""; + let rootid = obj.id; + + while (selectOptions.data.getParentId(rootid)) { + selectOptions.data.each(function (par) { + if (selectOptions.data.getParentId(rootid) === par.id) { + html = `${par.text}: ${html}`; + } + }); + + rootid = selectOptions.data.getParentId(rootid); + } + + html += obj.text; + branches.push(html); + } + }); + + const myHex = "#4CAF50"; + + let nodeHTML = "
"; + + branches.forEach(function (item) { + nodeHTML += `${item}`; + }); + + nodeHTML += "
"; + domTree.innerHTML = nodeHTML; + + let height = 33; + + if (domTree.scrollHeight > 33) height = domTree.scrollHeight; + + const $detailItem = $$(this.ids.detailItem); + + $detailItem.config.height = height; + $detailItem.resize(); + }, 50); + } + }; +} diff --git a/AppBuilder/platform/views/ABViewCarousel.js b/AppBuilder/platform/views/ABViewCarousel.js deleted file mode 100644 index 9fbf4e86..00000000 --- a/AppBuilder/platform/views/ABViewCarousel.js +++ /dev/null @@ -1,80 +0,0 @@ -const ABViewCarouselCore = require("../../core/views/ABViewCarouselCore"); -import ABViewCarouselComponent from "./viewComponent/ABViewCarouselComponent"; - -// const ABViewPropertyFilterData = require("./viewProperties/ABViewPropertyFilterData"); -// const ABViewPropertyLinkPage = require("./viewProperties/ABViewPropertyLinkPage"); - -import ABViewPropertyFilterData from "./viewProperties/ABViewPropertyFilterData"; -import ABViewPropertyLinkPage from "./viewProperties/ABViewPropertyLinkPage"; - -let PopupCarouselFilterMenu = null; - -export default class ABViewCarousel extends ABViewCarouselCore { - constructor(values, application, parent, defaultValues) { - super(values, application, parent, defaultValues); - } - - /// - /// Instance Methods - /// - - /** - * @method fromValues() - * - * initialze this object with the given set of values. - * @param {obj} values - */ - fromValues(values) { - super.fromValues(values); - - // filter property - this.filterHelper.fromSettings(this.settings.filter); - } - - /** - * @method component() - * return a UI component based upon this view. - * @return {obj} UI component - */ - component() { - var dv = this.datacollection; - if (dv) { - this.filterHelper.objectLoad(dv.datasource); - this.filterHelper.fromSettings(this.settings.filter); - } - - return new ABViewCarouselComponent(this); - } - - get idBase() { - return `ABViewCarousel_${this.id}`; - } - - get filterHelper() { - if (this.__filterHelper == null) - this.__filterHelper = new ABViewPropertyFilterData( - this.AB, - this.idBase - ); - - return this.__filterHelper; - } - - get linkPageHelper() { - if (this.__linkPageHelper == null) - this.__linkPageHelper = new ABViewPropertyLinkPage(); - - return this.__linkPageHelper; - } - - warningsEval() { - super.warningsEval(); - - let field = this.imageField; - if (!field) { - this.warningsMessage( - `can't resolve image field[${this.settings.field}]` - ); - } - } -} diff --git a/AppBuilder/platform/views/ABViewComment.js b/AppBuilder/platform/views/ABViewComment.js deleted file mode 100644 index c949dad8..00000000 --- a/AppBuilder/platform/views/ABViewComment.js +++ /dev/null @@ -1,42 +0,0 @@ -const ABViewCommentCore = require("../../core/views/ABViewCommentCore"); -const ABViewCommentComponent = require("./viewComponent/ABViewCommentComponent"); - -module.exports = class ABViewComment extends ABViewCommentCore { - // constructor(values, application, parent, defaultValues) { - // super(values, application, parent, defaultValues); - // } - - /** - * @method component() - * return a UI component based upon this view. - * @return {obj} UI component - */ - component() { - return new ABViewCommentComponent(this); - } - - warningsEval() { - super.warningsEval(); - - let field = this.getUserField(); - if (!field) { - this.warningsMessage( - `can't resolve user field[${this.settings.columnUser}]` - ); - } - - field = this.getCommentField(); - if (!field) { - this.warningsMessage( - `can't resolve comment field[${this.settings.columnComment}]` - ); - } - - field = this.getDateField(); - if (!field) { - this.warningsMessage( - `can't resolve date field[${this.settings.columnDate}]` - ); - } - } -}; diff --git a/AppBuilder/platform/views/ABViewDataSelect.js b/AppBuilder/platform/views/ABViewDataSelect.js deleted file mode 100644 index 478a6fb2..00000000 --- a/AppBuilder/platform/views/ABViewDataSelect.js +++ /dev/null @@ -1,35 +0,0 @@ -import ABViewDataSelectCore from "../../core/views/ABViewDataSelectCore"; -import ABViewDataSelectComponent from "./viewComponent/ABViewDataSelectComponent"; - -export default class ABViewDataSelect extends ABViewDataSelectCore { - /** - * @method component() - * return a UI component based upon this view. - * @param {obj} App - * @return {obj} UI component - */ - component() { - return new ABViewDataSelectComponent(this); - } - - warningsEval() { - super.warningsEval(); - - let DC = this.datacollection; - if (!DC) { - this.warningsMessage( - `can't resolve it's datacollection[${this.settings.dataviewID}]` - ); - } else { - if (this.settings.viewType == "connected") { - const object = DC.datasource; - const [field] = object.fields( - (f) => f.columnName === this.settings.field - ); - if (!field) { - this.warningsMessage(`can't resolve field reference`); - } - } - } - } -} diff --git a/AppBuilder/platform/views/ABViewDataview.js b/AppBuilder/platform/views/ABViewDataview.js deleted file mode 100644 index 240bdee8..00000000 --- a/AppBuilder/platform/views/ABViewDataview.js +++ /dev/null @@ -1,43 +0,0 @@ -const ABViewDataviewCore = require("../../core/views/ABViewDataviewCore"); -const ABViewDataviewComponent = require("./viewComponent/ABViewDataviewComponent"); - -const ABViewDataviewDefaults = ABViewDataviewCore.defaultValues(); - -const L = (...params) => AB.Multilingual.label(...params); - -module.exports = class ABViewDataview extends ABViewDataviewCore { - // constructor(values, application, parent, defaultValues) { - // super(values, application, parent, defaultValues); - // } - - /** - * @method fromValues() - * - * initialze this object with the given set of values. - * @param {obj} values - */ - fromValues(values) { - super.fromValues(values); - - this.settings.detailsPage = - this.settings.detailsPage ?? ABViewDataviewDefaults.detailsPage; - this.settings.editPage = - this.settings.editPage ?? ABViewDataviewDefaults.editPage; - this.settings.detailsTab = - this.settings.detailsTab ?? ABViewDataviewDefaults.detailsTab; - this.settings.editTab = - this.settings.editTab ?? ABViewDataviewDefaults.editTab; - } - - /** - * @method component() - * return a UI component based upon this view. - * @param {obj } v1App - * @param {string} idPrefix - define to support in 'Datacollection' widget - * - * @return {obj } UI component - */ - component() { - return new ABViewDataviewComponent(this); - } -}; diff --git a/AppBuilder/platform/views/ABViewDetail.js b/AppBuilder/platform/views/ABViewDetail.js deleted file mode 100644 index 77d923c4..00000000 --- a/AppBuilder/platform/views/ABViewDetail.js +++ /dev/null @@ -1,31 +0,0 @@ -const ABViewDetailCore = require("../../core/views/ABViewDetailCore"); -const ABViewDetailComponent = require("./viewComponent/ABViewDetailComponent"); - -module.exports = class ABViewDetail extends ABViewDetailCore { - // constructor(values, application, parent, defaultValues) { - // super(values, application, parent, defaultValues); - // } - - /** - * @method component() - * return a UI component based upon this view. - * @param {obj } v1App - * @param {string} idPrefix - define to support in 'Datacollection' widget - * - * @return {obj } UI component - */ - component() { - return new ABViewDetailComponent(this); - } - - warningsEval() { - super.warningsEval(); - - let DC = this.datacollection; - if (!DC) { - this.warningsMessage( - `can't resolve it's datacollection[${this.settings.dataviewID}]` - ); - } - } -}; diff --git a/AppBuilder/platform/views/ABViewDetailCheckbox.js b/AppBuilder/platform/views/ABViewDetailCheckbox.js deleted file mode 100644 index 7a464508..00000000 --- a/AppBuilder/platform/views/ABViewDetailCheckbox.js +++ /dev/null @@ -1,13 +0,0 @@ -const ABViewDetailCheckboxCore = require("../../core/views/ABViewDetailCheckboxCore"); -const ABViewDetailCheckboxComponent = require("./viewComponent/ABViewDetailCheckboxComponent"); - -module.exports = class ABViewDetailCheckbox extends ABViewDetailCheckboxCore { - /** - * @method component() - * return a UI component based upon this view. - * @return {obj} UI component - */ - component() { - return new ABViewDetailCheckboxComponent(this); - } -}; diff --git a/AppBuilder/platform/views/ABViewDetailConnect.js b/AppBuilder/platform/views/ABViewDetailConnect.js deleted file mode 100644 index 24eeaa00..00000000 --- a/AppBuilder/platform/views/ABViewDetailConnect.js +++ /dev/null @@ -1,38 +0,0 @@ -const ABViewDetailConnectCore = require("../../core/views/ABViewDetailConnectCore"); -const ABViewPropertyAddPage = - require("./viewProperties/ABViewPropertyAddPage").default; - -const ABViewDetailConnectComponent = require("./viewComponent/ABViewDetailConnectComponent"); - -module.exports = class ABViewDetailConnect extends ABViewDetailConnectCore { - /// - /// Instance Methods - /// - - /** - * @method fromValues() - * - * initialze this object with the given set of values. - * @param {obj} values - */ - fromValues(values) { - super.fromValues(values); - this.addPageTool.fromSettings(this.settings); - } - - /** - * @method component() - * return a UI component based upon this view. - * @return {obj} UI component - */ - component() { - return new ABViewDetailConnectComponent(this); - } - - get addPageTool() { - if (this.__addPageTool == null) - this.__addPageTool = new ABViewPropertyAddPage(); - - return this.__addPageTool; - } -}; diff --git a/AppBuilder/platform/views/ABViewDetailCustom.js b/AppBuilder/platform/views/ABViewDetailCustom.js deleted file mode 100644 index e8f92375..00000000 --- a/AppBuilder/platform/views/ABViewDetailCustom.js +++ /dev/null @@ -1,13 +0,0 @@ -const ABViewDetailCustomCore = require("../../core/views/ABViewDetailCustomCore"); -const ABViewDetailCustomComponent = require("./viewComponent/ABViewDetailCustomComponent"); - -module.exports = class ABViewDetailCustom extends ABViewDetailCustomCore { - /** - * @method component() - * return a UI component based upon this view. - * @return {obj} UI component - */ - component() { - return new ABViewDetailCustomComponent(this); - } -}; diff --git a/AppBuilder/platform/views/ABViewDetailImage.js b/AppBuilder/platform/views/ABViewDetailImage.js deleted file mode 100644 index 8f4f1de4..00000000 --- a/AppBuilder/platform/views/ABViewDetailImage.js +++ /dev/null @@ -1,13 +0,0 @@ -const ABViewDetailImageCore = require("../../core/views/ABViewDetailImageCore"); -const ABViewDetailImageComponent = require("./viewComponent/ABViewDetailImageComponent"); - -module.exports = class ABViewDetailImage extends ABViewDetailImageCore { - /** - * @method component() - * return a UI component based upon this view. - * @return {obj} UI component - */ - component() { - return new ABViewDetailImageComponent(this); - } -}; diff --git a/AppBuilder/platform/views/ABViewDetailItem.js b/AppBuilder/platform/views/ABViewDetailItem.js deleted file mode 100644 index 5188acf2..00000000 --- a/AppBuilder/platform/views/ABViewDetailItem.js +++ /dev/null @@ -1,17 +0,0 @@ -const ABViewDetailItemCore = require("../../core/views/ABViewDetailItemCore"); -const ABViewDetailItemComponent = require("./viewComponent/ABViewDetailItemComponent"); - -module.exports = class ABViewDetailItem extends ABViewDetailItemCore { - // constructor(values, application, parent, defaultValues) { - // super(values, application, parent, defaultValues); - // } - - /** - * @method component() - * return a UI component based upon this view. - * @return {obj} UI component - */ - component() { - return new ABViewDetailItemComponent(this); - } -}; diff --git a/AppBuilder/platform/views/ABViewDetailSelectivity.js b/AppBuilder/platform/views/ABViewDetailSelectivity.js deleted file mode 100644 index d82caa74..00000000 --- a/AppBuilder/platform/views/ABViewDetailSelectivity.js +++ /dev/null @@ -1,15 +0,0 @@ -const ABViewDetailSelectivityCore = require("../../core/views/ABViewDetailSelectivityCore"); -const ABViewDetailSelectivityComponent = require("./viewComponent/ABViewDetailSelectivityComponent"); - -module.exports = class ABViewDetailSelectivity extends ( - ABViewDetailSelectivityCore -) { - /** - * @component() - * return a UI component based upon this view. - * @return {obj} UI component - */ - component() { - return new ABViewDetailSelectivityComponent(this); - } -}; diff --git a/AppBuilder/platform/views/ABViewDetailText.js b/AppBuilder/platform/views/ABViewDetailText.js deleted file mode 100644 index f5b2015a..00000000 --- a/AppBuilder/platform/views/ABViewDetailText.js +++ /dev/null @@ -1,22 +0,0 @@ -const ABViewDetailTextCore = require("../../core/views/ABViewDetailTextCore"); -const ABViewDetailTextComponent = require("./viewComponent/ABViewDetailTextComponent"); - -module.exports = class ABViewDetailText extends ABViewDetailTextCore { - /** - * @param {obj} values key=>value hash of ABView values - * @param {ABApplication} application the application object this view is under - * @param {ABView} parent the ABView this view is a child of. (can be null) - */ - // constructor(values, application, parent, defaultValues) { - // super(values, application, parent, defaultValues); - // } - - /** - * @method component() - * return a UI component based upon this view. - * @return {obj} UI component - */ - component() { - return new ABViewDetailTextComponent(this); - } -}; diff --git a/AppBuilder/platform/views/ABViewDetailTree.js b/AppBuilder/platform/views/ABViewDetailTree.js deleted file mode 100644 index ef10cb49..00000000 --- a/AppBuilder/platform/views/ABViewDetailTree.js +++ /dev/null @@ -1,22 +0,0 @@ -const ABViewDetailTreeCore = require("../../core/views/ABViewDetailTreeCore"); -const ABViewDetailTreeComponent = require("./viewComponent/ABViewDetailTreeComponent"); - -module.exports = class ABViewDetailTree extends ABViewDetailTreeCore { - /** - * @param {obj} values key=>value hash of ABView values - * @param {ABApplication} application the application object this view is under - * @param {ABView} parent the ABView this view is a child of. (can be null) - */ - // constructor(values, application, parent, defaultValues) { - // super(values, application, parent, defaultValues); - // } - - /** - * @method component() - * return a UI component based upon this view. - * @return {obj} UI component - */ - component() { - return new ABViewDetailTreeComponent(this); - } -}; diff --git a/AppBuilder/platform/views/ABViewGrid.js b/AppBuilder/platform/views/ABViewGrid.js deleted file mode 100644 index 1b6eded4..00000000 --- a/AppBuilder/platform/views/ABViewGrid.js +++ /dev/null @@ -1,90 +0,0 @@ -const ABViewGridCore = require("../../core/views/ABViewGridCore"); -import ABViewGridComponent from "./viewComponent/ABViewGridComponent"; -import ABViewGridFilter from "./viewProperties/ABViewPropertyFilterData"; -const ABViewPropertyLinkPage = - require("./viewProperties/ABViewPropertyLinkPage").default; - -export default class ABViewGrid extends ABViewGridCore { - /** - * @param {obj} values key=>value hash of ABView values - * @param {ABApplication} application the application object this view is under - * @param {ABViewWidget} parent the ABViewWidget this view is a child of. (can be null) - */ - // constructor(values, application, parent, defaultValues) { - // super(values, application, parent, defaultValues); - // } - - /// - /// Instance Methods - /// - - /** - * @method fromValues() - * - * initialze this object with the given set of values. - * @param {obj} values - */ - fromValues(values) { - super.fromValues(values); - - // filter property - this.filterHelper.fromSettings(this.settings.gridFilter); - } - - propertyGroupByList(ids, groupBy) { - let colNames = groupBy || []; - if (typeof colNames == "string") { - colNames = colNames.split(","); - } - - let options = $$(ids.groupBy).getList().data.find({}); - - $$(ids.groupByList).clearAll(); - colNames.forEach((colName) => { - let opt = options.filter((o) => o.id == colName)[0]; - if (opt) { - $$(ids.groupByList).add(opt); - } - }); - } - - /** - * @method component() - * return a UI component based upon this view. - * @return {obj} UI component - */ - component() { - return new ABViewGridComponent(this); - } - - get filterHelper() { - if (this.__filterHelper == null) { - this.__filterHelper = new ABViewGridFilter( - this.AB, - `${this.id}_filterHelper` - ); - } - - return this.__filterHelper; - } - - get linkPageHelper() { - if (this.__linkPageHelper == null) - this.__linkPageHelper = new ABViewPropertyLinkPage(); - - return this.__linkPageHelper; - } - - warningsEval() { - super.warningsEval(); - let origWS = this.warningsSilent; - this.warningsSilent = true; - let DC = this.datacollection; - this.warningsSilent = origWS; - if (!DC) { - this.warningsMessage( - `can't resolve it's datacollection[${this.settings.dataviewID}]` - ); - } - } -} diff --git a/AppBuilder/platform/views/ABViewImage.js b/AppBuilder/platform/views/ABViewImage.js deleted file mode 100644 index a2a566ac..00000000 --- a/AppBuilder/platform/views/ABViewImage.js +++ /dev/null @@ -1,31 +0,0 @@ -const ABViewImageCore = require("../../core/views/ABViewImageCore"); -const ABViewImageComponent = require("./viewComponent/ABViewImageComponent"); - -let L = (...params) => AB.Multilingual.label(...params); - -module.exports = class ABViewImage extends ABViewImageCore { - // constructor(values, application, parent, defaultValues) { - // super(values, application, parent, defaultValues); - // } - - // - // Editor Related - // - - /** - * @method component() - * return a UI component based upon this view. - * @return {obj} UI component - */ - component() { - return new ABViewImageComponent(this); - } - - warningsEval() { - super.warningsEval(); - - if (!this.settings.filename) { - this.warningsMessage(`has no image set`); - } - } -}; diff --git a/AppBuilder/platform/views/ABViewLayout.js b/AppBuilder/platform/views/ABViewLayout.js deleted file mode 100644 index d6a46da5..00000000 --- a/AppBuilder/platform/views/ABViewLayout.js +++ /dev/null @@ -1,21 +0,0 @@ -const ABViewLayoutCore = require("../../core/views/ABViewLayoutCore"); -const ABViewLayoutComponent = require("./viewComponent/ABViewLayoutComponent"); - -module.exports = class ABViewLayout extends ABViewLayoutCore { - /** - * @function component() - * return a UI component based upon this view. - * @return {obj} UI component - */ - component() { - return new ABViewLayoutComponent(this); - } - - warningsEval() { - super.warningsEval(); - - if (this._views.length == 0) { - this.warningsMessage("has no columns set."); - } - } -}; diff --git a/AppBuilder/platform/views/ABViewList.js b/AppBuilder/platform/views/ABViewList.js deleted file mode 100644 index e19fc4eb..00000000 --- a/AppBuilder/platform/views/ABViewList.js +++ /dev/null @@ -1,29 +0,0 @@ -const ABViewListCore = require("../../core/views/ABViewListCore"); -const ABViewListComponent = require("./viewComponent/ABViewListComponent"); - -let L = (...params) => AB.Multilingual.label(...params); - -module.exports = class ABViewList extends ABViewListCore { - // constructor(values, application, parent, defaultValues) { - // super(values, application, parent, defaultValues); - // } - - /** - * @method component() - * return a UI component based upon this view. - * @return {obj} UI component - */ - component() { - return new ABViewListComponent(this); - } - - warningsEval() { - super.warningsEval(); - let DC = this.datacollection; - if (!DC) { - this.warningsMessage( - `can't resolve it's datacollection[${this.settings.dataviewID}]` - ); - } - } -}; diff --git a/AppBuilder/platform/views/ABViewTab.js b/AppBuilder/platform/views/ABViewTab.js deleted file mode 100644 index 27f1a979..00000000 --- a/AppBuilder/platform/views/ABViewTab.js +++ /dev/null @@ -1,47 +0,0 @@ -const ABViewTabCore = require("../../core/views/ABViewTabCore"); - -const ABViewTabComponent = require("./viewComponent/ABViewTabComponent"); - -module.exports = class ABViewTab extends ABViewTabCore { - /** - * @method component() - * return a UI component based upon this view. - * @param {obj} App - * @return {obj} UI component - */ - component(v1App = false) { - let component = new ABViewTabComponent(this); - - // if this is our v1Interface - if (v1App) { - const newComponent = component; - - component = { - ui: newComponent.ui(), - init: (options, accessLevel) => { - return newComponent.init(this.AB); - }, - onShow: (...params) => { - return newComponent.onShow?.(...params); - }, - }; - } - - return component; - } - - warningsEval() { - super.warningsEval(); - - let allViews = this.views(); - - if (allViews.length == 0) { - this.warningsMessage("has no tabs set"); - } - - // NOTE: this is done in ABView: - // (this.views() || []).forEach((v) => { - // v.warningsEval(); - // }); - } -}; diff --git a/AppBuilder/platform/views/ABViewText.js b/AppBuilder/platform/views/ABViewText.js deleted file mode 100644 index 78711424..00000000 --- a/AppBuilder/platform/views/ABViewText.js +++ /dev/null @@ -1,14 +0,0 @@ -const ABViewTextCore = require("../../core/views/ABViewTextCore"); - -const ABViewTextComponent = require("./viewComponent/ABViewTextComponent"); - -module.exports = class ABViewText extends ABViewTextCore { - /** - * @method component() - * return a UI component based upon this view. - * @return {obj} UI component - */ - component(parentId) { - return new ABViewTextComponent(this, parentId); - } -}; diff --git a/AppBuilder/platform/views/viewComponent/ABViewCarouselComponent.js b/AppBuilder/platform/views/viewComponent/ABViewCarouselComponent.js deleted file mode 100644 index b82a2a6b..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewCarouselComponent.js +++ /dev/null @@ -1,531 +0,0 @@ -import ABViewComponent from "./ABViewComponent"; - -export default class ABViewCarouselComponent extends ABViewComponent { - constructor(baseView, idBase, ids) { - super( - baseView, - idBase || `ABViewCarousel_${baseView.id}`, - Object.assign( - { - carousel: "", - }, - ids - ) - ); - - this._handler_doOnShow = () => { - this.onShow(); - }; - - this._handler_doReload = () => { - // this.datacollection?.reloadData(); - }; - - this._handler_doFilter = (fnFilter, filterRules) => { - // NOTE: fnFilter is depreciated and will be removed. - - // this.onShow(filterRules); - const dv = this.datacollection; - - if (!dv) return; - - dv.filterCondition(filterRules); - dv.reloadData(); - }; - - this._handler_busy = () => { - this.busy(); - }; - - this._handler_ready = () => { - this.ready(); - }; - } - - ui() { - const ids = this.ids; - - const baseView = this.view; - - this.filterUI = baseView.filterHelper; // component(/* App, idBase */); - this.linkPage = baseView.linkPageHelper.component(/* App, idBase */); - - const spacer = {}; - const settings = this.settings; - - if (settings.width === 0) - Object.assign(spacer, { - width: 1, - }); - - const _ui = super.ui([ - { - borderless: true, - cols: [ - spacer, // spacer - { - borderless: true, - rows: [ - this.filterUI.ui(), // filter UI - { - id: ids.carousel, - view: "carousel", - cols: [], - width: settings.width, - height: settings.height, - navigation: { - items: !settings.hideItem, - buttons: !settings.hideButton, - type: settings.navigationType, - }, - on: { - onShow: () => { - const activeIndex = $$( - ids.carousel - ).getActiveIndex(); - - this.switchImage(activeIndex); - }, - }, - }, - ], - }, - spacer, // spacer - ], - }, - ]); - - delete _ui.type; - - return _ui; - } - - // make sure each of our child views get .init() called - async init(AB) { - await super.init(AB); - - const dv = this.datacollection; - - if (!dv) { - AB.notify.builder(`Datacollection is ${dv}`, { - message: "This is an invalid datacollection", - }); - - return; - } - - const object = dv.datasource; - - if (!object) { - AB.notify.developer(`Object is ${dv}`, { - message: "This is an invalid object", - }); - - return; - } - - dv.removeListener("loadData", this._handler_doOnShow); - dv.on("loadData", this._handler_doOnShow); - - dv.removeListener("update", this._handler_doReload); - dv.on("update", this._handler_doReload); - - dv.removeListener("delete", this._handler_doReload); - dv.on("delete", this._handler_doReload); - - dv.removeListener("create", this._handler_doReload); - dv.on("create", this._handler_doReload); - - dv.removeListener("initializingData", this._handler_busy); - dv.on("initializingData", this._handler_busy); - - dv.removeListener("initializedData", this._handler_ready); - dv.on("initializedData", this._handler_ready); - - if (this.settings.filterByCursor) { - ["changeCursor", "cursorStale"].forEach((key) => { - dv.removeListener(key, this._handler_doOnShow); - dv.on(key, this._handler_doOnShow); - }); - } - - const baseView = this.view; - - // filter helper - baseView.filterHelper.objectLoad(object); - baseView.filterHelper.viewLoad(this); - - this.filterUI.init(this.AB); - this.filterUI.removeListener("filter.data", this._handler_doFilter); - this.filterUI.on("filter.data", this._handler_doFilter); - - // link page helper - this.linkPage.init({ - view: baseView, - datacollection: dv, - }); - - // set data-cy - const $carouselView = $$(this.ids.carousel)?.$view; - - if ($carouselView) { - $carouselView.setAttribute( - "data-cy", - `${baseView.key} ${baseView.id}` - ); - $carouselView - .querySelector(".webix_nav_button_prev") - ?.firstElementChild?.setAttribute( - "data-cy", - `${baseView.key} button previous ${baseView.id}` - ); - $carouselView - .querySelector(".webix_nav_button_next") - ?.firstElementChild?.setAttribute( - "data-cy", - `${baseView.key} button next ${baseView.id}` - ); - } - } - - /** - * @method detatch() - * Will make sure all our handlers are removed from any object - * we have attached them to. - * - * You'll want to call this in situations when we are dynamically - * creating and recreating instances of the same Widget (like in - * the ABDesigner). - */ - detatch() { - const dv = this.datacollection; - - if (!dv) return; - - dv.removeListener("loadData", this._handler_doOnShow); - - if (this._handler_doReload) { - dv.removeListener("update", this._handler_doReload); - dv.removeListener("delete", this._handler_doReload); - dv.removeListener("create", this._handler_doReload); - } - - dv.removeListener("initializingData", this._handler_busy); - - dv.removeListener("initializedData", this._handler_ready); - - if (this.settings.filterByCursor) - ["changeCursor", "cursorStale"].forEach((key) => { - dv.removeListener(key, this._handler_doOnShow); - }); - - this.filterUI.removeListener("filter.data", this._handler_doOnShow); - } - - myTemplate(row) { - if (row?.src) { - const settings = this.settings; - - return ``; - } - // empty image - else return ""; - } - - busy() { - const $carousel = $$(this.ids.carousel); - - $carousel?.disable(); - $carousel?.showProgress?.({ type: "icon" }); - } - - ready() { - const $carousel = $$(this.ids.carousel); - - $carousel?.enable(); - $carousel?.hideProgress?.(); - } - - async switchImage(currentPosition) { - const dv = this.datacollection; - - if (!dv) return; - - // Check want to load more images - if ( - currentPosition >= this._imageCount - 1 && // check last image - dv.totalCount > this._rowCount - ) { - // loading cursor - this.busy(); - - try { - await dv.loadData(this._rowCount || 0); - } catch (err) { - this.AB.notify.developer(err, { - message: - "ABViewCarousel:switchImage():Error when load data from a Data collection", - }); - } - - this.ready(); - } - } - - onShow(fnFilter = this.filterUI.getFilter()) { - const ids = this.ids; - const dv = this.datacollection; - - if (!dv) return; - - const obj = dv.datasource; - - if (!obj) return; - - const field = this.view.imageField; - - if (!field) return; - - if (dv.dataStatus == dv.dataStatusFlag.notInitial) { - // load data when a widget is showing - dv.loadData(); - - // it will call .onShow again after dc loads completely - return; - } - - const settings = this.settings; - - let rows = dv.getData(fnFilter); - - // Filter images by cursor - if (settings.filterByCursor) { - const cursor = dv.getCursor(); - - if (cursor) - rows = rows.filter( - (r) => - (r[obj.PK()] || r.id || r) === - (cursor[obj.PK()] || cursor.id || cursor) - ); - } - - const images = []; - - rows.forEach((r) => { - const imgFile = r[field.columnName]; - - if (imgFile) { - const imgData = { - id: r.id, - src: `/file/${imgFile}`, - imgFile, - }; - - // label of row data - if (settings.showLabel) imgData.label = obj.displayData(r); - - images.push({ - css: "image", - borderless: true, - template: (...params) => { - return this.myTemplate(...params); - }, - data: imgData, - }); - } - }); - - const ab = this.AB; - - // insert the default image to first item - if (field.settings.defaultImageUrl) - images.unshift({ - css: "image", - template: (...params) => this.myTemplate(...params), - data: { - id: ab.uuid(), - src: `/file/${field.settings.defaultImageUrl}`, - label: this.label("Default image"), - }, - }); - - // empty image - if (images.length < 1) - images.push({ - rows: [ - { - view: "label", - align: "center", - height: settings.height, - label: "
", - }, - { - view: "label", - align: "center", - label: this.label("No image"), - }, - ], - }); - - // store total of rows - this._rowCount = rows.length; - - // store total of images - this._imageCount = images.length; - - const $carousel = $$(ids.carousel); - const abWebix = ab.Webix; - - if ($carousel) { - // re-render - abWebix.ui(images, $carousel); - - // add loading cursor - abWebix.extend($carousel, abWebix.ProgressBar); - - // link pages events - const editPage = settings.editPage; - const detailsPage = settings.detailsPage; - - // if (detailsPage || editPage) { - $carousel.$view.onclick = async (e) => { - if (e.target.className) { - if (e.target.className.indexOf("ab-carousel-edit") > -1) { - abWebix.html.removeCss($carousel.getNode(), "fullscreen"); - abWebix.fullscreen.exit(); - let rowId = e.target.getAttribute("ab-row-id"); - this.linkPage.changePage(editPage, rowId); - } else if ( - e.target.className.indexOf("ab-carousel-detail") > -1 - ) { - abWebix.html.removeCss($carousel.getNode(), "fullscreen"); - abWebix.fullscreen.exit(); - let rowId = e.target.getAttribute("ab-row-id"); - this.linkPage.changePage(detailsPage, rowId); - } else if ( - e.target.className.indexOf("ab-carousel-fullscreen") > -1 - ) { - $carousel.define("css", "fullscreen"); - abWebix.fullscreen.set(ids.carousel, { - head: { - view: "toolbar", - css: "webix_dark", - elements: [ - {}, - { - view: "icon", - icon: "fa fa-times", - click: function () { - abWebix.html.removeCss( - $carousel.getNode(), - "fullscreen" - ); - abWebix.fullscreen.exit(); - }, - }, - ], - }, - }); - } else if ( - e.target.className.indexOf("ab-carousel-rotate-left") > -1 - ) { - const rowId = e.target.getAttribute("ab-row-id"); - const imgFile = e.target.getAttribute("ab-img-file"); - this.rotateImage(rowId, imgFile, field, "left"); - } else if ( - e.target.className.indexOf("ab-carousel-rotate-right") > -1 - ) { - const rowId = e.target.getAttribute("ab-row-id"); - const imgFile = e.target.getAttribute("ab-img-file"); - this.rotateImage(rowId, imgFile, field, "right"); - } else if ( - e.target.className.indexOf("ab-carousel-zoom-in") > -1 - ) { - this.zoom("in"); - } else if ( - e.target.className.indexOf("ab-carousel-zoom-out") > -1 - ) { - this.zoom("out"); - } - } - }; - } - } - - showFilterPopup($view) { - this.filterUI.showPopup($view); - } - - async rotateImage(rowId, imgFile, field, direction = "right") { - this.busy(); - - // call api to rotate - if (direction == "left") await field.rotateLeft(imgFile); - else await field.rotateRight(imgFile); - - // refresh image - const imgElm = document.getElementById(`${this.ids.component}-${rowId}`); - if (imgElm) { - await fetch(imgElm.src, { cache: "reload", mode: "no-cors" }); - imgElm.src = `${imgElm.src}#${new Date().getTime()}`; - } - - this.ready(); - } - - zoom(inOrOut = "in") { - const imgContainer = document.getElementsByClassName( - "ab-carousel-image-container" - )[0]; - if (!imgContainer) return; - - const imgElem = imgContainer.getElementsByTagName("img")[0]; - if (!imgElem) return; - - const step = 15; - const height = parseInt( - (imgElem.style.height || 100).toString().replace("%", "") - ); - const newHeight = inOrOut == "in" ? height + step : height - step; - imgElem.style.height = `${newHeight}%`; - - imgContainer.style.overflow = newHeight > 100 ? "auto" : ""; - } -} diff --git a/AppBuilder/platform/views/viewComponent/ABViewCommentComponent.js b/AppBuilder/platform/views/viewComponent/ABViewCommentComponent.js deleted file mode 100644 index 04d7cce5..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewCommentComponent.js +++ /dev/null @@ -1,310 +0,0 @@ -const ABViewComponent = require("./ABViewComponent").default; - -module.exports = class ABViewCommentComponent extends ABViewComponent { - constructor(baseView, idBase, ids) { - super( - baseView, - idBase || `ABViewComment_${baseView.id}`, - Object.assign( - { - comment: "", - }, - ids - ) - ); - } - - ui() { - const baseView = this.view; - const _ui = super.ui([ - { - id: this.ids.comment, - view: "comments", - users: baseView.getUserData(), - currentUser: baseView.getCurrentUserId(), - height: this.settings.height, - data: this.getCommentData(), - on: { - onBeforeAdd: (id, obj, index) => { - this.addComment(obj.text, new Date()); - }, - // NOTE: no update event of comment widget !! - // Updating event handles in .init function - // https://docs.webix.com/api__ui.comments_onbeforeeditstart_event.html#comment-4509366150 - - // onAfterEditStart: function (rowId) { - // let item = this.getItem(rowId); - - // _logic.updateComment(rowId, item); - // }, - onAfterDelete: (rowId) => { - this.deleteComment(rowId); - }, - }, - }, - ]); - - delete _ui.type; - - return _ui; - } - - async init(AB) { - await super.init(AB); - - const baseView = this.view; - - baseView.__dvEvents = baseView.__dvEvents || {}; - - const ids = this.ids; - const $comment = $$(ids.comment); - - if ($comment) { - const $commentList = $comment.queryView({ view: "list" }); - - if ($commentList) { - // Updating comment event - if (!baseView.__dvEvents.onStoreUpdated) - baseView.__dvEvents.onStoreUpdated = - $commentList.data.attachEvent( - "onStoreUpdated", - (rowId, data, operate) => { - if (operate === "update") { - this.updateComment(rowId, (data || {}).text); - } - } - ); - - // Implement progress bar - webix.extend($commentList, webix.ProgressBar); - } - } - - const dv = this.datacollection; - - if (!dv) return; - - // bind dc to component - // dv.bind($$(ids.comment)); - - if (!baseView.__dvEvents.create) - baseView.__dvEvents.create = dv.on("create", () => - this.refreshComment() - ); - - if (!baseView.__dvEvents.update) - baseView.__dvEvents.update = dv.on("update", () => - this.refreshComment() - ); - - if (!baseView.__dvEvents.delete) - baseView.__dvEvents.delete = dv.on("delete", () => - this.refreshComment() - ); - - if (!baseView.__dvEvents.loadData) - baseView.__dvEvents.loadData = dv.on("loadData", () => - this.refreshComment() - ); - - $comment.refresh(); - } - - getCommentData() { - const baseView = this.view; - const dv = this.datacollection; - - if (!dv) return null; - - const userCol = baseView.getUserField(); - const commentCol = baseView.getCommentField(); - const dateCol = baseView.getDateField(); - - if (!userCol || !commentCol) return null; - - const userColName = userCol.columnName; - const commentColName = commentCol.columnName; - const dateColName = dateCol ? dateCol.columnName : null; - const dataObject = dv.getData(); - const dataList = []; - - dataObject.forEach((item, index) => { - if (item[commentColName]) { - const user = baseView.getUserData().find((user) => { - return user.value === item[userColName]; - }); - const data = { - id: item.id, - user_id: user ? user.id : 0, - date: item[dateColName] ? new Date(item[dateColName]) : null, - default_date: new Date(item["created_at"]), - text: item[commentColName], - }; - - dataList.push(data); - } - }); - - dataList.sort(function (a, b) { - if (dateColName) - return new Date(a.date).getTime() - new Date(b.date).getTime(); - else - return ( - new Date(a.default_date).getTime() - - new Date(b.default_date).getTime() - ); - }); - - return dataList; - } - - refreshComment() { - const baseView = this.view; - - if (baseView.__refreshTimeout) clearTimeout(baseView.__refreshTimeout); - - this.busy(); - - const ids = this.ids; - - baseView.__refreshTimeout = setTimeout(() => { - const $comment = $$(ids.comment); - - if (!$comment) return; - - // clear comments - const $commentList = $comment.queryView({ view: "list" }); - - if ($commentList) $commentList.clearAll(); - - // populate comments - const commentData = this.getCommentData(); - - if (commentData) { - $comment.parse(commentData); - } - - // scroll to the last item - if ($commentList) $commentList.scrollTo(0, Number.MAX_SAFE_INTEGER); - - delete baseView.__refreshTimeout; - - this.ready(); - }, 90); - } - - addComment(commentText, dateTime) { - this.saveData(commentText, dateTime); - } - - async updateComment(rowId, commentText) { - const baseView = this.view; - const model = baseView.model(); - - if (!model) return; // already notified - - const commentField = baseView.getCommentField(); - - if (!commentField) return; // already notified - - const values = {}; - - values[commentField.columnName] = commentText ?? ""; - - return await model.update(rowId, values); - } - - async deleteComment(rowId) { - const baseView = this.view; - const model = baseView.model(); - - if (!model) return; - - return await model.delete(rowId); - } - - busy() { - const ids = this.ids; - const $comment = $$(ids.comment); - - if (!$comment) return; - - const $commentList = $comment.queryView({ view: "list" }); - - if (!$commentList) return; - - $commentList.disable(); - - if ($commentList.showProgress) - $commentList.showProgress({ type: "icon" }); - } - - ready() { - const ids = this.ids; - const $comment = $$(ids.comment); - - if (!$comment) return; - - const $commentList = $comment.queryView({ view: "list" }); - - if (!$commentList) return; - - $commentList.enable(); - - if ($commentList.hideProgress) $commentList.hideProgress(); - } - - async saveData(commentText, dateTime) { - if (!commentText) return; - - const dv = this.datacollection; - - if (!dv) return; - - const baseView = this.view; - const model = baseView.model(); - const ab = this.AB; - - if (!model) { - ab.notify.builder( - {}, - { - message: - "ABViewComment.saveData(): could not pull a model to work with.", - viewName: baseView.label, - } - ); - - return; - } - - const comment = {}; - const userField = baseView.getUserField(); - - if (userField) comment[userField.columnName] = ab.Account.username(); - - const commentField = baseView.getCommentField(); - - if (commentField) comment[commentField.columnName] = commentText; - - const dateField = baseView.getDateField(); - - if (dateField) comment[dateField.columnName] = dateTime; - - // add parent cursor to default - const dvLink = dv.datacollectionLink; - - if (dvLink?.getCursor()) { - const objectLink = dvLink.datasource; - const fieldLink = dv.fieldLink; - - if (objectLink && fieldLink) { - comment[fieldLink.columnName] = {}; - comment[fieldLink.columnName][objectLink.PK()] = - dvLink.getCursor().id; - } - } - - return await model.create(comment); - } -}; diff --git a/AppBuilder/platform/views/viewComponent/ABViewDataSelectComponent.js b/AppBuilder/platform/views/viewComponent/ABViewDataSelectComponent.js deleted file mode 100644 index eb249a74..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewDataSelectComponent.js +++ /dev/null @@ -1,54 +0,0 @@ -import ABViewComponent from "./ABViewComponent"; -export default class ABViewDataSelectComponent extends ABViewComponent { - constructor(baseView, idbase, ids) { - super( - baseView, - idbase || `ABViewDataSelect_${baseView.id}`, - Object.assign( - { - select: "", - }, - ids - ) - ); - } - - ui() { - const _ui = super.ui([ - { - view: "combo", - id: this.ids.select, - on: { - onChange: (n, o) => { - if (n !== o) this.cursorChange(n); - }, - }, - }, - ]); - delete _ui.type; - - return _ui; - } - - async onShow() { - super.onShow(); - const dc = this.datacollection; - if (!dc) return; - await dc.waitReady(); - const labelField = this.AB.definitionByID( - this.settings.labelField - )?.columnName; - const options = dc - .getData() - .map((o) => ({ id: o.id, value: o[labelField] })) - .sort((a, b) => (a.value > b.value ? 1 : -1)); - const $select = $$(this.ids.select); - $select.define("options", options); - $select.refresh(); - $select.setValue(dc.getCursor().id); - } - - cursorChange(n) { - this.datacollection.setCursor(n); - } -} diff --git a/AppBuilder/platform/views/viewComponent/ABViewDataviewComponent.js b/AppBuilder/platform/views/viewComponent/ABViewDataviewComponent.js deleted file mode 100644 index 47368207..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewDataviewComponent.js +++ /dev/null @@ -1,391 +0,0 @@ -const ABViewComponent = require("./ABViewComponent").default; -const ABViewDetailComponent = require("./ABViewDetailComponent"); -const ABViewPropertyLinkPage = - require("../viewProperties/ABViewPropertyLinkPage").default; - -module.exports = class ABViewDataviewComponent extends ABViewComponent { - constructor(baseView, idBase, ids) { - super( - baseView, - idBase || `ABViewDataview_${baseView.id}`, - Object.assign( - { - dataview: "", - reload: "", - }, - ids - ) - ); - - this.linkPage = null; - } - - ui() { - // NOTE: need to initial the detail component here - // because its dom width & height values are used .template function - this.initDetailComponent(); - - const ids = this.ids; - const L = (...params) => (this.AB ?? AB).Multilingual.label(...params); - const _ui = super.ui([ - { - view: "layout", - rows: [ - { - id: ids.reload, - view: "button", - value: L("New data available. Click to reload."), - css: "webix_primary webix_warn", - hidden: true, - click: (id, event) => { - this.reloadData(); - }, - }, - { - id: ids.dataview, - view: "dataview", - scroll: "y", - sizeToContent: true, - css: "borderless transparent", - xCount: this.settings.xCount != 1 ? this.settings.xCount : 0, - height: this.settings.height, - template: (item) => this.itemTemplate(item), - on: { - onAfterRender: () => { - this.applyClickEvent(); - this.addCyAttribute(); - }, - }, - }, - ], - }, - ]); - - return _ui; - } - - async init(AB) { - await super.init(AB); - - const dc = this.datacollection; - if (!dc) return; - - // Initial the link page helper - this.linkPage = this.linkPageHelper.component(); - this.linkPage.init({ - view: this.view, - datacollection: dc, - }); - - const ids = this.ids; - const $dataView = $$(ids.dataview); - AB.Webix.extend($dataView, AB.Webix.ProgressBar); - dc.bind($dataView); - - this.initRefreshWarning(); - - window.addEventListener("resize", () => { - clearTimeout(this._resizeEvent); - this._resizeEvent = setTimeout(() => { - this.resize($dataView.getParentView()); - delete this._resizeEvent; - }, 20); - }); - } - - /** - * @method initRefreshWarning - * - */ - initRefreshWarning() { - const dc = this.datacollection; - const includeInQuery = - (dc?.settings?.objectWorkspace?.filterConditions?.rules ?? []).filter( - (r) => - [ - "in_query", - "not_in_query", - "in_query_field", - "not_in_query_field", - ].includes(r.rule) - ).length > 0; - - if (!includeInQuery) return; - [ - "ab.datacollection.create", - "ab.datacollection.update", - "ab.datacollection.delete", - ].forEach((eventKey) => { - dc.on(eventKey, (data) => { - if (data.objectId == dc.datasource.id) - this.showRefreshWarning(data); - }); - }); - } - - showRefreshWarning() { - if (this.__throttleRefreshWarning) - clearTimeout(this.__throttleRefreshWarning); - - this.__throttleRefreshWarning = setTimeout(() => { - $$(this.ids.reload)?.show(); - }, 200); - } - - reloadData() { - const dc = this.datacollection; - dc?.reloadData(); - - $$(this.ids.reload)?.hide(); - } - - onShow() { - super.onShow(); - - this.resize(); - } - - resize(base_element) { - const $dataview = $$(this.ids.dataview); - if (!$dataview) { - // Not sure if its a problem so notify - this.AB.notify.developer( - new Error("Resize called on missing dataview component"), - { context: "ABViewDataviewComponent.resize()", ids: this.ids } - ); - return; - } - $dataview.resize(); - - const item_width = this.getItemWidth(base_element); - $dataview.customize({ width: item_width }); - $dataview.getTopParentView?.().resize?.(); - } - - initDetailComponent() { - const detailUI = this.getDetailUI(); - this._detail_ui = this.AB.Webix.ui(detailUI); - - // 2 - Always allow access to components inside data view - this.detailComponent.init(null, 2); - } - - getDetailUI() { - const detailCom = this.detailComponent; - const editPage = this.settings.editPage; - const detailsPage = this.settings.detailsPage; - - const _ui = detailCom.ui(); - // adjust the UI to make sure it will look like a "card" - _ui.type = "clean"; - _ui.css = "ab-detail-view"; - - if (detailsPage || editPage) { - _ui.css += ` ab-detail-hover ab-record-#itemId#`; - - if (detailsPage) _ui.css += " ab-detail-page"; - if (editPage) _ui.css += " ab-edit-page"; - } - - return _ui; - } - - itemTemplate(item) { - const detailCom = this.detailComponent; - const $dataview = $$(this.ids.dataview); - const $detail_item = this._detail_ui; - - // Mock up data to initialize height of item - if (!item || !Object.keys(item).length) { - item = item ?? {}; - this.datacollection?.datasource?.fields().forEach((f) => { - switch (f.key) { - case "string": - case "LongText": - item[f.columnName] = "Lorem Ipsum"; - break; - case "date": - case "datetime": - item[f.columnName] = new Date(); - break; - case "number": - item[f.columnName] = 7; - break; - } - }); - } - detailCom.displayData(item); - - const itemWidth = - $dataview.data.count() > 0 - ? $dataview.type.width - : ($detail_item.$width - 20) / this.settings.xCount; - - const itemHeight = - $dataview.data.count() > 0 - ? $dataview.type.height - : $detail_item.getChildViews()?.[0]?.$height; - - const tmp_dom = document.createElement("div"); - tmp_dom.appendChild($detail_item.$view); - - $detail_item.define("width", itemWidth - 24); - $detail_item.define("height", itemHeight + 15); - $detail_item.adjust(); - - // Add cy attributes - this.addCyItemAttributes(tmp_dom, item); - - return tmp_dom.innerHTML.replace(/#itemId#/g, item.id); - } - - getItemWidth(base_element) { - const $dataview = $$(this.ids.dataview); - - let currElem = base_element ?? $dataview; - let parentWidth = currElem?.$width; - while (currElem) { - if ( - currElem.config.view == "scrollview" || - currElem.config.view == "layout" - ) - parentWidth = - currElem?.$width < parentWidth ? currElem?.$width : parentWidth; - - currElem = currElem?.getParentView?.(); - } - - if (!parentWidth) - parentWidth = $dataview?.getParentView?.().$width || window.innerWidth; - - if (parentWidth > window.innerWidth) parentWidth = window.innerWidth; - - // check if the browser window minus webix default padding is the same as the parent window - // if so we need to check to see if there is a sidebar and reduce the usable space by the - // width of the sidebar - if (window.innerWidth - 19 <= parentWidth) { - const $sidebar = this.getTabSidebar(); - if ($sidebar) { - parentWidth -= $sidebar.$width; - } - } - - const recordWidth = Math.floor(parentWidth / this.settings.xCount); - - return recordWidth; - } - - getTabSidebar() { - const $dataview = $$(this.ids.dataview); - let $sidebar; - let currElem = $dataview; - while (currElem && !$sidebar) { - $sidebar = (currElem.getChildViews?.() ?? []).filter( - (item) => item?.config?.view == "sidebar" - )[0]; - - currElem = currElem?.getParentView?.(); - } - - return $sidebar; - } - - applyClickEvent() { - const editPage = this.settings.editPage; - const detailsPage = this.settings.detailsPage; - if (!detailsPage && !editPage) return; - - const $dataview = $$(this.ids.dataview); - if (!$dataview) return; - - $dataview.$view.onclick = (e) => { - let clicked = false; - let divs = e.path ?? []; - - // NOTE: Some web browser clients do not support .path - if (!divs.length) { - divs.push(e.target); - divs.push(e.target.parentNode); - } - - if (editPage) { - for (let p of divs) { - if ( - p.className && - p.className.indexOf("webix_accordionitem_header") > -1 - ) { - clicked = true; - p.parentNode.parentNode.classList.forEach((c) => { - if (c.indexOf("ab-record-") > -1) { - // var record = parseInt(c.replace("ab-record-", "")); - const record = c.replace("ab-record-", ""); - this.linkPage.changePage(editPage, record); - // com.logic.toggleTab(detailsTab, ids.component); - } - }); - break; - } - } - } - - if (detailsPage && !clicked) { - for (let p of divs) { - if ( - p.className && - p.className.indexOf("webix_accordionitem") > -1 - ) { - p.parentNode.parentNode.classList.forEach((c) => { - if (c.indexOf("ab-record-") > -1) { - // var record = parseInt(c.replace("ab-record-", "")); - const record = c.replace("ab-record-", ""); - this.linkPage.changePage(detailsPage, record); - // com.logic.toggleTab(detailsTab, ids.component); - } - }); - - break; - } - } - } - }; - } - - addCyAttribute() { - const baseView = this.view; - const $dataview = $$(this.ids.dataview); - const name = (baseView.name ?? "").replace(".dataview", ""); - - $dataview.$view.setAttribute( - "data-cy", - `dataview container ${name} ${baseView.id}` - ); - } - - addCyItemAttributes(dom, item) { - const baseView = this.view; - const uuid = item.uuid; - const name = (baseView.name ?? "").replace(".dataview", ""); - dom.querySelector(".webix_accordionitem_body")?.setAttribute( - "data-cy", - `dataview item ${name} ${uuid} ${baseView.id}` - ); - dom.querySelector(".webix_accordionitem_button")?.setAttribute( - "data-cy", - `dataview item button ${name} ${uuid} ${baseView.id}` - ); - } - - get detailComponent() { - return (this._detailComponent = - this._detailComponent ?? - new ABViewDetailComponent( - this.view, - `${this.ids.component}_detail_view` - )); - } - - get linkPageHelper() { - return (this.__linkPageHelper = - this.__linkPageHelper || new ABViewPropertyLinkPage()); - } -}; diff --git a/AppBuilder/platform/views/viewComponent/ABViewDetailCheckboxComponent.js b/AppBuilder/platform/views/viewComponent/ABViewDetailCheckboxComponent.js deleted file mode 100644 index 812d110a..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewDetailCheckboxComponent.js +++ /dev/null @@ -1,40 +0,0 @@ -const ABViewDetailItemComponent = require("./ABViewDetailItemComponent"); - -module.exports = class ABViewDetailCheckboxComponent extends ( - ABViewDetailItemComponent -) { - constructor(baseView, idBase, ids) { - super(baseView, idBase || `ABViewDetailCheckbox_${baseView.id}`, ids); - } - - ui() { - const baseView = this.view; - const field = baseView.field(); - - return super.ui({ - on: { - //Add data-cy attribute for Cypress Testing - onAfterRender: () => { - const dataCy = `detail checkbox ${field?.columnName} ${ - field?.id - } ${baseView.parentDetailComponent()?.id ?? baseView.parent.id}`; - - $$(this.ids.detailItem)?.$view.setAttribute("data-cy", dataCy); - }, - }, - }); - } - - setValue(val) { - let checkbox = ""; - - // Check - if (val && JSON.parse(val)) - checkbox = - ''; - // Uncheck - else checkbox = ''; - - super.setValue(checkbox); - } -}; diff --git a/AppBuilder/platform/views/viewComponent/ABViewDetailComponent.js b/AppBuilder/platform/views/viewComponent/ABViewDetailComponent.js deleted file mode 100644 index 005e53d4..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewDetailComponent.js +++ /dev/null @@ -1,228 +0,0 @@ -const ABObjectQuery = require("../../ABObjectQuery"); -const ABViewContainerComponent = require("./ABViewContainerComponent"); - -module.exports = class ABViewDetailComponent extends ABViewContainerComponent { - constructor(baseView, idBase, ids) { - super(baseView, idBase || `ABViewDetail_${baseView.id}`, ids); - this.idBase = idBase; - } - - ui() { - let _ui = super.ui(); - - // this wrapper allows the detail view to have a - // card appearance as well as enables the edit and - // details functions to work when clicked - return { - type: "form", - id: this.ids.component, - borderless: true, - rows: [ - { - body: _ui, - }, - ], - }; - } - - onShow() { - const baseView = this.view; - - try { - const dataCy = `Detail ${baseView.name?.split(".")[0]} ${baseView.id}`; - - $$(this.ids.component)?.$view.setAttribute("data-cy", dataCy); - } catch (e) { - console.warn("Problem setting data-cy", e); - } - - // listen DC events - const dv = this.datacollection; - - if (dv) { - const currData = dv.getCursor(); - - if (currData) this.displayData(currData); - - ["changeCursor", "cursorStale", "collectionEmpty"].forEach((key) => { - this.eventAdd({ - emitter: dv, - eventName: key, - listener: (...p) => this.displayData(...p), - }); - }); - - this.eventAdd({ - emitter: dv, - eventName: "create", - listener: (createdRow) => { - const currCursor = dv.getCursor(); - - if (currCursor?.id === createdRow.id) - this.displayData(createdRow); - }, - }); - - this.eventAdd({ - emitter: dv, - eventName: "update", - listener: (updatedRow) => { - const currCursor = dv.getCursor(); - - if (currCursor?.id === updatedRow.id) - this.displayData(updatedRow); - }, - }); - } - - super.onShow(); - } - - displayData(rowData = {}) { - // make sure we have data to work with. If null is passed in - // then pull current cursor. - if (rowData == null) { - rowData = this.datacollection.getCursor(); - } - - const views = (this.view.views() || []).sort((a, b) => { - if (!a?.field?.() || !b?.field?.()) return 0; - - // NOTE: sort order of calculated fields. - // FORMULA field type should be calculated before CALCULATE field type - if (a.field().key === "formula" && b.field().key === "calculate") - return -1; - else if (a.field().key === "calculate" && b.field().key === "formula") - return 1; - - return 0; - }); - - views.forEach((f) => { - let val; - - if (f.field) { - const field = f.field(); - - if (!field) return; - - // get value of relation when field is a connect field - switch (field.key) { - case "connectObject": - val = field.pullRelationValues(rowData); - - break; - - case "list": - val = rowData?.[field.columnName]; - - if (!val) { - val = ""; - - break; - } - - if (field.settings.isMultiple === 0) { - let myVal = ""; - - field.settings.options.forEach((options) => { - if (options.id === val) myVal = options.text; - }); - - if (field.settings.hasColors) { - let myHex = "#66666"; - let hasCustomColor = ""; - - field.settings.options.forEach((h) => { - if (h.text === myVal) { - myHex = h.hex; - hasCustomColor = "hascustomcolor"; - } - }); - - myVal = `${myVal}`; - } - - val = myVal; - } else { - const items = []; - - let myVal = ""; - - val.forEach((value) => { - let hasCustomColor = ""; - let optionHex = ""; - - if (field.settings.hasColors && value.hex) { - hasCustomColor = "hascustomcolor"; - optionHex = `background: ${value.hex};`; - } - - field.settings.options.forEach((options) => { - if (options.id === value.id) myVal = options.text; - }); - items.push( - `${myVal}` - ); - }); - - val = items.join(""); - } - - break; - - case "user": - val = field.pullRelationValues(rowData); - - break; - - case "file": - val = rowData?.[field.columnName]; - - if (!val) { - val = ""; - - break; - } - - break; - - case "formula": - if (rowData) { - // const dv = this.datacollection; - // const ds = dv ? dv.datasource : null; - // const needRecalculate = - // !ds || ds instanceof ABObjectQuery ? false : true; - - // NOTE: Could not to re-calculate because `__relation` data is extracted from full data at the moment - // rowData.__relation format - // { - // id: "string" - // text: "string" - // translations: [] - // uuid: "0cb52669-d626-4c9d-85ea-2d931751d0ce" - // value: "LABEL" - // } - const needRecalculate = false; - - val = field.format(rowData, needRecalculate); - } - - break; - - default: - val = field.format(rowData); - // break; - } - } - - // set value to each components - const vComponent = f.component(this.idBase); - - // vComponent?.onShow(); - - vComponent?.setValue?.(val); - vComponent?.displayText?.(rowData); - }); - } -}; diff --git a/AppBuilder/platform/views/viewComponent/ABViewDetailConnectComponent.js b/AppBuilder/platform/views/viewComponent/ABViewDetailConnectComponent.js deleted file mode 100644 index dfc1af08..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewDetailConnectComponent.js +++ /dev/null @@ -1,44 +0,0 @@ -const ABViewDetailItemComponent = require("./ABViewDetailItemComponent"); - -module.exports = class ABViewDetailConnectComponent extends ( - ABViewDetailItemComponent -) { - constructor(baseView, idBase, ids) { - super(baseView, idBase || `ABViewDetailConnect_${baseView.id}`, ids); - } - - ui() { - const baseView = this.view; - const settings = this.settings; - - return super.ui({ - on: { - //Add data-cy attribute for Cypress Testing - onAfterRender: () => { - const columnName = - baseView.field((fld) => fld.id === settings.fieldId) - ?.columnName ?? ""; - const dataCy = `detail connected ${columnName} ${ - settings.fieldId - } ${baseView.parentDetailComponent()?.id || baseView.parent.id}`; - - $$(this.ids.detailItem)?.$view.setAttribute("data-cy", dataCy); - }, - }, - }); - } - - setValue(val) { - const vals = []; - - if (Array.isArray(val)) - val.forEach((record) => { - vals.push( - `${record.text}` - ); - }); - else vals.push(`${val.text}`); - - super.setValue(vals.join("")); - } -}; diff --git a/AppBuilder/platform/views/viewComponent/ABViewDetailCustomComponent.js b/AppBuilder/platform/views/viewComponent/ABViewDetailCustomComponent.js deleted file mode 100644 index c50d18dc..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewDetailCustomComponent.js +++ /dev/null @@ -1,75 +0,0 @@ -const ABViewDetailItemComponent = require("./ABViewDetailItemComponent"); - -module.exports = class ABViewDetailCustomComponent extends ( - ABViewDetailItemComponent -) { - constructor(baseView, idBase, ids) { - super(baseView, idBase || `ABViewDetailCustom_${baseView.id}`, ids); - } - - ui() { - const baseView = this.view; - const field = baseView.field(); - const detailView = baseView.detailComponent(); - - let template = field ? field.columnHeader().template({}) : ""; - - return super.ui({ - minHeight: 45, - height: 60, - template, - on: { - //Add data-cy attribute for Cypress Testing - onAfterRender: () => { - const dataCy = `detail custom ${field?.columnName} ${ - field?.id - } ${baseView.parentDetailComponent()?.id || baseView.parent.id}`; - - $$(this.ids.detailItem)?.$view.setAttribute("data-cy", dataCy); - }, - }, - }); - } - - onShow() { - super.onShow(); - - const baseView = this.view; - const field = baseView.field(); - - if (!field) return; - - const $detailItem = $$(this.ids.detailItem); - - if (!$detailItem) return; - - const detailCom = baseView.detailComponent(), - rowData = detailCom.datacollection.getCursor() || {}, - node = $detailItem.$view; - - field.customDisplay(rowData, null, node, { - editable: false, - }); - // Hack: remove the extra webix_template class here, which adds padding so - // the item is not alligned with the others - node - .getElementsByClassName("webix_template")[1] - ?.removeAttribute("class"); - } - - setValue(val) { - const field = this.view.field(); - - if (!field) return; - - const $detailItem = $$(this.ids.detailItem); - - if (!$detailItem) return; - - const rowData = {}; - - rowData[field.columnName] = val; - - field.setValue($detailItem, rowData); - } -}; diff --git a/AppBuilder/platform/views/viewComponent/ABViewDetailImageComponent.js b/AppBuilder/platform/views/viewComponent/ABViewDetailImageComponent.js deleted file mode 100644 index dc308540..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewDetailImageComponent.js +++ /dev/null @@ -1,66 +0,0 @@ -const ABViewDetailItemComponent = require("./ABViewDetailItemComponent"); - -module.exports = class ABViewDetailImageComponent extends ( - ABViewDetailItemComponent -) { - constructor(baseView, idBase, ids) { - super(baseView, idBase || `ABViewDetailImage_${baseView.id}`, ids); - } - - ui() { - const baseView = this.view; - const field = baseView.field(); - const _ui = { - on: { - //Add data-cy attribute for Cypress Testing - onAfterRender: () => { - const dataCy = `detail image ${field?.columnName} ${field?.id} ${ - baseView.parentDetailComponent()?.id || baseView.parent.id - }`; - - $$(this.ids.detailItem)?.$view.setAttribute("data-cy", dataCy); - }, - }, - }; - const settings = this.settings; - - if (settings.height) _ui.height = settings.height; - - return super.ui(_ui); - } - - setValue(val) { - const field = this.view.field(); - - if (!field) { - super.setValue(""); - - return; - } - - const parsedImageUrl = val || field.settings.defaultImageUrl; - - if (!parsedImageUrl) { - super.setValue(""); - - return; - } - - const imageUrl = field.urlImage(parsedImageUrl); - const settings = this.settings; - const width = settings.width || field.settings.imageWidth || 200; - const height = settings.height - ? `${settings.height}px` - : field.settings.imageHeight - ? `${field.settings.imageHeight}px` - : "100%"; - const imageTemplate = [ - `
`, - `
`, - ``, - `
`, - ].join(""); - - super.setValue(imageTemplate); - } -}; diff --git a/AppBuilder/platform/views/viewComponent/ABViewDetailItemComponent.js b/AppBuilder/platform/views/viewComponent/ABViewDetailItemComponent.js deleted file mode 100644 index 2a35d41c..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewDetailItemComponent.js +++ /dev/null @@ -1,150 +0,0 @@ -const ABViewComponent = require("./ABViewComponent").default; - -const SAFE_HTML_TAGS = [ - "abbr", - "acronym", - "b", - "blockquote", - "br", - "code", - "div", - "em", - "i", - "li", - "ol", - "p", - "span", - "strong", - "table", - "td", - "tr", - "ul", - "h1", - "h2", - "h3", - "h4", - "h5", -]; - -module.exports = class ABViewDetailItemComponent extends ABViewComponent { - constructor(baseView, idBase, ids) { - super( - baseView, - idBase || `ABViewDetailItem_${baseView.id}`, - Object.assign( - { - detailItem: "", - detailItemLabel: "", - }, - ids - ) - ); - } - - ui(uiDetailItemComponent = {}) { - const baseView = this.view; - - // setup 'label' of the element - const settings = baseView.detailComponent()?.settings ?? {}; - const field = baseView.field(); - - const isLabelTop = settings.labelPosition == "top"; - - const group = []; - /** @const group will be used later as rows or cols depending on label position */ - if (settings.showLabel) { - const templateLabel = isLabelTop - ? "" - : ""; - - const labelUi = { - id: this.ids.detailItemLabel, - view: "template", - borderless: true, - height: 38, - template: templateLabel, - data: { label: field?.label ?? "" }, - }; - if (!isLabelTop) labelUi.width = settings.labelWidth + 24; // Add 24px to compensate for webix padding - group.push(labelUi); - } - - let height; - if (field?.settings?.useHeight === 1) - height = parseInt(field.settings.imageHeight) || height; - - const valueUi = Object.assign( - { - id: this.ids.detailItem, - view: "template", - borderless: true, - autowidth: true, - height, - isUsers: field?.key === "user", - template: isLabelTop - ? "
#display#
" - : "
#display#
", - data: { display: "" }, // show empty data in template - }, - uiDetailItemComponent - ); - // height = 0 behaves a bit differently then autoheight here. - if (!valueUi.height || valueUi.height == 0) { - delete valueUi.height; - valueUi.autoheight = true; - } - group.push(valueUi); - const itemUi = {}; - settings.labelPosition == "top" - ? (itemUi.rows = group) - : (itemUi.cols = group); - const _ui = super.ui([itemUi]); - - delete _ui.type; - - return _ui; - } - - // async init(AB) { - // await super.init(AB); - // } - - setValue(val, detailId) { - const $detailItem = $$(detailId ?? this.ids.detailItem); - - if (!$detailItem) return; - - const field = this.view.field(); - - switch (field?.key) { - case "string": - case "LongText": { - const strVal = (val || "") - .toString() - // Sanitize all of HTML tags - .replace(/[<]/gm, "<") - // Allow safe HTML tags - .replace( - new RegExp(`(<(/)?(${SAFE_HTML_TAGS.join("|")}))`, "gm"), - "<$2$3" - ); - - $detailItem.setValues({ display: strVal }); - break; - } - case "json": { - let jsonVal = val; - - if (typeof val == "object") { - jsonVal = JSON.stringify(val, null, 2); - } - - $detailItem.setValues({ display: jsonVal }); - break; - } - default: - $detailItem.setValues({ display: val }); - break; - } - } -}; diff --git a/AppBuilder/platform/views/viewComponent/ABViewDetailSelectivityComponent.js b/AppBuilder/platform/views/viewComponent/ABViewDetailSelectivityComponent.js deleted file mode 100644 index 9be6fe12..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewDetailSelectivityComponent.js +++ /dev/null @@ -1,83 +0,0 @@ -const ABViewDetailItemComponent = require("./ABViewDetailItemComponent"); - -module.exports = class ABViewDetailSelectivityComponent extends ( - ABViewDetailItemComponent -) { - constructor(baseView, idBase, ids) { - super( - baseView, - idBase || `ABViewDetailSelectivityComponent_${baseView.id}`, - ids - ); - } - - ui() { - const baseView = this.view; - const settings = this.settings; - const field = baseView.field(); - const _ui = { - on: { - //Add data-cy attribute for Cypress Testing - onAfterRender: () => { - const dataCy = `detail selectivity ${field?.columnName} ${ - field?.id - } ${baseView.parentDetailComponent()?.id || baseView.parent.id}`; - - $$(this.ids.detailItem)?.$view.setAttribute("data-cy", dataCy); - }, - }, - }; - - if (settings.height) _ui.height = settings.height; - - return super.ui(_ui); - } - - async init(AB) { - await super.init(AB); - - // add div of selectivity to detail - this.setValue( - this.ids.detailItem, - `
` - ); - } - - getDomSelectivity() { - const elem = $$(this.ids.component); - - if (!elem) return; - - return elem.$view.getElementsByClassName("ab-detail-selectivity")[0]; - } - - setValue(val) { - // convert value to array - if (val && !(val instanceof Array)) val = [val]; - - setTimeout(() => { - // get selectivity dom - const domSelectivity = this.getDomSelectivity(); - const isUsers = this.ui().isUsers ?? false; - - // render selectivity to html dom - const selectivitySettings = { - multiple: true, - readOnly: true, - isUsers: isUsers, - }; - const field = this.view.field(); - - field.selectivityRender( - domSelectivity, - selectivitySettings, - // App - null, - {} - ); - - // set value to selectivity - field.selectivitySet(domSelectivity, val, /*App*/ null); - }, 50); - } -}; diff --git a/AppBuilder/platform/views/viewComponent/ABViewDetailTextComponent.js b/AppBuilder/platform/views/viewComponent/ABViewDetailTextComponent.js deleted file mode 100644 index 5004926f..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewDetailTextComponent.js +++ /dev/null @@ -1,31 +0,0 @@ -const ABViewDetailItemComponent = require("./ABViewDetailItemComponent"); - -module.exports = class ABViewDetailTextComponent extends ( - ABViewDetailItemComponent -) { - constructor(baseView, idBase, ids) { - super(baseView, idBase || `ABViewDetailText_${baseView.id}`, ids); - } - - ui() { - const field = this.view.field(); - const _ui = { - css: "ab-text", - on: { - //Add data-cy attribute for Cypress Testing - onAfterRender: () => { - const dataCy = `detail text ${field?.columnName} ${field?.id} ${ - this.view.parentDetailComponent()?.id || this.view.parent.id - }`; - - $$(this.ids.component)?.$view.setAttribute("data-cy", dataCy); - }, - }, - }; - const settings = this.settings; - - if (settings.height) _ui.height = settings.height; - - return super.ui(_ui); - } -}; diff --git a/AppBuilder/platform/views/viewComponent/ABViewDetailTreeComponent.js b/AppBuilder/platform/views/viewComponent/ABViewDetailTreeComponent.js deleted file mode 100644 index ba3bae66..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewDetailTreeComponent.js +++ /dev/null @@ -1,118 +0,0 @@ -const ABViewDetailItemComponent = require("./ABViewDetailItemComponent"); - -module.exports = class ABViewDetailTreeComponent extends ( - ABViewDetailItemComponent -) { - constructor(baseView, idBase, ids) { - super(baseView, idBase || `ABViewDetailTree_${baseView.id}`, ids); - } - - get className() { - return "ab-detail-tree"; - } - - async init(AB) { - await super.init(AB); - - // add div of tree to detail - this.setValue(`
`); - } - - getDomTree() { - const $detailItem = $$(this.ids.detailItem); - - if (!$detailItem) return; - - return $detailItem.$view.getElementsByClassName(this.className)[0]; - } - - setValue(val) { - // convert value to array - let vals = []; - - if (Array.isArray(val)) { - vals = val; - } else if (val) { - // if it is the initial html string, then just set it and return - if (typeof val == "string" && val.indexOf(this.className) > -1) { - super.setValue(val); - return; - } - - try { - const parsed = JSON.parse(val); - - if (Array.isArray(parsed)) { - vals = parsed; - } else { - vals.push(parsed); - } - } catch (e) { - if (typeof val == "string") - vals = val.split(",").filter((v) => v !== ""); - else vals.push(val); - } - - // Normalize all entries to IDs - vals = vals.map((v) => - v && typeof v === "object" && v.id ? v.id : v - ); - } - - setTimeout(() => { - // get tree dom - const domTree = this.getDomTree(); - - if (!domTree) return false; - - const field = this.view.field(); - const branches = []; - - let selectOptions = this.AB.cloneDeep(field.settings.options); - - selectOptions = new this.AB.Webix.TreeCollection({ - data: selectOptions, - }); - - selectOptions.data.each(function (obj) { - if (vals.some((v) => v == obj.id)) { - let html = ""; - let rootid = obj.id; - - while (selectOptions.data.getParentId(rootid)) { - selectOptions.data.each(function (par) { - if (selectOptions.data.getParentId(rootid) === par.id) { - html = `${par.text}: ${html}`; - } - }); - - rootid = selectOptions.data.getParentId(rootid); - } - - html += obj.text; - branches.push(html); - } - }); - - const myHex = "#4CAF50"; - - let nodeHTML = "
"; - - branches.forEach(function (item) { - nodeHTML += `${item}`; - }); - - nodeHTML += "
"; - domTree.innerHTML = nodeHTML; - - let height = 33; - - if (domTree.scrollHeight > 33) height = domTree.scrollHeight; - - const $detailItem = $$(this.ids.detailItem); - - $detailItem.config.height = height; - $detailItem.resize(); - }, 50); - } -}; diff --git a/AppBuilder/platform/views/viewComponent/ABViewGridComponent.js b/AppBuilder/platform/views/viewComponent/ABViewGridComponent.js deleted file mode 100644 index cecbf639..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewGridComponent.js +++ /dev/null @@ -1,2402 +0,0 @@ -import ABViewComponent from "./ABViewComponent"; -import ABPopupExport from "../../plugins/included/view_grid/ABViewGridPopupExport"; -import ABPopupMassUpdateClass from "../../plugins/included/view_grid/ABViewGridPopupMassUpdate"; -import ABPopupSortField from "../ABViewPopupSortFields"; - -export default class ABViewGridComponent extends ABViewComponent { - constructor(baseView, idBase, ids) { - super( - baseView, - idBase || `ABViewGrid_${baseView.id}`, - Object.assign( - { - table: "", - - // component: `${base}_component`, - toolbar: "", - buttonDeleteSelected: "", - - buttonFilter: "", - buttonMassUpdate: "", - buttonSort: "", - buttonExport: "", - - globalSearchToolbar: "", - - datatable: "", - }, - ids - ) - ); - - this._handler_filterData = (fnFilter, filterRules) => { - this.callbackFilterData(fnFilter, filterRules); // be notified when there is a change in the filter - }; - - this.handler_select = (...params) => { - this.selectRow(...params); - }; - // {fn} .handler_select - // the callback fn for our selectRow() - // We want this called when the .datacollection we are linked to - // emits an "onChange" event. - - this.detatch(); - baseView.filterHelper.on("filter.data", this._handler_filterData); - - // derive these from viewGrid - this.id = baseView.id; - - ///// - ///// For TEsting: - ///// - // this.settings.showToolbar = 1; - // this.settings.isEditable = 1; - // this.settings.isExportable = 1; - // this.settings.gridFilter = { - // filterOption: 1, - // userFilterPosition: "form", - // isGlobalToolbar: 1, - // }; - - // this.settings.detailsPage = "some-uuid"; - // this.settings.detailTab = "some_uuid_2"; - // this.settings.trackView = 1; - ///// - ///// end testing - ///// - - this.columnSplitLeft = 0; - // {integer} - // Which column to "split"/"freeze" from the left side of the grid. - - this.columnSplitRight = 0; - // {integer} - // The # columns to the right to freeze. - - // this.datacollection = null; - // // {ABDataCollection} - // // The Webix DataCollection that manages the data we are displaying. - - this.validationError = false; - // {bool} - // Has a Validation Error occured? - - this.linkPage = baseView.linkPageHelper.component(); - // {ABViewPropertyLinkPage} - // - - const idTable = this.ids.table; - const ab = this.AB; - - this.PopupExport = new ABPopupExport(idTable); - this.PopupExport.init(ab); - // {ABViewGridPopupExport} - // Popup for managing how to export our data. - - this.PopupMassUpdateComponent = new ABPopupMassUpdateClass(this, idTable); - this.PopupMassUpdateComponent.init(ab); - // this.PopupMassUpdateComponent.on("") - // {} - // The popup for performing a Mass Edit operation. - - this.PopupSortDataTableComponent = new ABPopupSortField(idTable); - this.PopupSortDataTableComponent.init(ab); - this.PopupSortDataTableComponent.on("changed", (sortOptions) => { - this.callbackSortData(sortOptions); - }); - // {ABViewGridPopupSortFields} - // The popup for adding sort criteria to our grid. - - this.skippableColumns = [ - "appbuilder_select_item", - "appbuilder_view_detail", - "appbuilder_view_track", - "appbuilder_view_edit", - "appbuilder_trash", - ]; - // {array} - // An array of column names that should be skipped from some of our - // event handlers. - - // this.EditField = null; - // // {ABFieldXXX} - // // Which ABField is the focus of our PopupHeader menu? - - // this.EditNode = null; - // // {HTML DOM} - // // The webix.$node where the ABField Header is that our PopupHeader - // // should be displayed at. - - this.ignoreLocalSettings = false; - // {bool} - // should we ignore our local settings in our current context? - // (used in ABDesigner when our settings will change as we need to - // use those instead of the saved settings.) - - this._gridSettings = null; - // {hash} { grid.id : [ {columnHeader}, {columnHeader} ...]} - // Keep a global copy of our local Grid settings, so we can optimize the header - // sizes. - - this._isDatacollectionLoaded = false; - } - - // {string} - // the unique key for ABViewGrids to store/retrieve their local settings - get keyStorageSettings() { - return "abviewgrid_settings"; - } - - detatch() { - this.view.filterHelper.removeAllListeners("filter.data"); - ["changeCursor", "cursorStale", "cursorSelect"].forEach((key) => { - this.datacollection?.removeListener(key, this.handler_select); - }); - } - - /** - * @method getColumnIndex() - * return the Datatable.getColumnIndex() value - * @param {string} id - * the uuid of the column we are referencing. - * @return {integer} - */ - getColumnIndex(id) { - let indx = this.getDataTable().getColumnIndex(id); - if (!this.settings.massUpdate) { - // the index is 0 based. So if the massUpdate feature isn't - // enabled, we need to add 1 to the result so they look like - // a 1, 2, ... - - indx++; - } - return indx; - } - - uiDatatable() { - const ids = this.ids; - const settings = this.settings; - const self = this; - - let view = "datatable"; - - if (settings.isTreeDatable || settings.groupBy) - // switch datatable to support tree - view = "treetable"; - - let selectType = "cell"; - - if (!settings.isEditable && (settings.detailsPage || settings.editPage)) - selectType = "row"; - - return { - view, - id: ids.datatable, - resizeColumn: { size: 10 }, - resizeRow: { size: 10 }, - prerender: false, - editable: settings.isEditable, - fixedRowHeight: false, - height: settings.height || 0, - editaction: "custom", - select: selectType, - footer: - // show footer when there are summary columns - settings.summaryColumns.length > 0 || - settings.countColumns.length > 0, - tooltip: true, - // tooltip: { - // // id: ids.tooltip, - // template: (obj, common) => { - // return this.toolTip(obj, common); - // }, - // on: { - // // When showing a larger image preview the tooltip sometime displays part of the image off the screen...this attempts to fix that problem - // onBeforeRender: function () { - // self.toolTipOnBeforeRender(this.getNode()); - // }, - // onAfterRender: function (data) { - // self.toolTipOnAfterRender(this.getNode()); - // }, - // }, - // }, - dragColumn: true, - on: { - onBeforeSelect: function (data, preserve) { - if (self.skippableColumns.indexOf(data.column) != -1) { - return false; - } else if (settings.isEditable) { - const currObject = self.datacollection.datasource; - const selectField = currObject.fields( - (f) => f.columnName === data.column - )[0]; - - if (selectField == null) return true; - - const cellNode = this.getItemNode({ - row: data.row, - column: data.column, - }), - rowData = this.getItem(data.row); - - return selectField.customEdit(rowData, null, cellNode); - } else if (!settings.detailsPage && !settings.editPage) - return false; - }, - onAfterSelect: (data, preserve) => { - // {ABObject} data - // the selected object - // {bool} prevent - // indicates whether the previous selection state should - // be saved. (is multiselect and they are holding SHIFT) - if (this.settings.isEditable) { - this.onAfterSelect(data, preserve); - } - }, - // onBeforeEditStart: function (/*id*/) { - // // Not sure what this is suposed to check, but this condition - // // will always be false. - // if (!this.getItem(id) == "appbuilder_select_item") return false; - // }, - onCheck: function (row, col, val) { - // Update checkbox data - if (col == "appbuilder_select_item") { - // do nothing because we will parse the table once we decide - // if we are deleting or updating rows - self.toggleUpdateDelete(); - } else { - if (settings.isEditable) { - // get the field related to this col - const currObject = self.datacollection.datasource; - const selectField = currObject.fields( - (f) => f.columnName === col - )[0]; - - // if the colum is not the select item column move on to - // the next step to save - const state = { - value: val, - }; - const editor = { - row: row, - column: col, - config: { fieldID: selectField?.id ?? null }, - }; - - self.onAfterEditStop(state, editor); - } else { - const node = this.getItemNode({ - row: row, - column: col, - }); - const checkbox = node.querySelector( - 'input[type="checkbox"]' - ); - - if (val == 1) { - checkbox.checked = false; - } else { - checkbox.checked = true; - } - } - } - }, - onBeforeEditStop: function (state, editor) { - // Check if data loading is complete - const oldValue = state.old; - let newValue = state.value; - if (!Array.isArray(newValue)) newValue = [newValue]; - if ( - oldValue != null && - oldValue != "" && - // If options does not load completely, then Webix returns state.value as ['', '', ''] - newValue.filter((val) => val != null && val != "").length < - 1 && - // Check if no data load to the option - editor.getPopup?.().getList?.().data?.find({}).length < 1 - ) { - return false; - } - }, - onAfterEditStop: (state, editor, ignoreUpdate) => { - if (this.validationError == false) - this.onAfterEditStop(state, editor, ignoreUpdate); - }, - onValidationError: function () { - this.validationError = true; - }, - onValidationSuccess: function () { - this.validationError = false; - }, - - // We are sorting with server side requests now so we can remove this - // onAfterLoad: function () { - // _logic.onAfterLoad(); - // }, - onColumnResize: function ( - columnName, - newWidth, - oldWidth, - user_action - ) { - // if we resize the delete column we want to resize the last - // column but Webix will not allow since the column is split - const rightSplitItems = [ - "appbuilder_view_detail", - "appbuilder_view_track", - "appbuilder_view_edit", - "appbuilder_trash", - ]; - - if (rightSplitItems.indexOf(columnName) != -1) { - // Block events so we can leave the delete column alone - this.blockEvent(); - // keeps original width - this.setColumnWidth(columnName, oldWidth); - this.unblockEvent(); - // Listen to events again - - // find the last column's config - const column = self.getLastColumn(); - - columnName = column.id; - - // determine if we are making the column larger or smaller - if (newWidth < oldWidth) { - newWidth = column.width + 40; - // add 40 because there is not any more space to drag so we - // will allow 40px increments - } else { - newWidth = column.width - (newWidth - 40); - // take the column's width and subtrack the difference of - // the expanded delet column drag - } - // we don't want columns to be smaller than 50 ?? do we ?? - // I could be wrong maybe a checkbox could be smaller so this - // could change - if (newWidth < 50) { - newWidth = 50; - } - // minWidth is important because we are using fillspace:true - column.minWidth = newWidth; - // Sets the UI - this.setColumnWidth(columnName, newWidth); - } - // Saves the new width - if (user_action) { - self.onColumnResize( - columnName, - newWidth, - oldWidth, - user_action - ); - } - }, - onRowResize: (rowId) => { - // V2: we no longer do anything onRowResize() - // before we saved the row height in the record. - // this.onRowResize(rowId); - }, - onBeforeColumnDrag: (sourceId, event) => - !(this.skippableColumns.indexOf(sourceId) !== -1), - onBeforeColumnDrop: (sourceId, targetId, event) => - // Make sure we are not trying to drop onto one of our special - // columns ... - !(this.skippableColumns.indexOf(targetId) !== -1), - onAfterColumnDrop: (sourceId, targetId, event) => - this.onAfterColumnDrop(sourceId, targetId, event), - // onAfterColumnShow: function (id) { - // // console.warn("!! ToDo: onAfterColumnShow()"); - // // $$(self.webixUiId.visibleFieldsPopup).showField(id); - // }, - // onAfterColumnHide: function (id) { - // // console.warn("!! ToDo: onAfterColumnHide()"); - // // $$(self.webixUiId.visibleFieldsPopup).hideField(id); - // }, - - onHeaderClick: (id, e, node) => { - /* if (settings.configureHeaders) */ - this.onHeaderClick(id, e, node); - }, - }, - }; - } - - uiFilter() { - return this.view.filterHelper.ui(); - - // make sure onFilterData is now .emit()ed instead of passing in a callback. - } - - /** - * @method uiToolbar() - * Return the webix definition for the toolbar row for our Grids. - * @return {json} - */ - uiToolbar() { - const ids = this.ids; - const self = this; - - return { - view: "toolbar", - id: ids.toolbar, - hidden: true, - css: "ab-data-toolbar", - cols: [ - { - view: "button", - id: ids.buttonMassUpdate, - css: "webix_transparent", - label: this.label("Edit"), - icon: "fa fa-pencil-square-o", - type: "icon", - disabled: true, - autowidth: true, - click: function () { - self.toolbarMassUpdate(this.$view); - }, - }, - { - view: "button", - id: ids.buttonDeleteSelected, - css: "webix_transparent", - label: this.label("Delete"), - icon: "fa fa-trash", - type: "icon", - disabled: true, - autowidth: true, - click: function () { - self.toolbarDeleteSelected(this.$view); - }, - }, - { - view: "button", - id: ids.buttonFilter, - css: "webix_transparent", - label: this.label("Filters"), - icon: "fa fa-filter", - type: "icon", - autowidth: true, - click: function () { - self.toolbarFilter(this.$view); - }, - }, - { - view: "button", - id: ids.buttonSort, - css: "webix_transparent", - label: this.label("Sort"), - icon: "fa fa-sort", - type: "icon", - autowidth: true, - click: function () { - self.toolbarSort(this.$view); - }, - }, - { - view: "button", - id: ids.buttonExport, - css: "webix_transparent", - label: this.label("Export"), - icon: "fa fa-print", - type: "icon", - autowidth: true, - click: function () { - self.toolbarExport(this.$view); - }, - }, - {}, - { - id: ids.globalSearchToolbar, - view: "search", - placeholder: this.label("Search..."), - on: { - onTimedKeyPress: () => { - const searchText = $$(ids.globalSearchToolbar).getValue(); - - this.view.filterHelper.externalSearchText(searchText); - }, - }, - }, - ], - }; - } - - ui() { - const _uiGrid = { - id: this.ids.table, - type: "space", - borderless: true, - rows: [ - {}, - { - view: "label", - label: this.label("Select an object to load."), - inputWidth: 200, - align: "center", - }, - {}, - ], - }; - - const settings = this.settings; - - if (this.datacollection || settings.dataviewID !== "") { - _uiGrid.padding = settings.padding; - _uiGrid.rows = []; - if (settings.showToolbar) { - _uiGrid.rows.push(this.uiToolbar()); - } - if (this.settings.gridFilter.filterOption) { - _uiGrid.rows.push(this.uiFilter()); - } - - _uiGrid.rows.push(this.uiDatatable()); - } - - const _ui = super.ui([_uiGrid]); - - delete _ui.type; - - return _ui; - } - - async init(AB, accessLevel = 2) { - if (AB) await super.init(AB); - - const self = this; - const ids = this.ids; - - // WORKAROUND : Where should we define this ?? - // For include PDF.js - const abWebix = AB.Webix; - - abWebix.codebase = ""; - abWebix.cdn = "/js/webix"; - - // this shows the options to Hide, Filter, sort , etc... - // only in Designer? - // PopupHeaderEditComponent.init({ - // onClick: _logic.callbackHeaderEdit, // be notified when there is a change in the hidden fields - // }); - - // NOTE: register the onAfterRender() here, so it only registers - // one. - const $DataTable = this.getDataTable(); - - let throttleCustomDisplay = null; - let scrollStarted = null; - - if (!$DataTable) return; - - abWebix.extend($DataTable, abWebix.ProgressBar); - - $DataTable.config.accessLevel = accessLevel; - - if (accessLevel < 2) $DataTable.define("editable", false); - - const settings = this.settings; - - const customDisplays = (data) => { - const CurrentObject = this.datacollection?.datasource; - - if (!CurrentObject || !$DataTable.data) return; - - const displayRecords = []; - - const verticalScrollState = $DataTable.getScrollState().y, - rowHeight = $DataTable.config.rowHeight, - height = - $DataTable.$view.querySelector(".webix_ss_body").clientHeight, - startRecIndex = Math.floor(verticalScrollState / rowHeight), - endRecIndex = startRecIndex + $DataTable.getVisibleCount(); - - let index = 0; - - $DataTable.data.order.each((id) => { - if (id != null && startRecIndex <= index && index <= endRecIndex) - displayRecords.push(id); - - index++; - }); - - let editable = settings.isEditable; - - if ($DataTable.config.accessLevel < 2) editable = false; - - CurrentObject.customDisplays( - data, - this.AB._App, - $DataTable, - displayRecords, - editable - ); - }; - - $DataTable.attachEvent("onAfterRender", function (data) { - $DataTable.resize(); - - if (throttleCustomDisplay) clearTimeout(throttleCustomDisplay); - - throttleCustomDisplay = setTimeout(() => { - if (scrollStarted) clearTimeout(scrollStarted); - customDisplays(this.data); - }, 350); - - AB.ClassUI.CYPRESS_REF($DataTable); - Object.keys(ids).forEach((key) => { - const $el = $$(ids[key]); - if ($el) { - AB.ClassUI.CYPRESS_REF($el); - } - }); - }); - - // we have some data types that have custom displays that don't look - // right after scrolling large data sets we need to call customDisplays - // again - $DataTable.attachEvent("onScroll", function () { - if (scrollStarted) clearTimeout(scrollStarted); - - if (throttleCustomDisplay) clearTimeout(throttleCustomDisplay); - - scrollStarted = setTimeout(() => { - customDisplays(this.data); - }, 1500); - }); - $DataTable.attachEvent("onAfterScroll", function () { - if (throttleCustomDisplay) clearTimeout(throttleCustomDisplay); - - throttleCustomDisplay = setTimeout(() => { - if (scrollStarted) clearTimeout(scrollStarted); - customDisplays(this.data); - }, 350); - }); - - // Process our onItemClick events. - // this is a good place to check if our delete/trash icon was clicked. - $DataTable.attachEvent("onItemClick", function (id, e, node) { - // make sure we have an object selected before processing this. - const dc = self.datacollection; - const CurrentObject = dc?.datasource; - - if (!CurrentObject) return; - - if (settings.isEditable === 0) { - const items = $DataTable.getItem(id); - } - // if this was our edit icon: - // console.log(e.target.className); - if (e === "auto" || e.target.className.indexOf("eye") > -1) { - // View a Details Page: - self.changePage(dc, id, settings.detailsPage); - self.toggleTab(settings.detailsTab, this); - } else if (e.target.className.indexOf("pencil") > -1) { - self.changePage(dc, id, settings.editPage); - self.toggleTab(settings.editTab, this); - } else if (e.target.className.indexOf("track") > -1) - self.emit("object.track", CurrentObject, id.row); - // App.actions.openObjectTrack(CurrentObject, id.row); - else if (e.target.className.indexOf("clear-combo-value") > -1) { - const clearValue = {}; - - clearValue[id.column] = ""; - - const updateRow = async () => { - try { - const response = await CurrentObject.model().update( - id.row, - clearValue - ); - - // console.log(response); - } catch (err) { - self.AB.notify.developer(err, { - context: "ABViewGridComponent.onItemClick", - message: "Error updating item", - obj: CurrentObject.toObj(), - id: id.row, - }); - } - }; - - updateRow(); - } - // if this was our trash icon: - else if (e.target.className.indexOf("trash") > -1) { - // If the confirm popup is showing, then skip to show a new one - if (!this._deleteConfirmPopup) { - this._deleteConfirmPopup = abWebix.confirm({ - title: self.label("Delete data"), - text: self.label("Do you want to delete this row?"), - callback: (result) => { - delete this._deleteConfirmPopup; - if (result) { - const deleteRow = async () => { - try { - const response = - await CurrentObject.model().delete(id.row); - - if (response.numRows > 0) { - $DataTable.remove(id); - $DataTable.clearSelection(); - } else - abWebix.alert({ - text: self.label( - "No rows were effected. This does not seem right." - ), - }); - } catch (err) { - self.AB.notify.developer(err, { - context: "ABViewGridComponent.onItemClick", - message: "Error deleting item", - obj: CurrentObject.toObj(), - id: id.row, - }); - - //// TODO: what do we do here? - } - }; - - deleteRow(); - } - - $DataTable.clearSelection(); - - return true; - }, - }); - } - } else if (settings.detailsPage.length) { - // If an icon wasn't selected but a details page is set - // view the details page - self.changePage(dc, id, settings.detailsPage); - self.toggleTab(settings.detailsTab, this); - } else if (settings.editPage.length) { - // If an icon wasn't selected but an edit page is set - // view the edit page - self.changePage(dc, id, settings.editPage); - self.toggleTab(settings.editTab, this); - } - }); - - // ABViewGrid Original init(); - if (settings.showToolbar) { - if ( - settings.massUpdate || - settings.isSortable || - settings.isExportable || - (settings.gridFilter && - settings.gridFilter.filterOption && - settings.gridFilter.userFilterPosition === "toolbar") - ) - $$(ids.toolbar).show(); - - if (!settings.massUpdate) { - $$(ids.buttonMassUpdate).hide(); - $$(ids.buttonDeleteSelected).hide(); - } - - if (!settings.allowDelete) $$(ids.buttonDeleteSelected).hide(); - - if (settings.gridFilter) { - if ( - settings.gridFilter.filterOption !== 1 || - settings.gridFilter.userFilterPosition !== "toolbar" - ) - $$(ids.buttonFilter).hide(); - - if ( - settings.gridFilter.filterOption === 3 && - settings.gridFilter.globalFilterPosition === "single" - ) - $DataTable.hide(); - - if (settings.gridFilter.isGlobalToolbar) - $$(ids.globalSearchToolbar).show(); - else $$(ids.globalSearchToolbar).hide(); - - if (settings.gridFilter.filterOption) - this.view.filterHelper.init(this.AB); - } - - if (!settings.isSortable) $$(ids.buttonSort).hide(); - - if (!settings.isExportable) $$(ids.buttonExport).hide(); - } - - if (settings.hideHeader) this.hideHeader(); - - const dc = - this.datacollection || this.AB.datacollectionByID(settings.dataviewID); - - if (!this._isDatacollectionLoaded) this.datacollectionLoad(dc); - - // Make sure - this._gridSettings = - this._gridSettings || - (await this.AB.Storage.get(this.keyStorageSettings)) || - {}; - - if (dc?.datasource) { - // TRANSITION: ABViewGrid_orig line 862 ... - - this.linkPage.init({ - view: this.view, - datacollection: dc, - }); - - this.refreshHeader(); - } - } - - /** - * @method busy() - * Indicate that our datatable is currently busy loading/processing - * data. - */ - busy() { - this.getDataTable()?.showProgress?.({ type: "icon" }); - } - - /** - * @method callbackFilterData() - * Process the provided filter options from our filterHelper. - * @param {fn} fnFilter - * A function that returns true/false for each row of data - * to determine if is should exist. - * @param {array} filterRules - * Any Filter Rules added by the user. - */ - callbackFilterData(fnFilter, filterRules = []) { - const ids = this.ids; - const $ButtonFilter = $$(ids.buttonFilter); - - if ($ButtonFilter) { - const onlyFilterRules = this.view.filterHelper.filterRules(); - - $ButtonFilter.define("badge", onlyFilterRules?.rules?.length ?? 0); - $ButtonFilter.refresh(); - } - - const dc = this.datacollection; - - dc.filterCondition(filterRules); - dc.reloadData(); - } - - async callbackSortData(sortRules = []) { - const $buttonSort = $$(this.ids.buttonSort); - - $buttonSort.define("badge", sortRules.length || null); - $buttonSort.refresh(); - - const gridElem = this.getDataTable(); - - if (gridElem.data.find({}).length < gridElem.data.count()) { - try { - // NOTE: Webix's client sorting does not support dynamic loading. - // If the data does not be loaded, then load all data. - await this.datacollection.reloadData(0, 0); - } catch (err) { - this.AB.notify.developer(err, { - context: - "ABViewGrid:callbackSortData(): Error perform datacollection.reloadData()", - }); - } - } - - // wait until the grid component will done to repaint UI - setTimeout(() => { - gridElem.sort((a, b) => this.PopupSortDataTableComponent.sort(a, b)); - }, 777); - } - - /** - * @method changePage() - * Helper method to switch to another View. - * @param {ABDataCollection} dv - * The DataCollection we are working with. - * @param {obj} rowItem - * the { row:#, column:{string} } of the item that was clicked. - * @param {ABViewPage.uuid} page - * The .uuid of the ABViewPage/ABViewTab we are to swtich to. - * - */ - changePage(dv, rowItem, page) { - const rowId = rowItem?.row ?? null; - - // Set cursor to data view - if (dv) dv.setCursor(rowId); - - // Pass settings to link page module - if (this.linkPage) this.linkPage.changePage(page, rowId); - else super.changePage(page); - } - - columnConfig(headers = []) { - this.settings.columnConfig = headers; - } - /** - * @method datacollectionLoad() - * Assign an ABDataCollection to this component to use instead of any - * provided .dataviewID in our settings. - * NOTE: this primarily happens in the ABDesigner's Object Workspace. - * @param {ABDataCollection} dc - */ - datacollectionLoad(dc) { - const oldDC = this.datacollection; - this.datacollection = dc; - - const CurrentObject = dc?.datasource; - const $DataTable = this.getDataTable(); - - if ($DataTable) { - // preventing too many handlers - if (!this.__handler_dc_busy) { - this.__handler_dc_busy = () => { - this.busy(); - }; - - this.__handler_dc_ready = () => { - this.ready(); - this.populateGroupData(); - }; - - this.__handler_dc_loadData = () => { - this.populateGroupData(); - }; - } - - if (oldDC) { - // remove our listeners from the previous DC - oldDC.removeListener("initializingData", this.__handler_dc_busy); - oldDC.removeListener("initializedData", this.__handler_dc_ready); - oldDC.removeListener("loadData", this.__handler_dc_loadData); - } - - if (dc) { - if (dc.datacollectionLink && dc.fieldLink) - dc.bind($DataTable, dc.datacollectionLink, dc.fieldLink); - else dc.bind($DataTable); - - // making sure we only have 1 registered listener on this dc - dc.removeListener("initializingData", this.__handler_dc_busy); - dc.on("initializingData", this.__handler_dc_busy); - dc.removeListener("initializedData", this.__handler_dc_ready); - dc.on("initializedData", this.__handler_dc_ready); - dc.removeListener("loadData", this.__handler_dc_loadData); - dc.on("loadData", this.__handler_dc_loadData); - this.grouping(); - - this._isDatacollectionLoad = true; - } else $DataTable.unbind(); - - // Be sure to pass on our CurrentObject to our dependent components. - if (CurrentObject) { - this.view.filterHelper.objectLoad(CurrentObject); - this.PopupMassUpdateComponent.objectLoad( - CurrentObject, - this.getDataTable() - ); - this.PopupSortDataTableComponent.objectLoad(CurrentObject); - - this.PopupExport.objectLoad(CurrentObject); - this.PopupExport.dataCollectionLoad(dc); - this.PopupExport.setGridComponent(this.getDataTable()); - this.PopupExport.setHiddenFields(this.settings.hiddenFields); - this.PopupExport.setFilename(this.view.label); - } - } - } - - /** - * @function enableUpdateDelete - * - * disable the update or delete buttons in the toolbar if there no items selected - * we will make this externally accessible so we can call it from within the datatable component - */ - disableUpdateDelete() { - $$(this.ids.buttonMassUpdate)?.disable(); - $$(this.ids.buttonDeleteSelected)?.disable(); - // externally indicate that no rows are selected - this.emit("selection.cleared"); - } - - /** - * @function enableUpdateDelete - * - * enable the update or delete buttons in the toolbar if there are any items selected - * we will make this externally accessible so we can call it from within the datatable component - */ - enableUpdateDelete() { - $$(this.ids.buttonMassUpdate)?.enable(); - $$(this.ids.buttonDeleteSelected)?.enable(); - // externally indicate that a row has been selected - this.emit("selection"); - } - - freezeDeleteColumn() { - // we are going to always freeze the delete column if the datatable - // is wider than the container so it is easy to get to - return this.getDataTable().define("rightSplit", this.columnSplitRight); - } - - /** - * @method getDataTable() - * return the webix grid component. - * @return {webix.grid} - */ - getDataTable() { - return $$(this.ids.datatable); - } - - /** - * @method getLastColumn - * return the last column of a datagrid that is resizeable - */ - getLastColumn() { - const $DataTable = this.getDataTable(); - - let lastColumn = {}; - - // Loop through each columns config to find out if it is in the split 1 region and set it as the last item...then it will be overwritten by next in line - $DataTable.eachColumn((columnId) => { - const columnConfig = $DataTable.getColumnConfig(columnId); - - if (columnConfig.split === 1) lastColumn = columnConfig; - }); - - return lastColumn; - } - - /** - * @method grouping() - * perform any grouping operations - */ - grouping() { - if (!this.settings.groupBy) return; - - const $treetable = this.getDataTable(); - - // map: { - // votes:["votes", "sum"], - // title:["year"] - // } - const baseGroupMap = {}; - const CurrentObject = this.datacollection.datasource; - - CurrentObject.fields().forEach((f) => { - switch (f.key) { - case "number": - baseGroupMap[f.columnName] = [f.columnName, "sum"]; - - break; - - case "calculate": - case "formula": - baseGroupMap[f.columnName] = [ - f.columnName, - (prop, listData) => { - if (!listData) return 0; - - let sum = 0; - - listData.forEach((r) => { - // we only want numbers returned so pass `true` as third param - // to signify that this is part of a grouping row - sum += f.format(r, false, true) * 1; - }); - - // simulate reformat from ABFieldFormulaCore - if (!f.fieldLink || f.fieldLink.key === "calculate") - return sum; - else { - const rowDataFormat = {}; - - rowDataFormat[f.fieldLink.columnName] = sum; - - return f.fieldLink.format(rowDataFormat); - } - }, - ]; - - break; - - case "connectObject": - baseGroupMap[f.columnName] = [ - f.columnName, - (prop, listData) => { - if (!listData || !listData.length) return 0; - - let count = 0; - - listData.forEach((r) => { - const valRelation = r[f.relationName()]; - - // array - if (valRelation?.length) count += valRelation.length; - // object - else if (valRelation) count += 1; - }); - - return count; - }, - ]; - - break; - - default: - baseGroupMap[f.columnName] = [ - f.columnName, - function (prop, listData) { - if (!listData || !listData.length) return 0; - - let count = 0; - - listData.forEach((r) => { - const val = prop(r); - - // count only exists data - if (val) count += 1; - }); - - return count; - }, - ]; - - break; - } - }); - - // set group definition - // $DataTable.define("scheme", { - // $group: { - // by: settings.groupBy, - // map: groupMap - // } - // }); - - // NOTE: https://snippet.webix.com/e3a2bf60 - let groupBys = (this.settings.groupBy || "") - .split(",") - .map((g) => g.trim()); - // Reverse the array NOTE: call .group from child to root - groupBys = groupBys.reverse(); - - groupBys.forEach((colName, gIndex) => { - const groupMap = this.AB.cloneDeep(baseGroupMap); - - let by; - - // Root - if (gIndex === groupBys.length - 1) by = colName; - // Sub groups - else { - by = (row) => { - let byValue = row[colName]; - - for (let i = gIndex + 1; i < groupBys.length; i++) { - byValue = `${row[groupBys[i]]} - ${byValue}`; - } - - return byValue; - }; - - // remove parent group data - groupBys.forEach((gColName) => { - if (gColName !== colName) groupMap[gColName] = [gColName]; - }); - } - - $treetable.data.group({ - by: by, - map: groupMap, - }); - }); - } - - hideHeader() { - const $DataTable = this.getDataTable(); - - $DataTable.define("header", false); - $DataTable.refresh(); - } - - /** - * @function onAfterColumnDrop - * When an editor drops a column to save a new column order - * @param {string} sourceId - * the columnName of the item dragged - * @param {string} targetId - * the columnName of the item dropped on - * @param {event} event - */ - async onAfterColumnDrop(sourceId, targetId, event) { - const $DataTable = this.getDataTable(); - const CurrentObject = this.datacollection.datasource; - const settings = this.settings; - const columnConfig = this.localSettings(); - - // Reorder our current columnConfig - // We know what was moved and what item it has replaced/pushed forward - // so first we want to splice the item moved out of the array of fields - // and store it so we can put it somewhere else - let itemMoved = null; - let oPos = 0; // original position - - for (let i = 0; i < columnConfig.length; i++) - if (columnConfig[i].id == sourceId) { - itemMoved = columnConfig[i]; - columnConfig.splice(i, 1); - oPos = i; - - break; - } - // once we have removed/stored it we can find where its new position - // will be by looping back through the array and finding the item it - // is going to push forward - for (let j = 0; j < columnConfig.length; j++) - if (columnConfig[j].id == targetId) { - // if the original position was before the new position we will - // follow webix's logic that the drop should go after the item - // it was placed on - if (oPos <= j) j++; - - columnConfig.splice(j, 0, itemMoved); - - break; - } - - // special case: dropped on end and need to update .fillspace - // if (j == columnConfig.length - 1) { - // if (columnConfig[j - 1].fillspace) { - // columnConfig[j - 1].fillspace = false; - // columnConfig[j].fillspace = true; - // } - // } - - // if we allow local changes - this.localSettings(columnConfig); - - if (settings.saveLocal) this.localSettingsSave(); - - // Now emit this event, in case an external object is wanting to - // respond to this: ABDesigner.objectBuilder, Interface Designer, - // we send back an array[ ABField.id, ...] in the order we have - // them. - this.emit( - "column.order", - columnConfig.map((c) => c.fieldID) - ); - - this.refreshHeader(); - - // CurrentObject.fieldReorder(sourceId, targetId) - // .then(() => { - // // reset each column after a drop so we do not have multiple fillspace and minWidth settings - // var editiable = settings.isEditable; - // if ($DataTable.config.accessLevel < 2) { - // editiable = false; - // } - // var columnHeaders = CurrentObject.columnHeaders(true, editiable); - // columnHeaders.forEach(function (col) { - // if (col.id == sourceId && col.fillspace == true) { - // columnHeader.fillspace = false; - // columnHeader.minWidth = columnHeader.width; - // } - // }); - - // _logic.callbacks.onColumnOrderChange(CurrentObject); - // // freeze columns: - // let frozenColumnID = - // settings.frozenColumnID != null - // ? settings.frozenColumnID - // : CurrentObject.workspaceFrozenColumnID; - // if (frozenColumnID != "") { - // $DataTable.define( - // "leftSplit", - // $DataTable.getColumnIndex(frozenColumnID) + columnSplitLeft - // ); - // } else { - // $DataTable.define("leftSplit", columnSplitLeft); - // } - // _logic.freezeDeleteColumn(); - // $DataTable.refreshColumns(); - // }) - // .catch((err) => { - // OP.Error.log("Error saving new column order:", { - // error: err, - // }); - // }); - } - - /** - * @function onAfterEditStop - * When an editor is finished. - * @param {json} state - * @param {} editor - * @param {} ignoreUpdate - * @return - */ - async onAfterEditStop(state, editor, ignoreUpdate) { - // state: {value: "new value", old: "old value"} - // editor: { column:"columnName", row:ID, value:'value', getInputNode:fn(), config:{}, focus: fn(), getValue: fn(), setValue: function, getInputNode: function, render: function…} - - const $DataTable = this.getDataTable(); - - // if you don't edit an empty cell we just need to move on - if ( - (!state.old && state.value === "") || - (state.old === "" && state.value === "") - ) { - $DataTable?.clearSelection(); - - return false; - } - - const CurrentObject = this.datacollection.datasource; - - if (editor.config) - switch (editor.config.editor) { - case "number": - state.value = parseFloat(state.value); - - break; - - case "datetime": - state.value = state.value.getTime(); - - if (state && state.old && state.old.getTime) - state.old = state.old.getTime(); - - break; - - default: - // code block - } - - // lets make sure we are comparing things properly: - // reduce newValue and oldValue down to PK if they were objects - let newVal = state.value; - if (newVal) { - newVal = newVal[CurrentObject.PK()] || newVal; - } - let oldVal = state.old; - if (oldVal) { - oldVal = oldVal[CurrentObject.PK()] || oldVal; - } - - // NOTE: != vs !== : - // want to handle when newVal = "3" and oldVal = 3 - // that is why we don't use !== so that we convert the values into - // the same case. - if (newVal != oldVal) { - const item = $DataTable?.getItem(editor.row); - - item[editor.column] = state.value; - - $DataTable.removeCellCss(item.id, editor.column, "webix_invalid"); - $DataTable.removeCellCss(item.id, editor.column, "webix_invalid_cell"); - - //maxlength field - const f = CurrentObject.fieldByID(editor.config?.fieldID); - if ( - f?.settings.maxLength && - state.value.length > f.settings.maxLength - ) { - this.AB.alert({ - title: this.label("Limit max length"), - text: this.label( - "You can enter a maximum of " + - f.settings.maxLength + - " characters" - ), - }); - $DataTable.addCellCss(item.id, editor.column, "webix_invalid_cell"); - $DataTable.refresh(editor.row); - $DataTable.clearSelection(); - return false; - } - - const validator = CurrentObject.isValidData(item); - - if (validator.pass()) { - const patch = {}; - patch[editor.column] = item[editor.column]; - - const ab = this.AB; - - try { - await CurrentObject.model().update(item.id, patch); - - if ($DataTable.exists(editor.row)) { - $DataTable.updateItem(editor.row, item); - $DataTable.clearSelection(); - $DataTable.refresh(editor.row); - } - } catch (err) { - ab.notify.developer(err, { - context: "ABViewGrid:onAfterEditStop(): Error saving item", - item, - editor, - state, - object: CurrentObject.toObj(), - }); - - $DataTable.clearSelection(); - - if ( - ab.Validation.isGridValidationError( - err, - editor.row, - $DataTable - ) - ) { - // Do we reset the value? - // item[editor.column] = state.old; - // $DataTable.updateItem(editor.row, item); - } else { - // this was some other Error! - } - } - // CurrentObject.model() - // .update(item.id, item) - // .then(() => { - // if ($DataTable.exists(editor.row)) { - // $DataTable.updateItem(editor.row, item); - // $DataTable.clearSelection(); - // $DataTable.refresh(editor.row); - // } - // }) - // .catch((err) => { - // OP.Error.log("Error saving item:", { - // error: err - // }); - - // $DataTable.clearSelection(); - // if ( - // OP.Validation.isGridValidationError( - // err, - // editor.row, - // $DataTable - // ) - // ) { - // // Do we reset the value? - // // item[editor.column] = state.old; - // // $DataTable.updateItem(editor.row, item); - // } else { - // // this was some other Error! - // } - // }); - } else validator.updateGrid(editor.row, $DataTable); - } else $DataTable?.clearSelection(); - - return false; - - // var item = $$(self.webixUiId.objectDatatable).getItem(editor.row); - - // self.updateRowData(state, editor, ignoreUpdate) - // .fail(function (err) { // Cached - // item[editor.column] = state.old; - // $$(self.webixUiId.objectDatatable).updateItem(editor.row, item); - // $$(self.webixUiId.objectDatatable).refresh(editor.row); - - // // TODO : Message - - // $$(self.webixUiId.objectDatatable).hideProgress(); - // }) - // .then(function (result) { - // if (item) { - // item[editor.column] = state.value; - - // if (result && result.constructor.name === 'Cached' && result.isUnsync()) - // item.isUnsync = true; - - // $$(self.webixUiId.objectDatatable).updateItem(editor.row, item); - // } - - // // TODO : Message - - // $$(self.webixUiId.objectDatatable).hideProgress(); - // }); - } - - /** - * @function onAfterSelect - * This is when a user clicks on a cell. We use the onAfterSelect to - * trigger a normal .editCell() if there isn't a custom editor for this field. - * @param {json} data webix cell data - * @return - */ - onAfterSelect(data /*, preserve */) { - // data: {row: 1, column: "name", id: "1_name", toString: function} - // data.row: ABObject.id - // data.column => columnName of the field - - // Normal update data - this.getDataTable()?.editCell(data.row, data.column); - } - - /** - * @function onColumnResizeResize - * This is when a user adjusts the size of a column - * @param {} columnName - * @param {int} newWidth - * @param {int} oldWidth - * @param {} user_action - * @return - */ - async onColumnResize(columnName, newWidth, oldWidth, user_action) { - // update the settings - - let requireRefresh = false; - - const ab = this.AB; - - if (newWidth < 30) { - newWidth = 30; - requireRefresh = true; - - ab.Webix.message({ - type: "info", - text: this.label("minimum column width is {0}", [30]), - expire: 1000, - }); - } - - const localSettings = this.localSettings(); - - if (localSettings) { - const header = localSettings.find((h) => h.id == columnName); - - if (header) { - header.width = newWidth; - - delete header.adjust; - } - } - - this.localSettings(localSettings); - - if (this.settings.saveLocal) { - await this.localSettingsSave(); - // for (const item in GridSettings) { - // GridSettings[item].forEach((item) => { - // // we cannot include field info because of the cicular structure - // if (item?.footer?.field) { - // delete item.footer.field; - // } - // }); - // } - // await this.AB.Storage.set(this.keyStorageSettings, GridSettings); - } - - // refresh the display - if (requireRefresh) this.refreshHeader(); - - this.freezeDeleteColumn(); - - // this.getDataTable().refreshColumns(); - - // TODO: allow external app to respond in special cases: - // eg: ABDesigner object workspace, interface builder, etc... - this.emit("column.resize", columnName, newWidth, oldWidth); - } - - /** - * @method onHeaderClick - * process the user clicking on the header for one of our columns. - */ - onHeaderClick(id, e, node) { - if (this.skippableColumns.indexOf(id.column) !== -1) return false; - - // save our EditNode & EditField: - // this.EditNode = node; - - const EditField = this.datacollection.datasource.fields( - (f) => f.columnName === id.column - )[0]; - // if (this.EditField) { - // // show the popup - // PopupHeaderEditComponent.show(node, this.EditField); - // } - - this.emit("column.header.clicked", node, EditField); - - return false; - } - - /** - * @method onShow() - * perform any preparations necessary when showing this component. - */ - onShow() { - super.onShow(); - - // make sure our grid is properly .adjust()ed to the screen. - this.getDataTable()?.adjust(); - - const dv = this.datacollection; - - if (dv) - ["changeCursor", "cursorStale", "cursorSelect"].forEach((key) => { - this.eventAdd({ - emitter: dv, - eventName: key, - listener: this.handler_select.bind(this), - }); - }); - } - - /** - * @method ready() - * Indicate that our datatable is currently ready for operation. - */ - ready() { - const dc = this.datacollection; - if ( - this.isCustomGroup && - dc?.dataStatus != dc?.dataStatusFlag.initialized - ) - return; - - this.getDataTable()?.hideProgress?.(); - } - - /** - * @function refreshHeader() - * - * refresh the header for the table apart from the refresh() command - * @param {bool} ignoreLocal - * Should we ignore our local settings and build directly from - * our config settings? - */ - refreshHeader(ignoreLocal = this.ignoreLocal) { - // columnSplitRight = 0; - // wait until we have an Object defined: - const CurrentObject = this.datacollection.datasource; - - if (!CurrentObject) return; - - const ids = this.ids; - const $DataTable = $$(ids.datatable); - - if (!$DataTable) return; - - const accessLevel = $DataTable.config.accessLevel; - - $DataTable.define("leftSplit", 0); - $DataTable.define("rightSplit", 0); - - let rowHeight = 0; - - CurrentObject.imageFields().forEach((image) => { - const settings = image.getSettings(); - - if (settings.useHeight && settings.imageHeight > rowHeight) - rowHeight = settings.imageHeight; - }); - - if (rowHeight) $DataTable.define("rowHeight", rowHeight); - - // $DataTable.clearAll(); - - const settings = this.settings; - - let editable = settings.isEditable; - - if ($DataTable.config.accessLevel < 2) editable = false; - - //// update DataTable structure: - // get column list from our local settings - const objColumnHeaders = CurrentObject.columnHeaders( - true, - editable, - // TRANSITION: moving these from .columnHeaders() to here: - [], //settings.summaryColumns, - [], //settings.countColumns, - [] //settings.hiddenFields - ); - - let columnHeaders = this.localSettings(); - - const ab = this.AB; - - // if that is empty, pull from our settings.columnConfig - if (!columnHeaders || ignoreLocal) - columnHeaders = ab.cloneDeep(this.settings.columnConfig); - - // if that is empty for some reason, rebuild from our CurrentObject - if (!columnHeaders || columnHeaders.length === 0) - columnHeaders = objColumnHeaders; - - // sanity check: - // columnHeaders can't contain a column that doesn't exist in objColumHeaders: - // (eg: a field might have been removed but localStorage doesn't know that ) - const objColumnHeaderIDs = objColumnHeaders.map((h) => h.fieldID); - - columnHeaders = columnHeaders.filter( - (c) => objColumnHeaderIDs.indexOf(c.fieldID) > -1 - ); - - // default our columnConfig values to our columnHeaders: - columnHeaders.forEach((c) => { - // we want to overwrite our default settings with anything stored - // in local storage - const origCol = objColumnHeaders.find((h) => h.fieldID === c.fieldID); - - // none of our functions can be stored in localStorage, so scan - // the original column and attach any template functions to our - // stashed copy. - // also the suggest for selects and connected fields may contain a - // function so go ahead and copy the original suggest to the column - Object.keys(origCol).forEach((k) => { - if (typeof origCol[k] === "function" || k === "suggest") { - c[k] = origCol[k]; - } - }); - - const f = CurrentObject.fieldByID(c.fieldID); - - if (!f) return; - - // if it's a hidden field: - if (settings.hiddenFields.indexOf(f.columnName) > -1) { - c.hidden = true; - } - - // add summary footer: - if (settings.summaryColumns.indexOf(f.id) > -1) { - if (f.key == "calculate" || f.key == "formula") - c.footer = { content: "totalColumn", field: f }; - else c.footer = { content: "summColumn" }; - } - // or add the count footer - else if (settings.countColumns.indexOf(f.id) > -1) - c.footer = { content: "countColumn" }; - }); - - let localSettings = this.localSettings(); - - if (!localSettings || ignoreLocal) { - this.localSettings(columnHeaders); - - localSettings = columnHeaders; - } - - columnHeaders = ab.cloneDeep(localSettings); - - const fieldValidations = []; - const rulePops = []; - - columnHeaders.forEach((col) => { - col.fillspace = false; - - // parse the rules because they were stored as a string - // check if rules are still a string...if so lets parse them - if (col.validationRules) { - if (typeof col.validationRules === "string") { - col.validationRules = JSON.parse(col.validationRules); - } - - if (col.validationRules.length) { - const validationUI = []; - - // there could be more than one so lets loop through and build the UI - col.validationRules.forEach((rule) => { - const Filter = ab.filterComplexNew( - col.id /*+ "_" + webix.uid()*/ - ); - // add the new ui to an array so we can add them all at the same time - validationUI.push(Filter.ui); - // store the filter's info so we can assign values and settings after the ui is rendered - fieldValidations.push({ - filter: Filter, - view: Filter.ids.querybuilder, - columnName: col.id, - validationRules: rule.rules, - invalidMessage: rule.invalidMessage, - }); - }); - - // create a unique view id for popup - const popUpId = - ids.rules + "_" + col.id; /* + "_" + webix.uid() */ - - // store the popup ids so we can remove the later - rulePops.push(popUpId); - // add the popup to the UI but don't show it - ab.Webix.ui({ - view: "popup", - css: "ab-rules-popup", - id: popUpId, - body: { - rows: validationUI, - }, - }); - } - } - - // group header - if ( - settings.groupBy && - (settings.groupBy || "").indexOf(col.id) > -1 - ) { - const groupField = CurrentObject.fieldByID(col.fieldID); - - if (groupField) - col.template = (obj, common) => { - // return common.treetable(obj, common) + obj.value; - if (obj.$group) { - const rowData = ab.cloneDeep(obj); - - rowData[groupField.columnName] = rowData.value; - - return ( - common.treetable(obj, common) + - groupField.format(rowData) - ); - } else return groupField.format(obj); - }; - } - }); - - if (fieldValidations.length) { - // we need to store the rules for use later so lets build a container array - const complexValidations = []; - - fieldValidations.forEach((f) => { - // init each ui to have the properties (app and fields) of the object we are editing - // f.filter.applicationLoad(CurrentObject.application); - f.filter.fieldsLoad(CurrentObject.fields()); - // now we can set the value because the fields are properly initialized - f.filter.setValue(f.validationRules); - // if there are validation rules present we need to store them in a lookup hash - // so multiple rules can be stored on a single field - if (!Array.isArray(complexValidations[f.columnName])) - complexValidations[f.columnName] = []; - - // now we can push the rules into the hash - complexValidations[f.columnName].push({ - filters: f.filter.getValue(), - values: $DataTable.getSelectedItem[f.columnName], - invalidMessage: f.invalidMessage, - }); - }); - - const rules = {}; - - // store the rules in a data param to be used later - $DataTable.$view.complexValidations = complexValidations; - // use the lookup to build the validation rules - Object.keys(complexValidations).forEach((key) => { - rules[key] = (value, data) => { - // default valid is true - let isValid = true; - let invalidMessage = ""; - - $DataTable.$view.complexValidations[key].forEach((filter) => { - // convert rowData from { colName : data } to { id : data } - const newData = {}; - - (CurrentObject.fields() || []).forEach((field) => { - newData[field.id] = data[field.columnName]; - }); - - // for the case of "this_object" conditions: - if (data.uuid) { - newData["this_object"] = data.uuid; - data["this_object"] = data.uuid; - } - - // use helper funtion to check if valid - // const ruleValid = filter.filters(newData); - const filterComplex = ab.filterComplexNew( - `rule-validate-${key}` - ); - filterComplex.fieldsLoad( - CurrentObject.fields(), - CurrentObject - ); - const ruleValid = filterComplex.isValid(data, filter.filters); - - // if invalid we need to tell the field - if (!ruleValid) { - isValid = false; - invalidMessage = filter.invalidMessage; - } - }); - - // we also need to define an error message - if (!isValid) - ab.Webix.message({ - type: "error", - text: invalidMessage, - }); - - return isValid; - }; - }); - // define validation rules - $DataTable.define("rules", rules); - // store the array of view ids on the webix object so we can get it later - $DataTable.config.rulePops = rulePops; - $DataTable.refresh(); - } else { - // check if the previous datatable had rule popups and remove them - if ($DataTable.config.rulePops) - $DataTable.config.rulePops.forEach((popup) => { - if ($$(popup)) $$(popup).destructor(); - }); - // remove any validation rules from the previous table - $DataTable.define("rules", {}); - $DataTable.refresh(); - } - - const addedColumns = []; - // {array} the .id of the columnHeaders we add based upon our settings. - // this will help us pick the lastColumn that is part of the - // object. - - if (settings.labelAsField) { - // console.log(CurrentObject); - columnHeaders.unshift({ - id: "appbuilder_label_field", - header: "Label", - fillspace: true, - template: (obj) => CurrentObject.displayData(obj), - // css: { 'text-align': 'center' } - }); - addedColumns.push("appbuilder_label_field"); - } - - if (settings.massUpdate && accessLevel === 2) { - columnHeaders.unshift({ - id: "appbuilder_select_item", - header: { content: "masterCheckbox", contentId: "mch" }, - width: 40, - template: "
{common.checkbox()}
", - css: { "text-align": "center" }, - }); - this.columnSplitLeft = 1; - addedColumns.push("appbuilder_select_item"); - } else this.columnSplitLeft = 0; - - if (settings.detailsPage !== "" && !settings.hideButtons) { - columnHeaders.push({ - id: "appbuilder_view_detail", - header: "", - width: 40, - template: (obj, common) => - "
", - css: { "text-align": "center" }, - }); - // columnSplitRight++; - addedColumns.push("appbuilder_view_detail"); - } - - if (settings.trackView !== 0 && accessLevel === 2) { - columnHeaders.push({ - id: "appbuilder_view_track", - header: "", - width: 40, - template: - "
", - css: { "text-align": "center", cursor: "pointer" }, - }); - // columnSplitRight++; - addedColumns.push("appbuilder_view_track"); - } - - if ( - settings.editPage !== "" && - !settings.hideButtons && - accessLevel === 2 - ) { - columnHeaders.push({ - id: "appbuilder_view_edit", - header: "", - width: 40, - template: "
{common.editIcon()}
", - css: { "text-align": "center" }, - }); - // columnSplitRight++; - addedColumns.push("appbuilder_view_edit"); - } - - if (settings.allowDelete && accessLevel === 2) { - columnHeaders.push({ - id: "appbuilder_trash", - header: "", - width: 40, - template: "
{common.trashIcon()}
", - css: { "text-align": "center" }, - }); - // columnSplitRight++; - addedColumns.push("appbuilder_trash"); - } - - // find our last displayed column (that isn't one we added); - let lastCol = null; - - for (let i = columnHeaders.length - 1; i >= 0; i--) { - const col = columnHeaders[i]; - if (!col.hidden && addedColumns.indexOf(col.id) === -1) { - lastCol = col; - break; - } - } - - if (lastCol) { - lastCol.fillspace = true; - lastCol.minWidth = lastCol.width; - lastCol.width = 150; // set a width for last column but by default it will fill the available space or use the minWidth to take up more - } - - $DataTable.refreshColumns(columnHeaders); - - // the addedColumns represent the additional icons that can be added. - this.columnSplitRight = addedColumns.length; - - // the .massUpdate gets added to Left so don't include that in split right: - if (addedColumns.indexOf("appbuilder_select_item") > -1) - this.columnSplitRight -= 1; - // .columnSplitRight can't be < 0 - if (this.columnSplitRight < 0) this.columnSplitRight = 0; - - // freeze columns: - const frozenColumnID = settings.frozenColumnID; - - if (frozenColumnID != "") - $DataTable.define( - "leftSplit", - $DataTable.getColumnIndex(frozenColumnID) + 1 - ); - else $DataTable.define("leftSplit", this.columnSplitLeft); - - this.freezeDeleteColumn(); - $DataTable.refreshColumns(); - // } - } - - /** - * localSettingsSave() - * Persist our current working copy of our GridSettings to localStorage. - * @return {Promise} - */ - async localSettingsSave() { - const ab = this.AB; - const savedLocalSettings = - (await ab.Storage.get(this.keyStorageSettings)) || {}; - const _gridSettings = this._gridSettings; - - savedLocalSettings[this.settingsID()] = _gridSettings[this.settingsID()] - ? _gridSettings[this.settingsID()] - : []; - - for (const item in savedLocalSettings) { - savedLocalSettings[item].forEach((item) => { - // we cannot include field info because of the cicular structure - if (item?.footer?.field) delete item.footer.field; - }); - } - - await ab.Storage.set(this.keyStorageSettings, savedLocalSettings); - } - - /** - * @method localSettings() - * An interface method to handle get/set operations on our local GridSettings - * storage. - * .localStorage() : a getter to return the current value - * .localStorage(value) : a setter to save value as our current value. - * @param {various} value - * the value to set to our settings. - * @return {various} - */ - localSettings(value = null) { - const _gridSettings = this._gridSettings; - - if (value) _gridSettings[this.settingsID()] = value; - else return _gridSettings[this.settingsID()]; - } - - /** - * @method selectRow() - * Select the grid row that correspondes to the provided rowData. - * @param {json} rowData - * A key=>value hash of data that matches an entry in the grid. - * rowData.id should match an existing entry. - */ - selectRow(rowData) { - let id = rowData?.id ?? rowData; - if (this.__timeout_selectRow) { - console.log("Duplicate selectRow():", id); - clearTimeout(this.__timeout_selectRow); - } - this.__timeout_selectRow = setTimeout(() => { - const $DataTable = this.getDataTable(); - if (!$DataTable) return; - - if (!id) $DataTable.unselect(); - else if ($DataTable.exists(id)) { - $DataTable.select(id, false); - $DataTable.showItem(id); - } else $DataTable.select(null, false); - - this.__timeout_selectRow = null; - }, 15); - } - - /** - * @method settingsID() - * return the unique key for this Grid + object combo to store data - * in our localStorage. - * @return {string} - */ - settingsID() { - const CurrentObject = this.datacollection.datasource; - - return `${this.id}-${CurrentObject ? CurrentObject.id : "0"}`; - } - - /** - * @method toggleTab() - * recursively toggle tabs into view once a user chooses a detail/edit view - * to display. - * @param {ABView.id} parentTab - * @param {webix.view} wb - */ - toggleTab(parentTab, wb) { - // find the tab || if we didn't pass and id we may have passed a domNode - const tab = - wb.getTopParentView().queryView({ id: parentTab }) || $$(parentTab); - - if (!tab) return; - - // set the tabbar to to the tab - const tabbar = tab.getParentView().getParentView(); - - if (!tabbar) return; - - // if we have reached the top we won't have a tab - if (tabbar.setValue) tabbar.setValue(parentTab); - - // find if it is in a multiview of a tab - const nextTab = tabbar.queryView({ view: "scrollview" }, "parent"); - - // if so then do this again - if (nextTab) this.toggleTab(nextTab, wb); - } - - toggleUpdateDelete() { - const $DataTable = this.getDataTable(); - - let checkedItems = 0; - - $DataTable.data.each((obj) => { - if ( - typeof obj !== "undefined" && - Object.prototype.hasOwnProperty.call( - obj, - "appbuilder_select_item" - ) && - obj.appbuilder_select_item === 1 - ) - checkedItems++; - }); - - if (checkedItems > 0) this.enableUpdateDelete(); - else this.disableUpdateDelete(); - } - - toolbarDeleteSelected($view) { - const $DataTable = this.getDataTable(); - const CurrentObject = this.datacollection.datasource; - const deleteTasks = []; - - $DataTable.data.each((row) => { - if ( - typeof row !== "undefined" && - // row.hasOwnProperty("appbuilder_select_item") && - Object.prototype.hasOwnProperty.call( - row, - "appbuilder_select_item" - ) && - row.appbuilder_select_item === 1 - ) { - // NOTE: store a fn() to run later. - deleteTasks.push(() => CurrentObject.model().delete(row.id)); - } - }); - - const abWebix = this.AB.Webix; - - if (deleteTasks.length > 0) - abWebix.confirm({ - title: this.label("Delete Multiple Records"), - text: this.label( - "Are you sure you want to delete the selected records?" - ), - callback: async (result) => { - if (result) { - // Now run those functions - await Promise.all(deleteTasks.map((t) => t())); - - // Anything we need to do after we are done. - this.disableUpdateDelete(); - } - }, - }); - else - abWebix.alert({ - title: this.label("No Records Selected"), - text: this.label( - "You need to select at least one record...did you drink your coffee today?" - ), - }); - } - - toolbarFilter($view) { - this.view.filterHelper.showPopup($view); - } - - toolbarSort($view) { - this.PopupSortDataTableComponent.show($view); - } - - toolbarExport($view) { - this.PopupExport.show($view); - } - - toolbarMassUpdate($view) { - this.PopupMassUpdateComponent.show($view); - } - - /** - * @function toolTip() - * - * Retrieve the items toolTip - */ - toolTip(obj, common) { - const CurrentObject = this.datacollection.datasource; - const imageFieldColNames = CurrentObject.imageFields().map( - (f) => f.columnName - ); - - let tip = ""; - - const columnName = common.column.id.replace(" ", ""); - - if (Array.isArray(obj[columnName])) { - obj[columnName].forEach(function (o) { - if (o.text) tip += o.text + "
"; - }); - } else if ( - typeof obj[columnName + "__relation"] !== "undefined" && - typeof obj[columnName] === "number" - ) - tip = obj[columnName + "__relation"].text; - else if (typeof obj[columnName + "__relation"] !== "undefined") { - let relationData = obj[columnName + "__relation"]; - - if (!Array.isArray(relationData)) relationData = [relationData]; - - (relationData || []).forEach(function (o) { - if (o) tip += o.text + "
"; - }); - } else if (imageFieldColNames.indexOf(columnName) !== -1) { - if (!obj[columnName]) { - return ""; - } else { - // TODO: we need to get this URL from the ABFieldImage object! - tip = ``; - } - } else if (common.column.editor === "date") - tip = common.column.format(obj[columnName]); - else if (common.column.editor === "richselect") - CurrentObject.fields().forEach((f) => { - if (f.columnName === columnName) { - if (f.settings.options) { - f.settings.options.forEach((o) => { - if (o.id === obj[columnName]) { - tip = o.text; - } - }); - } - } - }); - else tip = obj[columnName]; - - if (!tip) return ""; - else return tip; - } - - /** - * @function toolTipOnBeforeRender() - * - * Add visibility "hidden" to all tooltips before render so we can move to a new location without the visual jump - */ - toolTipOnBeforeRender(node) { - // var node = $$(ids.tooltip).getNode(); - node.style.visibility = "hidden"; - } - - /** - * @function toolTipOnAfterRender() - * - * If the tooltip is displaying off the screen we want to try to reposition it for a better experience - */ - toolTipOnAfterRender(node) { - // var node = $$(ids.tooltip).getNode(); - if (node.firstChild?.nodeName === "IMG") { - setTimeout(() => { - const imgBottom = parseInt(node.style.top.replace("px", "")) + 500; - const imgRight = parseInt(node.style.left.replace("px", "")) + 500; - - if (imgBottom > window.innerHeight) { - const imgOffsetY = imgBottom - window.innerHeight; - const newTop = - parseInt(node.style.top.replace("px", "")) - imgOffsetY; - node.style.top = newTop + "px"; - } - - if (imgRight > window.innerWidth) { - const imgOffsetX = imgRight - window.innerWidth; - const newLeft = - parseInt(node.style.left.replace("px", "")) - imgOffsetX; - node.style.left = newLeft + "px"; - } - - node.style.visibility = "visible"; - }, 250); - } else node.style.visibility = "visible"; - } - - get isCustomGroup() { - const dc = this.datacollection; - const CurrentObject = dc?.datasource; - const $DataTable = this.getDataTable(); - - return ( - $DataTable?.config?.view === "treetable" && !CurrentObject?.isGroup - ); - } - - populateGroupData() { - if (!this.isCustomGroup) return; - - this.busy(); - - const dc = this.datacollection; - const $DataTable = this.getDataTable(); - - $DataTable.clearAll(); - $DataTable.parse(dc.getData() || []); - - this.grouping(); - this.ready(); - } -} diff --git a/AppBuilder/platform/views/viewComponent/ABViewImageComponent.js b/AppBuilder/platform/views/viewComponent/ABViewImageComponent.js deleted file mode 100644 index 39288cc8..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewImageComponent.js +++ /dev/null @@ -1,51 +0,0 @@ -const ABViewComponent = require("./ABViewComponent").default; - -module.exports = class ABViewImageComponent extends ABViewComponent { - constructor(baseView, idBase, ids) { - super( - baseView, - idBase || `ABViewImage_${baseView.id}`, - Object.assign({ image: "" }, ids) - ); - } - - ui() { - const settings = this.settings; - const _ui = super.ui([ - { - cols: [ - { - id: this.ids.image, - view: "template", - template: "", - height: settings.height, - width: settings.width, - }, - {}, - ], - }, - ]); - - delete _ui.type; - - return _ui; - } - - async init(AB) { - await super.init(AB); - - const $image = $$(this.ids.image); - if (!$image) return; - - const settings = this.settings; - - if (settings.filename) - $image.define( - "template", - `` - ); - else $image.define("template", ""); - - $image.refresh(); - } -}; diff --git a/AppBuilder/platform/views/viewComponent/ABViewLayoutComponent.js b/AppBuilder/platform/views/viewComponent/ABViewLayoutComponent.js deleted file mode 100644 index 451f8cb2..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewLayoutComponent.js +++ /dev/null @@ -1,73 +0,0 @@ -const ABViewComponent = require("./ABViewComponent").default; - -module.exports = class ABViewLayoutComponent extends ABViewComponent { - constructor(baseView, idBase, ids) { - super(baseView, idBase || `ABViewLayout_${baseView.id}`, ids); - - const viewComponents = this.viewComponents ?? {}; // { viewId: viewComponent, ..., viewIdn: viewComponent } - - baseView.views().forEach((v) => { - viewComponents[v.id] = v.component(); - }); - - this.viewComponents = viewComponents; - } - - ui() { - const viewComponents = this.viewComponents; - const uiComponents = Object.keys(viewComponents) - .map((vId) => viewComponents[vId].ui()) - .filter((ui) => ui); - - if (uiComponents.length == 0) { - uiComponents.push({}); - uiComponents.push({ - view: "label", - label: this.label("no content"), - }); - uiComponents.push({}); - } - - const _ui = super.ui([ - { - view: "layout", - cols: uiComponents, - }, - ]); - - delete _ui.type; - - return _ui; - } - - async init(AB, accessLevel) { - await super.init(AB); - - const baseView = this.view; - - // make sure each of our child views get .init() called - baseView.views().forEach((v) => { - const component = this.viewComponents[v.id]; - - // initial sub-component - component?.init(AB, accessLevel); - - // Trigger 'changePage' event to parent - baseView.eventAdd({ - emitter: v, - eventName: "changePage", - listener: (pageId) => { - baseView.changePage(pageId); - }, - }); - }); - } - - onShow() { - // calll .onShow in child components - this.view.views().forEach((v) => { - const component = this.viewComponents[v.id]; - component?.onShow(); - }); - } -}; diff --git a/AppBuilder/platform/views/viewComponent/ABViewListComponent.js b/AppBuilder/platform/views/viewComponent/ABViewListComponent.js deleted file mode 100644 index d5ec3cfe..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewListComponent.js +++ /dev/null @@ -1,52 +0,0 @@ -const ABViewComponent = require("./ABViewComponent").default; - -module.exports = class ABViewListComponent extends ABViewComponent { - constructor(baseView, idBase, ids) { - super( - baseView, - idBase || `ABViewList_${baseView.id}`, - Object.assign({ list: "" }, ids) - ); - } - - ui() { - const settings = this.settings; - const _uiList = { - id: this.ids.list, - view: "dataview", - type: { - width: 1000, - height: 30, - }, - template: (item) => { - const field = this.view.field(); - - if (!field) return ""; - - return field.format(item); - }, - }; - - // set height or autoHeight - if (settings.height !== 0) _uiList.height = settings.height; - else _uiList.autoHeight = true; - - const _ui = super.ui([_uiList]); - - delete _ui.type; - - return _ui; - } - - async init(AB) { - await super.init(AB); - - const dc = this.datacollection; - - if (!dc) return; - - // bind dc to component - dc.bind($$(this.ids.list)); - // $$(ids.list).sync(dv); - } -}; diff --git a/AppBuilder/platform/views/viewComponent/ABViewTabComponent.js b/AppBuilder/platform/views/viewComponent/ABViewTabComponent.js deleted file mode 100644 index 5d7d6f2e..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewTabComponent.js +++ /dev/null @@ -1,577 +0,0 @@ -const ABViewComponent = require("./ABViewComponent").default; - -module.exports = class ABViewTabComponent extends ABViewComponent { - constructor(baseView, idBase, ids) { - super( - baseView, - idBase || `ABViewTab_${baseView.id}`, - Object.assign( - { - tab: "", - - sidebar: "", - expandMenu: "", - collapseMenu: "", - - popupTabManager: "", - popupTabManagerForm: "", - popupTabManagerSaveButton: "", - }, - ids - ) - ); - - this.viewComponents = - this.viewComponents || - baseView - .views((v) => v.getUserAccess()) - .map((v) => { - return { - view: v, - // component: v.component(App) - }; - }); - } - - ui() { - const ids = this.ids; - const baseView = this.view; - const ab = this.AB; - const abWebix = ab.Webix; - - let _ui = null; - - // We are going to make a custom icon using the first letter of a menu item for menu items that don't have an icon - // to do this we need to modify the default template with the method webix recommended form this snippet https://snippet.webix.com/b566d9f8 - abWebix.type(abWebix.ui.tree, { - baseType: "sideBar", // inherit everything else from sidebar type - name: "customIcons", - icon: (obj, common) => { - if (obj.icon.length) - return [ - "", - ].join(""); - - return [ - "", - obj.value.charAt(0).toUpperCase(), - "", - ].join(""); - }, - }); - - const viewComponents = this.viewComponents; - const settings = this.settings; - - if (viewComponents.length > 0) { - if (settings.stackTabs) { - // define your menu items from the view components - const menuItems = viewComponents.map((vc) => { - const view = vc.view; - - return { - id: `${view.id}_menu`, - value: view.label, - icon: view.tabicon ? view.tabicon : "", - }; - }); - - if (menuItems.length) { - // create a menu item for the collapse option to use later - const collapseMenu = { - id: ids.collapseMenu, - value: this.label("Collapse Menu"), - icon: "chevron-circle-left", - }; - - // create a menu item from the expand option to use later - const expandMenu = { - id: ids.expandMenu, - value: this.label("Expand Menu"), - icon: "chevron-circle-right", - hidden: true, - }; - - // find out what the first option is so we can set it later - let selectedItem = `${viewComponents[0].view.id}_menu`; - - const abStorage = ab.Storage; - const sidebar = { - view: "sidebar", - type: "customIcons", // define the sidebar type with the new template created above - id: ids.sidebar, - height: settings.height, - width: settings.sidebarWidth ? settings.sidebarWidth : 0, - scroll: true, - position: settings.sidebarPos ? settings.sidebarPos : "left", - css: settings.darkTheme ? "webix_dark" : "", - data: menuItems.concat(collapseMenu), // add you menu items along with the collapse option to start - on: { - onItemClick: (id) => { - // when a menu item is clicked - if (id === ids.collapseMenu) { - // if it was the collapse menu item - setTimeout(() => { - const $sidebar = $$(ids.sidebar); - - // remove the collapse option from the menu - $sidebar.remove(ids.collapseMenu); - // add the expand option to the menu - $sidebar.add(expandMenu); - // toggle the sidebar state - $sidebar.toggle(); - // we just clicked the collapse...but we don't wanted highlighted - // so highlight the previously selected menu item - $sidebar.select(selectedItem); - // store this state in local storage the user preference is - // remembered next time they see this sidebar - abStorage.set( - `${ids.tab}-state`, - $sidebar.getState() - ); - }, 0); - } else if (id === ids.expandMenu) { - setTimeout(() => { - const $sidebar = $$(ids.sidebar); - - // remove the expand option from the menu - $sidebar.remove(ids.expandMenu); - // add the collapse option to the menu - $sidebar.add(collapseMenu); - // toggle the sidebar state - $sidebar.toggle(); - // we just clicked the collapse...but we don't wanted highlighted - // so highlight the previously selected menu item - $sidebar.select(selectedItem); - // store this state in local storage the user preference is - // remembered next time they see this sidebar - abStorage.set( - `${ids.tab}-state`, - $sidebar.getState() - ); - }, 0); - } else { - // store the selecte menu item just in case someone toggles the menu later - selectedItem = id; - // if the menu item is a regular menu item - // call the onShow with the view id to load the view - - id = id.replace("_menu", ""); - let node = $$(id); - if (node) { - node.show(false, false); - } else { - // How often does this occure? - let msg = `ABViewTabComponent[${this.name}][${this.id}] could not resolve UI panel for provided menu [${selectedItem}].`; - this.AB.notify("developer", msg, {}); - } - // $$(id).show(false, false); - - // onShow(id); - } - }, - onSelectChange: () => { - addDataCy(); - }, - onAfterRender: () => { - addDataCy(); - }, - }, - }; - - const multiview = { - view: "multiview", - id: ids.tab, - keepViews: true, - minWidth: settings.minWidth, - cells: viewComponents.map((view) => { - const tabUi = { - id: view.view.id, - // ui will be loaded when its tab is opened - view: "layout", - rows: [], - }; - - return tabUi; - }), - on: { - onViewChange: (prevId, nextId) => { - this.onShow(nextId); - }, - }, - }; - - const addDataCy = function () { - const $sidebar = $$(ids.sidebar); - - // set ids of controller buttons - const collapseNode = $sidebar?.$view.querySelector( - `[webix_tm_id="${ids.collapseMenu}"]` - ); - - if (collapseNode) - collapseNode.setAttribute( - "data-cy", - `tab-collapseMenu-${ids.collapseMenu}` - ); - - const expandNode = $sidebar?.$view.querySelector( - `[webix_tm_id="${ids.expandMenu}"]` - ); - - if (expandNode) - expandNode.setAttribute( - "data-cy", - `tab-expandMenu-${ids.expandMenu}` - ); - - baseView.views((view) => { - const node = $sidebar?.$view?.querySelector( - `[webix_tm_id="${view.id}_menu"]` - ); - - if (!node) { - return; - } - - node.setAttribute( - "data-cy", - `tab-${view.name.replace(" ", "")}-${view.id}-${ - baseView.id - }` - ); - }); - }; - - let columns = [sidebar, multiview]; - - if (settings.sidebarPos === "right") { - columns = [multiview, sidebar]; - } - - _ui = { - cols: columns, - }; - } else - _ui = { - view: "spacer", - }; - } else { - const cells = baseView - .views((view) => { - const accessLevel = view.getUserAccess(); - - if (accessLevel > 0) { - return view; - } - }) - .map((view) => { - const tabUi = { - id: view.id, - // ui will be loaded when its tab is opened - view: "layout", - rows: [], - }; - - let tabTemplate = ""; - - // tab icon - if (view.tabicon) { - if (settings.iconOnTop) - tabTemplate = [ - "

", - view.label, - "
", - ].join(""); - else - tabTemplate = [ - " ", - view.label, - ].join(""); - } - - // no icon - else tabTemplate = view.label; - - return { - header: tabTemplate, - body: tabUi, - }; - }); - - // if there are cells to display then return a tabview - if (cells.length) { - _ui = { - rows: [ - { - view: "tabview", - id: ids.tab, - minWidth: settings.minWidth, - height: settings.height, - tabbar: { - height: 60, - type: "bottom", - css: settings.darkTheme ? "webix_dark" : "", - on: { - onAfterRender: () => { - baseView.views((view) => { - const node = $$( - ids.tab - )?.$view?.querySelector( - `[button_id="${view.id}"]` - ); - - if (!node) return; - - node.setAttribute( - "data-cy", - `tab ${view.name} ${view.id} ${baseView.id}` - ); - }); - }, - }, - }, - multiview: { - on: { - onViewChange: (prevId, nextId) => { - this.onShow(nextId); - }, - }, - }, - cells: cells, - }, - ], - }; - } - // else we return a spacer - else - _ui = { - view: "spacer", - }; - } - } else - _ui = { - view: "spacer", - }; - - _ui = super.ui([_ui]); - - delete _ui.type; - - return _ui; - } - - async init(AB) { - await super.init(AB); - - const ids = this.ids; - const $tab = $$(ids.tab); - const ab = this.AB; - const abWebix = ab.Webix; - - if ($tab) abWebix.extend($tab, abWebix.ProgressBar); - - const baseView = this.view; - const viewComponents = this.viewComponents; - - viewComponents.forEach((vc) => { - // vc.component.init(AB); - - // Trigger 'changePage' event to parent - this.eventAdd({ - emitter: vc.view, - eventName: "changePage", - listener: (...p) => this.changePage(...p), - }); - }); - - // Trigger 'changeTab' event to parent - this.eventAdd({ - emitter: baseView, - eventName: "changeTab", - listener: (...p) => this.changeTab(...p), - }); - - // initialize the sidebar and figure out if it should be collased or not - const $sidebar = $$(ids.sidebar); - - if (!$sidebar) return; - - const state = await ab.Storage.get(`${ids.tab}-state`); - - if (!state) return; - - // create a menu item for the collapse option to use later - const collapseMenu = { - id: ids.collapseMenu, - value: this.label("Collapse Menu"), - icon: "chevron-circle-left", - }; - - // create a menu item from the expand option to use later - const expandMenu = { - id: ids.expandMenu, - value: this.label("Expand Menu"), - icon: "chevron-circle-right", - hidden: true, - }; - - // this will collapse or expand the sidebar - $sidebar.setState(state); - - const checkCollapseMenu = $sidebar.getItem(ids.collapseMenu) ?? null; - const checkExpandMenu = $sidebar.getItem(ids.expandMenu) ?? null; - - // if the state is collapsed we need to make sure the expand option is available - if (state.collapsed) { - if (checkCollapseMenu && checkExpandMenu) - // $sidebar.remove(ids.collapseMenu); - $sidebar.add(expandMenu); - } else if (checkCollapseMenu && checkExpandMenu) - // $sidebar.remove(ids.collapseMenu); - $sidebar.add(collapseMenu); - } - - changePage(pageId) { - const $tab = $$(this.ids.tab); - - $tab?.blockEvent(); - this.view.changePage(pageId); - $tab?.unblockEvent(); - } - - changeTab(tabViewId) { - const baseView = this.view; - const $tabViewId = $$(tabViewId); - - // switch tab view - this.toggleParent(baseView.parent); - - if (this.settings.stackTabs) - if (!$tabViewId.isVisible()) { - const showIt = setInterval(() => { - if ($tabViewId.isVisible()) clearInterval(showIt); - - $tabViewId.show(false, false); - }, 200); - } else $$(this.ids.tab).setValue(tabViewId); - } - - toggleParent(view) { - const $viewID = $$(view.id); - - if (view.key === "tab" || view.key === "viewcontainer") { - $viewID?.show(false, false); - } - if (view.parent) { - this.toggleParent(view.parent); - } - } - - onShow(viewId) { - const ids = this.ids; - - let defaultViewIsSet = false; - - const $sidebar = $$(ids.sidebar); - - // if no viewId is given, then try to get the currently selected ID - if (!viewId && $sidebar) - viewId = $sidebar.getSelectedId().replace("_menu", ""); - - const baseView = this.view; - const viewComponents = this.viewComponents; - - viewComponents.forEach((vc) => { - // set default view id - const currView = baseView.views((view) => { - return view.id === vc.view.id; - }); - - let accessLevel = 0; - - if (currView.length) accessLevel = currView[0].getUserAccess(); - - // choose the 1st View if we don't have one we are looking for. - if (!viewId && !defaultViewIsSet && accessLevel > 0) { - viewId = vc.view.id; - - defaultViewIsSet = true; - } - - // create view's component once - const $tab = $$(ids.tab); - const settings = this.settings; - - if (!vc?.component && vc?.view?.id === viewId) { - // show loading cursor - if ($tab?.showProgress) $tab.showProgress({ type: "icon" }); - - vc.component = vc.view.component(); - - const $viewID = $$(vc.view.id); - const ab = this.AB; - const abWebix = ab.Webix; - - if (settings.stackTabs) { - // update multiview UI - abWebix.ui( - { - // able to 'scroll' in tab view - id: vc.view.id, - view: "scrollview", - css: "ab-multiview-scrollview", - body: vc.component.ui(), - }, - $viewID - ); - } else { - // update tab UI - abWebix.ui( - { - // able to 'scroll' in tab view - id: vc.view.id, - view: "scrollview", - css: "ab-tabview-scrollview", - body: vc.component.ui(), - }, - $viewID - ); - } - - // for tabs we need to look at the view's accessLevels - accessLevel = vc.view.getUserAccess(); - - vc.component.init(ab, accessLevel); - - // done - setTimeout(() => { - // $$(v.view.id).adjust(); - - $tab?.hideProgress?.(); - // check if tab has a hint - // if (vc?.view?.settings?.hintID) { - // // fetch the steps for the hint - // let hint = ab.hintID(vc.view.settings.hintID); - // hint.createHintUI(); - // } - }, 10); - } - - // show UI - if (vc?.view?.id === viewId && vc?.component?.onShow) - vc.component.onShow(); - - if (settings.stackTabs && vc?.view?.id === viewId) { - $$(viewId)?.show(false, false); - $sidebar?.select(`${viewId}_menu`); - } - }); - } -}; diff --git a/AppBuilder/platform/views/viewComponent/ABViewTextComponent.js b/AppBuilder/platform/views/viewComponent/ABViewTextComponent.js deleted file mode 100644 index 38364803..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewTextComponent.js +++ /dev/null @@ -1,70 +0,0 @@ -const ABViewComponent = require("./ABViewComponent").default; - -module.exports = class ABViewTextComponent extends ABViewComponent { - constructor(baseView, idBase, ids) { - super( - baseView, - idBase || `ABViewText_${baseView.id}`, - Object.assign( - { - text: "", - }, - ids - ) - ); - } - - ui() { - const ids = this.ids; - const settings = this.settings; - - const _uiText = { - id: ids.text, - view: "template", - minHeight: 10, - css: "ab-custom-template", - borderless: true, - }; - - if (settings.height) _uiText.height = settings.height; - else _uiText.autoheight = true; - - const _ui = super.ui([_uiText]); - - delete _ui.type; - - return _ui; - } - - displayText(value) { - const ids = this.ids; - const result = this.view.displayText(value, ids.text); - - const $text = $$(ids.text); - - if (!$text) return; - - $text.define("template", result); - $text.refresh(); - } - - onShow() { - super.onShow(); - - // listen DC events - const dataview = this.datacollection; - const baseView = this.view; - - if (dataview && baseView.parent.key !== "dataview") { - ["changeCursor", "cursorStale"].forEach((key) => { - baseView.eventAdd({ - emitter: dataview, - eventName: key, - listener: (...p) => this.displayText(...p), - }); - }); - } - - this.displayText(); - } -}; From 677ef4575b540a887ae8fcd01f35a675879baa25 Mon Sep 17 00:00:00 2001 From: benthongtiang Date: Fri, 3 Jul 2026 10:47:30 +0700 Subject: [PATCH 2/6] Potential fix for pull request finding 'CodeQL / Unused variable, import, function or class' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .../platform/plugins/included/view_dataview/FNAbviewdataview.js | 1 - 1 file changed, 1 deletion(-) diff --git a/AppBuilder/platform/plugins/included/view_dataview/FNAbviewdataview.js b/AppBuilder/platform/plugins/included/view_dataview/FNAbviewdataview.js index ec75954e..f229ed3a 100644 --- a/AppBuilder/platform/plugins/included/view_dataview/FNAbviewdataview.js +++ b/AppBuilder/platform/plugins/included/view_dataview/FNAbviewdataview.js @@ -8,7 +8,6 @@ export default function FNAbviewdataview(API) { const { AB, ABViewComponentPlugin, - ABViewContainer, ABViewContainerComponent, ABViewPropertyLinkPage, } = API; From efd981c1b6838dd33827276d6d39570a2085b45f Mon Sep 17 00:00:00 2001 From: benthongtiang Date: Fri, 3 Jul 2026 10:47:40 +0700 Subject: [PATCH 3/6] Potential fix for pull request finding 'CodeQL / Unused variable, import, function or class' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .../platform/plugins/included/view_detail/FNAbviewdetail.js | 1 - 1 file changed, 1 deletion(-) diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js index 7340a415..b9288496 100644 --- a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js @@ -1,7 +1,6 @@ import * as dComponents from "./DetailComponents.js"; import FNAbviewdetailComponent from "./viewComponent/FNAbviewdetailComponent.js"; import FNAbviewdetailCore from "./core/ABViewDetailCore.js"; -import FNAbviewdetailItemCore from "./core/ABViewDetailItemCore.js"; export default function FNAbviewdetail(API) { const { From 07bde0f6077d7a5a945d974e2d2d51fc68cd578c Mon Sep 17 00:00:00 2001 From: BenWirachot Date: Fri, 3 Jul 2026 14:01:08 +0700 Subject: [PATCH 4/6] Standardize detail view component access by adding static getters and a centralized test helper. --- .../included/view_detail/FNAbviewdetail.js | 4 ++ .../view_detail/FNAbviewdetailCheckbox.js | 4 ++ .../view_detail/FNAbviewdetailConnect.js | 4 ++ .../view_detail/FNAbviewdetailCustom.js | 4 ++ .../view_detail/FNAbviewdetailImage.js | 4 ++ .../view_detail/FNAbviewdetailItem.js | 4 ++ .../view_detail/FNAbviewdetailText.js | 4 ++ .../view_detail/FNAbviewdetailTree.js | 4 ++ .../views/ABViewDetailCheckbox.test.js | 6 +- .../views/ABViewDetailConnect.test.js | 6 +- .../platform/views/ABViewDetailCustom.test.js | 6 +- .../platform/views/ABViewDetailImage.test.js | 6 +- .../platform/views/ABViewDetailItem.test.js | 6 +- .../platform/views/ABViewDetailText.test.js | 6 +- .../platform/views/ABViewDetailTree.test.js | 6 +- .../ABViewDetailCheckboxComponent.js.test.js | 4 +- .../ABViewDetailConnectComponent.js.test.js | 4 +- .../ABViewDetailTextComponent.test.js | 4 +- test/AppBuilder/platform/views/viewHelper.js | 59 +++++++++++++++++++ 19 files changed, 118 insertions(+), 27 deletions(-) create mode 100644 test/AppBuilder/platform/views/viewHelper.js diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js index b9288496..69223881 100644 --- a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js @@ -88,6 +88,10 @@ export default function FNAbviewdetail(API) { * Return a UI component based upon this view. * @return {obj} UI component */ + static get Component() { + return ABViewDetailComponent; + } + component() { return new ABViewDetailComponent(this); } diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCheckbox.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCheckbox.js index b49ae6cd..8fa7938e 100644 --- a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCheckbox.js +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCheckbox.js @@ -21,6 +21,10 @@ export default function FNAbviewdetailCheckbox({ return "view"; } + static get Component() { + return ABViewDetailCheckboxComponent; + } + component() { return new ABViewDetailCheckboxComponent(this); } diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailConnect.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailConnect.js index 54c1779b..15858d60 100644 --- a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailConnect.js +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailConnect.js @@ -27,6 +27,10 @@ export default function FNAbviewdetailConnect({ this.addPageTool.fromSettings(this.settings); } + static get Component() { + return ABViewDetailConnectComponent; + } + component() { return new ABViewDetailConnectComponent(this); } diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCustom.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCustom.js index 76973264..dd31c209 100644 --- a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCustom.js +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCustom.js @@ -21,6 +21,10 @@ export default function FNAbviewdetailCustom({ return "view"; } + static get Component() { + return ABViewDetailCustomComponent; + } + component() { return new ABViewDetailCustomComponent(this); } diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailImage.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailImage.js index 957f92d7..27518081 100644 --- a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailImage.js +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailImage.js @@ -21,6 +21,10 @@ export default function FNAbviewdetailImage({ return "view"; } + static get Component() { + return ABViewDetailImageComponent; + } + component() { return new ABViewDetailImageComponent(this); } diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailItem.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailItem.js index 25117da4..f53fcb99 100644 --- a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailItem.js +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailItem.js @@ -11,6 +11,10 @@ export default function FNAbviewdetailItem({ ); const ABViewDetailItem = class ABViewDetailItem extends ABViewDetailItemCore { + static get Component() { + return ABViewDetailItemComponent; + } + component() { return new ABViewDetailItemComponent(this); } diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailText.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailText.js index fa3f770b..ec5ad115 100644 --- a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailText.js +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailText.js @@ -20,6 +20,10 @@ export default function FNAbviewdetailText({ return "view"; } + static get Component() { + return ABViewDetailTextComponent; + } + component() { return new ABViewDetailTextComponent(this); } diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailTree.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailTree.js index 3c693aea..afcd761f 100644 --- a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailTree.js +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailTree.js @@ -20,6 +20,10 @@ export default function FNAbviewdetailTree({ return "view"; } + static get Component() { + return ABViewDetailTreeComponent; + } + component() { return new ABViewDetailTreeComponent(this); } diff --git a/test/AppBuilder/platform/views/ABViewDetailCheckbox.test.js b/test/AppBuilder/platform/views/ABViewDetailCheckbox.test.js index 74b1729a..caa3ad0f 100644 --- a/test/AppBuilder/platform/views/ABViewDetailCheckbox.test.js +++ b/test/AppBuilder/platform/views/ABViewDetailCheckbox.test.js @@ -1,10 +1,10 @@ import assert from "assert"; import ABFactory from "../../../../AppBuilder/ABFactory"; -import ABViewDetailCheckbox from "../../../../AppBuilder/platform/views/ABViewDetailCheckbox"; -import ABViewDetailCheckboxComponent from "../../../../AppBuilder/platform/views/viewComponent/ABViewDetailCheckboxComponent"; +import { getDetailClasses } from "./viewHelper"; function getTarget() { const AB = new ABFactory(); + const { ABViewDetailCheckbox } = getDetailClasses(AB); const application = AB.applicationNew({}); return new ABViewDetailCheckbox({}, application); } @@ -15,6 +15,6 @@ describe("ABViewDetailCheckbox widget", function () { const result = target.component(); - assert.equal(true, result instanceof ABViewDetailCheckboxComponent); + assert.equal(true, result instanceof target.constructor.Component); }); }); diff --git a/test/AppBuilder/platform/views/ABViewDetailConnect.test.js b/test/AppBuilder/platform/views/ABViewDetailConnect.test.js index 0fe0cb8b..2f56fd73 100644 --- a/test/AppBuilder/platform/views/ABViewDetailConnect.test.js +++ b/test/AppBuilder/platform/views/ABViewDetailConnect.test.js @@ -1,10 +1,10 @@ import assert from "assert"; import ABFactory from "../../../../AppBuilder/ABFactory"; -import ABViewDetailConnect from "../../../../AppBuilder/platform/views/ABViewDetailConnect"; -import ABViewDetailConnectComponent from "../../../../AppBuilder/platform/views/viewComponent/ABViewDetailConnectComponent"; +import { getDetailClasses } from "./viewHelper"; function getTarget() { const AB = new ABFactory(); + const { ABViewDetailConnect } = getDetailClasses(AB); const application = AB.applicationNew({}); return new ABViewDetailConnect({}, application); } @@ -15,6 +15,6 @@ describe("ABViewDetailConnect widget", function () { const result = target.component(); - assert.equal(true, result instanceof ABViewDetailConnectComponent); + assert.equal(true, result instanceof target.constructor.Component); }); }); diff --git a/test/AppBuilder/platform/views/ABViewDetailCustom.test.js b/test/AppBuilder/platform/views/ABViewDetailCustom.test.js index c86b3ae8..4bac8811 100644 --- a/test/AppBuilder/platform/views/ABViewDetailCustom.test.js +++ b/test/AppBuilder/platform/views/ABViewDetailCustom.test.js @@ -1,10 +1,10 @@ import assert from "assert"; import ABFactory from "../../../../AppBuilder/ABFactory"; -import ABViewDetailCustom from "../../../../AppBuilder/platform/views/ABViewDetailCustom"; -import ABViewDetailCustomComponent from "../../../../AppBuilder/platform/views/viewComponent/ABViewDetailCustomComponent"; +import { getDetailClasses } from "./viewHelper"; function getTarget() { const AB = new ABFactory(); + const { ABViewDetailCustom } = getDetailClasses(AB); const application = AB.applicationNew({}); return new ABViewDetailCustom({}, application); } @@ -15,6 +15,6 @@ describe("ABViewDetailCustom widget", function () { const result = target.component(); - assert.equal(true, result instanceof ABViewDetailCustomComponent); + assert.equal(true, result instanceof target.constructor.Component); }); }); diff --git a/test/AppBuilder/platform/views/ABViewDetailImage.test.js b/test/AppBuilder/platform/views/ABViewDetailImage.test.js index 94cdc306..a5deaabd 100644 --- a/test/AppBuilder/platform/views/ABViewDetailImage.test.js +++ b/test/AppBuilder/platform/views/ABViewDetailImage.test.js @@ -1,10 +1,10 @@ import assert from "assert"; import ABFactory from "../../../../AppBuilder/ABFactory"; -import ABViewDetailImage from "../../../../AppBuilder/platform/views/ABViewDetailImage"; -import ABViewDetailImageComponent from "../../../../AppBuilder/platform/views/viewComponent/ABViewDetailImageComponent"; +import { getDetailClasses } from "./viewHelper"; function getTarget() { const AB = new ABFactory(); + const { ABViewDetailImage } = getDetailClasses(AB); const application = AB.applicationNew({}); return new ABViewDetailImage({}, application); } @@ -15,6 +15,6 @@ describe("ABViewDetailImage widget", function () { const result = target.component(); - assert.equal(true, result instanceof ABViewDetailImageComponent); + assert.equal(true, result instanceof target.constructor.Component); }); }); diff --git a/test/AppBuilder/platform/views/ABViewDetailItem.test.js b/test/AppBuilder/platform/views/ABViewDetailItem.test.js index e9bc2fba..94cc8842 100644 --- a/test/AppBuilder/platform/views/ABViewDetailItem.test.js +++ b/test/AppBuilder/platform/views/ABViewDetailItem.test.js @@ -1,10 +1,10 @@ import assert from "assert"; import ABFactory from "../../../../AppBuilder/ABFactory"; -import ABViewDetailItem from "../../../../AppBuilder/platform/views/ABViewDetailItem"; -import ABViewDetailItemComponent from "../../../../AppBuilder/platform/views/viewComponent/ABViewDetailItemComponent"; +import { getDetailClasses } from "./viewHelper"; function getTarget() { const AB = new ABFactory(); + const { ABViewDetailItem } = getDetailClasses(AB); const application = AB.applicationNew({}); return new ABViewDetailItem({}, application); } @@ -15,6 +15,6 @@ describe("ABViewDetailItem widget", function () { const result = target.component(); - assert.equal(true, result instanceof ABViewDetailItemComponent); + assert.equal(true, result instanceof target.constructor.Component); }); }); diff --git a/test/AppBuilder/platform/views/ABViewDetailText.test.js b/test/AppBuilder/platform/views/ABViewDetailText.test.js index 6442bdf9..b54cd4cd 100644 --- a/test/AppBuilder/platform/views/ABViewDetailText.test.js +++ b/test/AppBuilder/platform/views/ABViewDetailText.test.js @@ -1,10 +1,10 @@ import assert from "assert"; import ABFactory from "../../../../AppBuilder/ABFactory"; -import ABViewDetailText from "../../../../AppBuilder/platform/views/ABViewDetailText"; -import ABViewDetailTextComponent from "../../../../AppBuilder/platform/views/viewComponent/ABViewDetailTextComponent"; +import { getDetailClasses } from "./viewHelper"; function getTarget() { const AB = new ABFactory(); + const { ABViewDetailText } = getDetailClasses(AB); const application = AB.applicationNew({}); return new ABViewDetailText({}, application); } @@ -15,6 +15,6 @@ describe("ABViewDetailText widget", function () { const result = target.component(); - assert.equal(true, result instanceof ABViewDetailTextComponent); + assert.equal(true, result instanceof target.constructor.Component); }); }); diff --git a/test/AppBuilder/platform/views/ABViewDetailTree.test.js b/test/AppBuilder/platform/views/ABViewDetailTree.test.js index 921348d7..cc0adf41 100644 --- a/test/AppBuilder/platform/views/ABViewDetailTree.test.js +++ b/test/AppBuilder/platform/views/ABViewDetailTree.test.js @@ -1,10 +1,10 @@ import assert from "assert"; import ABFactory from "../../../../AppBuilder/ABFactory"; -import ABViewDetailTree from "../../../../AppBuilder/platform/views/ABViewDetailTree"; -import ABViewDetailTreeComponent from "../../../../AppBuilder/platform/views/viewComponent/ABViewDetailTreeComponent"; +import { getDetailClasses } from "./viewHelper"; function getTarget() { const AB = new ABFactory(); + const { ABViewDetailTree } = getDetailClasses(AB); const application = AB.applicationNew({}); return new ABViewDetailTree({}, application); } @@ -15,6 +15,6 @@ describe("ABViewDetailTree widget", function () { const result = target.component(); - assert.equal(true, result instanceof ABViewDetailTreeComponent); + assert.equal(true, result instanceof target.constructor.Component); }); }); diff --git a/test/AppBuilder/platform/views/viewComponent/ABViewDetailCheckboxComponent.js.test.js b/test/AppBuilder/platform/views/viewComponent/ABViewDetailCheckboxComponent.js.test.js index 05cb3355..454f8bf7 100644 --- a/test/AppBuilder/platform/views/viewComponent/ABViewDetailCheckboxComponent.js.test.js +++ b/test/AppBuilder/platform/views/viewComponent/ABViewDetailCheckboxComponent.js.test.js @@ -1,11 +1,11 @@ import assert from "assert"; import sinon from "sinon"; import ABFactory from "../../../../../AppBuilder/ABFactory"; -import ABViewDetailCheckbox from "../../../../../AppBuilder/platform/views/ABViewDetailCheckbox"; -import ABViewDetailCheckboxComponent from "../../../../../AppBuilder/platform/views/viewComponent/ABViewDetailCheckboxComponent"; +import { getDetailClasses } from "../viewHelper"; function getTarget() { const AB = new ABFactory(); + const { ABViewDetailCheckbox, ABViewDetailCheckboxComponent } = getDetailClasses(AB); const application = AB.applicationNew({}); const detailCheckboxView = new ABViewDetailCheckbox({}, application); return new ABViewDetailCheckboxComponent(detailCheckboxView); diff --git a/test/AppBuilder/platform/views/viewComponent/ABViewDetailConnectComponent.js.test.js b/test/AppBuilder/platform/views/viewComponent/ABViewDetailConnectComponent.js.test.js index a564020c..44c4ede0 100644 --- a/test/AppBuilder/platform/views/viewComponent/ABViewDetailConnectComponent.js.test.js +++ b/test/AppBuilder/platform/views/viewComponent/ABViewDetailConnectComponent.js.test.js @@ -1,11 +1,11 @@ import assert from "assert"; import sinon from "sinon"; import ABFactory from "../../../../../AppBuilder/ABFactory"; -import ABViewDetailConnect from "../../../../../AppBuilder/platform/views/ABViewDetailConnect"; -import ABViewDetailConnectComponent from "../../../../../AppBuilder/platform/views/viewComponent/ABViewDetailConnectComponent"; +import { getDetailClasses } from "../viewHelper"; function getTarget() { const AB = new ABFactory(); + const { ABViewDetailConnect, ABViewDetailConnectComponent } = getDetailClasses(AB); const application = AB.applicationNew({}); const detailConnectView = new ABViewDetailConnect({}, application); return new ABViewDetailConnectComponent(detailConnectView); diff --git a/test/AppBuilder/platform/views/viewComponent/ABViewDetailTextComponent.test.js b/test/AppBuilder/platform/views/viewComponent/ABViewDetailTextComponent.test.js index 972fb8d7..1946e38f 100644 --- a/test/AppBuilder/platform/views/viewComponent/ABViewDetailTextComponent.test.js +++ b/test/AppBuilder/platform/views/viewComponent/ABViewDetailTextComponent.test.js @@ -1,10 +1,10 @@ import assert from "assert"; import ABFactory from "../../../../../AppBuilder/ABFactory"; -import ABViewDetailText from "../../../../../AppBuilder/platform/views/ABViewDetailText"; -import ABViewDetailTextComponent from "../../../../../AppBuilder/platform/views/viewComponent/ABViewDetailTextComponent"; +import { getDetailClasses } from "../viewHelper"; function getTarget() { const AB = new ABFactory(); + const { ABViewDetailText, ABViewDetailTextComponent } = getDetailClasses(AB); const application = AB.applicationNew({}); const detailTextView = new ABViewDetailText({}, application); return new ABViewDetailTextComponent(detailTextView); diff --git a/test/AppBuilder/platform/views/viewHelper.js b/test/AppBuilder/platform/views/viewHelper.js new file mode 100644 index 00000000..08550170 --- /dev/null +++ b/test/AppBuilder/platform/views/viewHelper.js @@ -0,0 +1,59 @@ +import FNAbviewdetail from "../../../../AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js"; +import FNAbviewdetailItem from "../../../../AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailItem.js"; +import FNAbviewdetailCheckboxComponent from "../../../../AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailCheckboxComponent.js"; +import FNAbviewdetailConnectComponent from "../../../../AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailConnectComponent.js"; +import FNAbviewdetailCustomComponent from "../../../../AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailCustomComponent.js"; +import FNAbviewdetailImageComponent from "../../../../AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailImageComponent.js"; +import FNAbviewdetailSelectivityComponent from "../../../../AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailSelectivityComponent.js"; +import FNAbviewdetailTextComponent from "../../../../AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTextComponent.js"; +import FNAbviewdetailTreeComponent from "../../../../AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTreeComponent.js"; + +export function getDetailClasses(AB) { + const api = AB.ClassManager.getPluginAPI(); + api.AB = AB; + + // Construct DetailAPI to mimic what FNAbviewdetail receives + const DetailAPI = Object.assign({}, api); + DetailAPI.ABViewWidget = api.ABViewWidgetPlugin; + DetailAPI.ABViewDetailItem = FNAbviewdetailItem(DetailAPI); + DetailAPI.ABViewDetailItemComponent = DetailAPI.ABViewDetailItem.ABViewDetailItemComponent; + + const classes = FNAbviewdetail(DetailAPI); + + const ABViewDetail = classes.find((c) => c.common?.().key === "detail"); + const ABViewDetailCheckbox = classes.find((c) => c.common?.().key === "detailcheckbox"); + const ABViewDetailConnect = classes.find((c) => c.common?.().key === "detailconnect"); + const ABViewDetailCustom = classes.find((c) => c.common?.().key === "detailcustom"); + const ABViewDetailImage = classes.find((c) => c.common?.().key === "detailimage"); + const ABViewDetailSelectivity = classes.find((c) => c.common?.().key === "detailselectivity"); + const ABViewDetailText = classes.find((c) => c.common?.().key === "detailtext"); + const ABViewDetailTree = classes.find((c) => c.common?.().key === "detailtree"); + + const ABViewDetailCheckboxComponent = FNAbviewdetailCheckboxComponent(DetailAPI.ABViewDetailItemComponent); + const ABViewDetailConnectComponent = FNAbviewdetailConnectComponent(DetailAPI.ABViewDetailItemComponent); + const ABViewDetailCustomComponent = FNAbviewdetailCustomComponent(DetailAPI.ABViewDetailItemComponent); + const ABViewDetailImageComponent = FNAbviewdetailImageComponent(DetailAPI.ABViewDetailItemComponent); + const ABViewDetailSelectivityComponent = FNAbviewdetailSelectivityComponent(DetailAPI.ABViewDetailItemComponent); + const ABViewDetailTextComponent = FNAbviewdetailTextComponent(DetailAPI.ABViewDetailItemComponent); + const ABViewDetailTreeComponent = FNAbviewdetailTreeComponent(DetailAPI.ABViewDetailItemComponent); + + return { + ABViewDetail, + ABViewDetailCheckbox, + ABViewDetailConnect, + ABViewDetailCustom, + ABViewDetailImage, + ABViewDetailSelectivity, + ABViewDetailText, + ABViewDetailTree, + ABViewDetailItem: DetailAPI.ABViewDetailItem, + ABViewDetailItemComponent: DetailAPI.ABViewDetailItemComponent, + ABViewDetailCheckboxComponent, + ABViewDetailConnectComponent, + ABViewDetailCustomComponent, + ABViewDetailImageComponent, + ABViewDetailSelectivityComponent, + ABViewDetailTextComponent, + ABViewDetailTreeComponent, + }; +} From 1e93127c050d2ecb4345de40d0fafee844dfe1ff Mon Sep 17 00:00:00 2001 From: BenWirachot Date: Mon, 6 Jul 2026 12:22:06 +0700 Subject: [PATCH 5/6] Update component references from ids.component to ids.detailItem in selectivity and text view components --- .../viewComponent/FNAbviewdetailSelectivityComponent.js | 2 +- .../view_detail/viewComponent/FNAbviewdetailTextComponent.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailSelectivityComponent.js b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailSelectivityComponent.js index 91868fdc..1ba064d0 100644 --- a/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailSelectivityComponent.js +++ b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailSelectivityComponent.js @@ -46,7 +46,7 @@ export default function (ABViewDetailItemComponent) { } getDomSelectivity() { - const elem = $$(this.ids.component); + const elem = $$(this.ids.detailItem); if (!elem) return; diff --git a/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTextComponent.js b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTextComponent.js index 5e27c2fe..126e4a2d 100644 --- a/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTextComponent.js +++ b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTextComponent.js @@ -18,7 +18,7 @@ export default function (ABViewDetailItemComponent) { this.view.parent.id }`; - $$(this.ids.component)?.$view.setAttribute("data-cy", dataCy); + $$(this.ids.detailItem)?.$view.setAttribute("data-cy", dataCy); }, }, }; From e5f2bf0bb4b2ac4716b82eaaceaafecb4fdfb9e0 Mon Sep 17 00:00:00 2001 From: BenWirachot Date: Mon, 6 Jul 2026 15:41:31 +0700 Subject: [PATCH 6/6] Update data-cy attribute selection and wrap in timeout for text component rendering --- .../viewComponent/FNAbviewdetailTextComponent.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTextComponent.js b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTextComponent.js index 126e4a2d..70447d9c 100644 --- a/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTextComponent.js +++ b/AppBuilder/platform/plugins/included/view_detail/viewComponent/FNAbviewdetailTextComponent.js @@ -11,14 +11,14 @@ export default function (ABViewDetailItemComponent) { on: { //Add data-cy attribute for Cypress Testing onAfterRender: () => { - const dataCy = `detail text ${field?.columnName} ${ - field?.id - } ${ - this.view.parentDetailComponent()?.id || + const dataCy = `detail text ${field?.columnName} ${field?.id + } ${this.view.parentDetailComponent()?.id || this.view.parent.id - }`; + }`; - $$(this.ids.detailItem)?.$view.setAttribute("data-cy", dataCy); + setTimeout(() => { + $$(this.ids.component)?.$view?.setAttribute("data-cy", dataCy); + }); }, }, };