ag-grid
Version:
Advanced Javascript Datagrid. Supports raw Javascript, AngularJS 1.x, AngularJS 2.0 and Web Components
634 lines (548 loc) • 27.6 kB
text/typescript
/// <reference path="constants.ts" />
/// <reference path="gridOptionsWrapper.ts" />
/// <reference path="utils.ts" />
/// <reference path="filter/filterManager.ts" />
/// <reference path="columnController.ts" />
/// <reference path="selectionController.ts" />
/// <reference path="selectionRendererFactory.ts" />
/// <reference path="rendering/rowRenderer.ts" />
/// <reference path="headerRendering/headerRenderer.ts" />
/// <reference path="rowControllers/inMemoryRowController.ts" />
/// <reference path="rowControllers/virtualPageRowController.ts" />
/// <reference path="rowControllers/paginationController.ts" />
/// <reference path="expressionService.ts" />
/// <reference path="templateService.ts" />
/// <reference path="gridPanel/gridPanel.ts" />
/// <reference path="toolPanel/toolPanel.ts" />
/// <reference path="widgets/agPopupService.ts" />
/// <reference path="entities/gridOptions.ts" />
/// <reference path="gridApi.ts" />
/// <reference path="valueService.ts" />
/// <reference path="masterSlaveService.ts" />
/// <reference path="logger.ts" />
/// <reference path="eventService.ts" />
module ag.grid {
export class Grid {
private virtualRowCallbacks = <any>{};
private gridOptions: GridOptions;
private gridOptionsWrapper: GridOptionsWrapper;
private inMemoryRowController: InMemoryRowController;
private doingVirtualPaging: boolean;
private paginationController: PaginationController;
private virtualPageRowController: VirtualPageRowController;
private finished: boolean;
private selectionController: SelectionController;
private columnController: ColumnController;
private rowRenderer: RowRenderer;
private headerRenderer: HeaderRenderer;
private filterManager: FilterManager;
private valueService: ValueService;
private masterSlaveService: MasterSlaveService;
private eventService: EventService;
private toolPanel: any;
private gridPanel: GridPanel;
private eRootPanel: any;
private toolPanelShowing: boolean;
private doingPagination: boolean;
private usingInMemoryModel: boolean;
private rowModel: any;
constructor(eGridDiv: any, gridOptions: any, globalEventListener: Function = null, $scope: any = null, $compile: any = null, quickFilterOnScope: any = null) {
this.gridOptions = gridOptions;
this.setupComponents($scope, $compile, eGridDiv, globalEventListener);
this.gridOptions.api = new GridApi(this, this.rowRenderer, this.headerRenderer, this.filterManager,
this.columnController, this.inMemoryRowController, this.selectionController,
this.gridOptionsWrapper, this.gridPanel, this.valueService, this.masterSlaveService, this.eventService);
this.gridOptions.columnApi = this.columnController.getColumnApi();
var that = this;
// if using angular, watch for quickFilter changes
if ($scope) {
$scope.$watch(quickFilterOnScope, function (newFilter: any) {
that.onQuickFilterChanged(newFilter);
});
}
var forPrint = this.gridOptionsWrapper.isForPrint();
if (!forPrint) {
window.addEventListener('resize', this.doLayout.bind(this));
}
this.inMemoryRowController.setAllRows(this.gridOptionsWrapper.getRowData());
this.setupColumns();
this.updateModelAndRefresh(Constants.STEP_EVERYTHING);
// if no data provided initially, and not doing infinite scrolling, show the loading panel
var showLoading = !this.gridOptionsWrapper.getRowData() && !this.gridOptionsWrapper.isVirtualPaging();
this.showLoadingPanel(showLoading);
// if datasource provided, use it
if (this.gridOptionsWrapper.getDatasource()) {
this.setDatasource();
}
this.doLayout();
this.finished = false;
this.periodicallyDoLayout();
// if ready function provided, use it
var readyParams = {api: gridOptions.api};
this.eventService.dispatchEvent(Events.EVENT_READY, readyParams);
}
public getRowModel(): any {
return this.rowModel;
}
private periodicallyDoLayout() {
if (!this.finished) {
var that = this;
setTimeout(function () {
that.doLayout();
that.periodicallyDoLayout();
}, 500);
}
}
private setupComponents($scope: any, $compile: any, eUserProvidedDiv: any, globalEventListener: Function) {
// create all the beans
var eventService = new EventService();
var gridOptionsWrapper = new GridOptionsWrapper();
var selectionController = new SelectionController();
var filterManager = new FilterManager();
var selectionRendererFactory = new SelectionRendererFactory();
var columnController = new ColumnController();
var rowRenderer: RowRenderer = new RowRenderer();
var headerRenderer = new HeaderRenderer();
var inMemoryRowController = new InMemoryRowController();
var virtualPageRowController = new VirtualPageRowController();
var expressionService = new ExpressionService();
var templateService = new TemplateService();
var gridPanel = new GridPanel();
var popupService = new PopupService();
var valueService = new ValueService();
var groupCreator = new GroupCreator();
var masterSlaveService = new MasterSlaveService();
var loggerFactory = new LoggerFactory();
// initialise all the beans
gridOptionsWrapper.init(this.gridOptions, eventService);
loggerFactory.init(gridOptionsWrapper);
gridPanel.init(gridOptionsWrapper, columnController, rowRenderer, masterSlaveService);
templateService.init($scope);
expressionService.init(loggerFactory);
selectionController.init(this, gridPanel, gridOptionsWrapper, $scope, rowRenderer, eventService);
filterManager.init(this, gridOptionsWrapper, $compile, $scope,
columnController, popupService, valueService);
selectionRendererFactory.init(this, selectionController);
columnController.init(this, selectionRendererFactory, gridOptionsWrapper,
expressionService, valueService, masterSlaveService, eventService);
rowRenderer.init(columnController, gridOptionsWrapper, gridPanel, this, selectionRendererFactory, $compile,
$scope, selectionController, expressionService, templateService, valueService, eventService);
headerRenderer.init(gridOptionsWrapper, columnController, gridPanel, this, filterManager,
$scope, $compile);
inMemoryRowController.init(gridOptionsWrapper, columnController, this, filterManager, $scope,
groupCreator, valueService, eventService);
virtualPageRowController.init(rowRenderer, gridOptionsWrapper, this);
valueService.init(gridOptionsWrapper, expressionService, columnController);
groupCreator.init(valueService);
masterSlaveService.init(gridOptionsWrapper, columnController, gridPanel, loggerFactory, eventService);
if (globalEventListener) {
eventService.addGlobalListener(globalEventListener);
}
var toolPanelLayout: any = null;
var toolPanel: any = null;
if (!gridOptionsWrapper.isForPrint()) {
toolPanel = new ToolPanel();
toolPanelLayout = toolPanel.layout;
toolPanel.init(columnController, inMemoryRowController, gridOptionsWrapper, popupService, eventService);
}
// this is a child bean, get a reference and pass it on
// CAN WE DELETE THIS? it's done in the setDatasource section
var rowModel = inMemoryRowController.getModel();
selectionController.setRowModel(rowModel);
filterManager.setRowModel(rowModel);
rowRenderer.setRowModel(rowModel);
gridPanel.setRowModel(rowModel);
// and the last bean, done in it's own section, as it's optional
var paginationController: any = null;
var paginationGui: any = null;
if (!gridOptionsWrapper.isForPrint()) {
paginationController = new PaginationController();
paginationController.init(this, gridOptionsWrapper);
paginationGui = paginationController.getGui();
}
this.rowModel = rowModel;
this.usingInMemoryModel = true;
this.selectionController = selectionController;
this.columnController = columnController;
this.inMemoryRowController = inMemoryRowController;
this.virtualPageRowController = virtualPageRowController;
this.rowRenderer = rowRenderer;
this.headerRenderer = headerRenderer;
this.paginationController = paginationController;
this.filterManager = filterManager;
this.toolPanel = toolPanel;
this.gridPanel = gridPanel;
this.valueService = valueService;
this.masterSlaveService = masterSlaveService;
this.eventService = eventService;
this.gridOptionsWrapper = gridOptionsWrapper;
this.eRootPanel = new BorderLayout({
center: gridPanel.getLayout(),
east: toolPanelLayout,
south: paginationGui,
dontFill: gridOptionsWrapper.isForPrint(),
name: 'eRootPanel'
});
popupService.init(this.eRootPanel.getGui());
// default is we don't show paging panel, this is set to true when datasource is set
this.eRootPanel.setSouthVisible(false);
// see what the grid options are for default of toolbar
this.showToolPanel(gridOptionsWrapper.isShowToolPanel());
eUserProvidedDiv.appendChild(this.eRootPanel.getGui());
eventService.addEventListener(Events.EVENT_COLUMN_EVERYTHING_CHANGED, this.onColumnChanged.bind(this));
eventService.addEventListener(Events.EVENT_COLUMN_GROUP_OPENED, this.onColumnChanged.bind(this));
eventService.addEventListener(Events.EVENT_COLUMN_MOVED, this.onColumnChanged.bind(this));
eventService.addEventListener(Events.EVENT_COLUMN_PINNED_COUNT_CHANGED, this.onColumnChanged.bind(this));
eventService.addEventListener(Events.EVENT_COLUMN_PIVOT_CHANGE, this.onColumnChanged.bind(this));
eventService.addEventListener(Events.EVENT_COLUMN_RESIZED, this.onColumnChanged.bind(this));
eventService.addEventListener(Events.EVENT_COLUMN_VALUE_CHANGE, this.onColumnChanged.bind(this));
eventService.addEventListener(Events.EVENT_COLUMN_VISIBLE, this.onColumnChanged.bind(this));
}
private onColumnChanged(event: ColumnChangeEvent): void {
if (event.isPivotChanged()) {
this.inMemoryRowController.onPivotChanged();
}
if (event.isValueChanged()) {
this.inMemoryRowController.doAggregate();
}
if (event.isIndividualColumnResized()) {
this.onIndividualColumnResized(event.getColumn());
} else {
this.refreshHeaderAndBody();
}
this.gridPanel.showPinnedColContainersIfNeeded();
}
public refreshPivot(): void {
this.inMemoryRowController.onPivotChanged();
this.refreshHeaderAndBody();
}
public getEventService(): EventService {
return this.eventService;
}
private onIndividualColumnResized(column: Column): void {
this.headerRenderer.onIndividualColumnResized(column);
this.rowRenderer.onIndividualColumnResized(column);
if (column.pinned) {
this.updatePinnedColContainerWidthAfterColResize();
} else {
this.updateBodyContainerWidthAfterColResize();
}
}
public showToolPanel(show: any) {
if (!this.toolPanel) {
this.toolPanelShowing = false;
return;
}
this.toolPanelShowing = show;
this.eRootPanel.setEastVisible(show);
}
public isToolPanelShowing() {
return this.toolPanelShowing;
}
public isUsingInMemoryModel(): boolean {
return this.usingInMemoryModel;
}
public setDatasource(datasource?: any) {
// if datasource provided, then set it
if (datasource) {
this.gridOptions.datasource = datasource;
}
// get the set datasource (if null was passed to this method,
// then need to get the actual datasource from options
var datasourceToUse = this.gridOptionsWrapper.getDatasource();
this.doingVirtualPaging = this.gridOptionsWrapper.isVirtualPaging() && datasourceToUse;
this.doingPagination = datasourceToUse && !this.doingVirtualPaging;
var showPagingPanel: any;
if (this.doingVirtualPaging) {
this.paginationController.setDatasource(null);
this.virtualPageRowController.setDatasource(datasourceToUse);
this.rowModel = this.virtualPageRowController.getModel();
this.usingInMemoryModel = false;
showPagingPanel = false;
} else if (this.doingPagination) {
this.paginationController.setDatasource(datasourceToUse);
this.virtualPageRowController.setDatasource(null);
this.rowModel = this.inMemoryRowController.getModel();
this.usingInMemoryModel = true;
showPagingPanel = true;
} else {
this.paginationController.setDatasource(null);
this.virtualPageRowController.setDatasource(null);
this.rowModel = this.inMemoryRowController.getModel();
this.usingInMemoryModel = true;
showPagingPanel = false;
}
this.selectionController.setRowModel(this.rowModel);
this.filterManager.setRowModel(this.rowModel);
this.rowRenderer.setRowModel(this.rowModel);
this.gridPanel.setRowModel(this.rowModel);
this.eRootPanel.setSouthVisible(showPagingPanel);
// because we just set the rowModel, need to update the gui
this.rowRenderer.refreshView();
this.doLayout();
}
// gets called after columns are shown / hidden from groups expanding
private refreshHeaderAndBody() {
this.headerRenderer.refreshHeader();
this.headerRenderer.updateFilterIcons();
this.headerRenderer.updateSortIcons();
this.gridPanel.setBodyContainerWidth();
this.gridPanel.setPinnedColContainerWidth();
this.rowRenderer.refreshView();
}
public setFinished() {
window.removeEventListener('resize', this.doLayout);
this.finished = true;
}
public onQuickFilterChanged(newFilter: any) {
var actuallyChanged = this.filterManager.setQuickFilter(newFilter);
if (actuallyChanged) {
this.onFilterChanged();
}
}
public onFilterModified() {
this.eventService.dispatchEvent(Events.EVENT_FILTER_MODIFIED);
}
public onFilterChanged() {
this.eventService.dispatchEvent(Events.EVENT_BEFORE_FILTER_CHANGED);
this.filterManager.onFilterChanged();
this.headerRenderer.updateFilterIcons();
if (this.gridOptionsWrapper.isEnableServerSideFilter()) {
// if doing server side filtering, changing the sort has the impact
// of resetting the datasource
this.setDatasource();
} else {
// if doing in memory filtering, we just update the in memory data
this.updateModelAndRefresh(Constants.STEP_FILTER);
}
this.eventService.dispatchEvent(Events.EVENT_AFTER_FILTER_CHANGED);
}
public onRowClicked(event: any, rowIndex: any, node: any) {
var params = {
node: node,
data: node.data,
event: event,
rowIndex: rowIndex
};
this.eventService.dispatchEvent(Events.EVENT_ROW_CLICKED, params)
// we do not allow selecting groups by clicking (as the click here expands the group)
// so return if it's a group row
if (node.group) {
return;
}
// we also don't allow selection of floating rows
if (node.floating) {
return;
}
// making local variables to make the below more readable
var gridOptionsWrapper = this.gridOptionsWrapper;
var selectionController = this.selectionController;
// if no selection method enabled, do nothing
if (!gridOptionsWrapper.isRowSelection()) {
return;
}
// if click selection suppressed, do nothing
if (gridOptionsWrapper.isSuppressRowClickSelection()) {
return;
}
// ctrlKey for windows, metaKey for Apple
var ctrlKeyPressed = event.ctrlKey || event.metaKey;
var doDeselect = ctrlKeyPressed
&& selectionController.isNodeSelected(node)
&& gridOptionsWrapper.isRowDeselection();
if (doDeselect) {
selectionController.deselectNode(node);
} else {
var tryMulti = ctrlKeyPressed;
selectionController.selectNode(node, tryMulti);
}
}
public showLoadingPanel(show: any) {
this.gridPanel.showLoading(show);
}
private setupColumns() {
this.columnController.onColumnsChanged();
this.gridPanel.showPinnedColContainersIfNeeded();
}
// rowsToRefresh is at what index to start refreshing the rows. the assumption is
// if we are expanding or collapsing a group, then only he rows below the group
// need to be refresh. this allows the context (eg focus) of the other cells to
// remain.
public updateModelAndRefresh(step: any, refreshFromIndex?: any) {
this.inMemoryRowController.updateModel(step);
this.rowRenderer.refreshView(refreshFromIndex);
}
public setRows(rows?: any, firstId?: any) {
if (rows) {
this.gridOptions.rowData = rows;
}
this.inMemoryRowController.setAllRows(this.gridOptionsWrapper.getRowData(), firstId);
this.selectionController.deselectAll();
this.filterManager.onNewRowsLoaded();
this.updateModelAndRefresh(Constants.STEP_EVERYTHING);
this.headerRenderer.updateFilterIcons();
this.showLoadingPanel(false);
}
public ensureNodeVisible(comparator: any) {
if (this.doingVirtualPaging) {
throw 'Cannot use ensureNodeVisible when doing virtual paging, as we cannot check rows that are not in memory';
}
// look for the node index we want to display
var rowCount = this.rowModel.getVirtualRowCount();
var comparatorIsAFunction = typeof comparator === 'function';
var indexToSelect = -1;
// go through all the nodes, find the one we want to show
for (var i = 0; i < rowCount; i++) {
var node = this.rowModel.getVirtualRow(i);
if (comparatorIsAFunction) {
if (comparator(node)) {
indexToSelect = i;
break;
}
} else {
// check object equality against node and data
if (comparator === node || comparator === node.data) {
indexToSelect = i;
break;
}
}
}
if (indexToSelect >= 0) {
this.gridPanel.ensureIndexVisible(indexToSelect);
}
}
public getFilterModel() {
return this.filterManager.getFilterModel();
}
public setFocusedCell(rowIndex: any, colIndex: any) {
this.gridPanel.ensureIndexVisible(rowIndex);
this.gridPanel.ensureColIndexVisible(colIndex);
var that = this;
setTimeout(function () {
that.rowRenderer.setFocusedCell(rowIndex, colIndex);
}, 10);
}
public getSortModel() {
var allColumns = this.columnController.getAllColumns();
var columnsWithSorting = <any>[];
var i: any;
for (i = 0; i < allColumns.length; i++) {
if (allColumns[i].sort) {
columnsWithSorting.push(allColumns[i]);
}
}
columnsWithSorting.sort(function (a: any, b: any) {
return a.sortedAt - b.sortedAt;
});
var result = <any>[];
for (i = 0; i < columnsWithSorting.length; i++) {
var resultEntry = {
colId: columnsWithSorting[i].colId,
sort: columnsWithSorting[i].sort
};
result.push(resultEntry);
}
return result;
}
public setSortModel(sortModel: any) {
if (!this.gridOptionsWrapper.isEnableSorting()) {
console.warn('ag-grid: You are setting the sort model on a grid that does not have sorting enabled');
return;
}
// first up, clear any previous sort
var sortModelProvided = sortModel !== null && sortModel !== undefined && sortModel.length > 0;
var allColumns = this.columnController.getAllColumns();
for (var i = 0; i < allColumns.length; i++) {
var column = allColumns[i];
var sortForCol: any = null;
var sortedAt = -1;
if (sortModelProvided && !column.colDef.suppressSorting) {
for (var j = 0; j < sortModel.length; j++) {
var sortModelEntry = sortModel[j];
if (typeof sortModelEntry.colId === 'string'
&& typeof column.colId === 'string'
&& sortModelEntry.colId === column.colId) {
sortForCol = sortModelEntry.sort;
sortedAt = j;
}
}
}
if (sortForCol) {
column.sort = sortForCol;
column.sortedAt = sortedAt;
} else {
column.sort = null;
column.sortedAt = null;
}
}
this.onSortingChanged();
}
public onSortingChanged() {
this.eventService.dispatchEvent(Events.EVENT_BEFORE_SORT_CHANGED);
this.headerRenderer.updateSortIcons();
if (this.gridOptionsWrapper.isEnableServerSideSorting()) {
// if doing server side sorting, changing the sort has the impact
// of resetting the datasource
this.setDatasource();
} else {
// if doing in memory sorting, we just update the in memory data
this.updateModelAndRefresh(Constants.STEP_SORT);
}
this.eventService.dispatchEvent(Events.EVENT_AFTER_SORT_CHANGED);
}
public addVirtualRowListener(rowIndex: any, callback: any) {
if (!this.virtualRowCallbacks[rowIndex]) {
this.virtualRowCallbacks[rowIndex] = [];
}
this.virtualRowCallbacks[rowIndex].push(callback);
}
public onVirtualRowSelected(rowIndex: any, selected: any) {
// inform the callbacks of the event
if (this.virtualRowCallbacks[rowIndex]) {
this.virtualRowCallbacks[rowIndex].forEach(function (callback: any) {
if (typeof callback.rowSelected === 'function') {
callback.rowSelected(selected);
}
});
}
this.rowRenderer.onRowSelected(rowIndex, selected);
}
public onVirtualRowRemoved(rowIndex: any) {
// inform the callbacks of the event
if (this.virtualRowCallbacks[rowIndex]) {
this.virtualRowCallbacks[rowIndex].forEach(function (callback: any) {
if (typeof callback.rowRemoved === 'function') {
callback.rowRemoved();
}
});
}
// remove the callbacks
delete this.virtualRowCallbacks[rowIndex];
}
public setColumnDefs(colDefs?: ColDef[]) {
if (colDefs) {
this.gridOptions.columnDefs = colDefs;
}
this.setupColumns();
this.updateModelAndRefresh(Constants.STEP_EVERYTHING);
// found that adding pinned column can upset the layout
this.doLayout();
}
public updateBodyContainerWidthAfterColResize() {
this.rowRenderer.setMainRowWidths();
this.gridPanel.setBodyContainerWidth();
}
public updatePinnedColContainerWidthAfterColResize() {
this.gridPanel.setPinnedColContainerWidth();
}
public doLayout() {
// need to do layout first, as drawVirtualRows and setPinnedColHeight
// need to know the result of the resizing of the panels.
var sizeChanged = this.eRootPanel.doLayout();
// both of the two below should be done in gridPanel, the gridPanel should register 'resize' to the panel
if (sizeChanged) {
this.rowRenderer.drawVirtualRows();
}
}
}
}