diff --git a/AppBuilder/core b/AppBuilder/core index 033d062a..c66267ca 160000 --- a/AppBuilder/core +++ b/AppBuilder/core @@ -1 +1 @@ -Subproject commit 033d062ac13b1b720955016e10fe146d0523c759 +Subproject commit c66267ca1afa394d1751a7f77a4eb9b0781f8464 diff --git a/AppBuilder/platform/plugins/included/index.js b/AppBuilder/platform/plugins/included/index.js index 1d707c93..e7a5fee0 100644 --- a/AppBuilder/platform/plugins/included/index.js +++ b/AppBuilder/platform/plugins/included/index.js @@ -1,7 +1,8 @@ import viewList from "./view_list/FNAbviewlist.js"; import viewTab from "./view_tab/FNAbviewtab.js"; +import viewDetail from "./view_detail/FNAbviewdetail.js"; -const AllPlugins = [viewTab, viewList]; +const AllPlugins = [viewTab, viewList, viewDetail]; export default { load: (AB) => { diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js new file mode 100644 index 00000000..6824168d --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetail.js @@ -0,0 +1,138 @@ +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, + ABViewComponentPlugin, +}) { + const ABViewDetailComponent = FNAbviewdetailComponent({ + ABViewComponentPlugin, + }); + + const ABViewDetailDefaults = { + key: "detail", + icon: "file-text-o", + labelKey: "Detail(plugin)", + }; + + const ABViewDetailPropertyComponentDefaults = { + dataviewID: null, + showLabel: true, + labelPosition: "left", + labelWidth: 120, + height: 0, + }; + + 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 + ); + } + + static getPluginType() { + return "view"; + } + + static getPluginKey() { + return this.common().key; + } + + static common() { + return ABViewDetailDefaults; + } + + static defaultValues() { + return ABViewDetailPropertyComponentDefaults; + } + + /** + * @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 + ); + } + + /** + * @method componentList + * Return the list of components available on this view to display in the editor. + */ + componentList() { + const viewsToAllow = ["label", "text"]; + const allComponents = this.application.viewAll(); + return allComponents.filter((c) => + viewsToAllow.includes(c.common().key) + ); + } + + addFieldToDetail(field, yPosition) { + if (field == null) return; + + const newView = field + .detailComponent() + .newInstance(this.application, this); + if (newView == null) return; + + newView.settings = newView.settings ?? {}; + newView.settings.fieldId = field.id; + newView.settings.labelWidth = + this.settings.labelWidth || + ABViewDetailPropertyComponentDefaults.labelWidth; + newView.settings.alias = field.alias; + newView.position.y = yPosition; + + this._views.push(newView); + return newView; + } + + /** + * @method component() + * Return a UI component based upon this view. + * @return {obj} UI component + */ + component() { + return new ABViewDetailComponent(this); + } + + warningsEval() { + super.warningsEval(); + + const DC = this.datacollection; + if (!DC) { + this.warningsMessage( + `can't resolve it's datacollection[${this.settings.dataviewID}]` + ); + } + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailComponent.js b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailComponent.js new file mode 100644 index 00000000..ca24a0b9 --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_detail/FNAbviewdetailComponent.js @@ -0,0 +1,51 @@ +export default function FNAbviewdetailComponent({ + /*AB,*/ + ABViewComponentPlugin, +}) { + return class ABAbviewdetailComponent extends ABViewComponentPlugin { + constructor(baseView, idBase, ids) { + super( + baseView, + idBase || `ABViewDetail_${baseView.id}`, + Object.assign({ detail: "" }, ids) + ); + } + + ui() { + const settings = this.settings; + const _uiDetail = { + id: this.ids.detail, + view: "dataview", + type: { + width: 1000, + height: 30, + }, + template: (item) => { + if (!item) return ""; + return JSON.stringify(item); + }, + }; + + // set height or autoHeight + if (settings.height !== 0) _uiDetail.height = settings.height; + else _uiDetail.autoHeight = true; + + const _ui = super.ui([_uiDetail]); + + 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.detail)); + } + }; +} diff --git a/test/AppBuilder/platform/views/ABViewDetail.test.js b/test/AppBuilder/platform/views/ABViewDetail.test.js index a3aed430..3fa4ed7d 100644 --- a/test/AppBuilder/platform/views/ABViewDetail.test.js +++ b/test/AppBuilder/platform/views/ABViewDetail.test.js @@ -1,20 +1,47 @@ import assert from "assert"; import ABFactory from "../../../../AppBuilder/ABFactory"; -import ABViewDetail from "../../../../AppBuilder/platform/views/ABViewDetail"; -import ABViewDetailComponent from "../../../../AppBuilder/platform/views/viewComponent/ABViewDetailComponent"; +import ABViewContainer from "../../../../AppBuilder/platform/views/ABViewContainer"; +import ABViewComponent from "../../../../AppBuilder/platform/views/viewComponent/ABViewComponent"; -function getTarget() { - const AB = new ABFactory(); - const application = AB.applicationNew({}); - return new ABViewDetail({}, application); -} +describe("ABViewDetail plugin", function () { -describe("ABViewDetail widget", function () { - it(".component - should return a instance of ABViewDetailComponent", function () { - const target = getTarget(); + let AB; + let application; + let viewDetail; - const result = target.component(); + before(function () { + AB = new ABFactory(); + // Only load plugins so "detail" is registered; skip full init() to avoid Network/socket.io in test env. + AB.pluginLocalLoad(); + application = AB.applicationNew({}); + viewDetail = application.viewNew({ key: "detail" }, application); + }); + + it("can pull a view from ABFactory given { key: 'detail' } values", function () { + assert.ok(viewDetail, "viewNew({ key: 'detail' }) should return a view"); + }); + + it("the resulting object is a type of ABViewContainer class", function () { + assert.ok( + viewDetail instanceof ABViewContainer, + "Detail view should extend ABViewContainer" + ); + }); + + it("the object has .component() method", function () { + assert.strictEqual( + typeof viewDetail.component, + "function", + "Detail view should have a .component() method" + ); + }); - assert.equal(true, result instanceof ABViewDetailComponent); + it(".component() returns an object of type ABViewComponent class", function () { + const result = viewDetail.component(); + assert.ok( + result instanceof ABViewComponent, + ".component() should return an instance of ABViewComponent" + ); }); + });