Skip to content

Commit

Permalink
Merge pull request #284 from ghiscoding/feat/custom-footer
Browse files Browse the repository at this point in the history
feat(footer): add custom footer to show metrics
  • Loading branch information
ghiscoding committed Jan 16, 2020
2 parents 04f4490 + 06af8b6 commit 5f81afc
Show file tree
Hide file tree
Showing 36 changed files with 483 additions and 23 deletions.
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

0 comments on commit 5f81afc

Please sign in to comment.