UNPKG

@syncfusion/ej2-kanban

Version:

The Kanban board is an efficient way to visualize the workflow at each stage along its path to completion. The most important features available are Swim lane, filtering, and editing.

1,145 lines (1,135 loc) 332 kB
import { formatUnit, isNullOrUndefined, extend, Property, ChildProperty, closest, classList, removeClass, addClass, remove, createElement, createInstance, detach, SanitizeHtmlHelper, Draggable, EventHandler, append, KeyboardEvents, initializeCSPTemplate, Touch, setStyleAttribute, Browser, debounce, Component, L10n, compile, Collection, Complex, Event, NotifyPropertyChanges } from '@syncfusion/ej2-base'; import { Dialog, Tooltip, Popup, createSpinner, showSpinner, hideSpinner } from '@syncfusion/ej2-popups'; import { DataManager, Query, UrlAdaptor, Deferred, Predicate } from '@syncfusion/ej2-data'; import { DropDownList } from '@syncfusion/ej2-dropdowns'; import { TextBox, NumericTextBox, FormValidator } from '@syncfusion/ej2-inputs'; import { Button } from '@syncfusion/ej2-buttons'; import { TreeView } from '@syncfusion/ej2-navigations'; /** * Kanban Constants */ // Constants for public events /** @private */ const actionBegin = 'actionBegin'; /** @private */ const actionComplete = 'actionComplete'; /** @private */ const actionFailure = 'actionFailure'; /** @private */ const cardClick = 'cardClick'; /** @private */ const cardDoubleClick = 'cardDoubleClick'; /** @private */ const cardRendered = 'cardRendered'; /** @private */ const queryCellInfo = 'queryCellInfo'; /** @private */ const dataBinding = 'dataBinding'; /** @private */ const dataBound = 'dataBound'; /** @private */ const dragStart = 'dragStart'; /** @private */ const drag = 'drag'; /** @private */ const dragStop = 'dragStop'; /** @private */ const documentClick = 'document-click'; /** @private */ const dialogOpen = 'dialogOpen'; /** @private */ const dialogClose = 'dialogClose'; // Constants for internal events /** @private */ const contentReady = 'content-ready'; /** @private */ const dataReady = 'data-ready'; /** @private */ const bottomSpace = 25; /** @private */ const cardSpace = 16; /** @private */ const toggleWidth = 50; /** @hidden */ const dataSourceChanged = 'dataSourceChanged'; /** @hidden */ const dataStateChange = 'dataStateChange'; /** @private */ const columnDragStart = 'columnDragStart'; /** @private */ const columnDrag = 'columnDrag'; /** @private */ const columnDrop = 'columnDrop'; /* eslint-disable @typescript-eslint/no-explicit-any */ /** * Kanban data module */ class Data { /** * Constructor for data module * * @param {Kanban} parent Accepts the instance of the Kanban */ constructor(parent) { this.initload = false; this.dataState = { isPending: false, resolver: null, isDataChanged: false }; this.parent = parent; this.keyField = this.parent.cardSettings.headerField; this.dataState = { isDataChanged: false }; this.isObservable = false; this.initDataManager(parent.dataSource, parent.query); this.refreshDataManager(); } /** * The function used to initialize dataManager` and query * * @param {Object[] | DataManager} dataSource Accepts the dataSource as collection of objects or Datamanager instance. * @param {Query} query Accepts the query to process the data from collections. * @returns {void} * @private */ initDataManager(dataSource, query) { this.dataManager = dataSource instanceof DataManager ? dataSource : new DataManager(dataSource); this.query = query instanceof Query ? query : new Query(); this.kanbanData = new DataManager(this.parent.kanbanData); } /** * @returns {boolean} returns whether its remote data * @hidden */ isRemote() { return this.dataManager.dataSource.offline !== true && this.dataManager.dataSource.url !== undefined && this.dataManager.dataSource.url !== ''; } /** * @returns {boolean} returns the column key fields * @hidden */ columnKeyFields() { const columns = []; for (const column of this.parent.columns) { if (column.keyField.toString().split(',').length > 1) { for (const innerColumns of column.keyField.toString().split(',')) { columns.push(innerColumns.trim()); } } else { columns.push(column.keyField.toString()); } } return columns; } parseViewportHeight(height) { const heightValue = height.toString().trim(); const viewportMatch = heightValue.match(/^([\d.]+)(vh)?$/i); const viewportBasedHeight = (window.innerHeight * parseFloat(viewportMatch[1])) / 100; return viewportBasedHeight; } /** * The function used to generate updated Query from schedule model * * @param {string} parameter Accepts the parameter that needs to be sent to the service end. * @returns {void} * @private */ getQuery(parameter) { const query = this.query.clone(); if (this.isRemote() && this.parent.enableVirtualization) { const cardHeight = this.parent.cardHeight === 'auto' ? 100 : parseInt(formatUnit(this.parent.cardHeight).split('px')[0], 10); const kanbanHeight = this.parent.height.toString(); let take; if (kanbanHeight === 'auto') { take = Math.ceil((window.innerHeight - 50) / cardHeight) * 2; } else if (kanbanHeight.endsWith('px')) { take = (Math.ceil((parseInt(formatUnit(this.parent.height).split('px')[0], 10) - 50) / cardHeight) * 2); } else if (kanbanHeight.endsWith('%')) { take = (Math.ceil((parseInt(formatUnit(this.parent.element.clientHeight).split('px')[0], 10) - 50) / cardHeight) * 2); } else if (kanbanHeight.toLowerCase().endsWith('vh')) { const viewportHeight = this.parseViewportHeight(this.parent.height); take = Math.ceil((viewportHeight - 50) / cardHeight) * 2; } else { take = Math.ceil((this.parent.element.getBoundingClientRect().height - 50) / cardHeight) * 2; } const columns = this.columnKeyFields(); for (let i = 0; i < columns.length; i++) { query.where(this.parent.keyField, 'equal', columns[i]); } query.take(take); if (isNullOrUndefined(parameter)) { parameter = 'KanbanVirtualization'; } query.addParams('KanbanVirtualization', parameter); } return query; } /** * The function used to get dataSource by executing given Query * * @param {Query} query - A Query that specifies to generate dataSource * @returns {void} * @private */ getData(query) { if (this.parent.dataSource && 'result' in this.parent.dataSource) { const def = this.eventPromise({ requestType: '' }, query); this.isObservable = true; return def.promise; } return this.dataManager.executeQuery(query); } setState(state) { return this.dataState = state; } getStateEventArgument(query) { const adaptr = new UrlAdaptor(); const dm = new DataManager({ url: '', adaptor: new UrlAdaptor }); const state = adaptr.processQuery(dm, query); const data = JSON.parse(state.data); return extend(data, state.pvtData); } eventPromise(args, query, index) { const dataArgs = args; const state = this.getStateEventArgument(query); const def = new Deferred(); const deff = new Deferred(); if (args.requestType !== undefined && this.dataState.isDataChanged !== false) { state.action = args; if (args.requestType === 'cardChanged' || args.requestType === 'cardRemoved' || args.requestType === 'cardCreated') { const editArgs = args; editArgs.promise = deff.promise; editArgs.state = state; editArgs.index = index; this.setState({ isPending: true, resolver: deff.resolve }); dataArgs.endEdit = deff.resolve; dataArgs.cancelEdit = deff.reject; this.parent.trigger(dataSourceChanged, editArgs); deff.promise.then(() => { this.setState({ isPending: true, resolver: def.resolve }); this.parent.trigger(dataStateChange, state); editArgs.addedRecords.forEach((data) => { this.parent.kanbanData.push(data); }); editArgs.changedRecords.forEach((changedRecord) => { const cardObj = this.parent.kanbanData.filter((data) => data[this.parent.cardSettings.headerField] === changedRecord[this.parent.cardSettings.headerField])[0]; extend(cardObj, changedRecord); }); editArgs.deletedRecords.forEach((deletedRecord) => { const index = this.parent.kanbanData.findIndex((data) => data[this.parent.cardSettings.headerField] === deletedRecord[this.parent.cardSettings.headerField]); this.parent.kanbanData.splice(index, 1); }); }).catch(() => { this.parent.hideSpinner(); }); } else { this.setState({ isPending: true, resolver: def.resolve }); this.parent.trigger(dataStateChange, state); } } else { this.setState({}); def.resolve(this.parent.dataSource); } return def; } /** * The function used to get the table name from the given Query * * @returns {string} Returns the table name. * @private */ getTable() { if (this.parent.query) { const query = this.getQuery(); return query.fromTable; } else { return null; } } /** * The function is used to send the request and get response from datamanager * * @returns {void} * @private */ refreshDataManager() { const dataManager = this.getData(this.getQuery()); dataManager.then((e) => this.dataManagerSuccess(e)).catch((e) => this.dataManagerFailure(e)); } /** * The function is used to handle the success response from dataManager * * @param {ReturnType} e Accepts the dataManager success result * @param type * @returns {void} * @private */ // eslint-disable-next-line dataManagerSuccess(e, type, offlineArgs, index) { if (this.parent.isDestroyed) { return; } if (type) { this.updateKanbanData(e); if (this.parent.enableVirtualization && this.isRemote()) { this.parent.virtualLayoutModule.refresh(); } } else { this.parent.trigger(dataBinding, e, (args) => { this.updateKanbanData(args); this.parent.notify(dataReady, { processedData: this.parent.kanbanData }); this.parent.trigger(dataBound, null, () => this.parent.hideSpinner()); }); } if (this.initload) { this.parent.layoutModule.refresh(); this.parent.renderTemplates(); } this.initload = true; } /** * The function is used to handle the update the column data count for remote, and update kanbanData while perform the CRUD action * * @param {ReturnType} args Accepts the dataManager success result * @returns {void} * @private */ updateKanbanData(args) { const resultData = extend([], !isNullOrUndefined(args.result.result) ? args.result.result : args.result, null, true); if (this.isRemote() && this.parent.enableVirtualization && resultData.length > 0 && !isNullOrUndefined(args.result.count)) { const columnsKeyFields = this.columnKeyFields(); for (let i = 0; i < columnsKeyFields.length; i++) { if (args.result.count[i].Key === columnsKeyFields[i]) { this.parent.columnDataCount[columnsKeyFields[i]] = args.result.count[i].Value; } } } this.parent.kanbanData = resultData; this.kanbanData = new DataManager(this.parent.kanbanData); } /** * The function is used to handle the failure response from dataManager * * @param {ReturnType} e Accepts the dataManager failure result * @returns {void} * @private */ dataManagerFailure(e) { if (this.parent.isDestroyed) { return; } this.parent.trigger(actionFailure, { error: e }, () => this.parent.hideSpinner()); } /** * The function is used to perform the insert, update, delete and batch actions in datamanager * * @param {string} updateType Accepts the update type action * @param {SaveChanges} params Accepts the savechanges params * @param {string} type Accepts the requestType as string * @param {Object} data Accepts the data to perform crud action * @param {number} index Accepts the index to refresh the data into UI * @param {boolean} isDropped Accepts the boolean value based on based if it is dragged and dropped * @param {string} dataDropIndexKeyFieldValue Accepts the dropped index key field value card * @param {number} draggedKey Accepts the dragged keyfield of the column * @param {number} droppedKey Accepts the dropped keyfield of the column * @param {number} isMultipleDrag Accepts boolean value based on the multiple drag of the cards * @returns {void} * @private */ updateDataManager(updateType, params, type, data, index, isDropped, dataDropIndexKeyFieldValue, draggedKey, droppedKey, isMultipleDrag) { this.parent.showSpinner(); let promise; const actionArgs = { requestType: type, cancel: false, addedRecords: params.addedRecords, changedRecords: params.changedRecords, deletedRecords: params.deletedRecords }; this.setState({ isDataChanged: true }); this.eventPromise(actionArgs, this.query, index); this.parent.trigger(actionComplete, actionArgs, (offlineArgs) => { if (!offlineArgs.cancel) { promise = this.syncDataSource(this.dataManager, updateType, params, data, isDropped, dataDropIndexKeyFieldValue); if (this.dataManager.dataSource.offline) { if (!this.isObservable) { this.syncDataSource(this.kanbanData, updateType, params, data, isDropped, dataDropIndexKeyFieldValue); index = draggedKey === droppedKey && isMultipleDrag ? index - 1 : index; this.refreshUI(offlineArgs, index, isDropped); if (this.parent.enableVirtualization) { this.parent.virtualLayoutModule.refreshColumnData(draggedKey, droppedKey, offlineArgs.requestType, data[this.parent.keyField]); } } } else { promise.then((args) => { if (this.parent.isDestroyed) { return; } const dataManager = this.getData(this.getQuery()); dataManager.then((e) => this.dataManagerSuccess(e, 'DataSourceChange', offlineArgs, index)).catch((e) => this.dataManagerFailure(e)); if (offlineArgs.requestType === 'cardCreated') { if (!Array.isArray(args)) { offlineArgs.addedRecords[0] = extend(offlineArgs.addedRecords[0], args); } else { this.modifyArrayData(offlineArgs.addedRecords, args); } } else if (offlineArgs.requestType === 'cardChanged') { if (!Array.isArray(args)) { offlineArgs.changedRecords[0] = extend(offlineArgs.changedRecords[0], args); } else { this.modifyArrayData(offlineArgs.changedRecords, args); } } else if (offlineArgs.requestType === 'cardRemoved') { if (!Array.isArray(args)) { offlineArgs.deletedRecords[0] = extend(offlineArgs.deletedRecords[0], args); } else { this.modifyArrayData(offlineArgs.deletedRecords, args); } } index = draggedKey === droppedKey && isMultipleDrag ? index - 1 : index; this.refreshUI(offlineArgs, index, isDropped); if (this.parent.enableVirtualization) { this.parent.virtualLayoutModule.refreshColumnData(draggedKey, droppedKey, offlineArgs.requestType, data[this.parent.keyField]); } }).catch((e) => { this.dataManagerFailure(e); }); } } }); } syncDataSource(dataManager, updateType, params, data, isDropped, dataDropIndexKeyFieldValue) { let promise; switch (updateType) { case 'insert': return dataManager.insert(data, this.getTable(), this.getQuery()); case 'update': if (this.parent.enableVirtualization && !this.parent.dataModule.isRemote() && isDropped) { promise = dataManager.remove(this.keyField, data, this.getTable(), this.getQuery()); promise = dataManager.insert(data, this.getTable(), this.getQuery(), dataManager.dataSource.json.findIndex((data) => data[this.parent.cardSettings.headerField] === dataDropIndexKeyFieldValue)); return promise; } else { return dataManager.update(this.keyField, data, this.getTable(), this.getQuery()); } case 'delete': return dataManager.remove(this.keyField, data, this.getTable(), this.getQuery()); case 'batch': if (!this.parent.dataModule.isRemote() && isDropped && this.parent.enableVirtualization && data) { for (let i = 0; i < data.length; i++) { promise = dataManager.remove(this.keyField, data[i], this.getTable(), this.getQuery()); } let currentIndex = dataManager.dataSource.json.findIndex((data) => data[this.parent.cardSettings.headerField] === dataDropIndexKeyFieldValue); for (let i = 0; i < data.length; i++, currentIndex++) { promise = dataManager.insert(data[i], this.getTable(), this.getQuery(), currentIndex); } return promise; } else { return dataManager.saveChanges(params, this.keyField, this.getTable(), this.getQuery()); } default: return promise; } } modifyArrayData(onLineData, e) { if (onLineData.length === e.length) { for (let i = 0; i < e.length; i++) { onLineData[i] = extend(onLineData[i], e[i]); } } return onLineData; } /** * The function is used to refresh the UI once the data manager action is completed * * @param {ActionEventArgs} args Accepts the ActionEventArgs to refresh UI. * @param {number} position Accepts the index to refresh UI. * @param {boolean} isDropped Accepts the boolean value based on based if it is dragged and dropped * @returns {void} */ refreshUI(args, position, isDropped) { if (this.parent.enableVirtualization) { this.parent.virtualLayoutModule.columnData = this.parent.virtualLayoutModule.getColumnCards(); args.addedRecords.forEach((data, index) => { this.parent.virtualLayoutModule.renderCardBasedOnIndex(data, position + index, isDropped, args.requestType); }); args.changedRecords.forEach((data) => { this.parent.virtualLayoutModule.removeCard(data); this.parent.virtualLayoutModule.renderCardBasedOnIndex(data, position, isDropped, args.requestType); if (this.parent.virtualLayoutModule.isSelectedCard) { this.parent.actionModule.SingleCardSelection(data); } if (this.parent.sortSettings.field && this.parent.sortSettings.sortBy === 'Index' && this.parent.sortSettings.direction === 'Descending' && position > 0) { --position; } else { position++; } }); args.deletedRecords.forEach((data) => { this.parent.virtualLayoutModule.removeCard(data); }); this.parent.virtualLayoutModule.refresh(); } else { this.parent.layoutModule.columnData = this.parent.layoutModule.getColumnCards(); if (this.parent.swimlaneSettings.keyField) { this.parent.layoutModule.kanbanRows = this.parent.layoutModule.getRows(); this.parent.layoutModule.swimlaneData = this.parent.layoutModule.getSwimlaneCards(); } args.addedRecords.forEach((data, index) => { if (this.parent.swimlaneSettings.keyField && !data[this.parent.swimlaneSettings.keyField]) { data[this.parent.swimlaneSettings.keyField] = ''; } this.parent.layoutModule.renderCardBasedOnIndex(data, position + index); }); args.changedRecords.forEach((data) => { if (this.parent.swimlaneSettings.keyField && !data[this.parent.swimlaneSettings.keyField]) { data[this.parent.swimlaneSettings.keyField] = ''; } this.parent.layoutModule.removeCard(data); this.parent.layoutModule.renderCardBasedOnIndex(data, position); if (this.parent.layoutModule.isSelectedCard) { this.parent.actionModule.SingleCardSelection(data); } if (this.parent.sortSettings.field && this.parent.sortSettings.sortBy === 'Index' && this.parent.sortSettings.direction === 'Descending' && position > 0) { --position; } else { position++; } }); args.deletedRecords.forEach((data) => { this.parent.layoutModule.removeCard(data); }); this.parent.layoutModule.refresh(); } this.parent.renderTemplates(); this.parent.notify(contentReady, {}); this.parent.trigger(dataBound, args, () => this.parent.hideSpinner()); } } var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; /** * Holds the configuration of swimlane settings in kanban board. */ class SwimlaneSettings extends ChildProperty { } __decorate([ Property() ], SwimlaneSettings.prototype, "keyField", void 0); __decorate([ Property() ], SwimlaneSettings.prototype, "textField", void 0); __decorate([ Property(false) ], SwimlaneSettings.prototype, "showEmptyRow", void 0); __decorate([ Property(true) ], SwimlaneSettings.prototype, "showItemCount", void 0); __decorate([ Property(false) ], SwimlaneSettings.prototype, "allowDragAndDrop", void 0); __decorate([ Property() ], SwimlaneSettings.prototype, "template", void 0); __decorate([ Property('Ascending') ], SwimlaneSettings.prototype, "sortDirection", void 0); __decorate([ Property() ], SwimlaneSettings.prototype, "sortComparer", void 0); __decorate([ Property(true) ], SwimlaneSettings.prototype, "showUnassignedRow", void 0); __decorate([ Property(false) ], SwimlaneSettings.prototype, "enableFrozenRows", void 0); var __decorate$1 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; /** * Holds the configuration of card settings in kanban board. */ class CardSettings extends ChildProperty { } __decorate$1([ Property(true) ], CardSettings.prototype, "showHeader", void 0); __decorate$1([ Property() ], CardSettings.prototype, "headerField", void 0); __decorate$1([ Property() ], CardSettings.prototype, "contentField", void 0); __decorate$1([ Property() ], CardSettings.prototype, "tagsField", void 0); __decorate$1([ Property() ], CardSettings.prototype, "grabberField", void 0); __decorate$1([ Property() ], CardSettings.prototype, "footerCssField", void 0); __decorate$1([ Property() ], CardSettings.prototype, "template", void 0); __decorate$1([ Property('Single') ], CardSettings.prototype, "selectionType", void 0); var __decorate$2 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; /** * Holds the configuration of editor settings. */ class DialogSettings extends ChildProperty { } __decorate$2([ Property() ], DialogSettings.prototype, "template", void 0); __decorate$2([ Property([]) ], DialogSettings.prototype, "fields", void 0); __decorate$2([ Property(null) ], DialogSettings.prototype, "model", void 0); var __decorate$3 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; /** * Holds the configuration of columns in kanban board. */ class Columns extends ChildProperty { } __decorate$3([ Property() ], Columns.prototype, "keyField", void 0); __decorate$3([ Property() ], Columns.prototype, "headerText", void 0); __decorate$3([ Property() ], Columns.prototype, "template", void 0); __decorate$3([ Property(false) ], Columns.prototype, "allowToggle", void 0); __decorate$3([ Property(true) ], Columns.prototype, "isExpanded", void 0); __decorate$3([ Property() ], Columns.prototype, "minCount", void 0); __decorate$3([ Property() ], Columns.prototype, "maxCount", void 0); __decorate$3([ Property(true) ], Columns.prototype, "showItemCount", void 0); __decorate$3([ Property(false) ], Columns.prototype, "showAddButton", void 0); __decorate$3([ Property(true) ], Columns.prototype, "allowDrag", void 0); __decorate$3([ Property(true) ], Columns.prototype, "allowDrop", void 0); __decorate$3([ Property([]) ], Columns.prototype, "transitionColumns", void 0); var __decorate$4 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; /** * Holds the configuration of stacked header settings in kanban board. */ class StackedHeaders extends ChildProperty { } __decorate$4([ Property() ], StackedHeaders.prototype, "text", void 0); __decorate$4([ Property() ], StackedHeaders.prototype, "keyFields", void 0); var __decorate$5 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; /** * Holds the configuration of sort settings in kanban board. */ class SortSettings extends ChildProperty { } __decorate$5([ Property('Index') ], SortSettings.prototype, "sortBy", void 0); __decorate$5([ Property() ], SortSettings.prototype, "field", void 0); __decorate$5([ Property('Ascending') ], SortSettings.prototype, "direction", void 0); /** * Kanban CSS Constants */ /** @private */ const ROOT_CLASS = 'e-kanban'; /** @private */ const RTL_CLASS = 'e-rtl'; /** @private */ const DEVICE_CLASS = 'e-device'; /** @private */ const ICON_CLASS = 'e-icons'; /** @private */ const TEMPLATE_CLASS = 'e-template'; /** @private */ const SWIMLANE_CLASS = 'e-swimlane'; /** @private */ const TABLE_CLASS = 'e-kanban-table'; /** @private */ const HEADER_CLASS = 'e-kanban-header'; /** @private */ const HEADER_TABLE_CLASS = 'e-header-table'; /** @private */ const HEADER_CELLS_CLASS = 'e-header-cells'; /** @private */ const HEADER_WRAP_CLASS = 'e-header-wrap'; /** @private */ const HEADER_TITLE_CLASS = 'e-header-title'; /** @private */ const HEADER_TEXT_CLASS = 'e-header-text'; /** @private */ const HEADER_ICON_CLASS = 'e-header-icon'; /** @private */ const STACKED_HEADER_ROW_CLASS = 'e-stacked-header-row'; /** @private */ const STACKED_HEADER_CELL_CLASS = 'e-stacked-header-cell'; /** @private */ const CONTENT_CELLS_CLASS = 'e-content-cells'; /** @private */ const CONTENT_CLASS = 'e-kanban-content'; /** @private */ const CONTENT_TABLE_CLASS = 'e-content-table'; /** @private */ const HEADER_ROW_TOGGLE_CLASS = 'e-toggle-header'; /** @private */ const HEADER_ROW_CLASS = 'e-header-row'; /** @private */ const CONTENT_ROW_CLASS = 'e-content-row'; /** @private */ const SWIMLANE_ROW_CLASS = 'e-swimlane-row'; /** @private */ const SWIMLANE_ROW_EXPAND_CLASS = 'e-swimlane-row-expand'; /** @private */ const SWIMLANE_ROW_COLLAPSE_CLASS = 'e-swimlane-row-collapse'; /** @private */ const SWIMLANE_ROW_TEXT_CLASS = 'e-swimlane-text'; /** @private */ const CARD_ITEM_COUNT_CLASS = 'e-item-count'; /** @private */ const CARD_WRAPPER_CLASS = 'e-card-wrapper'; /** @private */ const CARD_VIRTUAL_WRAPPER_CLASS = 'e-card-virtual-wrapper'; /** @private */ const CARD_CLASS = 'e-card'; /** @private */ const DROPPABLE_CLASS = 'e-droppable'; /** @private */ const DRAG_CLASS = 'e-drag'; /** @private */ const DROP_CLASS = 'e-drop'; /** @private */ const DISABLED_CLASS = 'e-disabled'; /** @private */ const CARD_HEADER_CLASS = 'e-card-header'; /** @private */ const CARD_CONTENT_CLASS = 'e-card-content'; /** @private */ const CARD_HEADER_TEXT_CLASS = 'e-card-header-caption'; /** @private */ const CARD_HEADER_TITLE_CLASS = 'e-card-header-title'; /** @private */ const CARD_TAGS_CLASS = 'e-card-tags'; /** @private */ const CARD_TAG_CLASS = 'e-card-tag'; /** @private */ const CARD_COLOR_CLASS = 'e-card-color'; /** @private */ const CARD_LABEL_CLASS = 'e-card-label'; /** @private */ const CARD_FOOTER_CLASS = 'e-card-footer'; /** @private */ const EMPTY_CARD_CLASS = 'e-empty-card'; /** @private */ const CARD_FOOTER_CSS_CLASS = 'e-card-footer-css'; /** @private */ const COLUMN_EXPAND_CLASS = 'e-column-expand'; /** @private */ const COLUMN_COLLAPSE_CLASS = 'e-column-collapse'; /** @private */ const COLLAPSE_HEADER_TEXT_CLASS = 'e-collapse-header-text'; /** @private */ const COLLAPSED_CLASS = 'e-collapsed'; /** @private */ const DIALOG_CLASS = 'e-kanban-dialog'; /** @private */ const FORM_CLASS = 'e-kanban-form'; /** @private */ const FORM_WRAPPER_CLASS = 'e-kanban-form-wrapper'; /** @private */ const ERROR_VALIDATION_CLASS = 'e-kanban-error'; /** @private */ const FIELD_CLASS = 'e-field'; /** @private */ const DRAGGED_CLONE_CLASS = 'e-target-dragged-clone'; /** @private */ const CLONED_CARD_CLASS = 'e-cloned-card'; /** @private */ const DRAGGED_CARD_CLASS = 'e-kanban-dragged-card'; /** @private */ const DROPPED_CLONE_CLASS = 'e-target-dropped-clone'; /** @private */ const DROPPING_CLASS = 'e-dropping'; /** @private */ const BORDER_CLASS = 'e-kanban-border'; /** @private */ const TOGGLE_VISIBLE_CLASS = 'e-toggle-visible'; /** @private */ const MULTI_CARD_WRAPPER_CLASS = 'e-multi-card-wrapper'; /** @private */ const MULTI_ACTIVE_CLASS = 'e-multi-active'; /** @private */ const TARGET_MULTI_CLONE_CLASS = 'e-target-multi-clone'; /** @private */ const MULTI_COLUMN_KEY_CLASS = 'e-column-key'; /** @private */ const CARD_SELECTION_CLASS = 'e-selection'; /** @private */ const TOOLTIP_CLASS = 'e-kanban-tooltip'; /** @private */ const TOOLTIP_TEXT_CLASS = 'e-tooltip-text'; /** @private */ const SWIMLANE_HEADER_CLASS = 'e-swimlane-header'; /** @private */ const SWIMLANE_HEADER_TOOLBAR_CLASS = 'e-swimlane-header-toolbar'; /** @private */ const TOOLBAR_MENU_CLASS = 'e-toolbar-menu'; /** @private */ const TOOLBAR_MENU_ICON_CLASS = 'e-icon-menu'; /** @private */ const TOOLBAR_LEVEL_TITLE_CLASS = 'e-toolbar-level-title'; /** @private */ const TOOLBAR_SWIMLANE_NAME_CLASS = 'e-toolbar-swimlane-name'; /** @private */ const SWIMLANE_OVERLAY_CLASS = 'e-swimlane-overlay'; /** @private */ const SWIMLANE_CONTENT_CLASS = 'e-swimlane-content'; /** @private */ const SWIMLANE_RESOURCE_CLASS = 'e-swimlane-resource'; /** @private */ const SWIMLANE_TREE_CLASS = 'e-swimlane-tree'; /** @private */ const LIMITS_CLASS = 'e-limits'; /** @private */ const MAX_COUNT_CLASS = 'e-max-count'; /** @private */ const MIN_COUNT_CLASS = 'e-min-count'; /** @private */ const MAX_COLOR_CLASS = 'e-max-color'; /** @private */ const MIN_COLOR_CLASS = 'e-min-color'; /** @private */ const POPUP_HEADER_CLASS = 'e-popup-header'; /** @private */ const CLOSE_CLASS = 'e-close'; /** @private */ const POPUP_CONTENT_CLASS = 'e-popup-content'; /** @private */ const POPUP_WRAPPER_CLASS = 'e-mobile-popup-wrapper'; /** @private */ const CLOSE_ICON_CLASS = 'e-close-icon'; /** @private */ const POPUP_OPEN_CLASS = 'e-popup-open'; /** @private */ const DIALOG_CONTENT_CONTAINER = 'e-kanban-dialog-content'; /** @private */ const SHOW_ADD_BUTTON = 'e-show-add-button'; /** @private */ const SHOW_ADD_ICON = 'e-show-add-icon'; /** @private */ const SHOW_ADD_FOCUS = 'e-show-add-focus'; /** @private */ const FROZEN_SWIMLANE_ROW_CLASS = 'e-frozen-swimlane-row'; /** @private */ const FROZEN_ROW_CLASS = 'e-frozen-row'; /** @private */ const TOOLBAR_SWIMLANE_ITEM_COUNT_CLASS = 'e-toolbar-swimlane-item-count'; /* eslint-disable @typescript-eslint/no-explicit-any */ /** * Action module is used to perform card actions. */ class Action { /** * Constructor for action module * * @param {Kanban} parent Accepts the kanban instance * @private */ constructor(parent) { this.parent = parent; this.columnToggleArray = []; this.selectionArray = []; this.lastCardSelection = null; this.lastSelectionRow = null; this.lastCard = null; this.selectedCardsElement = []; this.selectedCardsData = []; this.hideColumnKeys = []; } clickHandler(e) { const elementSelector = '.' + CARD_CLASS + ',.' + HEADER_ICON_CLASS + ',.' + CONTENT_ROW_CLASS + '.' + SWIMLANE_ROW_CLASS + ',.' + SHOW_ADD_BUTTON + ',.' + FROZEN_SWIMLANE_ROW_CLASS + ',.' + CONTENT_ROW_CLASS + ':not(.' + SWIMLANE_ROW_CLASS + ') .' + CONTENT_CELLS_CLASS; const target = closest(e.target, elementSelector); if (!target) { return; } if (target.classList.contains(CARD_CLASS)) { if (this.parent.allowKeyboard) { this.parent.keyboardModule.cardTabIndexRemove(); } this.cardClick(e); } else if (target.classList.contains(HEADER_ICON_CLASS)) { this.columnExpandCollapse(e); } else if (target.classList.contains(CONTENT_ROW_CLASS) && target.classList.contains(SWIMLANE_ROW_CLASS)) { this.rowExpandCollapse(e); } else if (target.classList.contains(SHOW_ADD_BUTTON)) { this.addButtonClick(target); } else if (target.classList.contains(FROZEN_SWIMLANE_ROW_CLASS)) { const swimlaneRows = [].slice.call(this.parent.element.querySelectorAll('.' + SWIMLANE_ROW_CLASS)); const targetIcon = this.parent.layoutModule.frozenSwimlaneRow.querySelector('.' + ICON_CLASS); this.rowExpandCollapse(e, swimlaneRows[this.parent.layoutModule.frozenOrder]); const isCollapsed = targetIcon.classList.contains(SWIMLANE_ROW_COLLAPSE_CLASS) ? true : false; if (isCollapsed) { classList(targetIcon, [SWIMLANE_ROW_EXPAND_CLASS], [SWIMLANE_ROW_COLLAPSE_CLASS]); } else { classList(targetIcon, [SWIMLANE_ROW_COLLAPSE_CLASS], [SWIMLANE_ROW_EXPAND_CLASS]); } } } addButtonClick(target) { const newData = {}; if (this.parent.kanbanData.length === 0) { newData[this.parent.cardSettings.headerField] = 1; } else if (typeof (this.parent.kanbanData[0])[this.parent.cardSettings.headerField] === 'number') { const id = this.parent.kanbanData.map((obj) => parseInt(obj[this.parent.cardSettings.headerField], 10)); newData[this.parent.cardSettings.headerField] = Math.max(...id) + 1; } newData[this.parent.keyField] = closest(target, '.' + CONTENT_CELLS_CLASS).getAttribute('data-key'); if (this.parent.sortSettings.sortBy === 'Index') { newData[this.parent.sortSettings.field] = 1; if (closest(target, '.' + CONTENT_CELLS_CLASS).querySelector('.' + CARD_CLASS)) { const card = this.parent.sortSettings.direction === 'Ascending' ? target.nextElementSibling.classList.contains(BORDER_CLASS) ? target.nextElementSibling.nextElementSibling.lastElementChild : target.nextElementSibling.lastElementChild : target.nextElementSibling.classList.contains(BORDER_CLASS) ? target.nextElementSibling.nextElementSibling.firstElementChild : target.nextElementSibling.firstElementChild; const data = this.parent.getCardDetails(card); newData[this.parent.sortSettings.field] = data[this.parent.sortSettings.field] + 1; } } if (this.parent.kanbanData.length !== 0 && this.parent.swimlaneSettings.keyField && closest(target, '.' + CONTENT_ROW_CLASS).previousElementSibling) { newData[this.parent.swimlaneSettings.keyField] = closest(target, '.' + CONTENT_ROW_CLASS).previousElementSibling.getAttribute('data-key'); } this.parent.openDialog('Add', newData); } doubleClickHandler(e) { const target = closest(e.target, '.' + CARD_CLASS); if (target) { this.cardDoubleClick(e); } } cardClick(e, selectedCard) { const target = closest((selectedCard) ? selectedCard : e.target, '.' + CARD_CLASS); const cardClickObj = this.parent.getCardDetails(target); if (cardClickObj) { this.parent.activeCardData = { data: cardClickObj, element: target }; const args = { data: cardClickObj, element: target, cancel: false, event: e }; this.parent.trigger(cardClick, args, (clickArgs) => { if (!clickArgs.cancel) { if (target.classList.contains(CARD_SELECTION_CLASS) && e.type === 'click') { removeClass([target], CARD_SELECTION_CLASS); if (this.parent.enableVirtualization) { this.parent.virtualLayoutModule.disableAttributeSelection(target); } else { this.parent.layoutModule.disableAttributeSelection(target); } } else { let isCtrlKey = e.ctrlKey; if (this.parent.isAdaptive && this.parent.touchModule) { isCtrlKey = (this.parent.touchModule.mobilePopup && this.parent.touchModule.tabHold) || isCtrlKey; } this.cardSelection(target, isCtrlKey, e.shiftKey); } if (this.parent.isAdaptive && this.parent.touchModule) { this.parent.touchModule.updatePopupContent(); } const cell = closest(target, '.' + CONTENT_CELLS_CLASS); if (this.parent.allowKeyboard) { const element = [].slice.call(cell.querySelectorAll('.' + CARD_CLASS)); element.forEach((e) => { e.setAttribute('tabindex', '0'); }); this.parent.keyboardModule.addRemoveTabIndex('Remove'); } } }); } } cardDoubleClick(e) { const target = closest(e.target, '.' + CARD_CLASS); const cardDoubleClickObj = this.parent.getCardDetails(target); this.parent.activeCardData = { data: cardDoubleClickObj, element: target }; this.cardSelection(target, false, false); const args = { data: cardDoubleClickObj, element: target, cancel: false, event: e }; this.parent.trigger(cardDoubleClick, args, (doubleClickArgs) => { if (!doubleClickArgs.cancel) { this.parent.dialogModule.openDialog('Edit', args.data); } }); } rowExpandCollapse(e, isFrozenElem) { const headerTarget = (e instanceof HTMLElement) ? e : e.target; const currentSwimlaneHeader = !isNullOrUndefined(isFrozenElem) ? isFrozenElem : headerTarget; const args = { cancel: false, target: headerTarget, requestType: 'rowExpandCollapse' }; this.parent.trigger(actionBegin, args, (actionArgs) => { if (!actionArgs.cancel) { const target = closest(currentSwimlaneHeader, '.' + SWIMLANE_ROW_CLASS); const key = target.getAttribute('data-key'); const tgtRow = this.parent.element.querySelector('.' + CONTENT_ROW_CLASS + `:nth-child(${target.rowIndex + 2})`); const targetIcon = target.querySelector(`.${SWIMLANE_ROW_EXPAND_CLASS},.${SWIMLANE_ROW_COLLAPSE_CLASS}`); const isCollapsed = target.classList.contains(COLLAPSED_CLASS) ? true : false; let tabIndex; if (isCollapsed) { removeClass([tgtRow, target], COLLAPSED_CLASS); classList(targetIcon, [SWIMLANE_ROW_EXPAND_CLASS], [SWIMLANE_ROW_COLLAPSE_CLASS]); this.parent.swimlaneToggleArray.splice(this.parent.swimlaneToggleArray.indexOf(key), 1); tabIndex = '0'; } else { addClass([tgtRow, target], COLLAPSED_CLASS); classList(targetIcon, [SWIMLANE_ROW_COLLAPSE_CLASS], [SWIMLANE_ROW_EXPAND_CLASS]); this.parent.swimlaneToggleArray.push(key); tabIndex = '-1'; } targetIcon.setAttribute('aria-label', isCollapsed ? key + ' Expand' : key + ' Collapse'); target.setAttribute('aria-expanded', isCollapsed.toString()); tgtRow.setAttribute('aria-expanded', isCollapsed.toString()); const rows = [].slice.call(tgtRow.querySelectorAll('.' + CONTENT_CELLS_CLASS)); rows.forEach((cell) => { cell.setAttribute('tabindex', tabIndex); }); this.parent.notify(contentReady, {}); this.parent.trigger(actionComplete, { target: headerTarget, requestType: 'rowExpandCollapse' }); } }); } columnExpandCollapse(e) { const headerTarget = (e instanceof HTMLElement) ? e : e.target; const args = { cancel: false, target: headerTarget, requestType: 'columnExpandCollapse' }; this.parent.trigger(actionBegin, args, (actionArgs) => { if (!actionArgs.cancel) { const target = closest(headerTarget, '.' + HEADER_CELLS_CLASS); const colIndex = target.cellIndex; this.columnToggle(target); const collapsed = this.parent.element.querySelectorAll(`.${HEADER_CELLS_CLASS}.${COLLAPSED_CLASS}`).length; if (collapsed === (this.parent.columns.length - this.hideColumnKeys.length)) { const index = (colIndex + 1 === collapsed) ? 1 : colIndex + 2; const headerSelector = `.${HEADER_CELLS_CLASS}:not(.${STACKED_HEADER_CELL_CLASS}):nth-child(${index})`; const nextCol = this.parent.element.querySelector(headerSelector); addClass([nextCol], COLLAPSED_CLASS); this.columnToggle(nextCol); } this.parent.notify(contentReady, {}); this.parent.trigger(actionComplete, { target: headerTarget, requestType: 'columnExpandCollapse' }); } }); } columnToggle(target) { const colIndex = target.cellIndex; const elementSelector = `.${CONTENT_ROW_CLASS}:not(.${SWIMLANE_ROW_CLASS})`; const targetRow = [].slice.call(this.parent.element.querySelectorAll(elementSelector)); const colSelector = `.${TABLE_CLASS} col:nth-child(${colIndex + 1})`; const targetIcon = target.querySelector(`.${COLUMN_EXPAND_CLASS},.${COLUMN_COLLAPSE_CLASS}`); const colGroup = [].slice.call(this.parent.element.querySelectorAll(colSelector)); if (target.classList.contains(COLLAPSED_CLASS)) { removeClass(colGroup, COLLAPSED_CLASS); if (this.parent.isAdaptive) { if (this.parent.enableVirtualization) { colGroup.forEach((col) => col.style.width = formatUnit(this.parent.virtualLayoutModule.getWidth())); } else { colGroup.forEach((col) => col.style.width = formatUnit(this.parent.layoutModule.getWidth())); } } classList(targetIcon, [COLUMN_EXPAND_CLASS], [COLUMN_COLLAPSE_CLASS]); for (const row of targetRow) { const targetCol = row.querySelector(`.${CONTENT_CELLS_CLASS}:nth-child(${colIndex + 1})`); removeClass([targetCol, target], COLLAPSED_CLASS); remove(targetCol.querySelector('.' + COLLAPSE_HEADER_TEXT_CLASS)); const showAddButton = targetCol.querySelector('.e-show-add-button'); if (showAddButton) { removeClass([showAddButton], COLLAPSED_CLASS); } target.setAttribute('aria-expanded', 'true'); targetCol.setAttribute('aria-expanded', 'true'); const collapsedCell = [].slice.call(targetCol.parentElement.querySelectorAll('.' + COLLAPSED_CLASS)); collapsedCell.forEach((cell) => { const collapasedText = cell.querySelector('.' + COLLAPSE_HEADER_TEXT_CLASS); collapasedText.style.height = 'auto'; if (collapasedText && targetCol.getBoundingClientRect().height < (collapasedText.getBoundingClientRect().height + 10)) { collapasedText.style.height = (targetCol.getBoundingClientRect().height - 4) + 'px'; } }); } if (this