#82 datatable project cleanup

basic implementation of main features:

- custom data sources
- column types
- sorting by columns
- click events
This commit is contained in:
Denys Vuika
2016-05-19 10:58:15 +01:00
parent faeaf7ad88
commit 2fa8283213
25 changed files with 253 additions and 1929 deletions

View File

@@ -16,43 +16,14 @@
*/
import { DataTableComponent } from './src/components/datatable.component';
import { DataColumnComponent } from './src/components/data-column.component';
import { DataColumnListComponent } from './src/components/data-column-list.component';
import { DataActionComponent } from './src/components/data-action.component';
import { DataActionListComponent } from './src/components/data-action-list.component';
import { FolderActionsService } from './src/services/folder-actions.service';
import { DocumentActionsService } from './src/services/document-actions.service';
import { AlfrescoService } from './src/services/alfresco.service';
// components
export * from './src/components/datatable.component';
export * from './src/components/data-column.component';
export * from './src/components/data-column-list.component';
export * from './src/components/data-action.component';
export * from './src/components/data-action-list.component';
// models
export * from './src/models/data-action.model';
export * from './src/models/data-column.model';
export * from './src/models/column-sorting.model';
// services
export * from './src/services/folder-actions.service';
export * from './src/services/document-actions.service';
export * from './src/services/alfresco.service';
// data
export * from './src/data/datatable-adapter';
export * from './src/data/object-datatable-adapter';
export const ALFRESCO_DATATABLE_DIRECTIVES: [any] = [
DataTableComponent,
DataColumnComponent,
DataColumnListComponent,
DataActionComponent,
DataActionListComponent
];
export const ALFRESCO_DATATABLE_PROVIDERS: [any] = [
AlfrescoService,
FolderActionsService,
DocumentActionsService
DataTableComponent
];

View File

@@ -1,41 +0,0 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Http} from 'angular2/http';
import {Observable} from 'rxjs/Observable';
import {AlfrescoSettingsService} from 'ng2-alfresco-core/services';
import {AlfrescoService} from './../../src/services/alfresco.service';
export class AlfrescoServiceMock extends AlfrescoService {
_folderToReturn: any = {};
constructor(
http: Http = null,
settings: AlfrescoSettingsService = null
) {
super(http, settings);
}
getFolder(folder: string) {
return Observable.create(observer => {
observer.next(this._folderToReturn);
observer.complete();
});
}
}

View File

@@ -1,42 +0,0 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Component} from 'angular2/core';
import { DataTableComponent } from './datatable.component';
import { DataActionModel } from './../models/data-action.model';
@Component({
selector: 'data-actions',
template: ''
})
export class DataActionListComponent {
constructor(
private dataTable: DataTableComponent) {
}
/**
* Registers action handler within the parent document list component.
* @param action Action model to register.
*/
registerAction(action: DataActionModel): void {
if (this.dataTable && action) {
this.dataTable.actions.push(action);
}
}
}

View File

@@ -1,179 +0,0 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
it,
describe,
expect,
beforeEach
} from 'angular2/testing';
import {EventEmitter} from 'angular2/core';
import { AlfrescoServiceMock } from '../assets/alfresco.service.mock';
import { DataTableComponent } from './datatable.component';
import { DataActionListComponent } from './data-action-list.component';
import { DataActionComponent } from './data-action.component';
import {DocumentActionsService} from '../services/document-actions.service';
import {FolderActionsService} from '../services/folder-actions.service';
describe('ContentAction', () => {
let dataTable: DataTableComponent;
let actionList: DataActionListComponent;
beforeEach(() => {
let alfrescoServiceMock = new AlfrescoServiceMock();
dataTable = new DataTableComponent(alfrescoServiceMock);
actionList = new DataActionListComponent(dataTable);
});
it('should register within parent actions list', () => {
spyOn(actionList, 'registerAction').and.stub();
let action = new DataActionComponent(actionList, null, null);
action.ngOnInit();
expect(actionList.registerAction).toHaveBeenCalled();
});
it('should setup and register model', () => {
let action = new DataActionComponent(actionList, null, null);
action.type = 'button';
action.target = 'document';
action.title = '<title>';
action.icon = '<icon>';
expect(dataTable.actions.length).toBe(0);
action.ngOnInit();
expect(dataTable.actions.length).toBe(1);
let model = dataTable.actions[0];
expect(model.type).toBe(action.type);
expect(model.target).toBe(action.target);
expect(model.title).toBe(action.title);
expect(model.icon).toBe(action.icon);
});
it('should get action handler from document actions service', () => {
let handler = function() {};
let documentActions = new DocumentActionsService(null);
spyOn(documentActions, 'getHandler').and.returnValue(handler);
let action = new DataActionComponent(actionList, documentActions, null);
action.type = 'button';
action.target = 'document';
action.handler = '<handler>';
action.ngOnInit();
expect(documentActions.getHandler).toHaveBeenCalledWith(action.handler);
expect(dataTable.actions.length).toBe(1);
let model = dataTable.actions[0];
expect(model.handler).toBe(handler);
});
it('should get action handler from folder actions service', () => {
let handler = function() {};
let folderActions = new FolderActionsService();
spyOn(folderActions, 'getHandler').and.returnValue(handler);
let action = new DataActionComponent(actionList, null, folderActions);
action.type = 'button';
action.target = 'folder';
action.handler = '<handler>';
action.ngOnInit();
expect(folderActions.getHandler).toHaveBeenCalledWith(action.handler);
expect(dataTable.actions.length).toBe(1);
let model = dataTable.actions[0];
expect(model.handler).toBe(handler);
});
it('should require target to get system handler', () => {
let folderActions = new FolderActionsService();
spyOn(folderActions, 'getHandler').and.stub();
let documentActions = new DocumentActionsService(null);
spyOn(documentActions, 'getHandler').and.stub();
let action = new DataActionComponent(actionList, documentActions, folderActions);
action.type = 'button';
action.handler = '<handler>';
action.ngOnInit();
expect(dataTable.actions.length).toBe(1);
expect(folderActions.getHandler).not.toHaveBeenCalled();
expect(documentActions.getHandler).not.toHaveBeenCalled();
action.target = 'document';
action.ngOnInit();
expect(documentActions.getHandler).toHaveBeenCalled();
action.target = 'folder';
action.ngOnInit();
expect(folderActions.getHandler).toHaveBeenCalled();
});
it('should be case insensitive for document target', () => {
let documentActions = new DocumentActionsService(null);
spyOn(documentActions, 'getHandler').and.stub();
let action = new DataActionComponent(actionList, documentActions, null);
action.target = 'DoCuMeNt';
action.type = 'button';
action.handler = '<handler>';
action.ngOnInit();
expect(documentActions.getHandler).toHaveBeenCalledWith(action.handler);
});
it('should be case insensitive for folder target', () => {
let folderActions = new FolderActionsService();
spyOn(folderActions, 'getHandler').and.stub();
let action = new DataActionComponent(actionList, null, folderActions);
action.target = 'FoLdEr';
action.type = 'button';
action.handler = '<handler>';
action.ngOnInit();
expect(folderActions.getHandler).toHaveBeenCalledWith(action.handler);
});
it('should use custom "execute" emitter', (done) => {
let emitter = new EventEmitter();
emitter.subscribe(e => {
expect(e.value).toBe('<obj>');
done();
});
let action = new DataActionComponent(actionList, null, null);
action.target = 'document';
action.type = 'button';
action.execute = emitter;
action.ngOnInit();
expect(dataTable.actions.length).toBe(1);
let model = dataTable.actions[0];
model.handler('<obj>');
});
});

View File

@@ -1,101 +0,0 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Component,
OnInit,
Input,
Output,
EventEmitter
} from 'angular2/core';
import { DataActionModel, DataActionHandler } from './../models/data-action.model';
import { DataActionListComponent } from './data-action-list.component';
import {DocumentActionsService} from '../services/document-actions.service';
import {FolderActionsService} from '../services/folder-actions.service';
@Component({
selector: 'data-action',
template: ''
})
export class DataActionComponent implements OnInit {
@Input()
title: string = 'Action';
@Input()
icon: string;
@Input()
handler: string;
@Input()
type: string;
@Input()
target: string;
@Output()
execute = new EventEmitter();
constructor(
private list: DataActionListComponent,
private documentActions: DocumentActionsService,
private folderActions: FolderActionsService) {
}
ngOnInit() {
let model = new DataActionModel();
model.type = this.type;
model.title = this.title;
model.icon = this.icon;
model.target = this.target;
if (this.handler) {
model.handler = this.getSystemHandler(this.target, this.handler);
} else if (this.execute) {
model.handler = (document: any): void => {
this.execute.emit({
value: document
});
};
}
this.list.registerAction(model);
}
private getSystemHandler(target: string, name: string): DataActionHandler {
if (target) {
let ltarget = target.toLowerCase();
if (ltarget === 'document') {
if (this.documentActions) {
return this.documentActions.getHandler(name);
}
return null;
}
if (ltarget === 'folder') {
if (this.folderActions) {
return this.folderActions.getHandler(name);
}
return null;
}
}
return null;
}
}

View File

@@ -1,49 +0,0 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
it,
describe,
expect,
beforeEach
} from 'angular2/testing';
import { DataTableComponent } from './datatable.component';
import { AlfrescoServiceMock } from '../assets/alfresco.service.mock';
import { DataColumnListComponent } from './data-column-list.component';
import { DataColumnModel } from '../models/data-column.model';
describe('ContentColumnList', () => {
let dataTable: DataTableComponent;
let dataColumnList: DataColumnListComponent;
beforeEach(() => {
let alfrescoServiceMock = new AlfrescoServiceMock();
dataTable = new DataTableComponent(alfrescoServiceMock);
dataColumnList = new DataColumnListComponent(dataTable);
});
it('should register column within parent document list', () => {
expect(dataTable.columns.length).toBe(0);
dataColumnList.registerColumn(new DataColumnModel());
expect(dataTable.columns.length).toBe(1);
});
});

View File

@@ -1,41 +0,0 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component } from 'angular2/core';
import { DataTableComponent } from './datatable.component';
import { DataColumnModel } from '../models/data-column.model';
@Component({
selector: 'data-columns',
template: ''
})
export class DataColumnListComponent {
constructor(
private dataTable: DataTableComponent) {
}
/**
* Registers column model within the parent document list component.
* @param column Column definition model to register.
*/
registerColumn(column: DataColumnModel): void {
if (this.dataTable && column) {
this.dataTable.columns.push(column);
}
}
}

View File

@@ -1,80 +0,0 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
it,
describe,
expect,
beforeEach
} from 'angular2/testing';
import {AlfrescoServiceMock} from '../assets/alfresco.service.mock';
import { DataTableComponent } from './datatable.component';
import { DataColumnComponent } from './data-column.component';
import { DataColumnListComponent } from './data-column-list.component';
describe('ContentColumn', () => {
let dataTable: DataTableComponent;
let columnList: DataColumnListComponent;
beforeEach(() => {
let alfrescoServiceMock = new AlfrescoServiceMock();
dataTable = new DataTableComponent(alfrescoServiceMock);
columnList = new DataColumnListComponent(dataTable);
});
it('should register model within parent column list', () => {
spyOn(columnList, 'registerColumn').and.callThrough();
let column = new DataColumnComponent(columnList);
column.ngOnInit();
expect(columnList.registerColumn).toHaveBeenCalled();
});
it('should setup model properties during registration', () => {
let column = new DataColumnComponent(columnList);
column.title = '<title>';
column.srTitle = '<sr-title>';
column.source = '<source>';
column.cssClass = '<css-class>';
column.ngOnInit();
expect(dataTable.columns.length).toBe(1);
let model = dataTable.columns[0];
expect(model.title).toBe(column.title);
expect(model.srTitle).toBe(column.srTitle);
expect(model.source).toBe(column.source);
expect(model.cssClass).toBe(column.cssClass);
});
it('should setup screen reader title for thumbnail column', () => {
let column = new DataColumnComponent(columnList);
column.source = '$thumbnail';
column.ngOnInit();
expect(dataTable.columns.length).toBe(1);
let model = dataTable.columns[0];
expect(model.srTitle).toBe('Thumbnail');
});
});

View File

@@ -1,63 +0,0 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, OnInit, Input } from 'angular2/core';
import { DataColumnListComponent } from './data-column-list.component';
import { DataColumnModel } from './../models/data-column.model';
@Component({
selector: 'data-column',
template: ''
})
export class DataColumnComponent implements OnInit {
@Input()
title: string = '';
/**
* Title to be used for screen readers.
*/
@Input('sr-title')
srTitle: string;
@Input()
source: string;
@Input('class')
cssClass: string;
constructor(
private columns: DataColumnListComponent) {
}
ngOnInit() {
let model = new DataColumnModel();
model.title = this.title;
model.srTitle = this.srTitle;
model.source = this.source;
model.cssClass = this.cssClass;
if (!model.srTitle && model.source === '$thumbnail') {
model.srTitle = 'Thumbnail';
}
if (this.columns) {
this.columns.registerColumn(model);
}
}
}

View File

@@ -11,6 +11,10 @@
cursor: pointer;
}
:host .data-cell {
cursor: pointer;
}
:host .column-header {
cursor: pointer;
user-select: none;

View File

@@ -1,18 +1,12 @@
<ol *ngIf="breadcrumb" class="breadcrumb">
<li *ngFor="#r of route; #last = last" [class.active]="last" [ngSwitch]="last">
<span *ngSwitchWhen="true">{{r.name}}</span>
<a *ngSwitchDefault href="#" (click)="goToRoute(r, $event)">{{r.name}}</a>
</li>
</ol>
<table *ngIf="folder" class="mdl-data-table mdl-js-data-table mdl-shadow--2dp full-width">
<table *ngIf="data" class="mdl-data-table mdl-js-data-table mdl-shadow--2dp full-width">
<thead>
<tr>
<!-- Columns -->
<th class="mdl-data-table__cell--non-numeric {{col.cssClass}}"
*ngFor="#col of columns"
*ngFor="#col of data.columns"
[class.column-header]="col.title"
[class.mdl-data-table__header--sorted-ascending]="sorting.key === col.source && sorting.direction === 'asc'"
[class.mdl-data-table__header--sorted-descending]="sorting.key === col.source && sorting.direction === 'desc'"
[class.mdl-data-table__header--sorted-ascending]="isColumnSorted(col, 'asc')"
[class.mdl-data-table__header--sorted-descending]="isColumnSorted(col, 'desc')"
(click)="onColumnHeaderClick(col)">
<span *ngIf="col.srTitle" class="sr-only">{{col.srTitle}}</span>
<span *ngIf="col.title">{{col.title}}</span>
@@ -24,77 +18,28 @@
</tr>
</thead>
<tbody>
<tr class="parent-folder-link" *ngIf="canNavigateParent()" (click)="onNavigateParentClick($event)">
<td [attr.colspan]="1 + columns?.length">
<button class="mdl-button mdl-js-button mdl-button--icon"
(click)="onNavigateParentClick($event)">
<i class="material-icons">arrow_upward</i>
</button>
</td>
</tr>
<!-- todo: special 'navigate parent row' support -->
<tr *ngFor="#content of folder.list.entries; #idx = index">
<!-- Columns -->
<td *ngFor="#col of columns" [ngSwitch]="col.source"
class="mdl-data-table__cell--non-numeric {{content.entry.isFolder ? 'folder-row-cell' : 'document-row-cell'}} {{col.cssClass}}"
(click)="onItemClick(content, $event)">
<div *ngSwitchWhen="'$thumbnail'">
<div *ngIf="content.entry.isFolder">
<i class="material-icons folder-thumbnail">{{folderIcon || 'folder_open'}}</i>
</div>
<div *ngIf="!content.entry.isFolder">
<img class="document-thumbnail" alt="" src="{{getDocumentThumbnailUrl(content)}}">
</div>
<tr *ngFor="#row of data.rows; #idx = index">
<td *ngFor="#col of data.columns" [ngSwitch]="col.type"
class="mdl-data-table__cell--non-numeric data-cell {{col.cssClass}}"
(click)="onRowClicked(row, $event)">
<div *ngSwitchWhen="'image'">
<i *ngIf="isIconValue(row, col)" class="material-icons folder-thumbnail">{{asIconValue(row, col)}}</i>
<img *ngIf="!isIconValue(row, col)" class="document-thumbnail" alt="" src="{{data.getValue(row, col)}}">
</div>
<div *ngSwitchWhen="'text'">
{{data.getValue(row, col)}}
</div>
<span *ngSwitchDefault>
{{getObjectValue(content.entry, col.source)}}
<!-- empty cell for unknown column type -->
</span>
</td>
<!-- Actions: folder -->
<td *ngIf="content.entry.isFolder">
<!-- action buttons -->
<button class="mdl-button mdl-js-button mdl-button--icon"
*ngFor="#action of getContentActions('folder', 'button')"
(click)="executeContentAction(content, action)">
<i class="material-icons">{{action.icon}}</i>
</button>
<td><!-- todo: actions --></td>
<!-- action menu -->
<button [id]="'folder_action_menu_' + idx" class="mdl-button mdl-js-button mdl-button--icon">
<i class="material-icons">more_vert</i>
</button>
<ul class="mdl-menu mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect"
[attr.for]="'folder_action_menu_' + idx">
<li class="mdl-menu__item"
*ngFor="#action of getContentActions('folder', 'menu')"
(click)="executeContentAction(content, action)">
{{action.title}}
</li>
</ul>
</td>
<!-- Actions: document -->
<td *ngIf="!content.entry.isFolder">
<!-- action buttons -->
<button class="mdl-button mdl-js-button mdl-button--icon"
*ngFor="#action of getContentActions('document', 'button')"
(click)="executeContentAction(content, action)">
<i class="material-icons">{{action.icon}}</i>
</button>
<!-- action menu -->
<button [id]="'document_action_menu_' + idx" class="mdl-button mdl-js-button mdl-button--icon">
<i class="material-icons">more_vert</i>
</button>
<ul class="mdl-menu mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect"
[attr.for]="'document_action_menu_' + idx">
<li class="mdl-menu__item"
*ngFor="#action of getContentActions('document', 'menu')"
(click)="executeContentAction(content, action)">
{{action.title}}
</li>
</ul>
</td>
</tr>
</tbody>
</table>

View File

@@ -23,20 +23,14 @@ import {
} from 'angular2/testing';
import { DataTableComponent } from './datatable.component';
import { DataColumnModel } from './../models/data-column.model';
import { AlfrescoServiceMock } from '../assets/alfresco.service.mock';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from '../models/document-library.model';
import { DataActionModel } from './../models/data-action.model';
describe('DocumentList', () => {
describe('DataTable', () => {
let alfrescoServiceMock: AlfrescoServiceMock;
let dataTable: DataTableComponent;
let eventMock: any;
beforeEach(() => {
alfrescoServiceMock = new AlfrescoServiceMock();
dataTable = new DataTableComponent(alfrescoServiceMock);
dataTable = new DataTableComponent();
eventMock = {
preventDefault: function () {
@@ -45,315 +39,8 @@ describe('DocumentList', () => {
};
});
it('should setup default columns', () => {
spyOn(dataTable, 'setupDefaultColumns').and.callThrough();
dataTable.ngAfterContentInit();
expect(dataTable.setupDefaultColumns).toHaveBeenCalled();
expect(dataTable.columns.length).not.toBe(0);
});
it('should use custom columns instead of default ones', () => {
let column: DataColumnModel = {
title: 'title',
source: 'source',
cssClass: 'css',
srTitle: ''
};
dataTable.columns.push(column);
dataTable.ngAfterContentInit();
expect(dataTable.columns.length).toBe(1);
expect(dataTable.columns[0]).toBe(column);
});
it('should setup default root for breadcrumb', () => {
dataTable.ngOnInit();
expect(dataTable.route.length).toBe(1);
expect(dataTable.route[0]).toBe(dataTable.rootFolder);
});
it('should display custom root path', () => {
spyOn(dataTable, 'displayFolderContent').and.stub();
let root = {
name: '<root>',
path: '<path>'
};
dataTable.currentFolderPath = root.path;
dataTable.rootFolder = root;
dataTable.ngOnInit();
expect(dataTable.displayFolderContent).toHaveBeenCalledWith(root.path);
});
it('should fetch folder', () => {
let folder = {
'nodeRef': 'workspace://SpacesStore/8bb36efb-c26d-4d2b-9199-ab6922f53c28'
};
alfrescoServiceMock._folderToReturn = folder;
dataTable.ngOnInit();
expect(dataTable.folder).toBe(folder);
});
it('should get content url', () => {
let url = 'URL';
spyOn(alfrescoServiceMock, 'getContentUrl').and.returnValue(url);
let result = dataTable.getContentUrl(null);
expect(result).toBe(url);
expect(alfrescoServiceMock.getContentUrl).toHaveBeenCalled();
});
it('should return no content url without service', () => {
let table = new DataTableComponent(null);
let node = new MinimalNodeEntity();
expect(table.getContentUrl(node)).toBeNull();
});
it('should get thumbnail url', () => {
let url = 'URL';
spyOn(alfrescoServiceMock, 'getDocumentThumbnailUrl').and.returnValue(url);
let result = dataTable.getDocumentThumbnailUrl(null);
expect(result).toBe(url);
expect(alfrescoServiceMock.getDocumentThumbnailUrl).toHaveBeenCalled();
});
it('should get no thumbnail url without service', () => {
let table = new DataTableComponent(null);
let node = new MinimalNodeEntity();
expect(table.getDocumentThumbnailUrl(node)).toBeNull();
});
it('should execute action with node', () => {
let node = new MinimalNodeEntity();
let action = new DataActionModel();
action.handler = function () {
console.log('mock handler');
};
spyOn(action, 'handler').and.stub();
dataTable.executeContentAction(node, action);
expect(action.handler).toHaveBeenCalledWith(node);
});
it('should execute action without node provided', () => {
let action = new DataActionModel();
action.handler = function () {
console.log('mock handler');
};
spyOn(action, 'handler').and.stub();
dataTable.executeContentAction(null, action);
expect(action.handler).toHaveBeenCalledWith(null);
});
it('should update current folder path', () => {
expect(dataTable.currentFolderPath).toBe(dataTable.rootFolder.path);
let path = '<path>';
dataTable.displayFolderContent(path);
expect(dataTable.currentFolderPath).toBe(path);
});
it('should give no content actions for empty target', () => {
let actions = dataTable.getContentActions(null, 'button');
expect(actions.length).toBe(0);
});
it('should give no content actions for empty type', () => {
let actions = dataTable.getContentActions('folder', null);
expect(actions.length).toBe(0);
});
it('should filter content actions for various types and targets', () => {
let folderButton = new DataActionModel();
folderButton.target = 'folder';
folderButton.type = 'button';
let folderMenu = new DataActionModel();
folderMenu.target = 'folder';
folderMenu.type = 'menu';
let documentButton = new DataActionModel();
documentButton.target = 'document';
documentButton.type = 'button';
let documentMenu = new DataActionModel();
documentMenu.target = 'document';
documentMenu.type = 'menu';
dataTable.actions = [
folderButton,
folderMenu,
documentButton,
documentMenu
];
let actions = dataTable.getContentActions('folder', 'button');
expect(actions.length).toBe(1);
expect(actions[0]).toBe(folderButton);
actions = dataTable.getContentActions('folder', 'menu');
expect(actions.length).toBe(1);
expect(actions[0]).toBe(folderMenu);
actions = dataTable.getContentActions('document', 'button');
expect(actions.length).toBe(1);
expect(actions[0]).toBe(documentButton);
actions = dataTable.getContentActions('document', 'menu');
expect(actions.length).toBe(1);
expect(actions[0]).toBe(documentMenu);
});
it('should be case insensitive when filtering content actions', () => {
let documentButton = new DataActionModel();
documentButton.target = 'document';
documentButton.type = 'button';
dataTable.actions = [documentButton];
let actions = dataTable.getContentActions('DoCuMeNt', 'BUTTON');
expect(actions.length).toBe(1);
expect(actions[0]).toBe(documentButton);
});
it('should find no content actions', () => {
let documentButton = new DataActionModel();
documentButton.target = 'document';
documentButton.type = 'button';
dataTable.actions = [documentButton];
let actions = dataTable.getContentActions('unknown', 'value');
expect(actions.length).toBe(0);
});
it('should emit itemClick event', (done) => {
let node: MinimalNodeEntity = new MinimalNodeEntity();
dataTable.itemClick.subscribe(e => {
expect(e.value).toBe(node);
done();
});
dataTable.onItemClick(node);
});
it('should prevent default events for item click', () => {
spyOn(eventMock, 'preventDefault').and.stub();
dataTable.onItemClick(null, eventMock);
expect(eventMock.preventDefault).toHaveBeenCalled();
});
it('should display folder content on click', () => {
let path = '/';
let node = new MinimalNodeEntity();
node.entry = new MinimalNodeEntryEntity();
node.entry.isFolder = true;
node.entry.name = '<display name>';
spyOn(dataTable, 'getNodePath').and.returnValue(path);
spyOn(dataTable, 'displayFolderContent').and.stub();
dataTable.onItemClick(node);
expect(dataTable.displayFolderContent).toHaveBeenCalledWith(path);
let routeEntry = dataTable.route.pop();
expect(routeEntry.name).toBe(node.entry.name);
expect(routeEntry.path).toBe(path);
});
it('should not display folder content when no target node provided', () => {
expect(dataTable.navigate).toBe(true);
spyOn(dataTable, 'displayFolderContent').and.stub();
dataTable.onItemClick(null);
expect(dataTable.displayFolderContent).not.toHaveBeenCalled();
});
it('should display folder content only on folder node click', () => {
expect(dataTable.navigate).toBe(true);
spyOn(dataTable, 'displayFolderContent').and.stub();
let node = new MinimalNodeEntity();
node.entry = new MinimalNodeEntryEntity();
node.entry.isFolder = false;
dataTable.onItemClick(node);
expect(dataTable.displayFolderContent).not.toHaveBeenCalled();
});
it('should not display folder content on click when navigation is off', () => {
spyOn(dataTable, 'displayFolderContent').and.stub();
let node = new MinimalNodeEntity();
node.entry = new MinimalNodeEntryEntity();
node.entry.isFolder = true;
node.entry.name = '<display name>';
dataTable.navigate = false;
dataTable.onItemClick(node);
expect(dataTable.displayFolderContent).not.toHaveBeenCalled();
});
it('should require node to get path', () => {
expect(dataTable.getNodePath(null)).toBe(null);
});
/*
it('should get node path', () => {
let location = new LocationEntity();
location.site = 'swsdp';
location.container = 'documentLibrary';
location.path = '\/';
let node = new DocumentEntity();
node.fileName = 'fileName';
node.location = location;
expect(documentList.getNodePath(node)).toBe('swsdp/documentLibrary/fileName');
});
*/
it('should return root object value', () => {
let target = {
key1: 'value1'
};
expect(dataTable.getObjectValue(target, 'key1')).toBe('value1');
});
it('should return no object value when key is missing', () => {
let target = {
key1: 'value1'
};
expect(dataTable.getObjectValue(target, 'missing')).toBeUndefined();
});
it('should return nested object value', () => {
let target = {
key1: {
key2: {
key3: 'value1'
}
}
};
expect(dataTable.getObjectValue(target, 'key1.key2.key3')).toBe('value1');
it('should pass', () => {
expect(true).toBe(true);
});
});

View File

@@ -21,15 +21,16 @@ import {
Input,
Output,
EventEmitter,
AfterContentInit,
AfterViewChecked
} from 'angular2/core';
import { AlfrescoService } from './../services/alfresco.service';
import { MinimalNodeEntity, NodePaging } from './../models/document-library.model';
import { DataActionModel } from './../models/data-action.model';
import { DataColumnModel } from './../models/data-column.model';
import { ColumnSortingModel } from './../models/column-sorting.model';
import {
DataTableAdapter,
DataRow,
DataColumn,
DataSorting
} from './../data/datatable-adapter';
import { ObjectDataTableAdapter } from '../data/object-datatable-adapter';
declare var componentHandler;
declare let __moduleName: string;
@@ -38,85 +39,21 @@ declare let __moduleName: string;
moduleId: __moduleName,
selector: 'alfresco-datatable',
styleUrls: ['./datatable.component.css'],
templateUrl: './datatable.component.html',
providers: [AlfrescoService]
templateUrl: './datatable.component.html'
})
export class DataTableComponent implements OnInit, AfterViewChecked, AfterContentInit {
DEFAULT_ROOT_FOLDER: string = '/Sites/swsdp/documentLibrary';
export class DataTableComponent implements OnInit, AfterViewChecked {
@Input()
navigate: boolean = true;
@Input()
breadcrumb: boolean = false;
@Input('folder-icon')
folderIcon: string;
data: DataTableAdapter;
@Output()
itemClick: EventEmitter<any> = new EventEmitter();
@Output()
folderClick: EventEmitter<any> = new EventEmitter();
rootFolder = {
name: '',
path: ''
};
@Input()
currentFolderPath: string = '';
folder: NodePaging;
errorMessage;
route: any[] = [];
actions: DataActionModel[] = [];
columns: DataColumnModel[] = [];
sorting: ColumnSortingModel = {
key: 'name',
direction: 'asc'
};
/**
* Determines whether navigation to parent folder is available.
* @returns {boolean}
*/
canNavigateParent(): boolean {
return this.navigate && !this.breadcrumb &&
this.currentFolderPath !== this.rootFolder.path;
}
constructor(
private _alfrescoService: AlfrescoService) {
}
_createRootFolder(): any {
let folderArray = this.currentFolderPath.split('/');
let nameFolder = folderArray[folderArray.length - 1];
return {
name: nameFolder,
path: this.currentFolderPath
};
}
onRowClick: EventEmitter<any> = new EventEmitter();
ngOnInit() {
this.currentFolderPath = this.currentFolderPath || this.DEFAULT_ROOT_FOLDER;
this.rootFolder = this._createRootFolder();
this.route.push(this.rootFolder);
this.displayFolderContent(this.rootFolder.path);
}
ngOnChanges(change) {
this.reload();
}
ngAfterContentInit() {
if (!this.columns || this.columns.length === 0) {
this.setupDefaultColumns();
if (this.data) {
console.log(this.data);
} else {
this.data = new ObjectDataTableAdapter([], []);
}
}
@@ -127,251 +64,34 @@ export class DataTableComponent implements OnInit, AfterViewChecked, AfterConten
}
}
/**
* Get a list of content actions based on target and type.
* @param target Target to filter actions by.
* @param type Type to filter actions by.
* @returns {ContentActionModel[]} List of actions filtered by target and type.
*/
getContentActions(target: string, type: string): DataActionModel[] {
if (target && type) {
let ltarget = target.toLowerCase();
let ltype = type.toLowerCase();
return this.actions.filter(entry => {
return entry.target.toLowerCase() === ltarget &&
entry.type.toLowerCase() === ltype;
});
}
return [];
}
/**
* Invoked when 'parent folder' element is clicked.
* @param e DOM event
*/
onNavigateParentClick(e) {
onRowClicked(row: DataRow, e?) {
if (e) {
e.preventDefault();
}
if (this.navigate) {
this.route.pop();
let parent = this.route.length > 0 ? this.route[this.route.length - 1] : this.rootFolder;
if (parent) {
this.folderClick.emit({
value: parent.path
});
this.displayFolderContent(parent.path);
}
}
}
/**
* Invoked when list row is clicked.
* @param item Underlying node item
* @param e DOM event (optional)
*/
onItemClick(item: MinimalNodeEntity, e = null) {
if (e) {
e.preventDefault();
}
this.itemClick.emit({
value: item
this.onRowClick.emit({
value: row
});
}
if (this.navigate && item && item.entry) {
if (item.entry.isFolder) {
let path = this.getNodePath(item);
this.folderClick.emit({
value: path
});
this.route.push({
name: item.entry.name,
path: path
});
this.displayFolderContent(path);
}
onColumnHeaderClick(column: DataColumn) {
if (column && column.sortable) {
let current = this.data.getSorting();
let newDirection = current.direction === 'asc' ? 'desc' : 'asc';
this.data.setSorting(new DataSorting(column.key, newDirection));
}
}
/**
* Invoked when a breadcrumb route is clicked.
* @param r Route to navigate to
* @param e DOM event
*/
goToRoute(r, e) {
if (e) {
e.preventDefault();
}
if (this.navigate) {
let idx = this.route.indexOf(r);
if (idx > -1) {
this.route.splice(idx + 1);
this.displayFolderContent(r.path);
}
}
isIconValue(row: DataRow, col: DataColumn) {
return row.getValue(col.key).startsWith('material-icons://');
}
/**
* Gets content URL for the given node.
* @param node Node to get URL for.
* @returns {string} URL address.
*/
getContentUrl(node: MinimalNodeEntity): string {
if (this._alfrescoService) {
return this._alfrescoService.getContentUrl(node);
}
return null;
asIconValue(row: DataRow, col: DataColumn) {
return row.getValue(col.key).replace('material-icons://', '');
}
/**
* Gets thumbnail URL for the given document node.
* @param node Node to get URL for.
* @returns {string} URL address.
*/
getDocumentThumbnailUrl(node: MinimalNodeEntity): string {
if (this._alfrescoService) {
return this._alfrescoService.getDocumentThumbnailUrl(node);
}
return null;
}
/**
* Invoked when executing content action for a document or folder.
* @param node Node to be the context of the execution.
* @param action Action to be executed against the context.
*/
executeContentAction(node: MinimalNodeEntity, action: DataActionModel) {
if (action) {
action.handler(node);
}
}
/**
* Loads and displays folder content
* @param path Node path
*/
displayFolderContent(path) {
if (path !== null) {
this.currentFolderPath = path;
this._alfrescoService
.getFolder(path)
.subscribe(
folder => this.folder = this.sort(folder, this.sorting),
error => this.errorMessage = <any>error
);
}
}
reload() {
if (this.currentFolderPath) {
this.displayFolderContent(this.currentFolderPath);
}
}
/**
* Gets a path for a given node.
* @param node
* @returns {string}
*/
getNodePath(node: MinimalNodeEntity): string {
if (node) {
let pathWithCompanyHome = node.entry.path.name;
return pathWithCompanyHome.replace('/Company Home', '') + '/' + node.entry.name;
}
return null;
}
/**
* Gets a value from an object by composed key
* documentList.getObjectValue({ item: { nodeType: 'cm:folder' }}, 'item.nodeType') ==> 'cm:folder'
* @param target
* @param key
* @returns {string}
*/
getObjectValue(target: any, key: string): any {
let keys = key.split('.');
key = '';
do {
key += keys.shift();
let value = target[key];
if (value !== undefined && (typeof value === 'object' || !keys.length)) {
target = value;
key = '';
} else if (!keys.length) {
target = undefined;
} else {
key += '.';
}
} while (keys.length);
return target;
}
/**
* Creates a set of predefined columns.
*/
setupDefaultColumns(): void {
let thumbnailCol = new DataColumnModel();
thumbnailCol.source = '$thumbnail';
let nameCol = new DataColumnModel();
nameCol.title = 'Name';
nameCol.source = 'name';
nameCol.cssClass = 'full-width name-column';
this.columns = [
thumbnailCol,
nameCol
];
}
onColumnHeaderClick(column: DataColumnModel) {
if (column && this._isSortableColumn(column)) {
if (this.sorting.key === column.source) {
this.sorting.direction = this.sorting.direction === 'asc' ? 'desc' : 'asc';
} else {
this.sorting = <ColumnSortingModel> {
key: column.source,
direction: 'asc'
};
}
this.sort(this.folder, this.sorting);
}
}
sort(node: NodePaging, options: ColumnSortingModel) {
if (this._hasEntries(node)) {
node.list.entries.sort((a: MinimalNodeEntity, b: MinimalNodeEntity) => {
if (a.entry.isFolder !== b.entry.isFolder) {
return options.direction === 'asc'
? (a.entry.isFolder ? -1 : 1)
: (a.entry.isFolder ? 1 : -1);
}
let left = this.getObjectValue(a.entry, options.key).toString();
let right = this.getObjectValue(b.entry, options.key).toString();
return options.direction === 'asc'
? left.localeCompare(right)
: right.localeCompare(left);
});
}
return node;
}
private _hasEntries(node: NodePaging): boolean {
return (node && node.list && node.list.entries && node.list.entries.length > 0);
}
private _isSortableColumn(column: DataColumnModel) {
return column && column.source && !column.source.startsWith('$');
isColumnSorted(col: DataColumn, direction: string) {
let sorting = this.data.getSorting();
return sorting.key === col.key && sorting.direction === direction;
}
}

View File

@@ -15,9 +15,33 @@
* limitations under the License.
*/
export class DataColumnModel {
export interface DataTableAdapter {
rows: DataRow[];
columns: DataColumn[];
getValue(row: DataRow, col: DataColumn): any;
getSorting(): DataSorting;
setSorting(sorting: DataSorting): void;
}
export interface DataRow {
hasValue(key: string): boolean;
getValue(key: string): any;
}
export interface DataColumn {
key: string;
type: string; // text|image
sortable: boolean;
title: string;
srTitle: string;
source: string;
cssClass: string;
}
export class DataSorting {
constructor(
public key: string,
public direction: string) {
}
}

View File

@@ -0,0 +1,135 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
DataTableAdapter,
DataRow,
DataColumn,
DataSorting
} from './datatable-adapter';
// Simple implementation of the DataTableAdapter interface.
export class ObjectDataTableAdapter implements DataTableAdapter {
private _sorting;
rows: DataRow[];
columns: DataColumn[];
constructor(data: any[], schema: DataColumn[]) {
this.rows = [];
if (data && data.length > 0) {
this.rows = data.map(item => {
return new ObjectDataRow(item);
});
this.columns = schema.map(item => {
return new ObjectDataColumn(item);
});
}
}
getValue(row: DataRow, col: DataColumn): any {
return row.getValue(col.key);
}
getSorting(): DataSorting {
return this._sorting;
}
getColumnByKey(key: string) {
let columns = this.columns.filter(col => col.key === key);
return columns.length > 0 ? columns[0] : null;
}
setSorting(sorting: DataSorting): void {
this._sorting = sorting;
this.rows.sort((a: DataRow, b: DataRow) => {
let left = a.getValue(sorting.key).toString();
let right = b.getValue(sorting.key).toString();
return sorting.direction === 'asc'
? left.localeCompare(right)
: right.localeCompare(left);
});
}
}
// Simple implementation of the DataRow interface.
class ObjectDataRow implements DataRow {
constructor(
private obj: any) {
}
/**
* Gets a value from an object by composed key
* documentList.getObjectValue({ item: { nodeType: 'cm:folder' }}, 'item.nodeType') ==> 'cm:folder'
* @param target
* @param key
* @returns {string}
*/
getObjectValue(target: any, key: string): any {
let keys = key.split('.');
key = '';
do {
key += keys.shift();
let value = target[key];
if (value !== undefined && (typeof value === 'object' || !keys.length)) {
target = value;
key = '';
} else if (!keys.length) {
target = undefined;
} else {
key += '.';
}
} while (keys.length);
return target;
}
getValue(key: string): any {
return this.getObjectValue(this.obj, key);
}
hasValue(key: string): boolean {
return this.getValue(key) ? true : false;
}
}
// Simple implementation of the DataColumn interface.
class ObjectDataColumn implements DataColumn {
key: string;
type: string; // text|image
sortable: boolean;
title: string;
srTitle: string;
cssClass: string;
constructor(private obj: any) {
this.key = obj.key;
this.type = obj.type;
this.sortable = obj.sortable;
this.title = obj.title;
this.srTitle = obj.srTitle;
this.cssClass = obj.cssClass;
}
}

View File

@@ -1,21 +0,0 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export class ColumnSortingModel {
key: string;
direction: string = 'asc';
}

View File

@@ -1,28 +0,0 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export class DataActionModel {
icon: string;
title: string;
handler: DataActionHandler;
type: string;
target: string;
}
export interface DataActionHandler {
(obj: any): any;
}

View File

@@ -1,117 +0,0 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// note: contains only limited subset of available fields
export class FolderEntity {
items: DocumentEntity[];
}
export class DocumentEntity {
nodeRef: string;
nodeType: string;
type: string;
mimetype: string;
isFolder: boolean;
isLink: boolean;
fileName: string;
displayName: string;
status: string;
title: string;
description: string;
author: string;
createdOn: string;
createdBy: string;
createdByUser: string;
modifiedOn: string;
modifiedBy: string;
modifiedByUser: string;
lockedBy: string;
lockedByUser: string;
size: number;
version: string;
contentUrl: string;
webdavUrl: string;
actionSet: string;
tags: string[];
activeWorkflows: string;
location: LocationEntity;
}
export class LocationEntity {
repositoryId: string;
site: string;
siteTitle: string;
container: string;
path: string;
file: string;
parent: LocationParentEntity;
}
export class LocationParentEntity {
nodeRef: string;
}
export class NodePaging {
list: NodePagingList;
}
export class NodePagingList {
entries: MinimalNodeEntity[];
}
export class MinimalNodeEntity {
entry: MinimalNodeEntryEntity;
}
export class MinimalNodeEntryEntity {
id: string;
parentId: string;
name: string;
nodeType: string;
isFolder: boolean;
isFile: boolean;
modifiedAt: string;
modifiedByUser: UserInfo;
createdAt: string;
createdByUser: UserInfo;
content: ContentInfo;
path: PathInfoEntity;
}
export class UserInfo {
displayName: string;
id: string;
}
export class ContentInfo {
mimeType: string;
mimeTypeName: string;
sizeInBytes: number;
encoding: string;
}
export class PathInfoEntity {
elements: PathElementEntity;
isComplete: boolean;
name: string;
}
export class PathElementEntity {
id: string;
name: string;
}

View File

@@ -1,120 +0,0 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from 'angular2/core';
import { Http, Response } from 'angular2/http';
import { Observable } from 'rxjs/Observable';
import { AlfrescoSettingsService } from 'ng2-alfresco-core/services';
import { NodePaging, MinimalNodeEntity } from './../models/document-library.model';
declare let AlfrescoApi: any;
/**
* Internal service used by Document List component.
*/
@Injectable()
export class AlfrescoService {
private _host: string = 'http://127.0.0.1:8080';
private _baseUrlPath: string = '/alfresco/api/-default-/public/alfresco/versions/1';
constructor(private http: Http,
private settings: AlfrescoSettingsService) {
if (settings) {
this._host = settings.host;
}
}
public get host(): string {
return this._host;
}
public set host(value: string) {
this._host = value;
}
private getBaseUrl(): string {
return this.host + this._baseUrlPath;
}
private getAlfrescoTicket() {
return localStorage.getItem('token');
}
private getAlfrescoClient() {
let defaultClient = new AlfrescoApi.ApiClient();
defaultClient.basePath = this.getBaseUrl();
// Configure HTTP basic authorization: basicAuth
let basicAuth = defaultClient.authentications['basicAuth'];
basicAuth.username = 'ROLE_TICKET';
basicAuth.password = this.getAlfrescoTicket();
return defaultClient;
}
private getNodesPromise(folder: string) {
let alfrescoClient = this.getAlfrescoClient();
let apiInstance = new AlfrescoApi.NodesApi(alfrescoClient);
let nodeId = '-root-';
let opts = {
relativePath: folder,
include: ['path']
};
return apiInstance.getNodeChildren(nodeId, opts);
}
/**
* Gets the folder node with the content.
* @param folder Path to folder.
* @returns {Observable<NodePaging>} Folder entity.
*/
getFolder(folder: string) {
return Observable.fromPromise(this.getNodesPromise(folder))
.map(res => <NodePaging> res)
.do(data => console.log('Node data', data)) // eyeball results in the console
.catch(this.handleError);
}
/**
* Get thumbnail URL for the given document node.
* @param document Node to get URL for.
* @returns {string} URL address.
*/
getDocumentThumbnailUrl(document: MinimalNodeEntity) {
return this.getContentUrl(document) + '/thumbnails/doclib?c=queue&ph=true&lastModified=1&alf_ticket=' + this.getAlfrescoTicket();
}
/**
* Get content URL for the given node.
* @param document Node to get URL for.
* @returns {string} URL address.
*/
getContentUrl(document: MinimalNodeEntity) {
return this._host +
'/alfresco/service/api/node/workspace/SpacesStore/' +
document.entry.id + '/content';
}
private handleError(error: Response) {
// in a real world app, we may send the error to some remote logging infrastructure
// instead of just logging it to the console
console.error(error);
return Observable.throw(error || 'Server error');
}
}

View File

@@ -1,58 +0,0 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
it,
describe,
expect,
beforeEach
} from 'angular2/testing';
import { DataActionHandler } from './../models/data-action.model';
import { DocumentActionsService } from './document-actions.service';
import { AlfrescoServiceMock } from '../assets/alfresco.service.mock';
describe('DocumentActionsService', () => {
let service: DocumentActionsService;
beforeEach(() => {
let alfrescoServiceMock = new AlfrescoServiceMock();
service = new DocumentActionsService(alfrescoServiceMock);
});
it('should register default download action', () => {
expect(service.getHandler('download')).not.toBeNull();
});
it('should register custom action handler', () => {
let handler: DataActionHandler = function (obj: any) {};
service.setHandler('<key>', handler);
expect(service.getHandler('<key>')).toBe(handler);
});
it('should not find handler that is not registered', () => {
expect(service.getHandler('<missing>')).toBeNull();
});
it('should be case insensitive for keys', () => {
let handler: DataActionHandler = function (obj: any) {};
service.setHandler('<key>', handler);
expect(service.getHandler('<KEY>')).toBe(handler);
});
});

View File

@@ -1,72 +0,0 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Injectable} from 'angular2/core';
import { DataActionHandler } from './../models/data-action.model';
import { AlfrescoService } from './alfresco.service';
@Injectable()
export class DocumentActionsService {
private handlers: { [id: string]: DataActionHandler; } = {};
constructor(private _alfrescoService: AlfrescoService) {
this.setupActionHandlers();
}
getHandler(key: string): DataActionHandler {
if (key) {
let lkey = key.toLowerCase();
return this.handlers[lkey] || null;
}
return null;
}
setHandler(key: string, handler: DataActionHandler): void {
if (key) {
let lkey = key.toLowerCase();
this.handlers[lkey] = handler;
}
}
private setupActionHandlers() {
this.handlers['download'] = this.download.bind(this);
// todo: just for dev/demo purposes, to be replaced with real actions
this.handlers['system1'] = this.handleStandardAction1.bind(this);
this.handlers['system2'] = this.handleStandardAction2.bind(this);
}
private handleStandardAction1(obj: any) {
window.alert('standard document action 1');
}
private handleStandardAction2(obj: any) {
window.alert('standard document action 2');
}
private download(obj: any) {
if (this._alfrescoService && obj && !obj.isFolder) {
let link = document.createElement('a');
document.body.appendChild(link);
link.setAttribute('download', 'download');
link.href = this._alfrescoService.getContentUrl(obj);
link.click();
document.body.removeChild(link);
}
}
}

View File

@@ -1,53 +0,0 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
it,
describe,
expect,
beforeEach
} from 'angular2/testing';
import { FolderActionsService } from './folder-actions.service';
import { DataActionHandler } from './../models/data-action.model';
describe('FolderActionsService', () => {
let service: FolderActionsService;
beforeEach(() => {
service = new FolderActionsService();
});
it('should register custom action handler', () => {
let handler: DataActionHandler = function (obj: any) {};
service.setHandler('<key>', handler);
expect(service.getHandler('<key>')).toBe(handler);
});
it('should not find handler that is not registered', () => {
expect(service.getHandler('<missing>')).toBeNull();
});
it('should be case insensitive for keys', () => {
let handler: DataActionHandler = function (obj: any) {};
service.setHandler('<key>', handler);
expect(service.getHandler('<KEY>')).toBe(handler);
});
});

View File

@@ -1,53 +0,0 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Injectable} from 'angular2/core';
import { DataActionHandler } from './../models/data-action.model';
@Injectable()
export class FolderActionsService {
private handlers: { [id: string]: DataActionHandler; } = {};
constructor() {
// todo: just for dev/demo purposes, to be replaced with real actions
this.handlers['system1'] = this.handleStandardAction1.bind(this);
this.handlers['system2'] = this.handleStandardAction2.bind(this);
}
getHandler(key: string): DataActionHandler {
if (key) {
let lkey = key.toLowerCase();
return this.handlers[lkey] || null;
}
return null;
}
setHandler(key: string, handler: DataActionHandler): void {
if (key) {
let lkey = key.toLowerCase();
this.handlers[lkey] = handler;
}
}
private handleStandardAction1(document: any) {
window.alert('standard folder action 1');
}
private handleStandardAction2(document: any) {
window.alert('standard folder action 2');
}
}