diff --git a/AppBuilder/platform/ABClassManager.js b/AppBuilder/platform/ABClassManager.js index f3832316..eaf1ff05 100644 --- a/AppBuilder/platform/ABClassManager.js +++ b/AppBuilder/platform/ABClassManager.js @@ -11,6 +11,10 @@ import ABViewEditorPlugin from "./plugins/ABViewEditorPlugin.js"; // some views need to reference ABViewContainer, import ABViewContainer from "./views/ABViewContainer.js"; +// view property helpers used by plugins +import ABViewPropertyFilterData from "./views/viewProperties/ABViewPropertyFilterData"; +import ABViewPropertyLinkPage from "./views/viewProperties/ABViewPropertyLinkPage"; + // MIGRATION: ABViewManager is depreciated. Use ABClassManager instead. import ABViewManager from "./ABViewManager.js"; @@ -62,6 +66,8 @@ export function getPluginAPI() { ABViewPropertiesPlugin, ABViewEditorPlugin, ABViewContainer, + ABViewPropertyFilterData, + ABViewPropertyLinkPage, // ABFieldPlugin, // ABViewPlugin, }; diff --git a/AppBuilder/platform/plugins/included/index.js b/AppBuilder/platform/plugins/included/index.js index 17d382b3..a690bc22 100644 --- a/AppBuilder/platform/plugins/included/index.js +++ b/AppBuilder/platform/plugins/included/index.js @@ -6,6 +6,7 @@ import viewText from "./view_text/FNAbviewtext.js"; import viewImage from "./view_image/FNAbviewimage.js"; import viewDataSelect from "./view_data-select/FNAbviewdataselect.js"; import viewPdfImporter from "./view_pdfImporter/FNAbviewpdfimporter.js"; +import viewCarousel from "./view_carousel/FNAbviewcarousel.js"; const AllPlugins = [ viewTab, @@ -15,6 +16,7 @@ const AllPlugins = [ viewImage, viewDataSelect, viewPdfImporter, + viewCarousel, ]; export default { diff --git a/AppBuilder/platform/plugins/included/view_carousel/FNAbviewcarousel.js b/AppBuilder/platform/plugins/included/view_carousel/FNAbviewcarousel.js new file mode 100644 index 00000000..404504f4 --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_carousel/FNAbviewcarousel.js @@ -0,0 +1,190 @@ +import FNAbviewcarouselComponent from "./FNAbviewcarouselComponent.js"; + +// FNAbviewcarousel Web +// A web side import for an ABView. +// +export default function FNAbviewcarousel({ + /*AB,*/ + ABViewWidgetPlugin, + ABViewComponentPlugin, + ABViewPropertyFilterData, + ABViewPropertyLinkPage, +}) { + const ABAbviewcarouselComponent = FNAbviewcarouselComponent({ + ABViewComponentPlugin, + }); + + const ABViewCarouselPropertyComponentDefaults = { + dataviewID: null, // uuid of ABDatacollection + field: null, // uuid + + width: 460, + height: 275, + showLabel: true, + hideItem: false, + hideButton: false, + navigationType: "corner", // "corner" || "side" + filterByCursor: false, + + detailsPage: null, // uuid + detailsTab: null, // uuid + editPage: null, // uuid + editTab: null, // uuid + }; + + const ABViewDefaults = { + key: "carousel", // {string} unique key for this view + icon: "clone", // {string} fa-[icon] reference for this view + labelKey: "Carousel", // {string} the multilingual label key for the class label + }; + + function parseIntOrDefault(_this, key) { + if (typeof _this.settings[key] != "undefined") { + _this.settings[key] = parseInt(_this.settings[key]); + } else { + _this.settings[key] = ABViewCarouselPropertyComponentDefaults[key]; + } + } + + function parseOrDefault(_this, key) { + try { + _this.settings[key] = JSON.parse(_this.settings[key]); + } catch (e) { + _this.settings[key] = ABViewCarouselPropertyComponentDefaults[key]; + } + } + + class ABViewCarouselCore extends ABViewWidgetPlugin { + constructor(values, application, parent, defaultValues) { + super(values, application, parent, defaultValues || ABViewDefaults); + } + + static common() { + return ABViewDefaults; + } + + static defaultValues() { + return ABViewCarouselPropertyComponentDefaults; + } + + /// + /// 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 + parseIntOrDefault(this, "width"); + parseIntOrDefault(this, "height"); + + // json + parseOrDefault(this, "showLabel"); + parseOrDefault(this, "hideItem"); + parseOrDefault(this, "hideButton"); + + this.settings.navigationType = + this.settings.navigationType || + ABViewCarouselPropertyComponentDefaults.navigationType; + + parseOrDefault(this, "filterByCursor"); + } + + /** + * @method componentList + * return the list of components available on this view to display in the editor. + */ + componentList() { + return []; + } + + get imageField() { + let dc = this.datacollection; + if (!dc) return null; + + let obj = dc.datasource; + if (!obj) return null; + + return obj.fieldByID(this.settings.field); + } + } + + return class ABViewCarousel extends ABViewCarouselCore { + /** + * @method getPluginKey + * return the plugin key for this view. + * @return {string} plugin key + */ + static getPluginKey() { + return this.common().key; + } + + /** + * @method component() + * return a UI component based upon this view. + * @return {obj} UI component + */ + component(parentId) { + return new ABAbviewcarouselComponent(this, parentId); + } + + 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); + } + + 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/plugins/included/view_carousel/FNAbviewcarouselComponent.js b/AppBuilder/platform/plugins/included/view_carousel/FNAbviewcarouselComponent.js new file mode 100644 index 00000000..38327066 --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_carousel/FNAbviewcarouselComponent.js @@ -0,0 +1,536 @@ +export default function FNAbviewcarouselComponent({ + /*AB,*/ + ABViewComponentPlugin, +}) { + return class ABAbviewcarouselComponent extends ABViewComponentPlugin { + 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_doFilter); + } + + myTemplate(row) { + if (row?.src) { + const settings = this.settings; + + return `