UNPKG

comindware.core.ui

Version:

Comindware Core UI provides the basic components like editors, lists, dropdowns, popups that we so desperately need while creating Marionette-based single-page applications.

1,208 lines (1,067 loc) 47.7 kB
/* eslint-disable no-param-reassign */ import form from 'form'; import dropdown from 'dropdown'; import { transliterator } from 'utils'; import LocalizationService from 'services/LocalizationService'; import { validationSeverityTypes, validationSeverityClasses } from 'Meta'; import template from '../templates/grid.hbs'; import CollectionView from './CollectionView'; import RowView from './RowView'; import GridHeaderView from './header/GridHeaderView'; import LoadingChildView from './LoadingRowView'; import ToolbarView from '../../components/toolbar/ToolbarView'; import ActionMenuView from '../../components/toolbar/views/ActionMenuView'; import MobileService from '../../services/MobileService'; import LoadingBehavior from '../../views/behaviors/LoadingBehavior'; import SearchBarView from '../../views/SearchBarView'; import EmptyGridView from './EmptyGridView'; import LayoutBehavior from '../../layout/behaviors/LayoutBehavior'; import { getDefaultActions, classes, configurationConstants, contextTypes } from '../meta'; import factory from '../factory'; import ErrorButtonView from '../../views/ErrorButtonView'; import InfoButtonView from '../../views/InfoButtonView'; import TooltipPanelView from '../../views/TooltipPanelView'; import ErrorsPanelView from '../../views/ErrorsPanelView'; import GlobalEventService from '../../services/GlobalEventService'; import { GraphModel } from '../../components/treeEditor/types'; import Backbone from 'backbone'; import { GridItemBehavior } from '../behaviors/GridItemBehavior'; /* Public interface: This view produce: trigger: positionChanged (sender, { oldPosition, position }) trigger: viewportHeightChanged (sender, { oldViewportHeight, viewportHeight }) This view react on: collection change (via Backbone.Collection events) position change (when we scroll with scrollbar for example): updatePosition(newPosition) */ const defaultOptions = options => ({ focusSearchOnAttach: !MobileService.isMobile, searchBarOptions: { isAutoHideable: true }, columns: [], emptyView: EmptyGridView, emptyViewOptions: { text: () => (options.columns?.length ? Localizer.get('CORE.GRID.EMPTYVIEW.EMPTY') : Localizer.get('CORE.GRID.NOCOLUMNSVIEW.ALLCOLUMNSHIDDEN')), colspan: options.columns ? options.columns.length + !!options.showCheckbox : 0 }, draggable: false, isSliding: true, showHeader: true, handleSearch: true, updateToolbarEvents: '', showTreeEditor: false, showContextMenu: true, treeEditorIsHidden: false, treeEditorConfig: new Map(), headerHeight: 30 }); /** * @name GridView * @memberof module:core.list.views * @class GridView * @constructor * @description View-контейнер для заголовка и контента * @extends Marionette.View * @param {Object} options Constructor options * @param {Array} options.collection массив элементов списка * @param {Array} options.columns массив колонок * @param {Backbone.View} options.emptyView View для отображения пустого списка (нет строк) или не инициализированы колонки. * @param {Number} options.childHeight высота строки списка (childView) * @param {Backbone.View} [options.childView] view строки списка * @param {Backbone.View} [options.childViewOptions] опции для childView * @param {Function} options.childViewSelector ? * @param {Object} [options.emptyViewOptions] опции для emptyView * @param {String} options.height задает как определяется высота строки, значения: fixed, auto * @param {Backbone.View} [options.loadingChildView] view-лоадер, показывается при подгрузке строк * @param {Number} options.maxRows максимальное количество отображаемых строк (используется с опцией height: auto) * @param {Boolean} options.useDefaultRowView использовать RowView по умолчанию. * @param {Array} options.excludeActions Array of strings. Example: <code>[ 'archive', 'delete' ]</code>. * @param {Array} options.additionalActions Array of objects <code>[ id, name,* type=button'|'checkbox', isChecked, iconClass, severity]</code>. * @param {Boolean} options.showCheckbox show or hide checkbox * В случае, если true — обязательно должны быть указаны cellView для каждой колонки * */ export default Marionette.View.extend({ initialize(options) { this.__onScroll = this.__onScroll.bind(this); _.defaults(this.options, defaultOptions(options)); const comparator = factory.getDefaultComparator(this.options.columns); this.columnsCollection = this.options.columnsCollection || new Backbone.Collection(this.options.columns.map(column => ({ id: column.id, ...column }))); this.columnCollectionDefault = this.columnsCollection.clone(); this.collection = factory.createWrappedCollection({ ...this.options, comparator }); if (this.collection === undefined) { throw new Error('You must provide a collection to display.'); } if (typeof this.options.transliteratedFields === 'object') { transliterator.setOptionsToFieldsOfNewSchema(this.options.columns, this.options.transliteratedFields); } this.options.onColumnSort && (this.onColumnSort = this.options.onColumnSort); //jshint ignore:line this.uniqueId = _.uniqueId('native-grid'); const HeaderView = this.options.headerView || GridHeaderView; if (this.options.showHeader !== false) { this.options.showHeader = true; } if (this.options.showHeader) { this.headerView = new HeaderView( _.defaultsPure( { columns: this.options.columns, columnsCollection: this.columnsCollection, gridEventAggregator: this, checkBoxPadding: options.checkBoxPadding || 0, uniqueId: this.uniqueId, isTree: this.options.isTree, expandOnShow: options.expandOnShow, showCheckbox: this.options.showCheckbox }, this.options ) ); this.listenTo(this.headerView, 'onColumnSort', this.onColumnSort, this); this.listenTo(this.headerView, 'update:width', (config: { index: number, newColumnWidth: number }) => this.__handleColumnWidthChange(config)); this.listenTo(this.headerView, 'change:isEveryColumnSetPxWidth', () => this.__toggleTableWidth()); this.listenTo(this.headerView, 'scroll:left', this.__scrollLeft); } this.isEditable = typeof this.options.editable === 'boolean' ? this.options.editable : this.options.columns.some(column => column.editable); if (this.isEditable) { this.editableCellsIndexes = []; this.options.columns.forEach((column, index) => { if (column.editable) { this.editableCellsIndexes.push(index); } }); this.listenTo(this.collection, 'move:left', () => this.__onCursorMove(-1)); this.listenTo(this.collection, 'move:right select:hidden', () => this.__onCursorMove(+1)); this.listenTo(this.collection, 'select:all select:some select:one', this.__onCollectionSelect); this.listenTo(this.collection, 'keydown:default', this.__onKeydown); this.listenTo(this.collection, 'keydown:escape', e => this.__triggerSelectedModel('selected:exit', e)); this.listenTo(this.collection, 'change', this.__validateModel); } if (this.options.showContextMenu) { this.listenTo(this.collection, 'contextmenu', this.__onContextMenu); } this.__initializeToolbar(); if (this.options.showSearch) { this.searchView = new SearchBarView(this.options.searchBarOptions); this.listenTo(this.searchView, 'search', this.__onSearch); } if (this.options.showTreeEditor) { this.__initTreeEditor(); } }, __initializeToolbar() { if (!this.options.showToolbar) { return; } this.toolbarView = new ToolbarView({ toolbarItems: this.__getToolbarActions() || [], skipActionHandlers: true }); const allToolbarActions = this.toolbarView.getToolbarItems(); const debounceUpdateAction = _.debounce(() => this.__updateActions(allToolbarActions), 10); this.__updateActions(allToolbarActions); if (this.options.showCheckbox) { this.listenTo(this.collection, 'check:all check:some check:none', debounceUpdateAction); } else { this.listenTo(this.collection, 'select:all select:some select:none deselect:one select:one', debounceUpdateAction); } if (this.options.updateToolbarEvents) { this.listenTo(this.collection.parentCollection, this.options.updateToolbarEvents, debounceUpdateAction); } this.listenTo(this.toolbarView, 'command:execute', (model, ...rest) => this.__executeAction(model, this.collection, ...rest)); }, __handleColumnWidthChange(config: { index: number, newColumnWidth: number }) { const { index, newColumnWidth } = config; const columnModel = this.options.columns[index].columnModel; if (columnModel) { columnModel.set('width', newColumnWidth); const configWidthColumn = new Map(); configWidthColumn.set(this.options.columns[index].columnModel.id, { width: newColumnWidth }); this.setConfigDiff(configWidthColumn); this.trigger('treeEditor:save', this.getConfigDiff()); } }, updatePosition(position, shouldScrollElement = false) { const newPosition = this.__checkFillingViewport(position); if ( newPosition === this.listView.state.position || !this.collection.isSliding || Math.abs(newPosition - this.listView.state.position) < configurationConstants.VISIBLE_COLLECTION_RESERVE_HALF - 1 ) { return; } this.collection.updatePosition(Math.max(0, newPosition - configurationConstants.VISIBLE_COLLECTION_RESERVE_HALF)); this.__updateTop(); this.listView.state.position = newPosition; if (shouldScrollElement) { this.internalScroll = true; const wraper = this.ui.tableTopMostWrapper.get(0); let scrollTop; if (position === this.collection.length - 1) { scrollTop = wraper.scrollHeight - wraper.clientHeight; } else { scrollTop = newPosition * this.listView.childHeight; } wraper.scrollTop = scrollTop; _.delay(() => (this.internalScroll = false), 100); } this.__updateTop(); return newPosition; }, __updateTop() { const top = Math.max(0, this.collection.indexOf(this.collection.visibleModels[0]) * this.listView.childHeight + (this.options.showHeader ? 0 : 1)); // first item border if (top !== this.oldTop) { this.oldTop = top; this.ui.content[0].style.top = `${top}px`; } }, __checkFillingViewport(position) { const maxPosFirstRow = Math.max(0, this.collection.length - this.listView.state.viewportHeight); return Math.max(0, Math.min(maxPosFirstRow, position)); }, __onScroll(event: MouseEvent) { const nextScroll = this.ui.tableTopMostWrapper[0].scrollTop; if ( this.listView.state.viewportHeight === undefined || this.__prevScroll === nextScroll || this.isDestroyed() || this.collection.length <= this.listView.state.viewportHeight || this.internalScroll ) { return; } this.__prevScroll = nextScroll; const newPosition = Math.max(0, Math.floor(nextScroll / this.listView.childHeight)); this.updatePosition(newPosition, false); }, __onCollectionSelect(collection, options) { this.stopListening(GlobalEventService, 'window:mousedown:captured', this.__checkBlur); this.listenTo(GlobalEventService, 'window:mousedown:captured', this.__checkBlur); this.__onCursorMove(0, options); }, __checkBlur(target: Element) { if (this.isDestroyed()) { return; } const popupContainer = document.querySelector('.js-global-popup-stack'); const spContainer = document.querySelector('.sp-container'); const isElementOutOfElementOrPopup = this.el.contains(target) || popupContainer?.contains(target) || spContainer?.contains(target); if (!isElementOutOfElementOrPopup) { this.collection.selectNone ? this.collection.selectNone() : this.collection.deselect(); this.stopListening(GlobalEventService, 'window:mousedown:captured', this.__checkBlur); } }, __onCursorMove(delta, options = {}) { const rangeVisibleCellIndex = this.editableCellsIndexes.filter((indexColumn: number) => { const column = this.options.columns[indexColumn]; if (column && column.getStateHidden) { return !column.getStateHidden(); } return true; }); const maxIndex = rangeVisibleCellIndex.length - 1; const currentSelectedIndex = rangeVisibleCellIndex.indexOf(this.pointedCell); const newPosition = Math.min(maxIndex, Math.max(0, currentSelectedIndex + delta)); const currentSelectedValue = rangeVisibleCellIndex[currentSelectedIndex]; const newSelectedValue = rangeVisibleCellIndex[newPosition]; const currentModel = this.collection.find(model => model.cid === this.collection.cursorCid); if (currentModel) { if (newSelectedValue === currentSelectedValue && delta !== 0) { const isPositiveDelta = delta >= 1; this.pointedCell = isPositiveDelta ? 0 : rangeVisibleCellIndex[rangeVisibleCellIndex.length - 1]; this.collection.trigger(isPositiveDelta ? 'nextModel' : 'prevModel'); return; } this.pointedCell = newSelectedValue; !options.isModelClick && currentModel.trigger('select:pointed', this.pointedCell, false); } }, __onKeydown(e) { this.__triggerSelectedModel('selected:enter', e); }, __triggerSelectedModel(triggerEvent, ...args) { const selectedModel = this.collection.find(model => model.cid === this.collection.cursorCid); if (selectedModel) { selectedModel.trigger(triggerEvent, ...args); } }, toggleSearchActivity(enableSearch) { this.searchView.toggleInputActivity(enableSearch); }, onColumnSort(column, comparator) { this.collection.comparator = comparator; this.collection.sort(); }, regions: { headerRegion: '.js-grid-header-view', contentRegion: { el: '.js-grid-content-view', replaceElement: true }, toolbarRegion: { el: '.js-grid-tools-toolbar-region', replaceElement: true }, searchRegion: { el: '.js-grid-tools-search-region', replaceElement: true }, loadingRegion: '.js-grid-loading-region', errorTextRegion: '.js-grid-error-text-region', helpTextRegion: '.js-grid-help-text-region' }, ui: { title: '.js-grid-title', tools: '.js-grid-tools', header: '.js-grid-header-view', content: '.js-grid-content', tableWrapper: '.js-grid-table-wrapper', table: '.grid-content-wrp', tableTopMostWrapper: '.grid-table-wrapper-war' }, events: { dragleave: '__handleDragLeave' }, className() { return `${this.options.class || ''} grid-container`; }, template: Handlebars.compile(template), behaviors: { LoadingBehavior: { behaviorClass: LoadingBehavior, region: 'loadingRegion' }, LayoutBehavior: { behaviorClass: LayoutBehavior } }, onRender() { if (this.options.showHeader) { this.showChildView('headerRegion', this.headerView); this.__toggleTableWidth(); } else { this.el.classList.add('grid__headless'); } if (this.options.showToolbar) { this.showChildView('toolbarRegion', this.toolbarView); } if (this.options.showSearch) { this.showChildView('searchRegion', this.searchView); } if (!(this.options.showToolbar || this.options.showSearch)) { this.ui.tools.hide(); } const title = this.getOption('title'); if (title) { this.ui.title.text(this.getOption('title')); } else { this.ui.title.parent().addClass('form-label_empty'); } if (this.options.helpText) { const viewModel = new Backbone.Model({ helpText: this.options.helpText, errorText: null }); const infoPopout = dropdown.factory.createPopout({ buttonView: InfoButtonView, panelView: TooltipPanelView, panelViewOptions: { model: viewModel, textAttribute: 'helpText' }, popoutFlow: 'right', customAnchor: true }); this.showChildView('helpTextRegion', infoPopout); } this.setRequired(this.options.required); this.__updateState(); if (Core.services.MobileService.isIE) { this.ui.tableTopMostWrapper[0].addEventListener('scroll', this.__onScroll); } else { this.ui.tableTopMostWrapper[0].addEventListener('scroll', this.__onScroll, { passive: true }); } }, onAttach() { if (this.options.maxHeight) { this.ui.tableTopMostWrapper.get(0).style.maxHeight = `${this.options.maxHeight}px`; } const childView = this.options.childView || RowView; const showRowIndex = this.getOption('showRowIndex'); const childViewOptions = (this.childViewOptions = Object.assign(this.options.childViewOptions || {}, { columns: this.options.columns, transliteratedFields: this.options.transliteratedFields, gridEventAggregator: this, isTree: this.options.isTree, showCheckbox: this.options.showCheckbox, draggable: this.options.draggable, showRowIndex, showContextMenu: this.options.showContextMenu })); this.listView = new CollectionView({ collection: this.collection, gridEventAggregator: this, childView, childViewSelector: this.options.childViewSelector, emptyView: this.options.emptyView, emptyViewOptions: this.options.emptyViewOptions, childHeight: this.options.childHeight, showHeader: this.options.showHeader, childViewOptions, filter: this.options.filter, loadingChildView: this.options.loadingChildView || LoadingChildView, maxRows: this.options.maxRows, height: this.options.height, isTree: this.options.isTree, isEditable: this.isEditable, draggable: this.options.draggable, showCheckbox: this.options.showCheckbox, showRowIndex, parentEl: this.ui.tableTopMostWrapper[0], parent$el: this.ui.tableTopMostWrapper, table$el: this.ui.table, minimumVisibleRows: this.options.minimumVisibleRows, selectOnCursor: this.options.selectOnCursor, headerHeight: this.options.showHeader ? this.options.headerHeight : 0 }); this.listenTo(this.listView, 'update:position:internal', state => this.updatePosition(state.topIndex, state.shouldScrollElement)); this.showChildView('contentRegion', this.listView); if (this.options.showSearch && this.options.focusSearchOnAttach) { this.searchView.focus(); } this.listenTo(this.listView, 'drag:drop', this.__onItemMoved); if (this.options.isSliding) { this.listenTo(GlobalEventService, 'window:resize', () => this.updateListViewResize({ newMaxHeight: window.innerHeight, shouldUpdateScroll: false })); } if (this.options.columns.length && this.options.showHeader) { this.__toggleNoColumnsMessage(this.options.columns); } }, getChildren() { return this.listView.children; }, update() { this.__updateState(); }, updateListViewResize(options) { if (options.newMaxHeight) { const table = this.ui.tableTopMostWrapper.get(0); table.style.maxHeight = `${options.newMaxHeight - table.getBoundingClientRect().top}px`; } this.listView.handleResize(options.shouldUpdateScroll); }, onBeforeDestroy() { this.__configurationPanel && this.__configurationPanel.destroy(); if (this.isRendered()) { if (Core.services.MobileService.isIE) { this.ui.tableTopMostWrapper[0].removeEventListener('scroll', this.__onScroll); } else { this.ui.tableTopMostWrapper[0].removeEventListener('scroll', this.__onScroll, { passive: true }); } } }, sortBy(columnIndex, sorting) { const column = this.options.columns[columnIndex]; if (sorting) { this.options.columns.forEach(c => (c.sorting = null)); column.sorting = sorting; switch (sorting) { case 'asc': this.collection.comparator = column.sortAsc; break; case 'desc': this.collection.comparator = column.sortDesc; break; default: break; } } else { sorting = column.sorting; this.options.columns.forEach(c => (c.sorting = null)); switch (sorting) { case 'asc': column.sorting = 'desc'; this.collection.comparator = column.sortDesc; break; case 'desc': column.sorting = 'asc'; this.collection.comparator = column.sortAsc; break; default: column.sorting = 'asc'; this.collection.comparator = column.sortAsc; break; } } this.onColumnSort(column, this.collection.comparator); if (this.options.showHeader) { this.headerView.updateSorting(); } }, handleResize() { if (this.options.showHeader) { this.headerView.handleResize(); } }, setLoading(state) { if (!this.isDestroyed()) { this.loading.setLoading(state); } }, validate() { const errors = []; if (this.required && this.collection.length === 0) { errors.push({ type: 'required', message: Localizer.get('CORE.FORM.VALIDATION.REQUIREDGRID'), severity: 'Error' }); } if (this.isEditable) { let isErrorInCells = false; this.collection.forEach(model => { const modelHasErrors = this.__validateModel(model); if (modelHasErrors) { isErrorInCells = true; } }); if (isErrorInCells) { errors.push({ type: 'gridError', message: Localizer.get('CORE.FORM.VALIDATION.GRIDERROR'), severity: 'Error' }); } } if (errors.length) { this.setError(errors); return errors; } this.clearError(); return null; }, setDraggable(draggable: boolean): void { this.childViewOptions.draggable = draggable; this.trigger('set:draggable', draggable); this.listView.setDraggable(draggable); }, __validateModel(model) { let hasErrors = false; delete model.validationError; if (!model.isValid()) { hasErrors = true; } this.options.columns.forEach(column => { if (!column.editable || !column.validators) { return; } column.validators.forEach(validatorOptions => { const validator = form.repository.getValidator(validatorOptions); const fieldError = validator(model.get(column.key), model.attributes); if (fieldError) { fieldError; hasErrors = true; if (!model.validationError) { model.validationError = {}; } if (!model.validationError[column.key]) { model.validationError[column.key] = []; } model.validationError[column.key].push(fieldError); } }); }); model.trigger('validated'); return hasErrors; }, replaceColumns(newColumns = []) { const columns = this.options.columns; newColumns.forEach(newColumn => { const index = columns.findIndex(column => column.key === newColumn.key); const [oldColumn] = columns.splice(index, 1, newColumn); newColumn.columnClass = oldColumn.columnClass; }); this.listView.render(); }, __handleDragLeave(event: { originalEvent: DragEvent }) { const dragToElement = event.originalEvent.relatedTarget; if (this.el.contains(dragToElement) || !document.body.contains(dragToElement)) { return; } this.collection.dragoverModel?.trigger('dragleave'); }, // __setCheckBoxColummWidth() { // const lastVisibleModelIndex = this.collection.indexOf(this.collection.visibleModels[this.collection.visibleModels.length - 1]) + 1; // const isMainTheme = Core.services.ThemeService.getTheme() === 'main'; // const baseWidth = isMainTheme ? 37 : 42; // const numberWidth = isMainTheme ? 7.3 : 7.44; // this.__setColumnWidth(this.options.columns.length, baseWidth + lastVisibleModelIndex.toString().length * numberWidth, undefined, true); // }, // __setColumnWidth(index: number, width = 0, allColumnsWidth, isCheckBoxCell: boolean) { // const style = this.styleSheet; // const columnClass = ''; //this.columnClasses[index]; // const regexp = isCheckBoxCell ? new RegExp(`.${columnClass} { width: \\d+\\.?\\d*px; } `) : new RegExp(`.${columnClass} { flex: [0,1] 0 [+, -]?\\S+\\.?\\S*; } `); // let basis; // if (width > 0) { // if (width < 1) { // basis = `${width * 100}%`; // } else { // basis = `${width}px`; // } // } else { // const column = this.options.columns[index]; // if (column.format === 'HTML') { // basis = '0%'; // } else { // const defaultWidth = columnWidthByType[column.dataType]; //what is it? // if (defaultWidth) { // basis = `${defaultWidth}px`; // } else { // basis = '0%'; // } // } // } // const grow = width > 0 ? 0 : 1; // const newValue = isCheckBoxCell ? `.${columnClass} { width: ${width}px; } ` : `.${columnClass} { flex: ${grow} 0 ${basis}; } `; // if (regexp.test(style.innerHTML)) { // style.innerHTML = style.innerHTML.replace(regexp, newValue); // } else { // style.innerHTML += newValue; // } // this.__updateEmptyView(allColumnsWidth); // }, __executeAction(model, collection, ...rest) { const selected = this.__getSelectedItems(collection); switch (model.get('id')) { case 'delete': this.__confirmUserAction( Localizer.get('CORE.GRID.ACTIONS.DELETE.CONFIRM.TEXT'), Localizer.get('CORE.GRID.ACTIONS.DELETE.CONFIRM.TITLE'), Localizer.get('CORE.GRID.ACTIONS.DELETE.CONFIRM.YESBUTTONTEXT'), Localizer.get('CORE.GRID.ACTIONS.DELETE.CONFIRM.NOBUTTONTEXT') ).then(result => { if (result) { this.__triggerAction(model, selected, ...rest); } }); break; case 'archive': this.__confirmUserAction( Localizer.get('CORE.GRID.ACTIONS.ARCHIVE.CONFIRM.TEXT'), Localizer.get('CORE.GRID.ACTIONS.ARCHIVE.CONFIRM.TITLE'), Localizer.get('CORE.GRID.ACTIONS.ARCHIVE.CONFIRM.YESBUTTONTEXT'), Localizer.get('CORE.GRID.ACTIONS.ARCHIVE.CONFIRM.NOBUTTONTEXT') ).then(result => { if (result) { this.__triggerAction(model, selected, ...rest); } }); break; case 'unarchive': this.__confirmUserAction( Localizer.get('CORE.GRID.ACTIONS.UNARCHIVE.CONFIRM.TEXT'), Localizer.get('CORE.GRID.ACTIONS.UNARCHIVE.CONFIRM.TITLE'), Localizer.get('CORE.GRID.ACTIONS.UNARCHIVE.CONFIRM.YESBUTTONTEXT'), Localizer.get('CORE.GRID.ACTIONS.UNARCHIVE.CONFIRM.NOBUTTONTEXT') ).then(result => { if (result) { this.__triggerAction(model, selected, ...rest); } }); break; case 'add': default: this.__triggerAction(model, selected, ...rest); break; } }, __confirmUserAction(text: string = '', title: string = '', yesButtonText: string = 'Yes', noButtonText: string = 'No') { return Core.services.MessageService.showMessageDialog(text, title, [ { id: false, isCancel: true, text: noButtonText }, { id: true, text: yesButtonText } ]); }, __triggerAction(model, selected, ...rest) { const handler = model.get('handler'); if (typeof handler === 'function') { handler(model, selected, ...rest); return; } this.trigger('execute', model, selected, ...rest); }, __onItemMoved(...args) { this.trigger('move', ...args); }, setError(errors: Array<any>): void { if (!this.__checkUiReady()) { return; } const isWarning = errors.every(error => error.severity?.toLowerCase() === validationSeverityTypes.WARNING); if (isWarning) { this.el.classList.add(validationSeverityClasses.WARNING); } else { this.el.classList.add(validationSeverityClasses.ERROR); } this.errorCollection ? this.errorCollection.reset(errors) : (this.errorCollection = new Backbone.Collection(errors)); if (!this.isErrorShown) { const errorPopout = dropdown.factory.createPopout({ buttonView: ErrorButtonView, panelView: ErrorsPanelView, panelViewOptions: { collection: this.errorCollection }, popoutFlow: 'right', customAnchor: true }); this.showChildView('errorTextRegion', errorPopout); this.isErrorShown = true; } }, clearError(): void { if (!this.__checkUiReady()) { return; } this.el.classList.remove(validationSeverityClasses.ERROR); this.el.classList.remove(validationSeverityClasses.WARNING); this.errorCollection && this.errorCollection.reset(); }, setRequired(required: boolean) { if (!this.__checkUiReady()) { return; } this.required = required; this.__updateEmpty(); if (required) { this.listenTo(this.collection, 'add remove reset update', this.__updateEmpty); } else { this.stopListening(this.collection, 'add remove reset update', this.__updateEmpty); } }, __updateEmpty() { if (this.required) { this.__toggleRequiredClass(this.collection.length === 0); } else { this.__toggleRequiredClass(false); } }, __toggleRequiredClass(required) { if (!this.__checkUiReady()) { return; } this.$el.toggleClass(classes.required, Boolean(required)); }, __checkUiReady() { return this.isRendered() && !this.isDestroyed(); }, __onSearch(text) { if (this.options.isTree) { this.trigger('toggle:collapse:all', !text && !this.options.expandOnShow); } if (!this.getOption('handleSearch')) { this.trigger('search', text); return; } if (text) { this.__applyFilter(new RegExp(text, 'i'), this.options.columns, this.collection); this.__highlightCollection(text, this.collection); } else { this.__clearFilter(this.collection); this.__unhighlightCollection(this.collection); } }, __applyFilter(regexp, columns, collection) { collection.filter(model => { let result = false; const searchableColumns = columns.filter(column => column.searchable !== false).map(column => column.key); searchableColumns.forEach(column => { const values = model.get(column); const testValueFunction = value => { if (value) { const testValue = value.name || value.text || value.toString(); return regexp.test(testValue); } }; if (Array.isArray(values) && values.length) { values.forEach(value => { result = result || testValueFunction(value); }); } else { result = result || testValueFunction(values); } }); return result; }); }, __clearFilter(collection) { collection.filter(); }, __highlightCollection(text, collection) { collection.each(model => { model.highlight(text); }); }, __unhighlightCollection(collection) { collection.each(model => { model.unhighlight(); }); }, updateToolbar() { this.__updateActions(this.toolbarView.getToolbarItems()); }, __getToolbarActions() { let toolbarActions = []; const defaultActions = getDefaultActions(); if (!this.options.excludeActions) { toolbarActions = defaultActions; } else if (this.options.excludeActions !== 'all') { toolbarActions = defaultActions.filter(action => this.options.excludeActions.indexOf(action.id) === -1); } if (this.options.additionalActions) { toolbarActions = toolbarActions.concat(this.options.additionalActions); } return toolbarActions; }, __updateActions(allToolbarActions, selected = this.__getSelectedItems(this.collection)) { const selectedLength = selected.length; const customItems = this.toolbarView.getCustomItems().toJSON(); const actionsCount = (this.originalToolbar ?? customItems).filter(action => ![contextTypes.any, contextTypes.one].includes(action.contextType)).length; if (allToolbarActions.parentCollection && actionsCount > 3) { if (selectedLength) { allToolbarActions.reset([...this.originalToolbar, ...this.toolbarView.getConstItems().toJSON()]); } else { if (!this.originalToolbar) { this.originalToolbar = customItems; } const actions = { type: 'Group', name: LocalizationService.get('CORE.GRID.ACTIONS'), items: customItems }; allToolbarActions.reset([actions, ...this.toolbarView.getConstItems().toJSON()]); } } return allToolbarActions.filter(action => { if (action.get('type') === 'Group') { const items = action.get('originalItems') || action.get('items'); const filtered = this.__updateActions(items, selected); if (!filtered.length) { return false; } action.set('originalItems', items); action.set('items', filtered); } let isActionApplicable; switch (action.get('contextType')) { case 'one': isActionApplicable = selectedLength === 1; break; case 'any': isActionApplicable = selectedLength; break; case 'void': isActionApplicable = !selectedLength; break; default: isActionApplicable = true; } const condition = action.get('condition'); if (isActionApplicable && condition && typeof condition === 'function') { isActionApplicable = condition(selected); } return isActionApplicable; }); }, __getSelectedItems(collection) { const selected = (this.options.showCheckbox ? collection.checked : collection.selected) || {}; if (selected instanceof Backbone.Model) { return [selected]; } return Object.values(selected); }, __initTreeEditor() { const columnsCollection = this.columnsCollection; this.listenTo(columnsCollection, 'columns:move', config => this.__reorderColumns(config)); this.treeEditor = new Core.components.TreeEditor({ hidden: this.options.treeEditorIsHidden, model: this.options.treeEditorModel, configDiff: this.options.treeEditorConfig, getNodeName: this.options.getNodeName || (model => model.get('title')), nestingOptions: this.options.nestingOptions, childsFilter: this.options.childsFilter, showPanelViewOnly: this.options.showPanelViewOnly, showTreeEditorHeader: this.options.showTreeEditorHeader }); this.__onDiffApplied(); this.listenTo(columnsCollection, 'add', (model: GraphModel) => { const configDiff = { oldIndex: this.options.columns.findIndex(col => col.id === model.id), newIndex: columnsCollection.indexOf(model) }; this.__moveColumn(configDiff); }); this.listenTo(columnsCollection, 'change:isHidden change:required', model => { this.__setColumnVisibility(model.id, !model.get('isHidden') || model.get('required')); }); }, getTreeEditorView() { return this.treeEditor.getView(); }, setConfigDiff(configDiff) { this.treeEditor.setConfigDiff(configDiff); }, resetConfigDiff() { this.treeEditor.resetConfigDiff(['index', 'isHidden']); }, setInitConfig(initConfig) { this.treeEditor.setInitConfig(initConfig); }, getConfigDiff() { return this.treeEditor.getConfigDiff(); }, __setVisibilityAllColumns() { this.options.columns.forEach(column => this.__setColumnVisibility(column.id, !column.isHidden)); }, __onDiffApplied() { const columnsCollection = this.columnsCollection; this.options.columns.forEach(column => { const model = columnsCollection.get(column.id || column.key); Object.entries(model.pick('width', 'isHidden')).forEach(([id, value]) => (column[id] = value)); }); this.__setVisibilityAllColumns(); }, __reorderColumns(config: string[]) { this.options.columns.sort((a, b) => config.indexOf(a.key) - config.indexOf(b.key)); // TODO a, b type: Column }, __moveColumn(options: { oldIndex: number, newIndex: number }) { const { oldIndex, newIndex } = options; const one = Number(!!this.el.querySelector('.js-cell_selection')); const headerElementsCollection = this.el.querySelectorAll('.grid-header-column'); if (newIndex === oldIndex) { return; } if (newIndex < 0 || newIndex >= headerElementsCollection.length) { return; } const moveElement = el => { const parentElement = el.parentElement; parentElement.removeChild(el); parentElement.insertBefore(el, parentElement.children[newIndex + one]); }; const element = headerElementsCollection[oldIndex]; if (element) { moveElement(element); const cells = Array.from(this.el.querySelectorAll(`tbody tr > td:nth-child(${oldIndex + 1 + one})`)); cells.forEach(row => moveElement(row)); this.__moveArrayElement(this.options.columns, oldIndex, newIndex); } }, __moveArrayElement(array: any[], oldIndex: number, newIndex: number) { const start = newIndex < 0 ? array.length + newIndex : newIndex; const deleteCount = 0; const item = array.splice(oldIndex, 1)[0]; array.splice(start, deleteCount, item); }, __setColumnVisibility(id: string, visibility = true) { if (!this.options.showHeader) { return; } const isHidden = !visibility; const columns = this.options.columns; const index = columns.findIndex(item => item.id === id); const columnToBeHidden = columns[index]; if (isHidden) { columnToBeHidden.isHidden = isHidden; } else { delete columnToBeHidden.isHidden; } this.setClassToColumn(id, isHidden, index, classes.hiddenByTreeEditorClass); }, setClassToColumn(id: string, state = false, index: number, classCell: string) { if (!this.options.showHeader) { return; } const columns = this.options.columns; let elementIndex = index + 1; const column = columns[index]; if (column.customClass && column.customClass.length) { const arrayCustomClasses = column.customClass.split(' '); const hasCustomClassCell = arrayCustomClasses.find(customClass => customClass === classCell); if (!hasCustomClassCell && state) { column.customClass = `${column.customClass} ${classCell}`; } else if (!state) { const indexDeleteClass = arrayCustomClasses.indexOf(classCell); if (indexDeleteClass !== -1) { arrayCustomClasses.splice(indexDeleteClass, 1).join(' '); column.customClass = arrayCustomClasses; } } } else if (state) { column.customClass = classCell; } if (!this.isAttached()) { return; } this.__toggleNoColumnsMessage(columns); if (this.el.querySelector('.js-cell_selection')) { elementIndex += 1; } const headerSelector = `.js-grid-header-view tr > *:nth-child(${elementIndex})`; this.el.querySelector(headerSelector).classList.toggle(classCell, state); const cellSelector = `.js-visible-collection tr > *:nth-child(${elementIndex})`; Array.from(this.el.querySelectorAll(cellSelector)).forEach(element => { element.classList.toggle(classCell, state); }); }, __toggleNoColumnsMessage(columns: Array<object>) { let hiddenColumnsCounter = 0; let isHidden; columns.forEach(col => { isHidden = col.getStateHidden ? col.getStateHidden() : col.isHidden; if (isHidden) { hiddenColumnsCounter++; } }); if (hiddenColumnsCounter === columns.length) { if (this.el.querySelectorAll('.tree-editor-no-columns-message').length < 1) { const noColumnsMessage = document.createElement('div'); noColumnsMessage.innerText = Localizer.get('CORE.GRID.NOCOLUMNSVIEW.ALLCOLUMNSHIDDEN'); noColumnsMessage.classList.add('tree-editor-no-columns-message', 'empty-view', 'empty-view_text'); this.el.querySelector('.js-grid-content').appendChild(noColumnsMessage); this.el.querySelector('tbody').classList.add(classes.hiddenByTreeEditorClass); this.el.querySelector('.grid-header').classList.add(classes.hiddenByTreeEditorClass); this.ui.tableWrapper.get(0).classList.add(classes.hiddenColumns); } } else if (this.el.querySelector('.tree-editor-no-columns-message')) { this.el.querySelector('.tree-editor-no-columns-message').remove(); this.el.querySelector('tbody').classList.remove(classes.hiddenByTreeEditorClass); this.el.querySelector('.grid-header').classList.remove(classes.hiddenByTreeEditorClass); this.ui.tableWrapper.get(0).classList.remove(classes.hiddenColumns); } }, __toggleTableWidth() { this.ui.table.get(0).classList.toggle(classes.tableWidthAuto, this.headerView.isEveryColumnSetPxWidth); }, __onContextMenu(model: Backbone.Model & GridItemBehavior, event: MouseEvent) { if (this.menu) { this.menu.destroy(); } if (!model.checked) { this.collection.uncheckAll(); } model.check(); const collection = this.toolbarView.getCustomItems(); this.menu = new ActionMenuView({ model: new Backbone.Model({ items: collection }), showName: true, adjustmentPosition: { x: event.pageX, y: event.pageY } }); this.listenTo(this.menu, 'action:click', (model, ...rest) => this.__executeAction(model, this.collection, ...rest)); this.menu.open(); event.preventDefault(); }, __scrollLeft(columnRight: number) { const containerEl = this.ui.tableTopMostWrapper.get(0); containerEl.scrollLeft = columnRight - containerEl.offsetWidth; } });