diff --git a/packages/ng2-qgrid/src/public-api.ts b/packages/ng2-qgrid/src/public-api.ts index 1a9abf457..271468a20 100644 --- a/packages/ng2-qgrid/src/public-api.ts +++ b/packages/ng2-qgrid/src/public-api.ts @@ -164,6 +164,7 @@ export { BlurDirective, BoolEditorComponent, BoolEditorModule, + CacheAlreadyRequestedPageStrategy, CaptionComponent, CaptionModule, CellEditorComponent, @@ -171,6 +172,7 @@ export { CellTooltipComponent, CellTooltipDirective, CellTooltipModule, + CheckNextPageCountStrategy, ColumnChooserComponent, ColumnChooserModule, ColumnChooserTriggerComponent, @@ -189,6 +191,12 @@ export { CurrencyPipe, DataManipulationComponent, DataManipulationModule, + DataProvider, + DataProviderComponent, + DataProviderContext, + DataProviderModule, + DataProviderPageServer, + DataProviderStrategy, DateDirective, DateMaskDirective, DateModule, @@ -247,10 +255,9 @@ export { ReferenceComponent, ReferenceEditorComponent, ReferenceEditorModule, + RequestTotalCountOnceStategy, RestComponent, RestModule, - DataProviderComponent, - DataProviderModule, RuleComponent, SerializationService, StatusBarComponent, diff --git a/packages/qgrid-ngx-examples/src/examples/data-provider/example-data-provider.component.html b/packages/qgrid-ngx-examples/src/examples/data-provider/example-data-provider.component.html index 071d89e2d..53c8cabeb 100644 --- a/packages/qgrid-ngx-examples/src/examples/data-provider/example-data-provider.component.html +++ b/packages/qgrid-ngx-examples/src/examples/data-provider/example-data-provider.component.html @@ -1,4 +1,4 @@ - + diff --git a/packages/qgrid-ngx-examples/src/examples/data-provider/example-data-provider.component.scss b/packages/qgrid-ngx-examples/src/examples/data-provider/example-data-provider.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/packages/qgrid-ngx-examples/src/examples/data-provider/example-data-provider.component.ts b/packages/qgrid-ngx-examples/src/examples/data-provider/example-data-provider.component.ts index 0f4e03ee2..e15c13b2c 100644 --- a/packages/qgrid-ngx-examples/src/examples/data-provider/example-data-provider.component.ts +++ b/packages/qgrid-ngx-examples/src/examples/data-provider/example-data-provider.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { GridModel } from 'ng2-qgrid'; -import { Observable } from 'rxjs'; -import { map, switchMap, tap } from 'rxjs/operators'; +import { CacheAlreadyRequestedPageStrategy, CheckNextPageCountStrategy, DataProvider, DataProviderPageServer, DataProviderStrategy, Grid, GridModel, RequestTotalCountOnceStategy } from 'ng2-qgrid'; +import { Observable, of } from 'rxjs'; +import { map } from 'rxjs/operators'; import { Atom, DataService } from '../data.service'; const EXAMPLE_TAGS = [ @@ -12,6 +12,7 @@ const EXAMPLE_TAGS = [ @Component({ selector: 'example-data-provider', templateUrl: 'example-data-provider.component.html', + styleUrls: ['example-data-provider.component.scss'], providers: [DataService], changeDetection: ChangeDetectionStrategy.OnPush }) @@ -21,34 +22,44 @@ export class ExampleDataProviderComponent { page$: Observable; + gridModel = this.qgrid.model(); + + private server = new FakeServer(this.dataService); + private dataProvider: DataProvider = new DataProvider(this.gridModel, [ + new CheckNextPageCountStrategy(this.server), + new RequestTotalCountOnceStategy(this.server), + new CacheAlreadyRequestedPageStrategy(this.server, { pagesToLoad: 1 }), + new ExampleReverseDataStrategy(), + ]); + constructor( private dataService: DataService, + private qgrid: Grid, ) { } onRequestRows(gridModel: GridModel): void { - const server = new FakeServer(this.dataService); - const pager = gridModel.pagination(); - - this.page$ = server.getTotal() - .pipe( - tap(total => gridModel.pagination({ count: total })), - switchMap(() => server.getPage(pager.current, pager.size)), - ); + this.page$ = this.dataProvider.getPage(); } } -class FakeServer { +class FakeServer implements DataProviderPageServer { constructor( private dataService: DataService, ) { } - getPage(pageNumber: number, pageSize: number): Observable { + getRecords(from: number, to: number): Observable { return this.dataService.getAtoms() - .pipe(map(atoms => atoms.splice(pageNumber * pageSize, pageSize))); + .pipe(map(atoms => atoms.slice(from, to))); } getTotal(): Observable { return this.dataService.getAtoms() .pipe(map(atoms => atoms.length)); } +} + +class ExampleReverseDataStrategy implements DataProviderStrategy { + process(memo: T[]): Observable { + return of(memo.slice().reverse()); + } } \ No newline at end of file diff --git a/packages/qgrid-ngx-plugins/src/lib/data-provider/cache-already-requested-page-strategy.ts b/packages/qgrid-ngx-plugins/src/lib/data-provider/cache-already-requested-page-strategy.ts new file mode 100644 index 000000000..37097d3a1 --- /dev/null +++ b/packages/qgrid-ngx-plugins/src/lib/data-provider/cache-already-requested-page-strategy.ts @@ -0,0 +1,54 @@ +import { GridModel } from '@qgrid/ngx'; +import { Observable, of } from 'rxjs'; +import { filter, tap } from 'rxjs/operators'; +import { DataProviderContext } from './data-provider'; +import { DataProviderPageServer } from './data-provider-page-server'; +import { DataProviderStrategy } from './data-provider-strategy'; + +export class CacheAlreadyRequestedPageStrategy implements DataProviderStrategy { + private pageCache: Map = new Map(); + private pagerSize: number; + + constructor( + private server: Pick, 'getRecords'>, + private options: { pagesToLoad: number } = { pagesToLoad: 1 }, + ) { } + + process(data: T[], { model }: DataProviderContext): Observable { + const { current, size } = model.pagination(); + + if (this.options.pagesToLoad) { + this.loadBackgroundPages(this.options.pagesToLoad, model); + } + + if (this.pageCache.has(current)) { + return of(this.pageCache.get(current)); + } + + const shouldRequestData = !data.length; + return (shouldRequestData ? this.server.getRecords(current * size, (current + 1) * size) : of(data)) + .pipe(tap(rows => this.pageCache.set(current, rows))); + } + + invalidate(gridModel: GridModel): void { + const { size } = gridModel.pagination(); + if (this.pagerSize !== size) { + this.pageCache.clear(); + this.pagerSize = size; + } + } + + private loadBackgroundPages(pagesToLoad: number, model: GridModel): void { + const { count, current, size } = model.pagination(); + const fromPage = current + 1; + const toPage = current + pagesToLoad; + const maxPage = Math.floor(count / size); + for (let page = fromPage; page < toPage; page++) { + if (page <= maxPage && !this.pageCache.has(page)) { + this.server.getRecords(page * size, (page + 1) * size) + .pipe(filter(rows => !!rows?.length)) + .subscribe(rows => this.pageCache.set(page, rows)); + } + } + } +} \ No newline at end of file diff --git a/packages/qgrid-ngx-plugins/src/lib/data-provider/check-next-page-count-strategy.ts b/packages/qgrid-ngx-plugins/src/lib/data-provider/check-next-page-count-strategy.ts new file mode 100644 index 000000000..ad374d447 --- /dev/null +++ b/packages/qgrid-ngx-plugins/src/lib/data-provider/check-next-page-count-strategy.ts @@ -0,0 +1,45 @@ +import { GridModel } from '@qgrid/ngx'; +import { Observable, of } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { DataProviderContext } from './data-provider'; +import { DataProviderPageServer } from './data-provider-page-server'; +import { DataProviderStrategy } from './data-provider-strategy'; + +export class CheckNextPageCountStrategy implements DataProviderStrategy { + private cache: Set = new Set(); + private pagerSize: number; + + constructor( + private server: Pick, 'getRecords'>, + ) { } + + process(data: T[], { model }: DataProviderContext): Observable { + const { current } = model.pagination(); + + if (this.cache.has(current)) { + return of(data); + } + + const { count, size } = model.pagination(); + return this.server.getRecords(current * size, (current + 1) * size + 1) + .pipe( + map((next: T[]) => { + if (next?.length > size) { + next.pop(); + } + model.pagination({ count: next.length + (count || 1) }); + this.cache.add(current); + return next; + }), + ); + } + + invalidate(gridModel: GridModel): void { + const { size } = gridModel.pagination(); + if (size !== this.pagerSize) { + this.cache.clear(); + gridModel.pagination({ count: 0 }); + this.pagerSize = size; + } + } +} \ No newline at end of file diff --git a/packages/qgrid-ngx-plugins/src/lib/data-provider/data-provider-page-server.ts b/packages/qgrid-ngx-plugins/src/lib/data-provider/data-provider-page-server.ts new file mode 100644 index 000000000..ca91fce3a --- /dev/null +++ b/packages/qgrid-ngx-plugins/src/lib/data-provider/data-provider-page-server.ts @@ -0,0 +1,6 @@ +import { Observable } from 'rxjs'; + +export interface DataProviderPageServer { + getRecords(from: number, to: number): Observable; + getTotal(): Observable; +} \ No newline at end of file diff --git a/packages/qgrid-ngx-plugins/src/lib/data-provider/data-provider-strategy.ts b/packages/qgrid-ngx-plugins/src/lib/data-provider/data-provider-strategy.ts new file mode 100644 index 000000000..38049b728 --- /dev/null +++ b/packages/qgrid-ngx-plugins/src/lib/data-provider/data-provider-strategy.ts @@ -0,0 +1,8 @@ +import { GridModel } from '@qgrid/ngx'; +import { Observable } from 'rxjs'; +import { DataProviderContext } from './data-provider'; + +export interface DataProviderStrategy { + process(data: T[], context: DataProviderContext): Observable; + invalidate?(gridModel: GridModel): void; +} \ No newline at end of file diff --git a/packages/qgrid-ngx-plugins/src/lib/data-provider/data-provider.ts b/packages/qgrid-ngx-plugins/src/lib/data-provider/data-provider.ts new file mode 100644 index 000000000..f44829c4a --- /dev/null +++ b/packages/qgrid-ngx-plugins/src/lib/data-provider/data-provider.ts @@ -0,0 +1,35 @@ +import { GridModel } from '@qgrid/ngx'; +import { Observable, of } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; +import { DataProviderStrategy } from './data-provider-strategy'; + +export interface DataProviderContext { + model: GridModel; +} + +export class DataProvider { + + constructor( + private gridModel: GridModel, + private strategies: DataProviderStrategy[], + ) { } + + getPage(): Observable { + return this.applyStrategies(); + } + + private applyStrategies(data = [], index = 0): Observable { + const strategy = this.strategies[index]; + const hasNext = !!this.strategies[index + 1]; + if (!strategy) { + return of(data); + } + + if (typeof strategy.invalidate === 'function') { + strategy.invalidate(this.gridModel); + } + + return strategy.process(data, { model: this.gridModel }) + .pipe(switchMap(x => hasNext ? this.applyStrategies(x, index + 1) : of(x))); + } +} \ No newline at end of file diff --git a/packages/qgrid-ngx-plugins/src/lib/data-provider/request-count-once.ts b/packages/qgrid-ngx-plugins/src/lib/data-provider/request-count-once.ts new file mode 100644 index 000000000..4a4b294f6 --- /dev/null +++ b/packages/qgrid-ngx-plugins/src/lib/data-provider/request-count-once.ts @@ -0,0 +1,29 @@ +import { Observable, of } from 'rxjs'; +import { switchMap, tap } from 'rxjs/operators'; +import { DataProviderContext } from './data-provider'; +import { DataProviderPageServer } from './data-provider-page-server'; +import { DataProviderStrategy } from './data-provider-strategy'; + +export class RequestTotalCountOnceStategy implements DataProviderStrategy { + private totalCount: number = 0; + + constructor( + private server: Pick, 'getTotal'>, + ) { } + + process(data: T[], { model }: DataProviderContext): Observable { + if (this.totalCount > 0) { + model.pagination({ count: this.totalCount }); + return of(data); + } + + return this.server.getTotal() + .pipe( + tap(count => { + this.totalCount = count; + model.pagination({ count }); + }), + switchMap(() => of(data)) + ) + } +} \ No newline at end of file diff --git a/packages/qgrid-ngx-plugins/src/public-api.ts b/packages/qgrid-ngx-plugins/src/public-api.ts index 3d296f9a7..13cd799d0 100644 --- a/packages/qgrid-ngx-plugins/src/public-api.ts +++ b/packages/qgrid-ngx-plugins/src/public-api.ts @@ -1,20 +1,17 @@ export { ActionBarComponent } from './lib/action-bar/action-bar.component'; export { ActionBarModule } from './lib/action-bar/action-bar.module'; -export { ActionComponent } from './lib/action/action.component'; -export { ActionCoreComponent } from './lib/action/action-core.component'; export { ActionListComponent } from './lib/action-bar/action-list.component'; +export { ActionCoreComponent } from './lib/action/action-core.component'; +export { ActionComponent } from './lib/action/action.component'; export { ActionModule } from './lib/action/action.module'; export { AltComponent } from './lib/alt/alt.component'; export { AltModule } from './lib/alt/alt.module'; -export { ArrayPipe } from './lib/pipes/array.pipe'; export { AutoCompleteEditorComponent } from './lib/autocomplete-editor/autocomplete-editor.component'; export { AutoCompleteEditorModule } from './lib/autocomplete-editor/autocomplete-editor.module'; -export { AutoFocusDirective } from './lib/focus/autofocus.directive'; export { BackdropComponent } from './lib/backdrop/backdrop.component'; export { BackdropDirective } from './lib/backdrop/backdrop.directive'; export { BackdropModule } from './lib/backdrop/backdrop.module'; export { BackdropService } from './lib/backdrop/backdrop.service'; -export { BlurDirective } from './lib/focus/blur.directive'; export { BoolEditorComponent } from './lib/bool-editor/bool-editor.component'; export { BoolEditorModule } from './lib/bool-editor/bool-editor.module'; export { CaptionComponent } from './lib/caption/caption.component'; @@ -22,55 +19,59 @@ export { CaptionModule } from './lib/caption/caption.module'; export { CellEditorComponent } from './lib/cell-editor/cell-editor.component'; export { CellEditorModule } from './lib/cell-editor/cell-editor.module'; export { CellTooltipComponent } from './lib/cell-tooltip/cell-tooltip.component'; -export { CellTooltipModule } from './lib/cell-tooltip/cell-tooltip.module'; export { CellTooltipDirective } from './lib/cell-tooltip/cell-tooltip.directive'; +export { CellTooltipModule } from './lib/cell-tooltip/cell-tooltip.module'; export { ColumnChooserComponent } from './lib/colum-chooser/column-chooser.component'; export { ColumnChooserModule } from './lib/colum-chooser/column-chooser.module'; -export { ColumnChooserTriggerModule } from './lib/column-chooser-trigger/column-chooser-trigger.module'; export { ColumnChooserTriggerComponent } from './lib/column-chooser-trigger/column-chooser-trigger.component'; +export { ColumnChooserTriggerModule } from './lib/column-chooser-trigger/column-chooser-trigger.module'; export { ColumnFilterByComponent } from './lib/column-filter/column-filter-by.component'; -export { ColumnFilterComponent } from './lib/column-filter/column-filter.component'; -export { ColumnFilterItemDirective } from './lib/column-filter/column-filter-item.directive'; export { ColumnFilterItemListDirective } from './lib/column-filter/column-filter-item-list.directive'; -export { ColumnFilterModule } from './lib/column-filter/column-filter.module'; +export { ColumnFilterItemDirective } from './lib/column-filter/column-filter-item.directive'; export { ColumnFilterTriggerComponent } from './lib/column-filter/column-filter-trigger.component'; +export { ColumnFilterComponent } from './lib/column-filter/column-filter.component'; +export { ColumnFilterModule } from './lib/column-filter/column-filter.module'; export { ColumnSortComponent } from './lib/column-sort/column-sort.component'; export { ColumnSortModule } from './lib/column-sort/column-sort.module'; export { CommandDirective } from './lib/command/command.directive'; export { CommandModule } from './lib/command/command.module'; -export { ConvertPipe } from './lib/pipes/convert.pipe'; -export { CurrencyPipe } from './lib/pipes/currency.pipe'; export { DataManipulationComponent } from './lib/data-manipulation/data-manipulation.component'; export { DataManipulationModule } from './lib/data-manipulation/data-manipulation.module'; +export { CacheAlreadyRequestedPageStrategy } from './lib/data-provider/cache-already-requested-page-strategy'; +export { CheckNextPageCountStrategy } from './lib/data-provider/check-next-page-count-strategy'; +export { DataProvider, DataProviderContext } from './lib/data-provider/data-provider'; +export { DataProviderPageServer } from './lib/data-provider/data-provider-page-server'; +export { DataProviderStrategy } from './lib/data-provider/data-provider-strategy'; export { DataProviderComponent } from './lib/data-provider/data-provider.component'; export { DataProviderModule } from './lib/data-provider/data-provider.module'; +export { RequestTotalCountOnceStategy } from './lib/data-provider/request-count-once'; export { DateMaskDirective } from './lib/date/date-mask.directive'; export { DateDirective } from './lib/date/date.directive'; export { DateModule } from './lib/date/date.module'; -export { DatePipe } from './lib/pipes/date.pipe'; export { DateService } from './lib/date/date.service'; -export { EbClassDirective } from './lib/expression-builder/eb-class.directive'; -export { EbExpressionComponent } from './lib/expression-builder/eb-expression.component'; -export { EbModule } from './lib/expression-builder/eb.module'; -export { EbNodeComponent } from './lib/expression-builder/eb-node.component'; -export { EbNodeService } from './lib/expression-builder/eb-node.service'; -export { EditFormComponent } from './lib/edit-form/edit-form.component'; export { EditFormControlComponent } from './lib/edit-form/edit-form-control.component'; -export { EditFormModule } from './lib/edit-form/edit-form.module'; export { EditFormTriggerComponent } from './lib/edit-form/edit-form-trigger.component'; +export { EditFormComponent } from './lib/edit-form/edit-form.component'; +export { EditFormModule } from './lib/edit-form/edit-form.module'; export { ExportComponent } from './lib/export/export.component'; export { ExportModule } from './lib/export/export.module'; +export { EbClassDirective } from './lib/expression-builder/eb-class.directive'; +export { EbExpressionComponent } from './lib/expression-builder/eb-expression.component'; +export { EbNodeComponent } from './lib/expression-builder/eb-node.component'; +export { EbNodeService } from './lib/expression-builder/eb-node.service'; +export { EbModule } from './lib/expression-builder/eb.module'; +export { SerializationService } from './lib/expression-builder/serialization.service'; export { FileDirective } from './lib/file/file.directive'; export { FileModule } from './lib/file/file.module'; -export { FilterPipe } from './lib/pipes/filter.pipe'; -export { FocusAfterRender } from './lib/focus/focus.service'; +export { AutoFocusDirective } from './lib/focus/autofocus.directive'; +export { BlurDirective } from './lib/focus/blur.directive'; export { FocusDirective } from './lib/focus/focus.directive'; export { FocusModule } from './lib/focus/focus.module'; -export { HighlightPipe } from './lib/pipes/highlight.pipe'; +export { FocusAfterRender } from './lib/focus/focus.service'; export { ImportComponent } from './lib/import/import.component'; export { ImportModule } from './lib/import/import.module'; -export { ItemLabelPipe } from './lib/pipes/item-label.pipe'; export { LayoutModule } from './lib/layout/layout.module'; +export { PositionDirective } from './lib/layout/position.directive'; export { LegendComponent } from './lib/legend/legend.component'; export { LegendModule } from './lib/legend/legend.module'; export { LiveCellComponent } from './lib/live-cell/live-cell.component'; @@ -79,43 +80,48 @@ export { LiveColumnComponent } from './lib/live-column/live-column.component'; export { LiveColumnModule } from './lib/live-column/live-column.module'; export { LiveRowComponent } from './lib/live-row/live-row.component'; export { LiveRowModule } from './lib/live-row/live-row.module'; -export { NumberPipe } from './lib/pipes/number.pipe'; +export { PagerTargetComponent } from './lib/pagination/pager-target.component'; export { PagerComponent } from './lib/pagination/pager.component'; export { PagerModule } from './lib/pagination/pager.module'; -export { PagerTargetComponent } from './lib/pagination/pager-target.component'; export { PaneComponent } from './lib/pane/pane.component'; export { PaneModule } from './lib/pane/pane.module'; +export { PersistencePanelComponent } from './lib/persistence/persistence-panel.component'; export { PersistenceComponent } from './lib/persistence/persistence.component'; export { PersistenceModule } from './lib/persistence/persistence.module'; -export { PersistencePanelComponent } from './lib/persistence/persistence-panel.component'; +export { ArrayPipe } from './lib/pipes/array.pipe'; +export { ConvertPipe } from './lib/pipes/convert.pipe'; +export { CurrencyPipe } from './lib/pipes/currency.pipe'; +export { DatePipe } from './lib/pipes/date.pipe'; +export { FilterPipe } from './lib/pipes/filter.pipe'; +export { HighlightPipe } from './lib/pipes/highlight.pipe'; +export { ItemLabelPipe } from './lib/pipes/item-label.pipe'; +export { NumberPipe } from './lib/pipes/number.pipe'; export { PipeModule } from './lib/pipes/pipe.module'; -export { PositionDirective } from './lib/layout/position.directive'; +export { TextPipe } from './lib/pipes/text.pipe'; +export { TimePipe } from './lib/pipes/time.pipe'; export { ProgressComponent } from './lib/progress/progress.component'; export { ProgressModule } from './lib/progress/progress.module'; +export { QueryBuilderPanelComponent } from './lib/query-builder/query-builder-panel.component'; export { QueryBuilderComponent } from './lib/query-builder/query-builder.component'; export { QueryBuilderModel } from './lib/query-builder/query-builder.model'; export { QueryBuilderModule } from './lib/query-builder/query-builder.module'; -export { QueryBuilderPanelComponent } from './lib/query-builder/query-builder-panel.component'; export { QueryBuilderPipe } from './lib/query-builder/query-builder.pipe'; export { QueryBuilderService } from './lib/query-builder/query-builder.service'; export { RaiseDirective } from './lib/raise/raise.directive'; export { RaiseModule } from './lib/raise/raise.module'; -export { ReferenceComponent } from './lib/reference-editor/reference.component'; export { ReferenceEditorComponent } from './lib/reference-editor/reference-editor.component'; export { ReferenceEditorModule } from './lib/reference-editor/reference-editor.module'; +export { ReferenceComponent } from './lib/reference-editor/reference.component'; export { RestComponent } from './lib/rest/rest.component'; export { RestModule } from './lib/rest/rest.module'; -export { RuleComponent } from './lib/validation/rule.component'; -export { SerializationService } from './lib/expression-builder/serialization.service'; export { StatusBarComponent } from './lib/status-bar/status-bar.component'; export { StatusBarModule } from './lib/status-bar/status-bar.module'; -export { TabTrapComponent } from './lib/tab-trap/tab-trap.component'; export { TabTrapInDirective } from './lib/tab-trap/tab-trap-in.directive'; +export { TabTrapComponent } from './lib/tab-trap/tab-trap.component'; export { TabTrapModule } from './lib/tab-trap/tab-trap.module'; -export { TextPipe } from './lib/pipes/text.pipe'; export { TimeDirective } from './lib/time/time.directive'; export { TimeModule } from './lib/time/time.module'; -export { TimePipe } from './lib/pipes/time.pipe'; +export { RuleComponent } from './lib/validation/rule.component'; export { ValidationComponent } from './lib/validation/validation.component'; export { ValidationModule } from './lib/validation/validation.module'; export { ValidatorComponent } from './lib/validation/validator.component';