Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(footer): add custom footer to show metrics #284

Merged
merged 2 commits into from
Jan 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions assets/i18n/en/aurelia-slickgrid.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"IN_COLLECTION_SEPERATED_BY_COMMA": "Search items in a collection, must be separated by a comma (a,b)",
"ITEMS": "items",
"ITEMS_PER_PAGE": "items per page",
"LAST_UPDATE": "Last Update",
"NOT_IN_COLLECTION_SEPERATED_BY_COMMA": "Search items not in a collection, must be separated by a comma (a,b)",
"OF": "of",
"OK": "OK",
Expand Down
1 change: 1 addition & 0 deletions assets/i18n/fr/aurelia-slickgrid.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"INVALID_FLOAT": "Le nombre doit être valide et avoir un maximum de {{maxDecimal}} décimales.",
"ITEMS": "éléments",
"ITEMS_PER_PAGE": "éléments par page",
"LAST_UPDATE": "Dernière mise à jour",
"NOT_IN_COLLECTION_SEPERATED_BY_COMMA": "Recherche excluant certain éléments d'une collection, doit être séparé par une virgule (a,b)",
"OF": "de",
"OK": "Terminé",
Expand Down
1 change: 1 addition & 0 deletions src/assets/i18n/en/aurelia-slickgrid.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"IN_COLLECTION_SEPERATED_BY_COMMA": "Search items in a collection, must be separated by a comma (a,b)",
"ITEMS": "items",
"ITEMS_PER_PAGE": "items per page",
"LAST_UPDATE": "Last Update",
"NOT_IN_COLLECTION_SEPERATED_BY_COMMA": "Search items not in a collection, must be separated by a comma (a,b)",
"OF": "of",
"OK": "OK",
Expand Down
1 change: 1 addition & 0 deletions src/assets/i18n/fr/aurelia-slickgrid.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"INVALID_FLOAT": "Le nombre doit être valide et avoir un maximum de {{maxDecimal}} décimales.",
"ITEMS": "éléments",
"ITEMS_PER_PAGE": "éléments par page",
"LAST_UPDATE": "Dernière mise à jour",
"NOT_IN_COLLECTION_SEPERATED_BY_COMMA": "Recherche excluant certain éléments d'une collection, doit être séparé par une virgule (a,b)",
"OF": "de",
"OK": "Terminé",
Expand Down
1 change: 1 addition & 0 deletions src/aurelia-slickgrid/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class Constants {
TEXT_ITEMS_PER_PAGE: 'items per page',
TEXT_OF: 'of',
TEXT_OK: 'OK',
TEXT_LAST_UPDATE: 'Last Update',
TEXT_PAGE: 'Page',
TEXT_REFRESH_DATASET: 'Refresh Dataset',
TEXT_REMOVE_FILTER: 'Remove Filter',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import 'jest-extended';
import { BindingEngine, Container } from 'aurelia-framework';
import { BindingSignaler } from 'aurelia-templating-resources';
import { EventAggregator } from 'aurelia-event-aggregator';
import { HttpClient } from 'aurelia-fetch-client';
import { BindingEngine, Container } from 'aurelia-framework';
import { DOM } from 'aurelia-pal';
import { I18N } from 'aurelia-i18n';

import { AureliaSlickgridCustomElement } from '../aurelia-slickgrid';
import { ExtensionUtility } from '../../extensions';
Expand All @@ -21,7 +23,18 @@ import {
SharedService,
SortService,
} from '../../services';
import { Column, CurrentFilter, CurrentSorter, GraphqlServiceApi, GraphqlServiceOption, GridOption, GridState, GridStateType, Pagination } from '../../models';
import {
Column,
CurrentFilter,
CurrentSorter,
GraphqlPaginatedResult,
GraphqlServiceApi,
GraphqlServiceOption,
GridOption,
GridState,
GridStateType,
Pagination
} from '../../models';
import { Filters } from '../../filters';
import { Editors } from '../../editors';
import * as utilities from '../../services/backend-utilities';
Expand Down Expand Up @@ -168,7 +181,7 @@ const mockDraggableGrouping = {
constructor: jest.fn(),
init: jest.fn(),
destroy: jest.fn(),
}
};

const mockSlickCore = {
handlers: [],
Expand Down Expand Up @@ -273,6 +286,7 @@ describe('Aurelia-Slickgrid Custom Component instantiated via Constructor', () =
let divContainer: HTMLDivElement;
let cellDiv: HTMLDivElement;
let ea: EventAggregator;
let i18n: I18N;
const http = new HttpStub();

const template = `
Expand All @@ -295,6 +309,7 @@ describe('Aurelia-Slickgrid Custom Component instantiated via Constructor', () =
document.body.appendChild(divContainer);

ea = new EventAggregator();
i18n = new I18N(ea, new BindingSignaler());
container = new Container();
customElement = new AureliaSlickgridCustomElement(
bindingEngineStub,
Expand All @@ -310,12 +325,23 @@ describe('Aurelia-Slickgrid Custom Component instantiated via Constructor', () =
gridServiceStub,
gridStateServiceStub,
groupingAndColspanServiceStub,
i18n,
paginationServiceStub,
resizerServiceStub,
sharedService,
sortServiceStub
);

i18n.setup({
resources: {
en: { translation: { ITEMS: 'items', OF: 'of', } },
fr: { translation: { ITEMS: 'éléments', OF: 'de', } }
},
lng: 'fr',
fallbackLng: 'en',
debug: false
});

customElement.gridId = 'grid1';
customElement.columnDefinitions = [{ id: 'name', field: 'name' }];
customElement.dataset = [];
Expand Down Expand Up @@ -731,7 +757,7 @@ describe('Aurelia-Slickgrid Custom Component instantiated via Constructor', () =

customElement.bind();
customElement.attached();
customElement.gridOptions.backendServiceApi.internalPostProcess({ data: { users: { nodes: [{ firstName: 'John' }], pageInfo: { hasNextPage: false }, totalCount: 2 } } });
customElement.gridOptions.backendServiceApi.internalPostProcess({ data: { users: { nodes: [{ firstName: 'John' }], totalCount: 2 } } } as GraphqlPaginatedResult);

expect(spy).toHaveBeenCalled();
expect(customElement.gridOptions.backendServiceApi.internalPostProcess).toEqual(expect.any(Function));
Expand All @@ -757,7 +783,7 @@ describe('Aurelia-Slickgrid Custom Component instantiated via Constructor', () =

customElement.bind();
customElement.attached();
customElement.gridOptions.backendServiceApi.internalPostProcess({ data: { notUsers: { nodes: [{ firstName: 'John' }], pageInfo: { hasNextPage: false }, totalCount: 2 } } });
customElement.gridOptions.backendServiceApi.internalPostProcess({ data: { notUsers: { nodes: [{ firstName: 'John' }], totalCount: 2 } } } as GraphqlPaginatedResult);

expect(spy).not.toHaveBeenCalled();
expect(customElement.dataset).toEqual([]);
Expand Down Expand Up @@ -1219,5 +1245,54 @@ describe('Aurelia-Slickgrid Custom Component instantiated via Constructor', () =
});
});
});

describe('Custom Footer', () => {
it('should have a Custom Footer when "showCustomFooter" is enabled and there are no Pagination used', (done) => {
const mockColDefs = [{ id: 'name', field: 'name', editor: undefined, internalColumnEditor: {} }];

customElement.gridOptions.enableTranslate = true;
customElement.gridOptions.showCustomFooter = true;
customElement.bind();
customElement.attached();
customElement.columnDefinitions = mockColDefs;

setTimeout(() => {
expect(customElement.columnDefinitions).toEqual(mockColDefs);
expect(customElement.showCustomFooter).toBeTrue();
expect(customElement.customFooterOptions).toEqual({
dateFormat: 'YYYY-DD-MM h:mm:ss a',
hideLastUpdateTimestamp: true,
hideTotalItemCount: false,
footerHeight: 20,
leftContainerClass: 'col-xs-12 col-sm-5',
metricSeparator: '|',
metricTexts: {
items: 'éléments',
itemsKey: 'ITEMS',
of: 'de',
ofKey: 'OF',
},
rightContainerClass: 'col-xs-6 col-sm-7',
});
done();
}, 1);
});

it('should NOT have a Custom Footer when "showCustomFooter" is enabled WITH Pagination in use', (done) => {
const mockColDefs = [{ id: 'name', field: 'name', editor: undefined, internalColumnEditor: {} }];

customElement.gridOptions.enablePagination = true;
customElement.gridOptions.showCustomFooter = true;
customElement.bind();
customElement.attached();
customElement.columnDefinitions = mockColDefs;

setTimeout(() => {
expect(customElement.columnDefinitions).toEqual(mockColDefs);
expect(customElement.showCustomFooter).toBeFalse();
done();
}, 1);
});
});
});
});
35 changes: 31 additions & 4 deletions src/aurelia-slickgrid/custom-elements/aurelia-slickgrid.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,40 @@
<template>
<div id="slickGridContainer-${gridId}" class="gridPane" css="width: ${gridWidth}px">
<div id.bind="gridId" class="slickgrid-container" style="width: 100%" css="height: ${gridHeight}px"
focusout.delegate="commitEdit($event.target)">
focusout.delegate="commitEdit($event.target)">
</div>

<!-- Pagination section under the grid -->
<slick-pagination id="slickPagingContainer-${gridId}" if.bind="showPagination"
asg-on-pagination-changed.delegate="paginationChanged($event.detail)" dataview.bind="dataview" grid.bind="grid"
enable-translate.bind="gridOptions.enableTranslate" options.bind="paginationOptions" locales.bind="locales"
total-items.bind="totalItems" backend-service-api.bind="backendServiceApi">
asg-on-pagination-changed.delegate="paginationChanged($event.detail)" dataview.bind="dataview"
grid.bind="grid"
enable-translate.bind="gridOptions.enableTranslate" options.bind="paginationOptions"
locales.bind="locales"
total-items.bind="totalItems" backend-service-api.bind="backendServiceApi">
</slick-pagination>

<!-- Custom Footer section under the grid -->
<div if.bind="showCustomFooter && customFooterOptions" class="slick-custom-footer" style="width: 100%;"
css="height: ${customFooterOptions.footerHeight || 20}px">
<span class="left-footer ${customFooterOptions.leftContainerClass}">
${customFooterOptions.leftFooterText}
</span>

<span class="right-footer metrics ${customFooterOptions.rightContainerClass}"
if.bind="metrics && !customFooterOptions.hideMetrics">
<span if.bind="!customFooterOptions.hideLastUpdateTimestamp">
<span>${customFooterOptions.metricTexts.lastUpdate}</span>

${metrics.endTime | asgDateFormat: customFooterOptions.dateFormat}
<span class="separator">${customFooterOptions.metricSeparator}</span>
</span>

${metrics.itemCount}
<span if.bind="!customFooterOptions.hideTotalItemCount">${customFooterOptions.metricTexts.of}
${metrics.totalItemCount}
</span>
${customFooterOptions.metricTexts.items}
</span>
</div>
</div>
</template>
66 changes: 64 additions & 2 deletions src/aurelia-slickgrid/custom-elements/aurelia-slickgrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import 'slickgrid/slick.dataview';
import 'slickgrid/slick.grid';

import { bindable, BindingEngine, bindingMode, Container, Factory, inject } from 'aurelia-framework';
import { DOM } from 'aurelia-pal';
import { EventAggregator, Subscription } from 'aurelia-event-aggregator';
import { DOM } from 'aurelia-pal';
import { I18N } from 'aurelia-i18n';

import { Constants } from '../constants';
import { GlobalGridOptions } from '../global-grid-options';
Expand All @@ -18,13 +19,15 @@ import {
BackendServiceApi,
BackendServiceOption,
Column,
CustomFooterOption,
ExtensionName,
GraphqlResult,
GraphqlPaginatedResult,
GridOption,
GridStateChange,
GridStateType,
Locale,
Metrics,
Pagination,
SlickEventHandler,
} from '../models/index';
Expand Down Expand Up @@ -69,6 +72,7 @@ const DEFAULT_SLICKGRID_EVENT_PREFIX = 'sg';
GridService,
GridStateService,
GroupingAndColspanService,
I18N,
PaginationService,
ResizerService,
SharedService,
Expand All @@ -83,8 +87,11 @@ export class AureliaSlickgridCustomElement {
private _hideHeaderRowAfterPageLoad = false;
groupItemMetadataProvider: any;
backendServiceApi: BackendServiceApi | undefined;
customFooterOptions: CustomFooterOption;
locales: Locale;
metrics: Metrics;
isGridInitialized = false;
showCustomFooter = false;
showPagination = false;
serviceList: any[] = [];
subscriptions: Subscription[] = [];
Expand Down Expand Up @@ -117,6 +124,7 @@ export class AureliaSlickgridCustomElement {
private gridService: GridService,
private gridStateService: GridStateService,
private groupingAndColspanService: GroupingAndColspanService,
private i18n: I18N,
private paginationService: PaginationService,
private resizerService: ResizerService,
private sharedService: SharedService,
Expand Down Expand Up @@ -290,6 +298,10 @@ export class AureliaSlickgridCustomElement {
resizerService: this.resizerService,
sortService: this.sortService,
};

// user could show a custom footer with the data metrics (dataset length and last updated timestamp)
this.optionallyShowCustomFooterWithMetrics();

this.dispatchCustomEvent(`${DEFAULT_AURELIA_EVENT_PREFIX}-on-aurelia-grid-created`, aureliaElementInstance);
}

Expand Down Expand Up @@ -430,6 +442,7 @@ export class AureliaSlickgridCustomElement {
this.extensionService.translateContextMenu();
this.extensionService.translateGridMenu();
this.extensionService.translateHeaderMenu();
this.translateCustomFooterTexts();
}
})
);
Expand Down Expand Up @@ -519,7 +532,16 @@ export class AureliaSlickgridCustomElement {
this.gridEventService.bindOnClick(grid, dataView);

if (dataView && grid) {
this._eventHandler.subscribe(dataView.onRowCountChanged, () => grid.invalidate());
this._eventHandler.subscribe(dataView.onRowCountChanged, (e: Event, args: any) => {
grid.invalidate();

this.metrics = {
startTime: new Date(),
endTime: new Date(),
itemCount: args && args.current || 0,
totalItemCount: this.dataset.length || 0
};
});

// without this, filtering data with local dataset will not always show correctly
// also don't use "invalidateRows" since it destroys the entire row and as bad user experience when updating a row
Expand Down Expand Up @@ -790,6 +812,32 @@ export class AureliaSlickgridCustomElement {
}
}

/**
* We could optionally display a custom footer below the grid to show some metrics (last update, item count with/without filters)
* It's an opt-in, user has to enable "showCustomFooter" and it cannot be used when there's already a Pagination since they display the same kind of info
*/
private optionallyShowCustomFooterWithMetrics() {
if (this.gridOptions) {
setTimeout(() => {
// we will display the custom footer only when there's no Pagination
if (!(this.gridOptions.backendServiceApi || this.gridOptions.enablePagination)) {
this.showCustomFooter = this.gridOptions.hasOwnProperty('showCustomFooter') ? this.gridOptions.showCustomFooter : false;
this.customFooterOptions = this.gridOptions.customFooterOptions || {};
}
});

if ((this.gridOptions.enableTranslate || this.gridOptions.i18n)) {
this.translateCustomFooterTexts();
} else if (this.gridOptions.customFooterOptions) {
const customFooterOptions = this.gridOptions.customFooterOptions;
customFooterOptions.metricTexts = customFooterOptions.metricTexts || {};
customFooterOptions.metricTexts.lastUpdate = this.locales && this.locales.TEXT_LAST_UPDATE || 'TEXT_LAST_UPDATE';
customFooterOptions.metricTexts.items = this.locales && this.locales.TEXT_ITEMS || 'TEXT_ITEMS';
customFooterOptions.metricTexts.of = this.locales && this.locales.TEXT_OF || 'TEXT_OF';
}
}
}

/**
* For convenience to the user, we provide the property "editor" as an Aurelia-Slickgrid editor complex object
* however "editor" is used internally by SlickGrid for it's own Editor Factory
Expand All @@ -811,6 +859,20 @@ export class AureliaSlickgridCustomElement {
});
}

/** Translate all Custom Footer Texts (footer with metrics) */
private translateCustomFooterTexts() {
if (this.i18n && this.i18n.tr) {
const customFooterOptions = this.gridOptions && this.gridOptions.customFooterOptions || {};
customFooterOptions.metricTexts = customFooterOptions.metricTexts || {};
for (const propName of Object.keys(customFooterOptions.metricTexts)) {
if (propName.lastIndexOf('Key') > 0) {
const propNameWithoutKey = propName.substring(0, propName.lastIndexOf('Key'));
customFooterOptions.metricTexts[propNameWithoutKey] = this.i18n.tr(customFooterOptions.metricTexts[propName] || ' ');
}
}
}
}

/**
* Update the "internalColumnEditor.collection" property.
* Since this is called after the async call resolves, the pointer will not be the same as the "column" argument passed.
Expand Down
Loading