#70 pagination support for document list

This commit is contained in:
Denys Vuika
2016-07-13 12:13:25 +01:00
parent 277f43538b
commit 7849ef4323
6 changed files with 104 additions and 34 deletions

View File

@@ -27,7 +27,8 @@ import {
OnChanges, OnChanges,
TemplateRef, TemplateRef,
NgZone, NgZone,
ViewChild ViewChild,
HostListener
} from '@angular/core'; } from '@angular/core';
import { Subject } from 'rxjs/Rx'; import { Subject } from 'rxjs/Rx';
import { CONTEXT_MENU_DIRECTIVES } from 'ng2-alfresco-core'; import { CONTEXT_MENU_DIRECTIVES } from 'ng2-alfresco-core';
@@ -53,15 +54,13 @@ declare let __moduleName: string;
styleUrls: ['./document-list.css'], styleUrls: ['./document-list.css'],
templateUrl: './document-list.html', templateUrl: './document-list.html',
providers: [DocumentListService], providers: [DocumentListService],
directives: [CONTEXT_MENU_DIRECTIVES, ALFRESCO_DATATABLE_DIRECTIVES], directives: [CONTEXT_MENU_DIRECTIVES, ALFRESCO_DATATABLE_DIRECTIVES]
host: {
'(contextmenu)': 'onShowContextMenu($event)'
}
}) })
export class DocumentList implements OnInit, AfterViewInit, AfterViewChecked, AfterContentInit, OnChanges { export class DocumentList implements OnInit, AfterViewInit, AfterViewChecked, AfterContentInit, OnChanges {
static SINGLE_CLICK_NAVIGATION: string = 'click'; static SINGLE_CLICK_NAVIGATION: string = 'click';
static DOUBLE_CLICK_NAVIGATION: string = 'dblclick'; static DOUBLE_CLICK_NAVIGATION: string = 'dblclick';
static DEFAULT_PAGE_SIZE: number = 20;
DEFAULT_ROOT_FOLDER: string = '/'; DEFAULT_ROOT_FOLDER: string = '/';
@@ -85,6 +84,9 @@ export class DocumentList implements OnInit, AfterViewInit, AfterViewChecked, Af
@Input() @Input()
contextMenuActions: boolean = false; contextMenuActions: boolean = false;
@Input()
pageSize: number = DocumentList.DEFAULT_PAGE_SIZE;
@Output() @Output()
nodeClick: EventEmitter<any> = new EventEmitter(); nodeClick: EventEmitter<any> = new EventEmitter();
@@ -154,6 +156,7 @@ export class DocumentList implements OnInit, AfterViewInit, AfterViewChecked, Af
ngOnInit() { ngOnInit() {
this.data.thumbnails = this.thumbnails; this.data.thumbnails = this.thumbnails;
this.data.maxItems = this.pageSize;
this.displayFolderContent(this.currentFolderPath); this.displayFolderContent(this.currentFolderPath);
this.contextActionHandler.subscribe(val => this.contextActionCallback(val)); this.contextActionHandler.subscribe(val => this.contextActionCallback(val));
} }
@@ -211,6 +214,7 @@ export class DocumentList implements OnInit, AfterViewInit, AfterViewChecked, Af
return []; return [];
} }
@HostListener('contextmenu', ['$event'])
onShowContextMenu(e?: Event) { onShowContextMenu(e?: Event) {
if (e) { if (e) {
e.preventDefault(); e.preventDefault();
@@ -365,5 +369,4 @@ export class DocumentList implements OnInit, AfterViewInit, AfterViewChecked, Af
this.executeContentAction(node, action); this.executeContentAction(node, action);
} }
} }
} }

View File

@@ -13,11 +13,15 @@
</li> </li>
</ul> </ul>
</span> </span>
<span class="mdl-paging__count">1-10 of 25</span> <span class="mdl-paging__count">{{summary}}</span>
<button class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon mdl-paging__prev"> <button (click)="showPrevPage()"
[disabled]="!prevPageAvail"
alfresco-mdl-button class="mdl-button--icon mdl-paging__prev">
<i class="material-icons">keyboard_arrow_left</i> <i class="material-icons">keyboard_arrow_left</i>
</button> </button>
<button class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon mdl-paging__next"> <button (click)="showNextPage()"
[disabled]="!nextPageAvail"
alfresco-mdl-button class="mdl-button--icon mdl-paging__next">
<i class="material-icons">keyboard_arrow_right</i> <i class="material-icons">keyboard_arrow_right</i>
</button> </button>
</div> </div>

View File

@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input } from '@angular/core';
import { import {
MATERIAL_DESIGN_DIRECTIVES, MATERIAL_DESIGN_DIRECTIVES,
PaginationProvider PaginationProvider
@@ -30,7 +30,9 @@ declare let __moduleName: string;
styleUrls: ['./pagination.component.css'], styleUrls: ['./pagination.component.css'],
directives: [MATERIAL_DESIGN_DIRECTIVES] directives: [MATERIAL_DESIGN_DIRECTIVES]
}) })
export class DocumentListPagination implements OnInit { export class DocumentListPagination {
DEFAULT_PAGE_SIZE: number = 20;
@Input() @Input()
supportedPageSizes: number[] = [5, 10, 20, 50, 100]; supportedPageSizes: number[] = [5, 10, 20, 50, 100];
@@ -38,17 +40,47 @@ export class DocumentListPagination implements OnInit {
@Input() @Input()
provider: PaginationProvider; provider: PaginationProvider;
@Input() get pageSize(): number {
pageSize: number = 10; if (this.provider) {
return this.provider.maxItems;
}
return this.DEFAULT_PAGE_SIZE;
}
constructor() {} set pageSize(value: number) {
if (this.provider) {
this.provider.maxItems = value;
}
}
setPageSize(value: number) { setPageSize(value: number) {
this.pageSize = value; this.pageSize = value;
} }
ngOnInit() { get summary(): string {
console.info(this.provider); let from = this.provider.skipCount;
if (from === 0) {
from = 1;
}
let to = this.provider.skipCount + this.provider.count;
let of = this.provider.totalItems;
return `${from}-${to} of ${of}`;
}
get nextPageAvail(): boolean {
return this.provider.hasMoreItems;
}
get prevPageAvail(): boolean {
return this.provider.skipCount > 0;
}
showNextPage() {
this.provider.skipCount += this.provider.maxItems;
}
showPrevPage() {
this.provider.skipCount -= this.provider.maxItems;
} }
} }

View File

@@ -94,13 +94,13 @@ describe('ShareDataTableAdapter', () => {
it('should fail when getting value for missing row', () => { it('should fail when getting value for missing row', () => {
let adapter = new ShareDataTableAdapter(null, null, null); let adapter = new ShareDataTableAdapter(null, null, null);
let check = () => { return adapter.getValue(null, <DataColumn>{}); }; let check = () => { return adapter.getValue(null, <DataColumn>{}); };
expect(check).toThrowError(ShareDataTableAdapter.ERR_ROW_NOT_FOUND); expect(check).toThrowError(adapter.ERR_ROW_NOT_FOUND);
}); });
it('should fail when getting value for missing column', () => { it('should fail when getting value for missing column', () => {
let adapter = new ShareDataTableAdapter(null, null, null); let adapter = new ShareDataTableAdapter(null, null, null);
let check = () => { return adapter.getValue(<DataRow>{}, null); }; let check = () => { return adapter.getValue(<DataRow>{}, null); };
expect(check).toThrowError(ShareDataTableAdapter.ERR_COL_NOT_FOUND); expect(check).toThrowError(adapter.ERR_COL_NOT_FOUND);
}); });
it('should require path to load data', () => { it('should require path to load data', () => {
@@ -283,7 +283,7 @@ describe('ShareDataTableAdapter', () => {
}); });
it('should resolve file thumbnail', () => { it('should resolve file thumbnail', () => {
let imageUrl: 'http://<addresss>'; let imageUrl: string = 'http://<addresss>';
spyOn(documentListService, 'getDocumentThumbnailUrl').and.returnValue(imageUrl); spyOn(documentListService, 'getDocumentThumbnailUrl').and.returnValue(imageUrl);
let adapter = new ShareDataTableAdapter(documentListService, basePath, null); let adapter = new ShareDataTableAdapter(documentListService, basePath, null);

View File

@@ -27,22 +27,24 @@ import { DocumentListService } from './../services/document-list.service';
export class ShareDataTableAdapter implements DataTableAdapter, PaginationProvider { export class ShareDataTableAdapter implements DataTableAdapter, PaginationProvider {
static ERR_ROW_NOT_FOUND: string = 'Row not found'; ERR_ROW_NOT_FOUND: string = 'Row not found';
static ERR_COL_NOT_FOUND: string = 'Column not found'; ERR_COL_NOT_FOUND: string = 'Column not found';
static DEFAULT_DATE_FORMAT: string = 'medium'; DEFAULT_DATE_FORMAT: string = 'medium';
static DEFAULT_PAGE_SIZE: number = 100; DEFAULT_PAGE_SIZE: number = 20;
MIN_PAGE_SIZE: number = 5;
private sorting: DataSorting; private sorting: DataSorting;
private rows: DataRow[]; private rows: DataRow[];
private columns: DataColumn[]; private columns: DataColumn[];
private page: NodePaging; private page: NodePaging;
private currentPath: string;
private _count: number = 0; private _count: number = 0;
private _hasMoreItems: boolean = false; private _hasMoreItems: boolean = false;
private _totalItems: number = 0; private _totalItems: number = 0;
private _skipCount: number = 0; private _skipCount: number = 0;
private _maxItems: number = ShareDataTableAdapter.DEFAULT_PAGE_SIZE; private _maxItems: number = this.DEFAULT_PAGE_SIZE;
thumbnails: boolean = false; thumbnails: boolean = false;
@@ -70,10 +72,24 @@ export class ShareDataTableAdapter implements DataTableAdapter, PaginationProvid
return this._skipCount; return this._skipCount;
} }
set skipCount(value: number) {
if (value !== this._skipCount) {
this._skipCount = value > 0 ? value : 0;
this.loadPath(this.currentPath);
}
}
get maxItems(): number { get maxItems(): number {
return this._maxItems; return this._maxItems;
} }
set maxItems(value: number) {
if (value !== this._maxItems) {
this._maxItems = value > this.MIN_PAGE_SIZE ? value : this.MIN_PAGE_SIZE;
this.loadPath(this.currentPath);
}
}
getRows(): Array<DataRow> { getRows(): Array<DataRow> {
return this.rows; return this.rows;
} }
@@ -94,16 +110,16 @@ export class ShareDataTableAdapter implements DataTableAdapter, PaginationProvid
getValue(row: DataRow, col: DataColumn): any { getValue(row: DataRow, col: DataColumn): any {
if (!row) { if (!row) {
throw new Error(ShareDataTableAdapter.ERR_ROW_NOT_FOUND); throw new Error(this.ERR_ROW_NOT_FOUND);
} }
if (!col) { if (!col) {
throw new Error(ShareDataTableAdapter.ERR_COL_NOT_FOUND); throw new Error(this.ERR_COL_NOT_FOUND);
} }
let value = row.getValue(col.key); let value = row.getValue(col.key);
if (col.type === 'date') { if (col.type === 'date') {
let datePipe = new DatePipe(); let datePipe = new DatePipe();
let format = col.format || ShareDataTableAdapter.DEFAULT_DATE_FORMAT; let format = col.format || this.DEFAULT_DATE_FORMAT;
try { try {
return datePipe.transform(value, format); return datePipe.transform(value, format);
} catch (err) { } catch (err) {
@@ -193,8 +209,12 @@ export class ShareDataTableAdapter implements DataTableAdapter, PaginationProvid
loadPath(path: string) { loadPath(path: string) {
if (path && this.documentListService) { if (path && this.documentListService) {
this.currentPath = path;
this.documentListService this.documentListService
.getFolder(path) .getFolder(path, {
maxItems: this._maxItems,
skipCount: this._skipCount
})
.subscribe(val => this.loadPage(<NodePaging>val), .subscribe(val => this.loadPage(<NodePaging>val),
error => console.error(error)); error => console.error(error));
} }
@@ -240,7 +260,7 @@ export class ShareDataTableAdapter implements DataTableAdapter, PaginationProvid
this._hasMoreItems = false; this._hasMoreItems = false;
this._totalItems = 0; this._totalItems = 0;
this._skipCount = 0; this._skipCount = 0;
this._maxItems = ShareDataTableAdapter.DEFAULT_PAGE_SIZE; this._maxItems = this.DEFAULT_PAGE_SIZE;
} }
} }

View File

@@ -72,15 +72,25 @@ export class DocumentListService {
return AlfrescoApi.getClientWithTicket(this.settings.getApiBaseUrl(), this.authService.getToken()); return AlfrescoApi.getClientWithTicket(this.settings.getApiBaseUrl(), this.authService.getToken());
} }
private getNodesPromise(folder: string) { private getNodesPromise(folder: string, opts?: any) {
let alfrescoClient = this.getAlfrescoClient(); let alfrescoClient = this.getAlfrescoClient();
let apiInstance = new AlfrescoApi.Core.NodesApi(alfrescoClient); let apiInstance = new AlfrescoApi.Core.NodesApi(alfrescoClient);
let nodeId = '-root-'; let nodeId = '-root-';
let opts = { let params: any = {
relativePath: folder, relativePath: folder,
include: ['path'] include: ['path']
}; };
return apiInstance.getNodeChildren(nodeId, opts);
if (opts) {
if (opts.maxItems) {
params.maxItems = opts.maxItems;
}
if (opts.skipCount) {
params.skipCount = opts.skipCount;
}
}
return apiInstance.getNodeChildren(nodeId, params);
} }
deleteNode(nodeId: string) { deleteNode(nodeId: string) {
let client = this.getAlfrescoClient(); let client = this.getAlfrescoClient();
@@ -93,10 +103,11 @@ export class DocumentListService {
/** /**
* Gets the folder node with the content. * Gets the folder node with the content.
* @param folder Path to folder. * @param folder Path to folder.
* @param opts Options
* @returns {Observable<NodePaging>} Folder entity. * @returns {Observable<NodePaging>} Folder entity.
*/ */
getFolder(folder: string): Observable<NodePaging> { getFolder(folder: string, opts?: any): Observable<NodePaging> {
return Observable.fromPromise(this.getNodesPromise(folder)) return Observable.fromPromise(this.getNodesPromise(folder, opts))
.map(res => <NodePaging> res) .map(res => <NodePaging> res)
// .do(data => console.log('Node data', data)) // eyeball results in the console // .do(data => console.log('Node data', data)) // eyeball results in the console
.catch(this.handleError); .catch(this.handleError);