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..f229ed3a 100644
--- a/AppBuilder/platform/plugins/included/view_dataview/FNAbviewdataview.js
+++ b/AppBuilder/platform/plugins/included/view_dataview/FNAbviewdataview.js
@@ -4,13 +4,14 @@ 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,
+ ABViewContainerComponent,
+ ABViewPropertyLinkPage,
+ } = API;
+
const ABAbviewdataviewComponent = FNAbviewdataviewComponent({
AB,
ABViewComponentPlugin,
@@ -32,10 +33,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..69223881 100644
--- a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js
+++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js
@@ -1,86 +1,54 @@
-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";
+
+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 +75,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;
@@ -120,6 +88,10 @@ export default function FNAbviewdetail({
* Return a UI component based upon this view.
* @return {obj} UI component
*/
+ static get Component() {
+ return ABViewDetailComponent;
+ }
+
component() {
return new ABViewDetailComponent(this);
}
@@ -135,4 +107,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..8fa7938e
--- /dev/null
+++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCheckbox.js
@@ -0,0 +1,32 @@
+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";
+ }
+
+ 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
new file mode 100644
index 00000000..15858d60
--- /dev/null
+++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailConnect.js
@@ -0,0 +1,45 @@
+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);
+ }
+
+ static get Component() {
+ return ABViewDetailConnectComponent;
+ }
+
+ 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..dd31c209
--- /dev/null
+++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailCustom.js
@@ -0,0 +1,32 @@
+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";
+ }
+
+ 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
new file mode 100644
index 00000000..27518081
--- /dev/null
+++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailImage.js
@@ -0,0 +1,32 @@
+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";
+ }
+
+ 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
new file mode 100644
index 00000000..f53fcb99
--- /dev/null
+++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailItem.js
@@ -0,0 +1,27 @@
+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 {
+ static get Component() {
+ return ABViewDetailItemComponent;
+ }
+
+ 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..ec5ad115
--- /dev/null
+++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailText.js
@@ -0,0 +1,31 @@
+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";
+ }
+
+ 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
new file mode 100644
index 00000000..afcd761f
--- /dev/null
+++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailTree.js
@@ -0,0 +1,31 @@
+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";
+ }
+
+ static get Component() {
+ return ABViewDetailTreeComponent;
+ }
+
+ 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..1ba064d0
--- /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.detailItem);
+
+ 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..70447d9c
--- /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
+ }`;
+
+ setTimeout(() => {
+ $$(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 `
-
-

- ${
- settings.showLabel
- ? `
${
- row.label || ""
- }
`
- : ""
- }
-
- ${
- settings.detailsPage || settings.detailsTab
- ? ``
- : ""
- }
- ${
- settings.editPage || settings.editTab
- ? ``
- : ""
- }
-
-
-
-
-
-
-
-
`;
- }
- // 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 [
- "",
- ].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();
- }
-};
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,
+ };
+}