@true-directive/grid
Version:
Angular Data Grid from Yopsilon.
999 lines • 129 kB
JavaScript
import * as tslib_1 from "tslib";
/**
* Copyright (c) 2018-2019 Aleksey Melnikov, True Directive Company.
* @link https://truedirective.com/
* @license MIT
*/
/*
View only. Missed features:
- Checkboxes.
- Editing.
- Selection.
*/
import { Component, Input, Output, ViewChild, ViewChildren, ContentChildren, ElementRef, QueryList, ChangeDetectorRef, KeyValueDiffers, EventEmitter, Inject } from '@angular/core';
import { Subject, Observable } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { RenderMode, DetectionMode, ColumnType } from '@true-directive/base';
import { CellPosition, GridSettings, RowLayout, SortInfo, SortType, MenuAction, LazyLoadingMode } from '@true-directive/base';
import { PagePipe, RowCalculator } from '@true-directive/base';
import { ScrollerComponent } from './scroller.component';
import { BaseComponent } from './base.component';
import { RowDirective } from './row.directive';
import { GridStateService } from './grid-state.service';
import { InternationalizationService } from './internationalization/internationalization.service';
import { FilterTextComponent } from './filters/datatypes/filter-text.component';
import { FilterDateComponent } from './filters/datatypes/filter-date.component';
import { FilterNumberComponent } from './filters/datatypes/filter-number.component';
import { FilterBooleanComponent } from './filters/datatypes/filter-boolean.component';
import { FilterPopupComponent } from './filters/filter-popup.component';
import { MenuStarterComponent } from './controls/menu-starter.component';
let GridViewComponent = class GridViewComponent extends BaseComponent {
constructor(state, intl, elementRef, changeDetector, keyValueDiffers) {
super();
this.state = state;
this.intl = intl;
this.elementRef = elementRef;
this.changeDetector = changeDetector;
this.keyValueDiffers = keyValueDiffers;
this.destroy$ = new Subject();
this._data = null;
/**
* The maximum grid height, unless explicitly specified.
*/
this.maxHeight = null;
/**
* Event that will be triggered when data retrieval options are changed (filters,
* sortings)
* @param 'dataQuery' [description]
* @return [description]
*/
this.dataQuery = new EventEmitter();
this.queryChanged = new EventEmitter();
this.startProcess = new EventEmitter();
this.endProcess = new EventEmitter();
this.headerContextMenu = new EventEmitter();
this.customCellEvent = new EventEmitter();
this.menuAction = new EventEmitter();
this.uiAction = null;
this._initialized = false; // Grid initialized
this._viewInitialized = false; // View initialized
this._lastUpdateTime = 0; // Last page refresh time
// Position and time of last scroll
this._lastScroll = { pos: -1, renderedPos: -1, time: 0 };
this.dataUpdating = false; // Updating data in progress
// Page processing required
this.need_recalc_page = true;
this._prevOffset = -1;
this._prevLimit = -1;
/**
* Current appearance css-class
*/
this._currentAppearance = '';
/**
* List of rows to be rendered.
*/
this._currentRendered = [];
this.viewPortLeft = -1;
this.viewPortWidth = -1;
this.RC = new RowCalculator(this.state);
this._inProcess = false;
// Locale was changed
this.intl.onLocaleChanged.pipe(takeUntil(this.destroy$)).subscribe(l => {
if (this._viewInitialized) {
this.detectChanges('locale');
}
});
// Запрос данных у слушателя события
this.state.events.onDataQuery.pipe(takeUntil(this.destroy$)).subscribe(q => {
this.dataUpdating = true;
this.dataQuery.emit(q);
});
// Данные получены - отображаем
this.state.events.onDataFetch.pipe(takeUntil(this.destroy$)).subscribe(q => {
if (this._viewInitialized) {
if (q.resetData) {
this.scrollToTop();
}
this.renderData();
}
});
// Изменены фильтры или сортировка или набор колонок или группировка
this.state.events.onQueryChanged.pipe(takeUntil(this.destroy$)).subscribe(q => {
if (this._initialized) {
this.updateData();
}
/*
Мы будем запрашивать здесь запрос. Потому что может быть ленивая загрузка хочет
его подправить.
Нет, его нельзя запросить, потому что у него уже идентификатор, с которым мы запросили
и он для нас важен */
this.queryChanged.emit(this.state.getQuery());
});
// Следует отобразить окно фильтра для заданной колонки
this.state.events.onFilterShow.pipe(takeUntil(this.destroy$)).subscribe(e => this.showFilter(e));
// Изменение списка суммирований колонки
this.state.events.onSummariesChanged.pipe(takeUntil(this.destroy$)).subscribe(v => {
this.state.updateSummaries();
this.updateView();
});
this.state.events.onHeaderContextMenu.pipe(takeUntil(this.destroy$)).subscribe(e => {
// Это нужно вынести куда-нибудь подальше
if (this.state.settings.enableHeaderContextMenu) {
const actions = this.state.settings.headerContextMenuActions;
const sorted = this.state.dataSource.sortedByField(e.column.fieldName);
if (actions.length > 0) {
actions.forEach(a => {
if (a === MenuAction.SORT_ASC) {
a.disabled = sorted && sorted.sortType === SortType.ASC;
}
if (a === MenuAction.SORT_DESC) {
a.disabled = sorted && sorted.sortType === SortType.DESC;
}
});
this.menuStarter.start(e.event, this.state.settings.headerContextMenuActions, e.column);
this.detectChanges();
e.event.preventDefault();
}
}
this.headerContextMenu.emit(e);
});
// Custom cell event
this.state.events.onCustomCellEvent.pipe(takeUntil(this.destroy$)).subscribe(e => {
this.customCellEvent.emit(e);
});
}
/**
* Grid data source. You can specify an observable object or array.
*/
set data(ds) {
// Unsubscribe from previous source
if (this._data !== null && ds === this._data) {
return;
}
if (this._data !== null) {
this._dataSubscription.unsubscribe();
this._data = null;
}
if (ds instanceof Observable) {
// Subscribe for new data
this._data = ds;
this._dataSubscription = this._data
.pipe(takeUntil(this.destroy$))
.subscribe(data => {
this.state.model = data;
this.updateData();
});
}
else {
// Just array
if (this.state.model !== ds) {
this.state.model = ds;
if (this._initialized) {
this.updateData();
}
}
}
}
/**
* Data source
* @return Array of the rows or observable object
*/
get data() {
return this._data !== null ? this._data : this.state.model;
}
/**
* List of the rows to display
* @return Array of the rows
*/
get rows() {
return this.state.model;
}
/**
* Resulting list of rows after applying filters and sorting
* @return Array of the rows
*/
get resultRows() {
return this.state.dataSource.resultRows;
}
/**
* Current locale name
*/
get locale() {
return this.intl.currentLocaleName;
}
/**
* List of the columns
*/
set columns(v) {
this.state.columns = v;
}
get columns() {
return this.state.columns;
}
/**
* Grid's settings
*/
set settings(v) {
this.state.settings = v;
}
get settings() {
return this.state.settings;
}
/**
* Text search data for all columns
*/
get searchString() {
return this.state.dataSource.searchString;
}
set searchString(v) {
if (this.state.dataSource.searchString !== v) {
this.state.dataSource.searchString = v;
// A pause is necessary so that when printing quickly, do not perform
// too frequent updates.
setTimeout(() => {
if (this.state.dataSource.searchString === v) {
this.updateData();
}
}, this.state.settings.searchDelay);
}
}
get displayedRows() {
if (this.state.settings.customTemplate) {
return this.displayedRows_template;
}
return this.displayedRowsCenter;
}
get selection() {
return this.state.selection;
}
_headerParts() {
return [this.gridHeader];
}
get headerParts() {
return this._headerParts();
}
_dataParts() {
return [this.displayedRowsCenter];
}
get dataParts() {
return this._dataParts();
}
immediateFilter(filter) {
this.state.dataSource.searchString = filter;
this.updateData();
}
/**
* List of the rows to render
* @return Array of the rows
*/
get visibleRows() {
if (!this.state.dataSource.resultRows) {
return [];
}
if (this._prevOffset !== this.state.pageInfo.offset ||
this._prevLimit !== this.state.pageInfo.limit ||
this.need_recalc_page) {
// Page refresh
this.state.lazyLoader.query(this.state.pageInfo.offset);
const toRender = new PagePipe().transform(this.state.dataSource.resultRows, this.state.pageInfo);
this._prevOffset = this.state.pageInfo.offset;
this._prevLimit = this.state.pageInfo.limit;
this._currentRendered = toRender;
this.need_recalc_page = false;
// Important!
this.state.displayedStartIndex = this.state.pageInfo.offset;
// Если мы.. Доскроллили до предела...
// Надо бы запросить еще данные.
}
return this._currentRendered;
}
// Keeping the height of all rows
saveRowHeights() {
this.displayedRowsCenter.forEach(el => {
let hh;
if (this.state.IE) {
hh = el.elementRef.nativeElement.getBoundingClientRect().height;
}
else {
hh = el.elementRef.nativeElement.clientHeight;
}
this.RC.setRowHeight(this.state.dataSource.resultRowCount, el.axI + this.state.pageInfo.offset, hh);
});
}
/**
* Page refresh
* @param forceChanges Force update even if there was no scrolling
* @param overwork The number of rows that will be rendered outside the viewport
*/
updatePage(log = '', forceChanges = false, overwork = null) {
// Это надо перекинуть в состояние
if (this.viewPortLeft !== this.scroller.scrollLeft || this.viewPortWidth !== this.scroller.viewPortWidth) {
this.viewPortLeft = this.scroller.scrollLeft;
this.viewPortWidth = this.scroller.viewPortWidth;
forceChanges = true;
}
if (!forceChanges) {
this.saveRowHeights();
}
var rc = this.state.dataSource.resultRowCount;
if (this.state.st.lazyLoading === LazyLoadingMode.FRAGMENTARY) {
rc = this.state.dataSource.totalRowCount;
}
const pageChanged = this.RC.updateRenderInfo(rc, this.scroller.scrollTop, this.scroller.viewPortHeight, overwork);
if (forceChanges || pageChanged) {
this.detectChanges('page');
}
}
/**
* Force view update
*/
updateView() {
// First we need to update all view params
this.detectChanges('view');
this.state.updateLayouts();
// Then the current page. Because its calculation depends on these parameters
this.updatePage('updateView', true);
}
cellPosition(row, fieldName) {
const ri = this.resultRows.indexOf(row);
return new CellPosition(row, ri, fieldName);
}
startSelect(cp, add = false) {
this.state.ui.startSelect(cp, add);
}
proceedToSelect(cp) {
this.state.ui.proceedToSelect(cp);
}
/**
* Hide all buttons in column headers
*/
hideHeaderBtns() {
this.headerParts.forEach(p => {
p.hideHeaderBtns();
});
}
/**
* Asynchronous data update. The Observable returns, by subscribing to which
* you can find out when this update ended.
* Use this method with lazy loading settings.
* @param data The data (array of objects).
* @return Observable object whose event will occur immediately after processing.
*/
updateDataAsync(data) {
this.data = data;
this.startProcess.emit('DataUpdate');
this.elementRef.nativeElement.classList.add('processing');
const subj = this.state.updateDataAsync();
subj.subscribe(e => {
this.endProcess.emit('DataUpdate');
});
return subj;
}
/**
* Updating data with external or internal change.
*/
updateData(async = true) {
if (!this._initialized) {
async = false;
}
// The changes are serious. Row heights update.
this.RC.clear();
// Through a timeout, so that when the filter is applied, the button does
// not have time to turn off before it is shown with an accent color
// setTimeout(() => this.hideHeaderBtns());
if (async) {
this.startProcess.emit('DataUpdate');
this.elementRef.nativeElement.classList.add('processing');
setTimeout(() => {
this.state.updateData();
this.endProcess.emit('DataUpdate');
}, 1);
}
else {
this.state.updateData(async);
}
}
updateSummaries() {
this.state.updateSummaries();
this.updateView();
}
/**
* Received data externally (from the parent component)
* @param query The request in response to which the data received.
* @param data Data that received in response to a given request.
*/
fetchData(query, data, totalRowCount = null) {
this.state.fetchData(query, data, totalRowCount);
}
renderData() {
setTimeout(() => this.hideHeaderBtns());
this.dataUpdating = false;
this.elementRef.nativeElement.classList.remove('processing');
this.need_recalc_page = true;
// Sent for further processing (aggregation, grouping)
// Refreshing page with forced detectChanges
this.updatePage('renderData', true);
// After adding data, the width of the client part of the grid may change
// due to the appearance of a scrollbar. Therefore, check the width of the grid.
if (this.state.settings.columnAutoWidth) {
this.checkSize();
}
// The height of the data changed, but the scroll position does not.
// Therefore, scroll the related parts.
this.scroller.scrollParts();
}
updatePageByScroll() {
this.updatePage('scroll', false);
this._lastUpdateTime = Date.now();
}
/**
* Scrolling data
* @param e Scroll event
*/
gridScroll(e) {
let scrollPos = e.target.scrollTop;
let scrollPosH = e.target.scrollLeft;
const dscrollH = scrollPosH - this.viewPortLeft;
const dscroll = scrollPos - this._lastScroll.renderedPos;
this._lastScroll.pos = scrollPos;
this.RC.currentScrollPos = scrollPos;
let needUpdate = false;
if (Math.abs(dscroll) > this.RC.currentRH * 2) {
// We have a stock of two lines. If the scroll is larger, then update.
needUpdate = true;
}
if (Math.abs(dscrollH) > 1 && this.state.settings.renderMode === RenderMode.VISIBLE) {
// When scrolling horizontally only if RenderMode.VISIBLE
needUpdate = true;
}
if (!needUpdate) {
// заменим на явное задание margin-top у
return;
}
if (this._inProcess && this.state.iOS) {
return;
}
if (scrollPosH < 0) {
return;
}
if (scrollPos < 0 || scrollPos > (this.scroller.scrollHeight - this.scroller.viewPortHeight)) {
return;
}
this._inProcess = true;
const dt = Date.now();
this._lastScroll.renderedPos = scrollPos;
this._lastScroll.time = dt;
const ms = dt - this._lastUpdateTime;
let dms = this.state.IE ? 40 : 10;
if (ms < dms && !this.state.safari) {
// Delaying
const delay = dms * 4;
setTimeout(() => {
if (this._lastScroll.time === dt) {
this.updatePageByScroll();
}
}, delay);
this._inProcess = false;
return;
}
this.updatePageByScroll();
this._inProcess = false;
}
/**
* Triggered by automatic scrolling during area selection
* @param dx How many pixels are scrolled horizontally?
*/
gridAutoScrollX(dx) {
this.gridHeader.autoScrollX(dx);
}
/**
* We need call this method for the changes to take effect.
* If dataAffected=true then re-filter and re-sort data.
* @param log Reason for detecting changes
* @param dataAffected Is the data changed
*/
detectChanges(log = '', dataAffected = false) {
if (dataAffected) {
this.updateData();
return;
}
if (this.state.settings.changeDetectionMode === DetectionMode.MANUAL) {
// Otherwise, it will check the changes. And we won’t pull the detector again..
this.changeDetector.detectChanges();
}
}
displayedRowsByXY(x, y, place = null) {
return this.displayedRows;
}
rowLayouts(rows) {
const result = [];
const firstRow = rows.first;
if (!firstRow) {
return result;
}
let ri = this.state.pageInfo.offset;
rows.forEach(el => {
const rl = new RowLayout();
rl.index = ri;
rl.rowComponent = el;
rl.clientRect = el.elementRef.nativeElement.getBoundingClientRect();
result.push(rl);
ri++;
});
return result;
}
rowByXY(rows, x, y) {
return RowLayout.rowByXY(this.rowLayouts(rows), x, y);
}
cellByXY(x, y, place = null) {
const rows = this.displayedRowsByXY(x, y, place);
const r = this.rowByXY(rows, x, y);
if (r && r.rowComponent) {
return this.state.ui.cellPosition(r.rowComponent.row, r.index, r.rowComponent.cellByXY(x, y));
}
return null;
}
// Перерисовка выделенного
refreshSelection(scrollTo) {
this.dataParts.forEach(p => p && p.forEach(el => el.setSelection()));
if (scrollTo) {
this.scrollTo(scrollTo);
}
}
/**
* Прокрутить скроллбоксы так, чтобы была видна ячейка, на которой находится
* виртуальный фокус
*/
scrollToFocused() {
if (this.state.selection.focusedCell) {
this.scrollTo(this.state.selection.focusedCell);
}
}
/**
* Прокрутка к указанной ячейке.
* Обычно вызываем после обработки нажатия клавиши, чтобы была видна строка
* с фокусом.
* Также вызывается после клика мышью по ячейке.. Если ячейка видна на экране
* только частично - немного скроллим.
* @param cellPos Позиция ячейки, к которой прокручиваем грид
*/
scrollTo(cellPos) {
if (!cellPos) {
return;
}
// Будем считать, что нам нужна только ячейка с фокусом
// Сохраним высоты строк.. Т.к. они могут быть еще не сохранены,
// если не случилось прокрутки
this.saveRowHeights();
let ri = cellPos.rowIndex;
let fieldName = cellPos.fieldName;
// сначала по вертикали
let scrollTop = this.scroller.scrollTop;
let scrollLeft = this.scroller.scrollLeft;
let scrollAreaHeight = this.scroller.viewPortHeight;
let scrollAreaWidth = this.scroller.viewPortWidth;
let rowTop = this.RC.getRowTop(ri);
let rowBottom = rowTop + this.RC.getRowHeight(ri);
if (rowTop < scrollTop) {
this.scroller.scrollTo(-1, rowTop);
}
else {
if (rowBottom > (scrollTop + scrollAreaHeight)) {
this.scroller.scrollTo(-1, rowBottom - scrollAreaHeight);
}
}
// Теперь по горизонтали
// Возьмем строку
this.displayedRowsCenter.some(row => {
if (row.row === cellPos.row) {
const hPos = row.cellHorizontalPos(fieldName);
if (hPos && hPos.l < scrollLeft) {
this.scroller.scrollTo(hPos.l);
}
if (hPos && hPos.r > scrollLeft + scrollAreaWidth) {
this.scroller.scrollTo(hPos.r - scrollAreaWidth);
}
return true;
}
return false;
});
}
scrollToTop() {
this.scroller.scrollTo(-1, 0);
}
toggleClass(v, c) {
if (v) {
this.elementRef.nativeElement.classList.add(c);
}
else {
this.elementRef.nativeElement.classList.remove(c);
}
}
/**
* Проверить, нужно ли отобразить левую или правую область для зафиксированных
* колонок
* @param xx [description]
* @param scrollRect [description]
* @param target [description]
* @return [description]
*/
checkParts(xx, scrollRect, target) {
// Нечего проверять в этой реализации
}
/**
* Выделение заданной строки
* @param r Заданная строка
* @return Найдена ли заданная строка в списке отображаемых строк
*/
locateRow(r) {
return this.state.locateRow(r);
}
/**
* Выделение строки по заданному значению ключевого поля
* @param keyValue Значение ключевого поля
* @param keyField Ключевое поле. Если не задано, то поле берется из settings
* @return Найдена ли строка с заданным ключом
*/
locateByKey(keyValue, keyField = '') {
this.state.locateByKey(keyValue, keyField);
}
/**
* Очистка выделения
*/
clearSelection() {
this.state.clearSelection();
}
/**
* Трек отображаемых записей для более быстрого рендера ангуляром
* @param index Индекс строки
* @param data Данные строки
*/
trackRow(index, data) {
return data;
}
/**
* При изменении размеров окна вызывается этот метод.
* Если размер компонента изменяется какими-то действиями пользователя помимо
* изменения размеров окна браузера (вьюпорта), то необходимо вызывать этот
* метод.
* Этот метод также следует вызывать при drop колонки, т.к. размер центральной
* области меняется, если колонка переброшена в левую или правую фиксированную
* область.
* @param update_page=false [description]
* @return [description]
*/
checkSize(update_page = false) {
// Указываем ширину клиентской области.
// Если она изменена, то в сеттере будет вызван updateLayouts
if (this.state.checkClientWidth(this.scroller.viewPortWidth) ||
this.state.checkClientHeight(this.scroller.viewPortHeight)) {
this.need_recalc_page = true;
if (this._initialized) {
this.updatePage('checkSize', true);
}
return true;
}
if (update_page) {
this.updatePage('checkSize2', true);
}
return false;
}
// Изменение размера окна
windowResize(e) {
this.checkSize(true);
}
/**
* Показать кнопку заголовка для колонки по заданному полю
* @param fieldName Заданное поле
*/
showHeaderBtn(fieldName) {
this.headerParts.forEach(p => {
p.showHeaderBtn(fieldName);
});
}
/**
* Data sorting
* @param sortings List of sortings
*/
sort(sortings, update = true) {
this.state.sort(sortings, update);
}
clearSorting(update = true) {
this.state.sort([], update);
}
/**
* Data filtering
*/
filter(filters, update = true) {
this.state.filter(filters, update);
}
filterToString(filter) {
return filter.toString(this.intl, this.state.dataSource.valueFormatter);
}
filterClosed(result) {
if (!result) {
this.hideHeaderBtns();
}
}
setFilter(f) {
this.state.setFilter(f);
}
resetFilter(f) {
this.state.resetFilter(f);
}
getFilterComponentType(filter) {
let filterType = FilterTextComponent;
if (filter.type === ColumnType.NUMBER) {
filterType = FilterNumberComponent;
}
if (filter.type === ColumnType.DATETIME) {
filterType = FilterDateComponent;
}
if (filter.type === ColumnType.BOOLEAN) {
filterType = FilterBooleanComponent;
}
const col = this.state.columnByFieldName(filter.fieldName);
if (!col) {
return null;
}
if (col.filterComponentType !== null) {
filterType = col.filterComponentType;
}
return filterType;
}
showFilter(e) {
if (this.menuStarter) {
this.menuStarter.finish();
}
let l = e.target.tagName === 'SPAN' ? e.target.parentElement : e.target;
if (this.filterPopup.visible) {
if (this.filterPopup.filter.fieldName === e.filter.fieldName) {
this.filterPopup.closePopup();
return;
}
else {
this.filterPopup.closePopup();
}
}
this.hideHeaderBtns();
this.showHeaderBtn(e.filter.fieldName);
const filterType = this.getFilterComponentType(e.filter);
if (filterType !== null) {
setTimeout(() => {
this.filterPopup.showByTarget(l, e.filter, filterType, this.state.model);
}, 10);
}
}
focus() {
this.scroller.focus();
}
get appearanceClass() {
return this.state.sta.class;
}
/**
* Установка класса внешнего вида
* @param appearanceClass Класс, который будет применен к компоненту
*/
setAppearance() {
const appearanceClass = this.appearanceClass;
if (appearanceClass === this._currentAppearance) {
// Без изменений
return;
}
if (this._currentAppearance !== '') {
// Убираем добавленный
this.elementRef.nativeElement.classList.remove(this._currentAppearance);
}
this.elementRef.nativeElement.classList.add(appearanceClass);
this._currentAppearance = appearanceClass;
}
menuItemClick(e) {
const col = e.target;
const action = e.action;
if (action === MenuAction.SORT_ASC) {
this.sort([new SortInfo(col.fieldName, SortType.ASC)]);
return;
}
if (action === MenuAction.SORT_DESC) {
this.sort([new SortInfo(col.fieldName, SortType.DESC)]);
return;
}
if (action === MenuAction.HIDE) {
this.state.hideColumn(col);
return;
}
this.menuAction.emit(e);
}
ngOnInit() {
// Отключаем детектор по настройке.
// Без отключения всё работает, но много чего мелькает не вовремя..
if (this.state.settings.changeDetectionMode === DetectionMode.MANUAL) {
this.changeDetector.detach();
}
// Grid appearance
this.setAppearance();
if (this.state.iOS || this.state.android) {
this.elementRef.nativeElement.classList.add('true-fix-touch');
}
if (this.state.IE) {
this.elementRef.nativeElement.classList.add('true-fix-ie');
}
this._settingsDiffer = this.keyValueDiffers.find(this.state.settings).create();
this._appearanceDiffer = this.keyValueDiffers.find(this.state.settings.appearance).create();
this._settingsDiffer.diff(this.state.settings);
this._appearanceDiffer.diff(this.state.settings.appearance);
this.updateData();
this._initialized = true;
}
ngAfterContentInit() {
//
}
ngAfterViewInit() {
// Сохраняем ширину
this.state.checkClientWidth(this.scroller.viewPortWidth);
// На этот момент может быть неизвестна высота грида. Которая нам очень нужна!
// Обычно это случается при сложной разметке, когда размер задан в процентах от
// родительского элемента.
let vh = this.scroller.viewPortHeight;
if (vh <= this.state.settings.rowHeight) {
// Это условие показывает, что что-то не так.
// Мы немного отложим первый рендер данных.
// Мы увидим белый экран перед тем как увидим данные.
setTimeout(() => this.renderData());
}
else {
// Будем оптимистами. Возможно, нас спасёт ngDoCheck
this.renderData();
}
// Запоминаем текущую высоту вьюпорта
this.state.checkClientHeight(vh);
// Добавляем пассивные слушатели тач-событий
this.addTouchListeners(this.gridData.nativeElement);
// Следим за размером окна, чтобы дорендерить невидимые ранее данные
this.addWindowResizeListener();
this._viewInitialized = true;
}
doCheckParts() {
// Обновляем изменение
// Фильтры не виновaты, что у нас отключен детектор изменений
if (this.filterPopup && this.filterPopup.visible) {
// Поэтому даём знать об изменениях
this.filterPopup.changes();
}
}
ngDoCheck() {
this.doCheckParts();
// Сверяем настройки
const sChanges = this._settingsDiffer.diff(this.state.settings);
const aChanges = this._appearanceDiffer.diff(this.state.settings.appearance);
if (sChanges || aChanges) {
if (this._viewInitialized) {
this.setAppearance();
if (!this.checkSize()) {
this.updateView();
}
return;
}
}
// Сверяем высоту грида. Это немного затормаживает нас.
// Но дает гарантию, что данные будут вовремя отрисованы
if (this.state.checkClientHeight(this.scroller.viewPortHeight)
&& this._viewInitialized) {
this.updatePage();
}
}
ngOnDestroy() {
// Отписаться от событий
this.destroy$.next(true);
this.destroy$.unsubscribe();
super.ngOnDestroy();
}
};
tslib_1.__decorate([
Input('data'),
tslib_1.__metadata("design:type", Object),
tslib_1.__metadata("design:paramtypes", [Object])
], GridViewComponent.prototype, "data", null);
tslib_1.__decorate([
Input('columns'),
tslib_1.__metadata("design:type", Array),
tslib_1.__metadata("design:paramtypes", [Array])
], GridViewComponent.prototype, "columns", null);
tslib_1.__decorate([
Input('settings'),
tslib_1.__metadata("design:type", GridSettings),
tslib_1.__metadata("design:paramtypes", [GridSettings])
], GridViewComponent.prototype, "settings", null);
tslib_1.__decorate([
Input('maxHeight'),
tslib_1.__metadata("design:type", Number)
], GridViewComponent.prototype, "maxHeight", void 0);
tslib_1.__decorate([
Input('searchString'),
tslib_1.__metadata("design:type", String),
tslib_1.__metadata("design:paramtypes", [String])
], GridViewComponent.prototype, "searchString", null);
tslib_1.__decorate([
Output('dataQuery'),
tslib_1.__metadata("design:type", EventEmitter)
], GridViewComponent.prototype, "dataQuery", void 0);
tslib_1.__decorate([
Output('queryChanged'),
tslib_1.__metadata("design:type", EventEmitter)
], GridViewComponent.prototype, "queryChanged", void 0);
tslib_1.__decorate([
Output(),
tslib_1.__metadata("design:type", EventEmitter)
], GridViewComponent.prototype, "startProcess", void 0);
tslib_1.__decorate([
Output(),
tslib_1.__metadata("design:type", EventEmitter)
], GridViewComponent.prototype, "endProcess", void 0);
tslib_1.__decorate([
Output(),
tslib_1.__metadata("design:type", EventEmitter)
], GridViewComponent.prototype, "headerContextMenu", void 0);
tslib_1.__decorate([
Output(),
tslib_1.__metadata("design:type", EventEmitter)
], GridViewComponent.prototype, "customCellEvent", void 0);
tslib_1.__decorate([
Output(),
tslib_1.__metadata("design:type", EventEmitter)
], GridViewComponent.prototype, "menuAction", void 0);
tslib_1.__decorate([
ViewChild('menuStarter', { static: true }),
tslib_1.__metadata("design:type", MenuStarterComponent)
], GridViewComponent.prototype, "menuStarter", void 0);
tslib_1.__decorate([
ViewChild('filterPopup', { static: false }),
tslib_1.__metadata("design:type", FilterPopupComponent)
], GridViewComponent.prototype, "filterPopup", void 0);
tslib_1.__decorate([
ViewChild('scroller', { static: true }),
tslib_1.__metadata("design:type", ScrollerComponent)
], GridViewComponent.prototype, "scroller", void 0);
tslib_1.__decorate([
ViewChild('grid', { static: true }),
tslib_1.__metadata("design:type", Object)
], GridViewComponent.prototype, "grid", void 0);
tslib_1.__decorate([
ViewChild('gridHeader', { static: true }),
tslib_1.__metadata("design:type", Object)
], GridViewComponent.prototype, "gridHeader", void 0);
tslib_1.__decorate([
ViewChild('gridData', { static: true }),
tslib_1.__metadata("design:type", Object)
], GridViewComponent.prototype, "gridData", void 0);
tslib_1.__decorate([
ViewChild('dragItem', { static: true }),
tslib_1.__metadata("design:type", Object)
], GridViewComponent.prototype, "dragItem", void 0);
tslib_1.__decorate([
ContentChildren(RowDirective),
tslib_1.__metadata("design:type", QueryList)
], GridViewComponent.prototype, "displayedRows_template", void 0);
tslib_1.__decorate([
ViewChildren('displayedRows', { read: RowDirective }),
tslib_1.__metadata("design:type", QueryList)
], GridViewComponent.prototype, "displayedRowsCenter", void 0);
tslib_1.__decorate([
ViewChild('customTemplate', { static: true }),
tslib_1.__metadata("design:type", Object)
], GridViewComponent.prototype, "customTemplate", void 0);
GridViewComponent = tslib_1.__decorate([
Component({
selector: 'true-grid-view',
template: "<true-menu-starter #menuStarter (itemClick)=\"menuItemClick($event)\"></true-menu-starter>\r\n<!-- Filter popup -->\r\n<true-filter-popup #filterPopup *ngIf=\"state.settings.allowFilter\"\r\n (setFilter)=\"setFilter($event)\"\r\n (resetFilter)=\"resetFilter($event)\"\r\n (closed)=\"filterClosed($event)\">\r\n</true-filter-popup>\r\n<!-- Scroll manager -->\r\n<true-scroller #scroller\r\n tabindex=\"0\"\r\n (autoscrollx)=\"gridAutoScrollX($event)\"\r\n (scroll)=\"gridScroll($event)\">\r\n <!-- \u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043A\u0438 -->\r\n <true-grid-header #gridHeader true-header\r\n [ngClass]=\"state.settings.appearance.headerAreaClass\"\r\n [layout]=\"state.layout\"\r\n [scroller]=\"scroller\">\r\n </true-grid-header>\r\n <div true-data [ngClass]=\"settings.appearance.getDataClass()\">\r\n <table #gridData class=\"true-grid-data\"\r\n [class.true-grid-data_fixed-height]=\"state.settings.fixedRowHeight\"\r\n [style.width]=\"state.layout.dataWidth\">\r\n <colgroup>\r\n <!-- Level columns -->\r\n <col *ngFor=\"let c of state.layout.levelColumns\" [style.width]=\"state.st.levelWidth\" />\r\n <!-- Data columns -->\r\n <col *ngFor=\"let c of state.layout.columns\" [style.width]=\"c.displayedWidthU\" />\r\n </colgroup>\r\n\r\n <tbody *ngIf=\"!state.st.customTemplate\">\r\n <tr *ngFor=\"let r of RC.ghostRows('start'); trackBy: RC.trackGhostRowStart\" [style.height.px]=\"r.H\" style=\"border:0\">\r\n <td [style.height.px]=\"r.H\" style=\"padding:0; border-right: 0;\"> </td>\r\n </tr>\r\n\r\n <tr *ngFor=\"let r of visibleRows; let i=index; trackBy: trackRow;\"\r\n true-row\r\n [row]=\"r\"\r\n [true-locale]=\"locale\"\r\n [true-layout]=\"state.layout\"\r\n [true-state]=\"state\"\r\n [true-i]=\"i\"\r\n [style.height.px]=\"state.settings.rowHeight\"\r\n #displayedRows>\r\n </tr>\r\n <tr *ngFor=\"let r of RC.ghostRows('end')\" [style.height.px]=\"r.H\">\r\n <td [style.height.px]=\"r.H\" style=\"padding:0; border-right: 0;\"> </td>\r\n </tr>\r\n <tr *ngIf=\"visibleRows.length === 0\"><td style=\"border: 0;\"></td></tr>\r\n </tbody>\r\n <!-- User's template -->\r\n <ng-content *ngIf=\"state.st.customTemplate\" select=\"[true-body]\"></ng-content>\r\n </table>\r\n </div>\r\n <!-- Footer -->\r\n <true-grid-footer #gridFooter true-footer [layout]=\"state.layout\"></true-grid-footer>\r\n</true-scroller>\r\n",
providers: [{ provide: 'gridState', useClass: GridStateService }],
styles: [":host{display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.true-grid{box-sizing:border-box;height:100%;width:100%}.true-grid-data{box-sizing:border-box;table-layout:fixed;border-spacing:0;border-collapse:collapse;outline:0}.true-grid-data td{overflow-x:hidden;overflow-y:hidden}.true-grid-drop-marker{box-sizing:border-box;visibility:hidden;position:fixed;width:3px;border:1px solid #757779;background-color:rgba(255,255,255,.05);pointer-events:none;z-index:7}.true-grid-data_fixed-height td:not(.true-cell-indent){white-space:nowrap}.true-grid-data_fixed-height td:not(.true-cell-indent):not(.true-cell-checkbox){text-overflow:ellipsis}.true-cell-checkbox.true-check-by-click{cursor:pointer}.true-grid-drag-item{visibility:hidden;position:fixed;overflow-x:hidden;text-overflow:ellipsis;pointer-events:none;z-index:8}::ng-deep .true-grid-btn{cursor:pointer;align-self:stretch;visibility:hidden;display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column;-webkit-box-pack:center;justify-content:center;flex-shrink:0}::ng-deep .true-grid-btn span{display:inline-block}::ng-deep .true-align-right{text-align:right}::ng-deep .true-align-center{text-align:center}::ng-deep .true-align-left{text-align:left!important}"]
}),
tslib_1.__param(0, Inject('gridState')),
tslib_1.__metadata("design:paramtypes", [GridStateService,
InternationalizationService,
ElementRef,
ChangeDetectorRef,
KeyValueDiffers])
], GridViewComponent);
export { GridViewComponent };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ3JpZC12aWV3LmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiJuZzovL0B0cnVlLWRpcmVjdGl2ZS9ncmlkLyIsInNvdXJjZXMiOlsic3JjL2dyaWQtdmlldy5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7O0VBSUU7QUFDRjs7Ozs7R0FLRztBQUNILE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxTQUFTLEVBQUUsWUFBWSxFQUFFLGVBQWUsRUFDbEUsVUFBVSxFQUFFLFNBQVMsRUFBRSxpQkFBaUIsRUFDeEMsZUFBZSxFQUFFLFlBQVksRUFBRSxNQUFNLEVBQ3RDLE1BQU0sZUFBZSxDQUFDO0FBRTlCLE9BQU8sRUFBRSxPQUFPLEVBQUUsVUFBVSxFQUFnQixNQUFNLE1BQU0sQ0FBQztBQUN6RCxPQUFPLEVBQUUsU0FBUyxFQUFRLE1BQU0sZ0JBQWdCLENBQUM7QUFFakQsT0FBTyxFQUFZLFVBQVUsRUFBRSxhQUFhLEVBQUUsVUFBVSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDdkYsT0FBTyxFQUFVLFlBQVksRUFBRSxZQUFZLEVBQUUsU0FBUyxFQUM3QyxRQUFRLEVBQUUsUUFBUSxFQUFxQixVQUFVLEVBQUUsZUFBZSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDMUcsT0FBTyxFQUFFLFFBQVEsRUFBRSxhQUFhLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUUvRCxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUN6RCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFFakQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBRS9DLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ3hELE9BQU8sRUFBRSwyQkFBMkIsRUFBRSxNQUFNLHFEQUFxRCxDQUFDO0FBRWxHLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxN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