From 99dabe735d38dcbfe3b90e0bb49291913283b650 Mon Sep 17 00:00:00 2001 From: roguisharcanetrickster Date: Mon, 27 Apr 2026 16:20:53 +0700 Subject: [PATCH 1/2] move_kanban_files Rename FNAbviewkanban.js to FNABViewKanban.js This is the only way to rename files now? Rename FNAbviewKanbanComponent.js to FNABViewKanbanComponent.js This is the only way to rename files now? Rename FNAbviewKanbanForm.js to FNABViewKanbanForm.js This is the only way to rename files now? This is the only way to rename files now? This is the only way to rename files now? --- AppBuilder/core | 2 +- AppBuilder/platform/plugins/included/index.js | 8 +- .../included/view_kanban/FNABViewKanban.js | 136 ++++ .../view_kanban/FNABViewKanbanComponent.js | 585 ++++++++++++++++++ .../view_kanban/FNABViewKanbanForm.js | 297 +++++++++ .../FNABViewKanbanFormSidePanel.js | 201 ++++++ AppBuilder/platform/views/ABViewKanban.js | 36 -- .../views/ABViewKanbanFormSidePanel.js | 256 -------- .../viewComponent/ABViewKanbanComponent.js | 577 ----------------- 9 files changed, 1225 insertions(+), 873 deletions(-) create mode 100644 AppBuilder/platform/plugins/included/view_kanban/FNABViewKanban.js create mode 100644 AppBuilder/platform/plugins/included/view_kanban/FNABViewKanbanComponent.js create mode 100644 AppBuilder/platform/plugins/included/view_kanban/FNABViewKanbanForm.js create mode 100644 AppBuilder/platform/plugins/included/view_kanban/FNABViewKanbanFormSidePanel.js delete mode 100644 AppBuilder/platform/views/ABViewKanban.js delete mode 100644 AppBuilder/platform/views/ABViewKanbanFormSidePanel.js delete mode 100644 AppBuilder/platform/views/viewComponent/ABViewKanbanComponent.js diff --git a/AppBuilder/core b/AppBuilder/core index 692e247a..e3784f40 160000 --- a/AppBuilder/core +++ b/AppBuilder/core @@ -1 +1 @@ -Subproject commit 692e247a484ef0ae1275b6889a2ab5b417747b59 +Subproject commit e3784f40e6e78211ea962470d16c7e638c91760b diff --git a/AppBuilder/platform/plugins/included/index.js b/AppBuilder/platform/plugins/included/index.js index bd2fbc35..01cf8e1c 100644 --- a/AppBuilder/platform/plugins/included/index.js +++ b/AppBuilder/platform/plugins/included/index.js @@ -1,11 +1,12 @@ -import viewDataview from "./view_dataview/FNAbviewdataview.js"; import viewCarousel from "./view_carousel/FNAbviewcarousel.js"; import viewComment from "./view_comment/FNAbviewcomment.js"; import viewCsvExporter from "./view_csvExporter/FNAbviewcsvexporter.js"; import viewCsvImporter from "./view_csvImporter/FNAbviewcsvimporter.js"; import viewDataSelect from "./view_data-select/FNAbviewdataselect.js"; +import viewDataview from "./view_dataview/FNAbviewdataview.js"; import viewDetail from "./view_detail/FNAbviewdetail.js"; import viewImage from "./view_image/FNAbviewimage.js"; +import viewKanban from "./view_kanban/FNABViewKanban.js"; import viewLabel from "./view_label/FNAbviewlabel.js"; import viewLayout from "./view_layout/FNAbviewlayout.js"; import viewList from "./view_list/FNAbviewlist.js"; @@ -18,17 +19,18 @@ const AllPlugins = [ viewComment, viewCsvExporter, viewCsvImporter, - viewCsvImporter, viewDataSelect, + viewDataview, viewDetail, viewImage, + viewKanban, viewLabel, viewLayout, viewList, viewPdfImporter, viewTab, viewText, -, viewDataview]; +]; export default { load: (AB) => { diff --git a/AppBuilder/platform/plugins/included/view_kanban/FNABViewKanban.js b/AppBuilder/platform/plugins/included/view_kanban/FNABViewKanban.js new file mode 100644 index 00000000..c599d161 --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_kanban/FNABViewKanban.js @@ -0,0 +1,136 @@ +import FNABViewKanbanComponent from "./FNABViewKanbanComponent.js"; +import FNABViewKanbanDetachedFormSave from "./FNABViewKanbanForm.js"; +import FNABViewKanbanFormSidePanel from "./FNABViewKanbanFormSidePanel.js"; + +// FNABViewKanban Web +// A web side import for an ABView. +// +export default function FNABViewKanban({ + AB, + ABViewWidgetPlugin, + ABViewComponentPlugin, + ABViewPropertyLinkPage, + ABViewPlugin, +}) { + const ABViewKanbanDetachedFormSave = FNABViewKanbanDetachedFormSave({ + AB, + ABViewPlugin, + ABViewComponentPlugin, + }); + const KanbanFormSidePanel = FNABViewKanbanFormSidePanel({ + ABViewComponentPlugin, + ABViewKanbanDetachedFormSave, + }); + const ABViewKanbanComponent = FNABViewKanbanComponent({ + AB, + ABViewComponentPlugin, + FNABViewKanbanFormSidePanel: KanbanFormSidePanel, + }); + + const ABViewKanbanPropertyComponentDefaults = { + dataviewID: null, // uuid ABDataCollection; DC resolves ABObject + editFields: [], // ABField.id[] fields shown in editor + verticalGroupingField: "", // ABField.id vertical lanes + horizontalGroupingField: "", // ABField.id optional horizontal grouping + ownerField: "", // ABFieldUser.id card owner + template: "", // json ABViewText card body; placeholders {field.id} + }; + + const ABViewDefaults = { + key: "kanban", // {string} unique view key + icon: "columns", // {string} font-awesome (no fa- prefix) + labelKey: "Kanban", // {string} multilingual label key → L(labelKey) + }; + + class ABViewKanbanCore extends ABViewWidgetPlugin { + constructor(values, application, parent, defaultValues) { + super(values, application, parent, defaultValues || ABViewDefaults); + } + + /// + /// Instance Methods + /// + + /** + * @method componentList + * return the list of components available on this view to display in the editor. + */ + componentList() { + return []; + } + + fromValues(values) { + super.fromValues(values); + + // set a default .template value + if (!this.settings.template) { + this.settings.template = { id: `${this.id}_template`, key: "text" }; + this.settings.template.text = this.settings.textTemplate; + } + + this.TextTemplate = AB.viewNewDetatched(this.settings.template); + } + + toObj() { + var obj = super.toObj(); + obj.settings.template = this.TextTemplate.toObj(); + // NOTE: this corrects the initial save where this.id == undefined + // all the rest will set the .id correctly. + obj.settings.template.id = `${this.id}_template`; + return obj; + } + + static common() { + return ABViewDefaults; + } + + static defaultValues() { + return ABViewKanbanPropertyComponentDefaults; + } + } + + return class ABViewKanban extends ABViewKanbanCore { + /** + * @method getPluginKey + * return the plugin key for this view. + * @return {string} plugin key + */ + static getPluginKey() { + return this.common().key; + } + get linkPageHelper() { + if (this.__linkPageHelper == null) + this.__linkPageHelper = new ABViewPropertyLinkPage(); + + return this.__linkPageHelper; + } + + /** + * @method component() + * return a UI component based upon this view. + * @return {obj} UI component + */ + component(parentId) { + return new ABViewKanbanComponent(this, parentId); + } + + // + // Editor Related + // + + get linkPageHelper() { + return (this.__linkPageHelper = + this.__linkPageHelper || new ABViewPropertyLinkPage()); + } + + 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/plugins/included/view_kanban/FNABViewKanbanComponent.js b/AppBuilder/platform/plugins/included/view_kanban/FNABViewKanbanComponent.js new file mode 100644 index 00000000..d0a78b2c --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_kanban/FNABViewKanbanComponent.js @@ -0,0 +1,585 @@ +export default function FNABViewKanbanComponent({ + AB, + ABViewComponentPlugin, + FNABViewKanbanFormSidePanel, +}) { + return class ABViewKanbanComponent extends ABViewComponentPlugin { + constructor(baseView, idBase, ids) { + super( + baseView, + idBase || `ABViewKanban_${baseView.id}`, + Object.assign( + { + kanbanView: "", + + kanban: "", + resizer: "", + formSidePanel: "", + }, + ids + ) + ); + + this.FormSide = new FNABViewKanbanFormSidePanel( + this, + this.ids.formSidePanel, + this.settings.editFields + ); + + this.CurrentVerticalField = null; + this.CurrentHorizontalField = null; + this.CurrentOwnerField = null; + + this.TextTemplate = baseView.TextTemplate; + + this._updatingOwnerRowId = null; + this._ABFieldConnect = null; + this._ABFieldUser = null; + this._ABFieldList = null; + } + + get ABFieldConnect() { + return (this._ABFieldConnect = + this._ABFieldConnect || + AB.Class.ABFieldManager.fieldByKey("connectObject")); + } + + get ABFieldUser() { + return (this._ABFieldUser = + this._ABFieldUser || AB.Class.ABFieldManager.fieldByKey("user")); + } + + get ABFieldList() { + return (this._ABFieldList = + this._ABFieldList || AB.Class.ABFieldManager.fieldByKey("list")); + } + + ui() { + const ids = this.ids; + const baseView = this.view; + const self = this; + this.linkPage = baseView.linkPageHelper.component(); + + const _ui = super.ui([ + { + id: ids.kanbanView, + cols: [ + { + id: ids.kanban, + view: "kanban", + cols: [], + userList: { + view: "menu", + // yCount: 8, + // scroll: false, + template: ' #value#', + width: 150, + on: { + onSelectChange: function () { + // get this row id from onAvatarClick event + if (!self._updatingOwnerRowId) return; + + const userId = this.getSelectedId(false); + if (!userId) return; + + self.updateOwner(self._updatingOwnerRowId, userId); + }, + }, + }, + editor: false, // we use side bar + users: [], + tags: [], + data: [], + on: { + onListAfterSelect: (itemId, list) => { + this.CurrentDatacollection?.setCursor(itemId); + this.emit("select", itemId); + + // link pages events + const editPage = this.settings.editPage; + if (editPage) + this.linkPage.changePage(editPage, itemId); + + const detailsPage = this.settings.detailsPage; + if (detailsPage) + this.linkPage.changePage(detailsPage, itemId); + }, + onAfterStatusChange: (rowId, status /*, list */) => { + this.updateStatus(rowId, status); + }, + onAvatarClick: (rowId /*, ev, node, list */) => { + // keep this row id for update owner data in .userList + this._updatingOwnerRowId = rowId; + }, + }, + }, + { + id: ids.resizer, + view: "resizer", + css: "bg_gray", + width: 11, + hidden: true, + }, + this.FormSide.ui(), + ], + }, + ]); + + delete _ui.type; + + return _ui; + } + + async init(AB) { + await super.init(AB); + + const abWebix = AB.Webix; + const baseView = this.view; + + if (this.$kb) abWebix.extend(this.$kb, abWebix.ProgressBar); + + this.FormSide.init(AB); + this.FormSide.on("add", (newVals) => { + this.saveData(newVals); + }); + this.FormSide.on("update", (updateVals) => { + this.saveData(updateVals); + }); + + let dc = baseView.datacollection; + if (dc) this.datacollectionLoad(dc); + + this.linkPage.init({ + view: baseView, + datacollection: dc, + }); + + this.show(); + } + + get $kb() { + return (this._kb = this._kb || $$(this.ids.kanban)); + } + + kanbanListTemplate() { + return { + icons: [ + // { icon: "mdi mdi-comment", show: function (obj) { return !!obj.comments }, template: "#comments.length#" }, + { + icon: "fa fa-trash-o", + click: (rowId /*, e */) => { + this.removeCard(rowId); + }, + }, + ], + // avatar template + templateAvatar: (obj) => { + if ( + this.CurrentOwnerField && + obj[this.CurrentOwnerField.columnName] + ) + return this.CurrentOwnerField.format(obj); + else return ""; + }, + // template for item body + // show item image and text + templateBody: (data) => { + // if (!this.settings.template) + if (!this.TextTemplate.text) + return this.CurrentObject?.displayData(data); + + // return our default text template + return this.TextTemplate.displayText(data); + }, + }; + } + + /** + * @function hide() + * + * hide this component. + */ + hide() { + $$(this.ids.kanbanView)?.hide(); + } + + /** + * @function show() + * Show this component. + */ + async show() { + const ids = this.ids; + + $$(ids.kanbanView)?.show(); + + this.FormSide.hide(); + + $$(ids.resizer)?.hide(); + + var CurrentObject = this.CurrentObject; + if (!CurrentObject) { + CurrentObject = this.datacollection?.datasource; + } + if (!CurrentObject) return; + + // Get vertical grouping field and populate to kanban list + // NOTE: this field should be the select list type + const CurrentVerticalField = CurrentObject.fieldByID( + this.settings.verticalGroupingField + ); + if (!CurrentVerticalField) return; + + this.CurrentVerticalField = CurrentVerticalField; + + let horizontalOptions = []; + + const CurrentHorizontalField = CurrentObject.fieldByID( + this.settings.horizontalGroupingField + ); + + this.CurrentHorizontalField = CurrentHorizontalField; + + if ( + CurrentHorizontalField && + CurrentHorizontalField instanceof this.ABFieldConnect + ) + // Pull horizontal options + horizontalOptions = await CurrentHorizontalField.getOptions(); + + // Option format - { id: "1543563751920", text: "Normal", hex: "#4CAF50" } + const verticalOptions = (CurrentVerticalField.settings.options || []).map( + (opt) => { + // Vertical & Horizontal fields + if (CurrentVerticalField && CurrentHorizontalField) { + let rows = [], + // [{ + // id: '', + // text: '' + // }] + horizontalVals = []; + + // pull options of the Horizontal field + if (CurrentHorizontalField instanceof this.ABFieldList) { + // make a copy of the settings. + horizontalVals = ( + CurrentHorizontalField.settings.options || [] + ).map((o) => o); + } else if (CurrentHorizontalField instanceof this.ABFieldUser) { + horizontalVals = CurrentHorizontalField.getUsers().map( + (u) => { + return { + id: u.id, + text: u.text || u.value, + }; + } + ); + } else if (CurrentHorizontalField instanceof this.ABFieldConnect) + horizontalVals = horizontalOptions.map(({ id, text }) => ({ + id, + text, + })); + + horizontalVals.push({ + id: null, + text: this.label("Other"), + }); + + horizontalVals.forEach((val) => { + const statusOps = {}; + + statusOps[CurrentVerticalField.columnName] = opt.id; + statusOps[CurrentHorizontalField.columnName] = val.id; + + // Header + rows.push({ + template: val.text, + height: 20, + css: "progress_header", + }); + + // Kanban list + rows.push({ + view: "kanbanlist", + status: statusOps, + type: this.kanbanListTemplate(), + }); + }); + + return { + header: opt.text, + body: { + margin: 0, + rows: rows, + }, + }; + } + // Vertical field only + else if (CurrentVerticalField) { + const statusOps = {}; + + statusOps[CurrentVerticalField.columnName] = opt.id; + + return { + header: opt.text, + body: { + view: "kanbanlist", + status: statusOps, + type: this.kanbanListTemplate(), + }, + }; + } + } + ); + + const ab = AB; + const abWebix = ab.Webix; + + // Rebuild kanban that contains options + // NOTE: webix kanban does not support dynamic vertical list + abWebix.ui(verticalOptions, $$(ids.kanban)); + $$(ids.kanban).reconstruct(); + + // Owner field + const CurrentOwnerField = CurrentObject.fieldByID( + this.settings.ownerField + ); + + this.CurrentOwnerField = CurrentOwnerField; + + if (CurrentOwnerField) { + const $menuUser = $$(ids.kanban).getUserList(); + + $menuUser.clearAll(); + + if (CurrentOwnerField instanceof this.ABFieldUser) { + const users = ab.Account.userList().map((u) => { + return { + id: u.username, + value: u.username, + }; + }); + + $menuUser.parse(users); + } else if (CurrentOwnerField instanceof this.ABFieldConnect) { + const options = await CurrentOwnerField.getOptions(); + + try { + $menuUser.parse( + options.map((opt) => { + return { + id: opt.id, + value: opt.text, + }; + }) + ); + } catch (e) { + // TODO: remove this. Trying to catch a random webix error: + // Cannot read properties of null (reading 'driver') + console.error(e); + console.warn(options); + } + } + } + } + + busy() { + this.$kb?.showProgress?.({ type: "icon" }); + } + + ready() { + this.$kb?.hideProgress?.(); + } + + objectLoad(object) { + super.objectLoad(object); + + this.TextTemplate.objectLoad(object); + this.FormSide.objectLoad(object); + } + + /** + * @method datacollectionLoad + * + * @param datacollection {ABDatacollection} + */ + datacollectionLoad(datacollection) { + super.datacollectionLoad(datacollection); + + const DC = this.CurrentDatacollection || datacollection; + + if (DC) { + DC.bind(this.$kb); + + const obj = DC.datasource; + + if (obj) this.objectLoad(obj); + + return; + } + + this.$kb.unbind(); + } + + async updateStatus(rowId, status) { + if (!this.CurrentVerticalField) return; + + // Show loading cursor + this.busy(); + + let patch = {}; + + // update multi-values + if (status instanceof Object) patch = status; + // update single value + else patch[this.CurrentVerticalField.columnName] = status; + + // update empty value + let needRefresh = false; + + for (const key in patch) + if (!patch[key]) { + patch[key] = ""; + + // WORKAROUND: if update data is empty, then it will need to refresh + // the kanban after update + needRefresh = true; + } + + try { + await this.CurrentObject?.model().update(rowId, patch); + + this.ready(); + + if (needRefresh) this.show(); + + // update form data + if (this.FormSide.isVisible()) { + const data = $$(this.ids.kanban).getItem(rowId); + + this.FormSide.refresh(data); + } + } catch (err) { + AB.notify.developer(err, { + context: "ABViewKanban:updateStatus(): Error saving item:", + rowId, + patch, + }); + } + } + + async updateOwner(rowId, val) { + if (!this.CurrentOwnerField) return; + + // Show loading cursor + this.busy(); + + const patch = {}; + + patch[this.CurrentOwnerField.columnName] = val; + + try { + const updatedRow = await this.CurrentObject?.model().update( + rowId, + patch + ); + + // update card + this.$kb?.updateItem(rowId, updatedRow); + + // update form data + if (this.FormSide.isVisible()) { + const data = this.$kb.getItem(rowId); + + this.FormSide.refresh(data); + } + + this.ready(); + } catch (err) { + AB.notify.developer(err, { + context: "ABViewKanban:updateOwner(): Error saving item:", + rowId, + val, + }); + + this.ready(); + } + } + + saveData(data) { + // update + if (data.id && this.$kb.exists(data.id)) + this.$kb.updateItem(data.id, data); + // insert + else this.$kb.add(data); + } + + unselect() { + if (this.$kb) + this.$kb.eachList((list /*, status*/) => { + list?.unselect?.(); + }); + } + + addCard() { + this.unselect(); + + // show the side form + this.FormSide.show(); + $$(this.ids.resizer).show(); + } + + async removeCard(rowId) { + const ab = AB; + const abWebix = ab.Webix; + + abWebix.confirm({ + title: this.label("Remove card"), + text: this.label("Do you want to delete this card?"), + callback: async (result) => { + if (!result) return; + + this.busy(); + + try { + const response = await this.CurrentObject?.model().delete(rowId); + + if (response.numRows > 0) { + this.$kb.remove(rowId); + } else { + abWebix.alert({ + text: this.label( + "No rows were effected. This does not seem right." + ), + }); + } + } catch (err) { + ab.notify.developer(err, { + message: "ABViewKanban:removeCard(): Error deleting item:", + rowId, + }); + } + + this.ready(); + }, + }); + } + + /** + * @method setFields() + * Save the current view options. + * @param options - { + * verticalGrouping: {ABField} - required + * horizontalGrouping: {ABField} - optional + * ownerField: {ABField} - optional + * } + */ + setFields(options) { + this.CurrentVerticalField = options.verticalGrouping; + this.CurrentHorizontalField = options.horizontalGrouping; + this.CurrentOwnerField = options.ownerField; + } + + + }; + +} diff --git a/AppBuilder/platform/plugins/included/view_kanban/FNABViewKanbanForm.js b/AppBuilder/platform/plugins/included/view_kanban/FNABViewKanbanForm.js new file mode 100644 index 00000000..5bfba3bc --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_kanban/FNABViewKanbanForm.js @@ -0,0 +1,297 @@ +/** + * Kanban sidebar detached form: save button only. + * No cross-folder imports — bases come from pluginAPI (see ABClassManager.getPluginAPI). + */ + +export default function createABViewKanbanDetachedFormSave({ + AB, + ABViewPlugin, + ABViewComponentPlugin, +}) { + + const ABViewFormButtonPropertyComponentDefaults = { + includeSave: true, + saveLabel: "", + includeCancel: false, + cancelLabel: "", + includeReset: false, + resetLabel: "", + includeDelete: false, + deleteLabel: "", + afterCancel: null, + alignment: "right", + isDefault: false, // mark default button of form widget + }; + + + class ABViewFormButtonCore extends ABViewPlugin { + static common() { + return { + key: "button", + // {string} unique key for this view + + icon: "square", + // {string} fa-[icon] reference for this view + + labelKey: "ab.components.button", + // {string} the multilingual label key for the class label + }; + } + constructor(values, application, parent, defaultValues) { + const ABViewFormButtonDefaults = { + key: "button", + // {string} unique key for this view + + icon: "square", + // {string} fa-[icon] reference for this view + + labelKey: "ab.components.button", + // {string} the multilingual label key for the class label + }; + super( + values, + application, + parent, + defaultValues || ABViewFormButtonDefaults + ) + } + + + static defaultValues() { + return ABViewFormButtonPropertyComponentDefaults; + } + + /// + /// Instance Methods + /// + + toObj() { + // labels are multilingual values: + let labels = []; + + if (this.settings.saveLabel) labels.push("saveLabel"); + + if (this.settings.cancelLabel) labels.push("cancelLabel"); + + if (this.settings.resetLabel) labels.push("resetLabel"); + + if (this.settings.deleteLabel) labels.push("deleteLabel"); + + this.unTranslate(this.settings, this.settings, labels); + + let result = super.toObj(); + + return result; + } + + /** + * @property datacollection + * return data source + * NOTE: this view doesn't track a DataCollection. + * @return {ABDataCollection} + */ + get datacollection() { + return null; + } + + fromValues(values) { + super.fromValues(values); + + // labels are multilingual values: + let labels = []; + + if (this.settings.saveLabel) labels.push("saveLabel"); + + if (this.settings.cancelLabel) labels.push("cancelLabel"); + + if (this.settings.resetLabel) labels.push("resetLabel"); + + if (this.settings.deleteLabel) labels.push("deleteLabel"); + + this.unTranslate(this.settings, this.settings, labels); + + // this.settings.includeSave = JSON.parse( + // (this.settings?.includeSave ?? true) && + // ABViewFormButtonPropertyComponentDefaults.includeSave + // ); + // this.settings.includeCancel = JSON.parse( + // this.settings.includeCancel || + // ABViewFormButtonPropertyComponentDefaults.includeCancel + // ); + // this.settings.includeReset = JSON.parse( + // this.settings.includeReset || + // ABViewFormButtonPropertyComponentDefaults.includeReset + // ); + // this.settings.includeDelete = JSON.parse( + // this.settings.includeDelete || + // ABViewFormButtonPropertyComponentDefaults.includeDelete + // ); + + // this.settings.isDefault = JSON.parse( + // this.settings.isDefault || + // ABViewFormButtonPropertyComponentDefaults.isDefault + // ); + } + + /** + * @method componentList + * return the list of components available on this view to display in the editor. + */ + componentList() { + return []; + } + } + + class formComponent extends ABViewComponentPlugin { + constructor(baseView, idBase, ids) { + super( + baseView, + idBase || `ABViewFormItem_${baseView.id}`, + Object.assign({ formItem: "" }, ids) + ); + } + + ui(uiFormItemComponent = {}) { + // setup 'label' of the element + const baseView = this.view; + const form = baseView.parentFormComponent(), + field = baseView.field?.() || null, + label = ""; + const settings = form?.settings || {}; + const _uiFormItem = { + id: this.ids.formItem, + labelPosition: settings.labelPosition, + labelWidth: settings.labelWidth, + label, + }; + + if (field) { + _uiFormItem.name = field.columnName; + + // default value + const data = {}; + + field.defaultValue(data); + + if (data[field.columnName]) _uiFormItem.value = data[field.columnName]; + + if (settings.showLabel) _uiFormItem.label = field.label; + + if (field.settings.required || baseView.settings?.required) + _uiFormItem.required = 1; + + if (baseView.settings?.disable === 1) _uiFormItem.disabled = true; + + // this may be needed if we want to format data at this point + // if (field.format) data = field.format(data); + + _uiFormItem.validate = (val, data, colName) => { + const validator = AB.Validation.validator(); + + field.isValidData(data, validator); + + return validator.pass(); + }; + } + + const _ui = super.ui([ + Object.assign({}, _uiFormItem, uiFormItemComponent), + ]); + + delete _ui.type; + + return _ui; + } + }; + + + class ABViewKanbanDetachedFormSaveComponent extends formComponent { + constructor(baseView, idBase, ids) { + super( + baseView, + idBase || `ABViewKanbanDetachedFormSave_${baseView.id}`, + ids + ); + } + + ui() { + const self = this; + const baseView = this.view; + const form = baseView.parentFormComponent(); + const settings = baseView.settings ?? {}; + const alignment = + settings.alignment || + baseView.constructor.defaultValues().alignment; + + const _ui = { cols: [] }; + + if (alignment === "center" || alignment === "right") { + _ui.cols.push({}); + } + + if (settings.includeSave) { + _ui.cols.push({ + view: "button", + type: "form", + css: "webix_primary", + autowidth: true, + value: settings.saveLabel || this.label("Save"), + click: function () { + self.onSave(this); + }, + on: { + onAfterRender: function () { + this.getInputNode().setAttribute( + "data-cy", + `button save ${form.id}` + ); + }, + }, + }); + } + + if (alignment === "center" || alignment === "left") { + _ui.cols.push({}); + } + + return super.ui(_ui); + } + + onSave(saveButton) { + if (!saveButton) { + console.error("Require the button element"); + return; + } + const form = this.view.parentFormComponent(); + const formView = saveButton.getFormView(); + + saveButton.disable?.(); + + form + .saveData(formView) + .then(() => { + saveButton.enable?.(); + form.focusOnFirst(); + }) + .catch((err) => { + console.error(err); + try { + saveButton.enable?.(); + } catch (e) { + AB.notify.developer(e, { + context: + "ABViewKanbanDetachedFormSave.onSave > saveButton.enable()", + buttonID: this?.view?.id, + formID: this?.view?.parent?.id, + }); + } + }); + } + } + + return class ABViewKanbanDetachedFormSave extends ABViewFormButtonCore { + component() { + return new ABViewKanbanDetachedFormSaveComponent(this); + } + }; +} diff --git a/AppBuilder/platform/plugins/included/view_kanban/FNABViewKanbanFormSidePanel.js b/AppBuilder/platform/plugins/included/view_kanban/FNABViewKanbanFormSidePanel.js new file mode 100644 index 00000000..be3eafe9 --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_kanban/FNABViewKanbanFormSidePanel.js @@ -0,0 +1,201 @@ +/* + * FNABViewKanbanFormSidePanel + * + * Form area for editing Kanban cards (included plugin; ESM). + */ + +export default function FNABViewKanbanFormSidePanel({ + ABViewComponentPlugin, + ABViewKanbanDetachedFormSave, +}) { + return class FNABViewKanbanFormSidePanel extends ABViewComponentPlugin { + constructor(comKanBan, idBase, editFields) { + super(comKanBan, idBase || `${comKanBan.view?.id}_formSidePanel`, { + form: "", + }); + + this.editFields = editFields; + + this._mockApp = this.AB.applicationNew({}); + } + + ui() { + const ids = this.ids; + const L = (...params) => this.AB.Multilingual.label(...params); + + return { + id: ids.component, + width: 300, + hidden: true, + rows: [ + { + view: "toolbar", + css: "webix_dark", + cols: [ + { + view: "label", + label: L("Edit Record"), + }, + { + view: "icon", + icon: "wxi-close", + align: "right", + click: () => { + this.hide(); + }, + }, + ], + }, + { + view: "scrollview", + body: { + rows: [ + { + id: ids.form, + view: "form", + type: "clean", + borderless: true, + rows: [], + }, + ], + }, + }, + ], + }; + } + + hide() { + $$(this.ids.component)?.hide(); + + this.emit("close"); + } + + show(data) { + $$(this.ids.component)?.show(); + + this.refreshForm(data); + } + + isVisible() { + return $$(this.ids.component)?.isVisible() ?? false; + } + + refreshForm(data) { + const ids = this.ids; + const $formView = $$(ids.form); + const CurrentObject = this.CurrentObject; + + if (!CurrentObject || !$formView) return; + + data = data || {}; + + const formAttrs = { + id: `${this.ids.component}_sideform`, + key: "form", + settings: { + columns: 1, + labelPosition: "top", + showLabel: 1, + clearOnLoad: 0, + clearOnSave: 0, + labelWidth: 120, + height: 0, + }, + }; + + const form = this.AB.viewNewDetatched(formAttrs); + + form.objectLoad(CurrentObject); + + CurrentObject.fields().forEach((f, index) => { + if (!this.editFields || this.editFields.indexOf(f.id) > -1) { + form.addFieldToForm(f, index); + } + }); + + form._views.push( + new ABViewKanbanDetachedFormSave( + { + settings: { + includeSave: true, + includeCancel: false, + includeReset: false, + }, + position: { + y: CurrentObject.fields().length, + }, + }, + this._mockApp, + form + ) + ); + + form._views.forEach( + (v, index) => (v.id = `${form.id}_${v.key}_${index}`) + ); + + const formCom = form.component(this.AB._App); + + webix.ui(formCom.ui().rows.concat({}), $formView); + webix.extend($formView, webix.ProgressBar); + + formCom.init( + this.AB, + 2, + { + onBeforeSaveData: () => { + const formVals = form.getFormValues($formView, CurrentObject); + + if (!form.validateData($formView, CurrentObject, formVals)) + return false; + + $formView?.showProgress({ type: "icon" }); + + if (formVals.id) { + CurrentObject.model() + .update(formVals.id, formVals) + .then((updateVals) => { + this.emit("update", updateVals); + + $formView?.hideProgress({ type: "icon" }); + }) + .catch((err) => { + this.AB.notify.developer(err, { + context: + "ABViewKanbanFormSidePanel:onBeforeSaveData():update(): Error updating value", + formVals, + }); + $formView?.hideProgress({ type: "icon" }); + }); + } else { + CurrentObject.model() + .create(formVals) + .then((newVals) => { + this.emit("add", newVals); + + $formView?.hideProgress({ type: "icon" }); + }) + .catch((err) => { + this.AB.notify.developer(err, { + context: + "ABViewKanbanFormSidePanel:onBeforeSaveData():.create(): Error creating value", + formVals, + }); + + $formView?.hideProgress({ type: "icon" }); + }); + } + + return false; + }, + }, + 2 + ); + + $formView.clear(); + $formView.parse(data); + + formCom.onShow(data); + } + }; +} diff --git a/AppBuilder/platform/views/ABViewKanban.js b/AppBuilder/platform/views/ABViewKanban.js deleted file mode 100644 index 19f4accf..00000000 --- a/AppBuilder/platform/views/ABViewKanban.js +++ /dev/null @@ -1,36 +0,0 @@ -const ABViewKanbanCore = require("../../core/views/ABViewKanbanCore"); -const ABViewKanbanComponent = require("./viewComponent/ABViewKanbanComponent"); - -const ABViewPropertyLinkPage = - require("./viewProperties/ABViewPropertyLinkPage").default; - -export default class ABViewKanban extends ABViewKanbanCore { - // - // Editor Related - // - - /** - * @method component() - * return a UI component based upon this view. - * @return {obj} UI component - */ - - component() { - return new ABViewKanbanComponent(this); - } - - get linkPageHelper() { - return (this.__linkPageHelper = - this.__linkPageHelper || new ABViewPropertyLinkPage()); - } - - 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/ABViewKanbanFormSidePanel.js b/AppBuilder/platform/views/ABViewKanbanFormSidePanel.js deleted file mode 100644 index a98847b3..00000000 --- a/AppBuilder/platform/views/ABViewKanbanFormSidePanel.js +++ /dev/null @@ -1,256 +0,0 @@ -/* - * ABViewKanbanFormSidePanel - * - * Provide a form area for editing data in the Kan Ban view. - * - */ - -const ABViewComponent = require("./viewComponent/ABViewComponent").default; -const ABViewForm = require("./ABViewForm"); -const ABViewFormButton = require("./ABViewFormButton"); - -var L = null; -// multilingual Label fn() - -module.exports = class ABWorkObjectKanBan extends ABViewComponent { - constructor(comKanBan, idBase, editFields) { - idBase = idBase || `${comKanBan.view?.id}_formSidePanel`; - super(idBase, { - form: "", - }); - - if (!L) { - L = (...params) => { - return this.AB.Multilingual.label(...params); - }; - } - - this.AB = comKanBan.AB; - - this.CurrentObjectID = null; - // {string} - // the ABObject.id of the object we are working with. - - this.editFields = editFields; - // {array} - // An array of {ABField.id} that determines which fields should show up - // in the editor. - - this._mockApp = this.AB.applicationNew({}); - // {ABApplication} - // Any ABViews we create are expected to be in relation to - // an ABApplication, so we create a "mock" app for our - // workspace views to use to display. - } - - /** - * @method CurrentObject() - * A helper to return the current ABObject we are working with. - * @return {ABObject} - */ - get CurrentObject() { - return this.AB.objectByID(this.CurrentObjectID); - } - - ui() { - var ids = this.ids; - - // Our webix UI definition: - return { - id: ids.component, - width: 300, - hidden: true, - rows: [ - { - view: "toolbar", - css: "webix_dark", - cols: [ - { - view: "label", - label: L("Edit Record"), - }, - { - view: "icon", - icon: "wxi-close", - align: "right", - click: (/* id */) => { - this.hide(); - }, - }, - ], - }, - { - view: "scrollview", - body: { - rows: [ - { - id: ids.form, - view: "form", - borderless: true, - rows: [], - }, - ], - }, - }, - ], - }; - } - - async init(AB) { - this.AB = AB; - } - - objectLoad(object) { - this.CurrentObjectID = object.id; - } - - hide() { - $$(this.ids.component)?.hide(); - - this.emit("close"); - } - - show(data) { - $$(this.ids.component)?.show(); - - this.refreshForm(data); - } - - isVisible() { - return $$(this.ids.component)?.isVisible() ?? false; - } - - refreshForm(data) { - var ids = this.ids; - let $formView = $$(ids.form); - let CurrentObject = this.CurrentObject; - - if (!CurrentObject || !$formView) return; - - data = data || {}; - - let formAttrs = { - id: `${this.ids.component}_sideform`, - key: ABViewForm.common().key, - settings: { - columns: 1, - labelPosition: "top", - showLabel: 1, - clearOnLoad: 0, - clearOnSave: 0, - labelWidth: 120, - height: 0, - }, - }; - - // let form = new ABViewForm(formAttrs, this._mockApp); - let form = this.AB.viewNewDetatched(formAttrs); - - form.objectLoad(CurrentObject); - - // Populate child elements - CurrentObject.fields().forEach((f, index) => { - // if this is one of our .editFields - if (!this.editFields || this.editFields.indexOf(f.id) > -1) { - form.addFieldToForm(f, index); - } - }); - - // add default button (Save button) - form._views.push( - new ABViewFormButton( - { - settings: { - includeSave: true, - includeCancel: false, - includeReset: false, - }, - position: { - y: CurrentObject.fields().length, // yPosition - }, - }, - this._mockApp, - form - ) - ); - - // add temp id to views - form._views.forEach( - (v, index) => (v.id = `${form.id}_${v.key}_${index}`) - ); - - let formCom = form.component(this.AB._App); - - // Rebuild form - webix.ui(formCom.ui().rows.concat({}), $formView); - webix.extend($formView, webix.ProgressBar); - - formCom.init( - this.AB, - 2, - { - onBeforeSaveData: () => { - // get update data - var formVals = form.getFormValues($formView, CurrentObject); - - // validate data - if (!form.validateData($formView, CurrentObject, formVals)) - return false; - - // show progress icon - $formView?.showProgress({ type: "icon" }); - - if (formVals.id) { - CurrentObject.model() - .update(formVals.id, formVals) - .then((updateVals) => { - this.emit("update", updateVals); - // _logic.callbacks.onUpdateData(updateVals); - - $formView?.hideProgress({ type: "icon" }); - }) - .catch((err) => { - // TODO : error message - this.AB.notify.developer(err, { - context: - "ABViewKanbanFormSidePanel:onBeforeSaveData():update(): Error updating value", - formVals, - }); - $formView?.hideProgress({ type: "icon" }); - }); - } - // else add new row - else { - CurrentObject.model() - .create(formVals) - .then((newVals) => { - // _logic.callbacks.onAddData(newVals); - this.emit("add", newVals); - - $formView?.hideProgress({ type: "icon" }); - }) - .catch((err) => { - // TODO : error message - this.AB.notify.developer(err, { - context: - "ABViewKanbanFormSidePanel:onBeforeSaveData():.create(): Error creating value", - formVals, - }); - - $formView?.hideProgress({ type: "icon" }); - }); - } - - return false; - }, - }, - 2 /* NOTE: if you can see this KanBan, you should be able to see the side form? */ - ); - - // display data - $formView.clear(); - $formView.parse(data); - - formCom.onShow(data); - } -}; diff --git a/AppBuilder/platform/views/viewComponent/ABViewKanbanComponent.js b/AppBuilder/platform/views/viewComponent/ABViewKanbanComponent.js deleted file mode 100644 index ed3da56e..00000000 --- a/AppBuilder/platform/views/viewComponent/ABViewKanbanComponent.js +++ /dev/null @@ -1,577 +0,0 @@ -const ABViewComponent = require("./ABViewComponent").default; -const ABFormSidePanel = require("../ABViewKanbanFormSidePanel"); - -module.exports = class ABViewKanbanComponent extends ABViewComponent { - constructor(baseView, idBase, ids) { - super( - baseView, - idBase || `ABViewKanBan_${baseView.id}`, - Object.assign( - { - kanbanView: "", - - kanban: "", - resizer: "", - formSidePanel: "", - }, - ids - ) - ); - - this.FormSide = new ABFormSidePanel( - this, - this.ids.formSidePanel, - this.settings.editFields - ); - - this.CurrentVerticalField = null; - this.CurrentHorizontalField = null; - this.CurrentOwnerField = null; - - this.TextTemplate = baseView.TextTemplate; - - this._updatingOwnerRowId = null; - this._ABFieldConnect = null; - this._ABFieldUser = null; - this._ABFieldList = null; - } - - get ABFieldConnect() { - return (this._ABFieldConnect = - this._ABFieldConnect || - this.AB.Class.ABFieldManager.fieldByKey("connectObject")); - } - - get ABFieldUser() { - return (this._ABFieldUser = - this._ABFieldUser || this.AB.Class.ABFieldManager.fieldByKey("user")); - } - - get ABFieldList() { - return (this._ABFieldList = - this._ABFieldList || this.AB.Class.ABFieldManager.fieldByKey("list")); - } - - ui() { - const ids = this.ids; - const self = this; - this.linkPage = this.view.linkPageHelper.component(); - - const _ui = super.ui([ - { - id: ids.kanbanView, - cols: [ - { - id: ids.kanban, - view: "kanban", - cols: [], - userList: { - view: "menu", - // yCount: 8, - // scroll: false, - template: ' #value#', - width: 150, - on: { - onSelectChange: function () { - // get this row id from onAvatarClick event - if (!self._updatingOwnerRowId) return; - - const userId = this.getSelectedId(false); - if (!userId) return; - - self.updateOwner(self._updatingOwnerRowId, userId); - }, - }, - }, - editor: false, // we use side bar - users: [], - tags: [], - data: [], - on: { - onListAfterSelect: (itemId, list) => { - this.CurrentDatacollection?.setCursor(itemId); - this.emit("select", itemId); - - // link pages events - const editPage = this.settings.editPage; - if (editPage) - this.linkPage.changePage(editPage, itemId); - - const detailsPage = this.settings.detailsPage; - if (detailsPage) - this.linkPage.changePage(detailsPage, itemId); - }, - onAfterStatusChange: (rowId, status /*, list */) => { - this.updateStatus(rowId, status); - }, - onAvatarClick: (rowId /*, ev, node, list */) => { - // keep this row id for update owner data in .userList - this._updatingOwnerRowId = rowId; - }, - }, - }, - { - id: ids.resizer, - view: "resizer", - css: "bg_gray", - width: 11, - hidden: true, - }, - this.FormSide.ui(), - ], - }, - ]); - - delete _ui.type; - - return _ui; - } - - async init(AB) { - await super.init(AB); - - const abWebix = this.AB.Webix; - - if (this.$kb) abWebix.extend(this.$kb, abWebix.ProgressBar); - - this.FormSide.init(AB); - this.FormSide.on("add", (newVals) => { - this.saveData(newVals); - }); - this.FormSide.on("update", (updateVals) => { - this.saveData(updateVals); - }); - - let dc = this.view.datacollection; - if (dc) this.datacollectionLoad(dc); - - this.linkPage.init({ - view: this.view, - datacollection: dc, - }); - - this.show(); - } - - get $kb() { - return (this._kb = this._kb || $$(this.ids.kanban)); - } - - kanbanListTemplate() { - return { - icons: [ - // { icon: "mdi mdi-comment", show: function (obj) { return !!obj.comments }, template: "#comments.length#" }, - { - icon: "fa fa-trash-o", - click: (rowId /*, e */) => { - this.removeCard(rowId); - }, - }, - ], - // avatar template - templateAvatar: (obj) => { - if ( - this.CurrentOwnerField && - obj[this.CurrentOwnerField.columnName] - ) - return this.CurrentOwnerField.format(obj); - else return ""; - }, - // template for item body - // show item image and text - templateBody: (data) => { - // if (!this.settings.template) - if (!this.TextTemplate.text) - return this.CurrentObject?.displayData(data); - - // return our default text template - return this.TextTemplate.displayText(data); - }, - }; - } - - /** - * @function hide() - * - * hide this component. - */ - hide() { - $$(this.ids.kanbanView)?.hide(); - } - - /** - * @function show() - * Show this component. - */ - async show() { - const ids = this.ids; - - $$(ids.kanbanView)?.show(); - - this.FormSide.hide(); - - $$(ids.resizer)?.hide(); - - var CurrentObject = this.CurrentObject; - if (!CurrentObject) { - CurrentObject = this.datacollection?.datasource; - } - if (!CurrentObject) return; - - // Get vertical grouping field and populate to kanban list - // NOTE: this field should be the select list type - const CurrentVerticalField = CurrentObject.fieldByID( - this.settings.verticalGroupingField - ); - if (!CurrentVerticalField) return; - - this.CurrentVerticalField = CurrentVerticalField; - - let horizontalOptions = []; - - const CurrentHorizontalField = CurrentObject.fieldByID( - this.settings.horizontalGroupingField - ); - - this.CurrentHorizontalField = CurrentHorizontalField; - - if ( - CurrentHorizontalField && - CurrentHorizontalField instanceof this.ABFieldConnect - ) - // Pull horizontal options - horizontalOptions = await CurrentHorizontalField.getOptions(); - - // Option format - { id: "1543563751920", text: "Normal", hex: "#4CAF50" } - const verticalOptions = (CurrentVerticalField.settings.options || []).map( - (opt) => { - // Vertical & Horizontal fields - if (CurrentVerticalField && CurrentHorizontalField) { - let rows = [], - // [{ - // id: '', - // text: '' - // }] - horizontalVals = []; - - // pull options of the Horizontal field - if (CurrentHorizontalField instanceof this.ABFieldList) { - // make a copy of the settings. - horizontalVals = ( - CurrentHorizontalField.settings.options || [] - ).map((o) => o); - } else if (CurrentHorizontalField instanceof this.ABFieldUser) { - horizontalVals = CurrentHorizontalField.getUsers().map( - (u) => { - return { - id: u.id, - text: u.text || u.value, - }; - } - ); - } else if (CurrentHorizontalField instanceof this.ABFieldConnect) - horizontalVals = horizontalOptions.map(({ id, text }) => ({ - id, - text, - })); - - horizontalVals.push({ - id: null, - text: this.label("Other"), - }); - - horizontalVals.forEach((val) => { - const statusOps = {}; - - statusOps[CurrentVerticalField.columnName] = opt.id; - statusOps[CurrentHorizontalField.columnName] = val.id; - - // Header - rows.push({ - template: val.text, - height: 20, - css: "progress_header", - }); - - // Kanban list - rows.push({ - view: "kanbanlist", - status: statusOps, - type: this.kanbanListTemplate(), - }); - }); - - return { - header: opt.text, - body: { - margin: 0, - rows: rows, - }, - }; - } - // Vertical field only - else if (CurrentVerticalField) { - const statusOps = {}; - - statusOps[CurrentVerticalField.columnName] = opt.id; - - return { - header: opt.text, - body: { - view: "kanbanlist", - status: statusOps, - type: this.kanbanListTemplate(), - }, - }; - } - } - ); - - const ab = this.AB; - const abWebix = ab.Webix; - - // Rebuild kanban that contains options - // NOTE: webix kanban does not support dynamic vertical list - abWebix.ui(verticalOptions, $$(ids.kanban)); - $$(ids.kanban).reconstruct(); - - // Owner field - const CurrentOwnerField = CurrentObject.fieldByID( - this.settings.ownerField - ); - - this.CurrentOwnerField = CurrentOwnerField; - - if (CurrentOwnerField) { - const $menuUser = $$(ids.kanban).getUserList(); - - $menuUser.clearAll(); - - if (CurrentOwnerField instanceof this.ABFieldUser) { - const users = ab.Account.userList().map((u) => { - return { - id: u.username, - value: u.username, - }; - }); - - $menuUser.parse(users); - } else if (CurrentOwnerField instanceof this.ABFieldConnect) { - const options = await CurrentOwnerField.getOptions(); - - try { - $menuUser.parse( - options.map((opt) => { - return { - id: opt.id, - value: opt.text, - }; - }) - ); - } catch (e) { - // TODO: remove this. Trying to catch a random webix error: - // Cannot read properties of null (reading 'driver') - console.error(e); - console.warn(options); - } - } - } - } - - busy() { - this.$kb?.showProgress?.({ type: "icon" }); - } - - ready() { - this.$kb?.hideProgress?.(); - } - - objectLoad(object) { - super.objectLoad(object); - - this.TextTemplate.objectLoad(object); - this.FormSide.objectLoad(object); - } - - /** - * @method datacollectionLoad - * - * @param datacollection {ABDatacollection} - */ - datacollectionLoad(datacollection) { - super.datacollectionLoad(datacollection); - - const DC = this.CurrentDatacollection || datacollection; - - if (DC) { - DC.bind(this.$kb); - - const obj = DC.datasource; - - if (obj) this.objectLoad(obj); - - return; - } - - this.$kb.unbind(); - } - - async updateStatus(rowId, status) { - if (!this.CurrentVerticalField) return; - - // Show loading cursor - this.busy(); - - let patch = {}; - - // update multi-values - if (status instanceof Object) patch = status; - // update single value - else patch[this.CurrentVerticalField.columnName] = status; - - // update empty value - let needRefresh = false; - - for (const key in patch) - if (!patch[key]) { - patch[key] = ""; - - // WORKAROUND: if update data is empty, then it will need to refresh - // the kanban after update - needRefresh = true; - } - - try { - await this.CurrentObject?.model().update(rowId, patch); - - this.ready(); - - if (needRefresh) this.show(); - - // update form data - if (this.FormSide.isVisible()) { - const data = $$(this.ids.kanban).getItem(rowId); - - this.FormSide.refresh(data); - } - } catch (err) { - this.AB.notify.developer(err, { - context: "ABViewKanban:updateStatus(): Error saving item:", - rowId, - patch, - }); - } - } - - async updateOwner(rowId, val) { - if (!this.CurrentOwnerField) return; - - // Show loading cursor - this.busy(); - - const patch = {}; - - patch[this.CurrentOwnerField.columnName] = val; - - try { - const updatedRow = await this.CurrentObject?.model().update( - rowId, - patch - ); - - // update card - this.$kb?.updateItem(rowId, updatedRow); - - // update form data - if (this.FormSide.isVisible()) { - const data = this.$kb.getItem(rowId); - - this.FormSide.refresh(data); - } - - this.ready(); - } catch (err) { - this.AB.notify.developer(err, { - context: "ABViewKanban:updateOwner(): Error saving item:", - rowId, - val, - }); - - this.ready(); - } - } - - saveData(data) { - // update - if (data.id && this.$kb.exists(data.id)) - this.$kb.updateItem(data.id, data); - // insert - else this.$kb.add(data); - } - - unselect() { - if (this.$kb) - this.$kb.eachList((list /*, status*/) => { - list?.unselect?.(); - }); - } - - addCard() { - this.unselect(); - - // show the side form - this.FormSide.show(); - $$(this.ids.resizer).show(); - } - - async removeCard(rowId) { - const ab = this.AB; - const abWebix = ab.Webix; - - abWebix.confirm({ - title: this.label("Remove card"), - text: this.label("Do you want to delete this card?"), - callback: async (result) => { - if (!result) return; - - this.busy(); - - try { - const response = await this.CurrentObject?.model().delete(rowId); - - if (response.numRows > 0) { - this.$kb.remove(rowId); - } else { - abWebix.alert({ - text: this.label( - "No rows were effected. This does not seem right." - ), - }); - } - } catch (err) { - ab.notify.developer(err, { - message: "ABViewKanban:removeCard(): Error deleting item:", - rowId, - }); - } - - this.ready(); - }, - }); - } - - /** - * @method setFields() - * Save the current view options. - * @param options - { - * verticalGrouping: {ABField} - required - * horizontalGrouping: {ABField} - optional - * ownerField: {ABField} - optional - * } - */ - setFields(options) { - this.CurrentVerticalField = options.verticalGrouping; - this.CurrentHorizontalField = options.horizontalGrouping; - this.CurrentOwnerField = options.ownerField; - } -}; From 6eda39af9bcbbd44485a9a5323836d6b85808b09 Mon Sep 17 00:00:00 2001 From: roguisharcanetrickster Date: Wed, 13 May 2026 11:33:52 +0700 Subject: [PATCH 2/2] remove dupe function --- .../platform/plugins/included/view_kanban/FNABViewKanban.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/AppBuilder/platform/plugins/included/view_kanban/FNABViewKanban.js b/AppBuilder/platform/plugins/included/view_kanban/FNABViewKanban.js index c599d161..a0ce6dfc 100644 --- a/AppBuilder/platform/plugins/included/view_kanban/FNABViewKanban.js +++ b/AppBuilder/platform/plugins/included/view_kanban/FNABViewKanban.js @@ -118,11 +118,6 @@ export default function FNABViewKanban({ // Editor Related // - get linkPageHelper() { - return (this.__linkPageHelper = - this.__linkPageHelper || new ABViewPropertyLinkPage()); - } - warningsEval() { super.warningsEval(); let DC = this.datacollection;