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
text/typescript
/* 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;
}
});