Merge pull request #372 from Alfresco/dev-denys-340

Integrate datatable in the documentlist #340
This commit is contained in:
Maurizio Vitale 2016-07-07 11:26:11 +01:00 committed by GitHub
commit 3881faec42
45 changed files with 960 additions and 1119 deletions

View File

@ -17,7 +17,8 @@ before_install:
- export CHROME_BIN=/usr/bin/google-chrome
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- (cd ng2-components/ng2-alfresco-core; npm version patch; sed -i "s/0\\.0\\.0-PLACEHOLDER/^0.1.0/g" package.json; npm install; npm link)
- (cd ng2-components/ng2-alfresco-core; npm install; npm link)
- (cd ng2-components/ng2-alfresco-datatable; npm install; npm link)
env:
matrix:
@ -30,7 +31,7 @@ env:
- MODULE=ng2-alfresco-viewer
before_script:
- cd ng2-components/$MODULE; npm version patch; sed -i "s/0\\.0\\.0-PLACEHOLDER/^0.1.0/g" package.json; npm install; npm link ng2-alfresco-core; npm run travis
- cd ng2-components/$MODULE; npm install; npm link ng2-alfresco-core; npm run travis
- ls -ltrh ./node_modules/
script: npm run test
# Send coverage data to Coveralls

View File

@ -5,3 +5,7 @@ app/**/*.js
app/**/*.js.map
.idea
dist/
# docker files
docker-compose.yml
Dockerfile

View File

@ -10,6 +10,8 @@
<alfresco-document-list
#documentList
[currentFolderPath]="currentPath"
[contextMenuActions]="true"
[contentActions]="true"
(preview)="showFile($event)"
(folderChange)="onFolderChanged($event)">
<!--
@ -20,10 +22,11 @@
</empty-folder-content>
-->
<content-columns>
<content-column source="$thumbnail" type="image"></content-column>
<content-column key="$thumbnail" type="image"></content-column>
<content-column
title="{{'DOCUMENT_LIST.COLUMNS.DISPLAY_NAME' | translate}}"
source="name"
key="name"
sortable="true"
class="full-width ellipsis-cell">
</content-column>
<!--
@ -34,14 +37,16 @@
-->
<content-column
title="{{'DOCUMENT_LIST.COLUMNS.CREATED_BY' | translate}}"
source="createdByUser.displayName"
key="createdByUser.displayName"
sortable="true"
class="desktop-only">
</content-column>
<content-column
title="{{'DOCUMENT_LIST.COLUMNS.CREATED_ON' | translate}}"
source="createdAt"
key="createdAt"
type="date"
format="medium"
sortable="true"
class="desktop-only">
</content-column>
</content-columns>
@ -50,25 +55,16 @@
<!-- folder actions -->
<content-action
target="folder"
type="button"
icon="delete"
handler="system1">
</content-action>
<content-action
target="folder"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.SYSTEM_1' | translate}}"
handler="system1">
</content-action>
<content-action
target="folder"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.CUSTOM' | translate}}"
(execute)="myFolderAction1($event)">
</content-action>
<content-action
target="folder"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
handler="delete">
</content-action>
@ -76,37 +72,21 @@
<!-- document actions -->
<content-action
target="document"
type="button"
icon="account_circle"
handler="my-handler">
</content-action>
<content-action
target="document"
type="button"
icon="cloud_download"
handler="download">
</content-action>
<content-action
target="document"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DOWNLOAD' | translate}}"
handler="download">
</content-action>
<content-action
target="document"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.SYSTEM_2' | translate}}"
handler="system2">
</content-action>
<content-action
target="document"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.CUSTOM' | translate}}"
(execute)="myCustomAction1($event)">
</content-action>
<content-action
target="document"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
handler="delete">
</content-action>

View File

@ -34,6 +34,7 @@ describe('ContextMenuDirective', () => {
done();
});
directive.links = [{}];
directive.onShowContextMenu(null);
});

View File

@ -26,17 +26,20 @@ import { ContextMenuService } from './../services/context-menu.service';
})
export class ContextMenuDirective {
@Input('context-menu')
links;
links: any[];
constructor(
private _contextMenuService: ContextMenuService) {}
onShowContextMenu(event?: MouseEvent) {
if (this._contextMenuService) {
this._contextMenuService.show.next({event: event, obj: this.links});
}
if (event) {
event.preventDefault();
}
if (this.links && this.links.length > 0) {
if (this._contextMenuService) {
this._contextMenuService.show.next({event: event, obj: this.links});
}
}
}
}

View File

@ -16,14 +16,17 @@
*/
import { DataTableComponent } from './src/components/datatable.component';
import { NoContentTemplateComponent } from './src/components/no-content-template.component';
// components
export * from './src/components/datatable.component';
export * from './src/components/no-content-template.component';
// data
export * from './src/data/datatable-adapter';
export * from './src/data/object-datatable-adapter';
export const ALFRESCO_DATATABLE_DIRECTIVES: [any] = [
DataTableComponent
DataTableComponent,
NoContentTemplateComponent
];

View File

@ -9,13 +9,15 @@ var map = {
'app': 'base/dist',
'rxjs': 'base/node_modules/rxjs',
'@angular': 'base/node_modules/@angular',
'ng2-alfresco-core/dist': '/base/node_modules/ng2-alfresco-core/dist'
'ng2-alfresco-core': '/base/node_modules/ng2-alfresco-core/dist',
'ng2-translate' : '/base/node_modules/ng2-translate'
};
var packages = {
'app': { main: 'main.js', defaultExtension: 'js' },
'rxjs': { defaultExtension: 'js' },
'ng2-alfresco-core/dist': { defaultExtension: 'js' }
'ng2-alfresco-core': { main: 'index.js', defaultExtension: 'js' },
'ng2-translate': { defaultExtension: 'js' }
};
var packageNames = [

View File

@ -16,6 +16,7 @@ module.exports = function (config) {
{pattern: 'node_modules/@angular/**/*.js', included: false, watched: false},
{pattern: 'node_modules/@angular/**/*.map', included: false, watched: false},
{pattern: 'node_modules/ng2-alfresco-core/dist/**/*.js', included: false, served: true, watched: false},
{pattern: 'node_modules/ng2-translate/**/*.js', included: false, served: true, watched: false},
{pattern: 'karma-test-shim.js', included: true, watched: true},

View File

@ -27,7 +27,7 @@
"posttest": "node_modules/.bin/remap-istanbul -i coverage/report/coverage-final.json -o coverage/report -t html",
"coverage": "npm run test && wsrv -o -p 9875 ./coverage/report",
"prepublish": "npm run build",
"travis": "echo 'placeholder'"
"travis": "npm link ng2-alfresco-core"
},
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
@ -67,7 +67,9 @@
"reflect-metadata": "0.1.3",
"rxjs": "5.0.0-beta.6",
"zone.js": "0.6.12",
"rimraf": "2.5.2"
"rimraf": "2.5.2",
"ng2-translate": "2.2.2",
"ng2-alfresco-core": "0.2.0"
},
"peerDependencies": {
"material-design-icons": "^2.2.3",

View File

@ -14,11 +14,58 @@
:host .data-cell {
cursor: default;
}
:host .cell-value {}
:host .column-header {
cursor: pointer;
user-select: none;
-webkit-user-select: none; /* Chrome/Safari/Opera */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE/Edge */
-webkit-touch-callout: none; /* iOS Safari */
}
/* Empty folder */
:host .no-content-container {
padding: 0 !important;
}
:host .no-content-container > img {
width: 100%;
}
:host .ellipsis-cell > div
{
position: relative;
overflow: hidden;
/*height: 1em;*/
}
/* visible content */
:host .ellipsis-cell > div > span
{
display: block;
position: absolute;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1em; /* for vertical align of text */
}
/* cell stretching content */
:host .ellipsis-cell > div:after
{
content: attr(title);
overflow: hidden;
height: 0;
display: block;
}
/* Utils */
:host .non-selectable {
@ -39,3 +86,22 @@
clip: rect(0,0,0,0);
border: 0;
}
/* small desktop */
@media all and (max-width: 1200px) {}
/* tablet */
@media all and (max-width: 1024px) {}
/* mobile phone */
@media all and (max-width: 768px) {
.desktop-only {
display: none;
}
}
@media (max-device-width: 768px){
.desktop-only {
display: none;
}
}

View File

@ -15,6 +15,7 @@
</th>
<th class="mdl-data-table__cell--non-numeric non-selectable {{col.cssClass}}"
*ngFor="let col of data.getColumns()"
[attr.data-automation-id]="'auto_id_' + col.key"
[class.column-header]="col.title"
[class.mdl-data-table__header--sorted-ascending]="isColumnSorted(col, 'asc')"
[class.mdl-data-table__header--sorted-descending]="isColumnSorted(col, 'desc')"
@ -41,22 +42,50 @@
</td>
<td *ngFor="let col of data.getColumns()" [ngSwitch]="col.type"
class="mdl-data-table__cell--non-numeric non-selectable data-cell {{col.cssClass}}"
(click)="onRowClick(row, $event)" (dblclick)="onRowDblClick(row, $event)">
<div *ngSwitchCase="'image'">
(click)="onRowClick(row, $event)"
(dblclick)="onRowDblClick(row, $event)"
[context-menu]="getContextMenuActions(row, col)">
<div *ngSwitchCase="'image'" class="cell-value">
<i *ngIf="isIconValue(row, col)" class="material-icons icon-cell">{{asIconValue(row, col)}}</i>
<img *ngIf="!isIconValue(row, col)" class="image-cell" alt="" src="{{data.getValue(row, col)}}">
</div>
<div *ngSwitchCase="'text'">
<div *ngSwitchCase="'date'" class="cell-value">
{{data.getValue(row, col)}}
</div>
<span *ngSwitchDefault>
<div *ngSwitchCase="'text'" class="cell-value">
{{data.getValue(row, col)}}
</div>
<span *ngSwitchDefault class="cell-value">
<!-- empty cell for unknown column type -->
</span>
</td>
<td *ngIf="actions"><!-- todo: actions --></td>
<td *ngIf="actions">
<!-- action menu -->
<button [id]="'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]="'action_menu_' + idx">
<li class="mdl-menu__item"
[attr.data-automation-id]="action.title"
*ngFor="let action of getRowActions(row)"
(click)="onExecuteRowAction(row, action)">
{{action.title}}
</li>
</ul>
</td>
</tr>
<tr *ngIf="data.getRows().length === 0">
<td class="mdl-data-table__cell--non-numeric no-content-container"
[attr.colspan]="1 + data.getColumns().length">
<template *ngIf="noContentTemplate"
ngFor [ngForOf]="[data]"
[ngForTemplate]="noContentTemplate">
</template>
</td>
</tr>
</tbody>
</table>

View File

@ -22,14 +22,18 @@ import {
Input,
Output,
EventEmitter,
AfterViewChecked
AfterViewChecked,
TemplateRef
} from '@angular/core';
import { CONTEXT_MENU_DIRECTIVES } from 'ng2-alfresco-core';
import {
DataTableAdapter,
DataRow,
DataColumn,
DataSorting
DataSorting,
DataRowEvent
} from './../data/datatable-adapter';
import { ObjectDataTableAdapter } from '../data/object-datatable-adapter';
@ -40,7 +44,8 @@ declare let __moduleName: string;
moduleId: __moduleName,
selector: 'alfresco-datatable',
styleUrls: ['./datatable.component.css'],
templateUrl: './datatable.component.html'
templateUrl: './datatable.component.html',
directives: [CONTEXT_MENU_DIRECTIVES]
})
export class DataTableComponent implements OnInit, AfterViewChecked {
@ -54,13 +59,24 @@ export class DataTableComponent implements OnInit, AfterViewChecked {
actions: boolean = false;
@Output()
rowClick: EventEmitter<any> = new EventEmitter();
rowClick: EventEmitter<DataRowEvent> = new EventEmitter<DataRowEvent>();
@Output()
rowDblClick: EventEmitter<any> = new EventEmitter();
rowDblClick: EventEmitter<DataRowEvent> = new EventEmitter<DataRowEvent>();
noContentTemplate: TemplateRef<any>;
isSelectAllChecked: boolean = false;
@Output()
showRowContextMenu: EventEmitter<any> = new EventEmitter();
@Output()
showRowActionsMenu: EventEmitter<any> = new EventEmitter();
@Output()
executeRowAction: EventEmitter<any> = new EventEmitter();
// TODO: left for reference, will be removed during future revisions
constructor(/*private _ngZone?: NgZone*/) {
}
@ -84,7 +100,8 @@ export class DataTableComponent implements OnInit, AfterViewChecked {
}
this.rowClick.emit({
value: row
value: row,
event: e
});
}
@ -94,7 +111,8 @@ export class DataTableComponent implements OnInit, AfterViewChecked {
}
this.rowDblClick.emit({
value: row
value: row,
event: e
});
}
@ -134,14 +152,16 @@ export class DataTableComponent implements OnInit, AfterViewChecked {
isIconValue(row: DataRow, col: DataColumn) {
if (row && col) {
return row.getValue(col.key).startsWith('material-icons://');
let value = row.getValue(col.key);
return value && value.startsWith('material-icons://');
}
return false;
}
asIconValue(row: DataRow, col: DataColumn) {
if (this.isIconValue(row, col)) {
return row.getValue(col.key).replace('material-icons://', '');
let value = row.getValue(col.key) || '';
return value.replace('material-icons://', '');
}
return null;
}
@ -153,4 +173,21 @@ export class DataTableComponent implements OnInit, AfterViewChecked {
}
return false;
}
getContextMenuActions(row: DataRow, col: DataColumn) {
let args = { row: row, col: col, actions: [] };
this.showRowContextMenu.emit({ args: args });
return args.actions;
}
getRowActions(row: DataRow, col: DataColumn) {
let args = { row: row, col: col, actions: [] };
this.showRowActionsMenu.emit({ args: args });
return args.actions;
}
onExecuteRowAction(row: DataRow, action: any) {
let args = { row: row, action: action };
this.executeRowAction.emit({ args: args });
}
}

View File

@ -16,22 +16,26 @@
*/
import {
it,
describe,
expect
} from '@angular/core/testing';
Directive,
ContentChild,
TemplateRef,
AfterContentInit
} from '@angular/core';
import { DataTableComponent } from './datatable.component';
import { ContentColumnModel } from './content-column.model';
@Directive({
selector: 'no-content-template'
})
export class NoContentTemplateComponent implements AfterContentInit {
describe('ContentColumnModel', () => {
@ContentChild(TemplateRef)
template: any;
it('should init with text type from config object', () => {
let model = new ContentColumnModel({});
expect(model.type).toBe(ContentColumnModel.TYPE_TEXT);
});
constructor(
private dataTable: DataTableComponent) {
}
it('should return supported types', () => {
expect(ContentColumnModel.getSupportedTypes().length).toBeGreaterThan(0);
});
});
ngAfterContentInit() {
this.dataTable.noContentTemplate = this.template;
}
}

View File

@ -16,7 +16,6 @@
*/
export interface DataTableAdapter {
getRows(): Array<DataRow>;
setRows(rows: Array<DataRow>): void;
getColumns(): Array<DataColumn>;
@ -25,33 +24,32 @@ export interface DataTableAdapter {
getSorting(): DataSorting;
setSorting(sorting: DataSorting): void;
sort(key?: string, direction?: string): void;
}
export interface DataRow {
isSelected: boolean;
hasValue(key: string): boolean;
getValue(key: string): any;
}
export interface DataColumn {
key: string;
type: string; // text|image
type: string; // text|image|date
format?: string;
sortable?: boolean;
title?: string;
srTitle?: string;
cssClass?: string;
}
export class DataSorting {
constructor(
public key?: string,
public direction?: string) {
}
}
export interface DataRowEvent {
value?: DataRow;
event?: Event;
}

View File

@ -15,6 +15,8 @@
* limitations under the License.
*/
import { DatePipe } from '@angular/common';
import {
DataTableAdapter,
DataRow,
@ -78,7 +80,20 @@ export class ObjectDataTableAdapter implements DataTableAdapter {
if (!col) {
throw new Error('Column not found');
}
return row.getValue(col.key);
let value = row.getValue(col.key);
if (col.type === 'date') {
let datePipe = new DatePipe();
let format = col.format || 'medium';
try {
return datePipe.transform(value, format);
} catch (err) {
console.error(`DocumentList: error parsing date ${value} to format ${format}`);
}
}
return value;
}
getSorting(): DataSorting {

View File

@ -9,7 +9,7 @@
"typings": "typings install",
"postinstall": "npm run typings && npm run build",
"start": "concurrently \"npm run build:w\" \"npm run server\" ",
"server": "wsrv -o -s",
"server": "wsrv -o -s -l",
"build": "npm run tslint && rimraf dist && tsc",
"build:w": "npm run tslint && rimraf dist && tsc -w",
"tslint": "npm run tslint-src && npm run tslint-root",
@ -41,7 +41,8 @@
"alfresco-js-api": "^0.1.0",
"ng2-alfresco-core": "^0.1.36",
"ng2-alfresco-documentlist": "^0.1.34"
"ng2-alfresco-documentlist": "^0.1.34",
"ng2-alfresco-datatable": "^0.1.17"
},
"devDependencies": {
"concurrently": "2.0.0",

View File

@ -38,9 +38,9 @@ import {
selector: 'alfresco-documentlist-demo',
template: `
<label for="token"><b>Insert a valid access token / ticket:</b></label><br>
<input id="token" type="text" size="48" (change)="updateToken();doclist.reload()" [(ngModel)]="token"><br>
<input id="token" type="text" size="48" (change)="updateToken();documentList.reload()" [(ngModel)]="token"><br>
<label for="token"><b>Insert the ip of your Alfresco instance:</b></label><br>
<input id="token" type="text" size="48" (change)="updateHost();doclist.reload()" [(ngModel)]="host"><br><br>
<input id="token" type="text" size="48" (change)="updateHost();documentList.reload()" [(ngModel)]="host"><br><br>
<div *ngIf="!authenticated" style="color:#FF2323">
Authentication failed to ip {{ host }} with user: admin, admin, you can still try to add a valid token to perform
operations.
@ -52,28 +52,40 @@ import {
[currentFolderPath]="currentPath"
[target]="documentList">
</alfresco-document-list-breadcrumb>
<alfresco-document-list #doclist
<alfresco-document-list
#documentList
[currentFolderPath]="currentPath"
[contextMenuActions]="true"
[contentActions]="true"
[multiselect]="true"
(folderChange)="onFolderChanged($event)">
<!--
<empty-folder-content>
<template>
<h1>Sorry, no content here</h1>
</template>
</empty-folder-content>
-->
<content-columns>
<content-column source="$thumbnail" type="image"></content-column>
<content-column key="$thumbnail" type="image"></content-column>
<content-column
title="{{'DOCUMENT_LIST.COLUMNS.DISPLAY_NAME' | translate}}"
source="name"
key="name"
sortable="true"
class="full-width ellipsis-cell">
</content-column>
<content-column
title="{{'DOCUMENT_LIST.COLUMNS.CREATED_BY' | translate}}"
source="createdByUser.displayName"
key="createdByUser.displayName"
sortable="true"
class="desktop-only">
</content-column>
<content-column
title="{{'DOCUMENT_LIST.COLUMNS.CREATED_ON' | translate}}"
source="createdAt"
key="createdAt"
type="date"
format="medium"
sortable="true"
class="desktop-only">
</content-column>
</content-columns>
@ -81,25 +93,16 @@ import {
<!-- folder actions -->
<content-action
target="folder"
type="button"
icon="delete"
handler="system1">
</content-action>
<content-action
target="folder"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.SYSTEM_1' | translate}}"
handler="system1">
</content-action>
<content-action
target="folder"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.CUSTOM' | translate}}"
(execute)="myFolderAction1($event)">
</content-action>
<content-action
target="folder"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
handler="delete">
</content-action>
@ -107,37 +110,21 @@ import {
<!-- document actions -->
<content-action
target="document"
type="button"
icon="account_circle"
handler="my-handler">
</content-action>
<content-action
target="document"
type="button"
icon="cloud_download"
handler="download">
</content-action>
<content-action
target="document"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DOWNLOAD' | translate}}"
handler="download">
</content-action>
<content-action
target="document"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.SYSTEM_2' | translate}}"
handler="system2">
</content-action>
<content-action
target="document"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.CUSTOM' | translate}}"
(execute)="myCustomAction1($event)">
</content-action>
<content-action
target="document"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
handler="delete">
</content-action>
@ -156,7 +143,8 @@ class DocumentListDemo implements OnInit {
currentPath: string = '/';
authenticated: boolean;
public host: string = 'http://devproducts-platform.alfresco.me';
// host: string = 'http://devproducts-platform.alfresco.me';
host: string = 'http://127.0.0.1:8080';
token: string;
@ -194,11 +182,13 @@ class DocumentListDemo implements OnInit {
}
myCustomAction1(event) {
alert('Custom document action for ' + event.value.displayName);
let entry = event.value.entry;
alert(`Custom document action for ${entry.name}`);
}
myFolderAction1(event) {
alert('Custom folder action for ' + event.value.displayName);
let entry = event.value.entry;
alert(`Custom folder action for ${entry.name}`);
}
login() {

View File

@ -12,6 +12,7 @@
'ng2-translate': 'node_modules/ng2-translate',
'ng2-alfresco-core': 'node_modules/ng2-alfresco-core/dist',
'ng2-alfresco-datatable': 'node_modules/ng2-alfresco-datatable/dist',
'ng2-alfresco-documentlist': 'node_modules/ng2-alfresco-documentlist/dist'
};
// packages tells the System loader how to load when no filename and/or no extension
@ -22,6 +23,7 @@
'ng2-translate': { defaultExtension: 'js' },
'ng2-alfresco-core': { main: 'index.js', defaultExtension: 'js' },
'ng2-alfresco-datatable': { main: 'index.js', defaultExtension: 'js' },
'ng2-alfresco-documentlist': { main: 'index.js', defaultExtension: 'js' }
};
var ngPackageNames = [

View File

@ -0,0 +1,6 @@
{
"watch": [
"node_modules/ng2-alfresco-datatable/dist/**/*.{html,htm,css,js}",
"node_modules/ng2-alfresco-documentlist/dist/**/*.{html,htm,css,js}"
]
}

View File

@ -25,7 +25,7 @@ import { DocumentListBreadcrumb } from './src/components/document-list-breadcrum
import { FolderActionsService } from './src/services/folder-actions.service';
import { DocumentActionsService } from './src/services/document-actions.service';
import { AlfrescoService } from './src/services/alfresco.service';
import { DocumentListService } from './src/services/document-list.service';
// components
export * from './src/components/document-list';
@ -36,13 +36,10 @@ export * from './src/components/content-action-list';
export * from './src/components/empty-folder-content';
export * from './src/components/document-list-breadcrumb.component';
// models
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';
export * from './src/services/document-list.service';
export const DOCUMENT_LIST_DIRECTIVES: [any] = [
DocumentList,
@ -55,7 +52,7 @@ export const DOCUMENT_LIST_DIRECTIVES: [any] = [
];
export const DOCUMENT_LIST_PROVIDERS: [any] = [
AlfrescoService,
DocumentListService,
FolderActionsService,
DocumentActionsService
];

View File

@ -9,15 +9,17 @@ var map = {
'app': 'base/dist',
'rxjs': 'base/node_modules/rxjs',
'@angular': 'base/node_modules/@angular',
'ng2-translate' : '/base/node_modules/ng2-translate',
'ng2-alfresco-core': '/base/node_modules/ng2-alfresco-core/dist',
'ng2-translate' : '/base/node_modules/ng2-translate'
'ng2-alfresco-datatable': '/base/node_modules/ng2-alfresco-datatable/dist'
};
var packages = {
'app': { main: 'main.js', defaultExtension: 'js' },
'rxjs': { defaultExtension: 'js' },
'ng2-translate': { defaultExtension: 'js' },
'ng2-alfresco-core': { main: 'index.js', defaultExtension: 'js' },
'ng2-translate': { defaultExtension: 'js' }
'ng2-alfresco-datatable': { main: 'index.js', defaultExtension: 'js' }
};
var packageNames = [

View File

@ -16,6 +16,7 @@ module.exports = function (config) {
{pattern: 'node_modules/@angular/**/*.js', included: false, watched: false},
{pattern: 'node_modules/@angular/**/*.map', included: false, watched: false},
{pattern: 'node_modules/ng2-alfresco-core/dist/**/*.js', included: false, served: true, watched: false},
{pattern: 'node_modules/ng2-alfresco-datatable/dist/**/*.js', included: false, served: true, watched: false},
{pattern: 'node_modules/ng2-translate/**/*.js', included: false, served: true, watched: false},
{pattern: 'karma-test-shim.js', included: true, watched: true},

View File

@ -28,7 +28,7 @@
"posttest": "node_modules/.bin/remap-istanbul -i coverage/report/coverage-final.json -o coverage/report -t html",
"coverage": "npm run test && wsrv -o -p 9875 ./coverage/report",
"prepublish": "npm run build",
"travis": "npm link ng2-alfresco-core"
"travis": "npm link ng2-alfresco-core ng2-alfresco-datatable"
},
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
@ -75,6 +75,7 @@
"rimraf": "2.5.2",
"ng2-translate": "2.2.2",
"ng2-alfresco-core": "0.2.0",
"ng2-alfresco-datatable": "0.2.0",
"alfresco-js-api": "^0.1.0"
},
"peerDependencies": {
@ -103,13 +104,7 @@
},
"license-check-config": {
"src": [
"**/*.js",
"**/*.ts",
"!/**/coverage/**/*",
"!/**/demo/**/*",
"!/**/node_modules/**/*",
"!/**/typings/**/*",
"!*.js"
"./dist/**/*.js"
],
"path": "assets/license_header.txt",
"blocking": true,

View File

@ -16,14 +16,14 @@
*/
import { Observable } from 'rxjs/Observable';
import {AlfrescoService} from '../../src/services/alfresco.service';
import { DocumentListService } from './../services/document-list.service';
import {
AlfrescoSettingsService,
AlfrescoAuthenticationService,
AlfrescoContentService
} from 'ng2-alfresco-core';
export class AlfrescoServiceMock extends AlfrescoService {
export class DocumentListServiceMock extends DocumentListService {
folderToReturn: any = {};
getFolderReject: boolean = false;

View File

@ -23,7 +23,7 @@ import {
} from '@angular/core/testing';
import { DocumentList } from './document-list';
import { AlfrescoServiceMock } from '../assets/alfresco.service.mock';
import { DocumentListServiceMock } from '../assets/document-list.service.mock';
import { ContentActionModel } from './../models/content-action.model';
import { ContentActionList } from './content-action-list';
@ -33,8 +33,8 @@ describe('ContentColumnList', () => {
let actionList: ContentActionList;
beforeEach(() => {
let alfrescoServiceMock = new AlfrescoServiceMock();
documentList = new DocumentList(alfrescoServiceMock, null);
let documentListService = new DocumentListServiceMock();
documentList = new DocumentList(documentListService, null);
actionList = new ContentActionList(documentList);
});

View File

@ -24,7 +24,7 @@ import {
import { EventEmitter } from '@angular/core';
import { DocumentList } from './document-list';
import { AlfrescoServiceMock } from '../assets/alfresco.service.mock';
import { DocumentListServiceMock } from '../assets/document-list.service.mock';
import { ContentActionList } from './content-action-list';
import { ContentAction } from './content-action';
import { DocumentActionsService } from '../services/document-actions.service';
@ -40,11 +40,11 @@ describe('ContentAction', () => {
let folderActions: FolderActionsService;
beforeEach(() => {
let alfrescoServiceMock = new AlfrescoServiceMock();
let documentServiceMock = new DocumentListServiceMock();
documentActions = new DocumentActionsService(null, null);
folderActions = new FolderActionsService(null);
documentList = new DocumentList(alfrescoServiceMock, null);
documentList = new DocumentList(documentServiceMock, null);
actionList = new ContentActionList(documentList);
});
@ -59,7 +59,6 @@ describe('ContentAction', () => {
it('should setup and register model', () => {
let action = new ContentAction(actionList, null, null);
action.type = 'button';
action.target = 'document';
action.title = '<title>';
action.icon = '<icon>';
@ -70,7 +69,6 @@ describe('ContentAction', () => {
expect(documentList.actions.length).toBe(1);
let model = documentList.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);
@ -82,7 +80,6 @@ describe('ContentAction', () => {
spyOn(documentActions, 'getHandler').and.returnValue(handler);
let action = new ContentAction(actionList, documentActions, null);
action.type = 'button';
action.target = 'document';
action.handler = '<handler>';
action.ngOnInit();
@ -99,7 +96,6 @@ describe('ContentAction', () => {
spyOn(folderActions, 'getHandler').and.returnValue(handler);
let action = new ContentAction(actionList, null, folderActions);
action.type = 'button';
action.target = 'folder';
action.handler = '<handler>';
action.ngOnInit();
@ -116,7 +112,6 @@ describe('ContentAction', () => {
spyOn(documentActions, 'getHandler').and.stub();
let action = new ContentAction(actionList, documentActions, folderActions);
action.type = 'button';
action.handler = '<handler>';
action.ngOnInit();
@ -138,7 +133,6 @@ describe('ContentAction', () => {
let action = new ContentAction(actionList, documentActions, null);
action.target = 'DoCuMeNt';
action.type = 'button';
action.handler = '<handler>';
action.ngOnInit();
@ -150,7 +144,6 @@ describe('ContentAction', () => {
let action = new ContentAction(actionList, null, folderActions);
action.target = 'FoLdEr';
action.type = 'button';
action.handler = '<handler>';
action.ngOnInit();
@ -167,7 +160,6 @@ describe('ContentAction', () => {
let action = new ContentAction(actionList, null, null);
action.target = 'document';
action.type = 'button';
action.execute = emitter;
action.ngOnInit();

View File

@ -15,7 +15,15 @@
* limitations under the License.
*/
import {Component, OnInit, OnChanges, Input, Output, EventEmitter} from '@angular/core';
import {
Component,
OnInit,
OnChanges,
Input,
Output,
EventEmitter
} from '@angular/core';
import { ContentActionModel } from './../models/content-action.model';
import { ContentActionList } from './content-action-list';
import { DocumentActionsService } from '../services/document-actions.service';
@ -37,9 +45,6 @@ export class ContentAction implements OnInit, OnChanges {
@Input()
handler: string;
@Input()
type: string;
@Input()
target: string;
@ -57,7 +62,6 @@ export class ContentAction implements OnInit, OnChanges {
ngOnInit() {
this.model = new ContentActionModel({
type: this.type,
title: this.title,
icon: this.icon,
target: this.target

View File

@ -22,10 +22,11 @@ import {
beforeEach
} from '@angular/core/testing';
import { DataColumn } from 'ng2-alfresco-datatable';
import { DocumentList } from './document-list';
import {AlfrescoServiceMock} from '../assets/alfresco.service.mock';
import { DocumentListServiceMock } from '../assets/document-list.service.mock';
import { ContentColumnList } from './content-column-list';
import {ContentColumnModel} from '../models/content-column.model';
describe('ContentColumnList', () => {
@ -33,23 +34,26 @@ describe('ContentColumnList', () => {
let columnList: ContentColumnList;
beforeEach(() => {
let alfrescoServiceMock = new AlfrescoServiceMock();
documentList = new DocumentList(alfrescoServiceMock, null);
let service = new DocumentListServiceMock();
documentList = new DocumentList(service, null);
columnList = new ContentColumnList(documentList);
});
it('should register column within parent document list', () => {
expect(documentList.columns.length).toBe(0);
let columns = documentList.data.getColumns();
expect(columns.length).toBe(0);
let result = columnList.registerColumn(new ContentColumnModel());
let column = <DataColumn> {};
let result = columnList.registerColumn(column);
expect(result).toBeTruthy();
expect(documentList.columns.length).toBe(1);
expect(columns.length).toBe(1);
expect(columns[0]).toBe(column);
});
it('should require document list instance to register action', () => {
columnList = new ContentColumnList(null);
let col = new ContentColumnModel();
let col = <DataColumn> {};
expect(columnList.registerColumn(col)).toBeFalsy();
});

View File

@ -17,7 +17,7 @@
import { Component } from '@angular/core';
import { DocumentList } from './document-list';
import {ContentColumnModel} from './../models/content-column.model';
import { DataColumn } from 'ng2-alfresco-datatable';
@Component({
selector: 'content-columns',
@ -33,9 +33,10 @@ export class ContentColumnList {
* Registers column model within the parent document list component.
* @param column Column definition model to register.
*/
registerColumn(column: ContentColumnModel): boolean {
registerColumn(column: DataColumn): boolean {
if (this.documentList && column) {
this.documentList.columns.push(column);
let columns = this.documentList.data.getColumns();
columns.push(column);
return true;
}
return false;

View File

@ -24,7 +24,7 @@ import {
import { DocumentList } from './document-list';
import { ContentColumn } from './content-column';
import {AlfrescoServiceMock} from '../assets/alfresco.service.mock';
import { DocumentListServiceMock } from '../assets/document-list.service.mock';
import { ContentColumnList } from './content-column-list';
describe('ContentColumn', () => {
@ -33,8 +33,8 @@ describe('ContentColumn', () => {
let columnList: ContentColumnList;
beforeEach(() => {
let alfrescoServiceMock = new AlfrescoServiceMock();
documentList = new DocumentList(alfrescoServiceMock, null);
let service = new DocumentListServiceMock();
documentList = new DocumentList(service, null);
columnList = new ContentColumnList(documentList);
});
@ -45,52 +45,18 @@ describe('ContentColumn', () => {
column.ngOnInit();
expect(columnList.registerColumn).toHaveBeenCalled();
});
it('should setup model properties during registration', () => {
let column = new ContentColumn(columnList);
column.title = '<title>';
column.srTitle = '<sr-title>';
column.source = '<source>';
column.cssClass = '<css-class>';
column.ngOnInit();
expect(documentList.columns.length).toBe(1);
let model = documentList.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);
let columns = documentList.data.getColumns();
expect(columns.length).toBe(1);
expect(columns[0]).toBe(column);
});
it('should setup screen reader title for thumbnail column', () => {
let column = new ContentColumn(columnList);
column.source = '$thumbnail';
column.key = '$thumbnail';
column.ngOnInit();
expect(documentList.columns.length).toBe(1);
let model = documentList.columns[0];
expect(model.srTitle).toBe('Thumbnail');
});
it('should sync localizable fields with model', () => {
let column = new ContentColumn(columnList);
column.title = 'title1';
column.srTitle = 'srTitle1';
column.ngOnInit();
expect(column.model.title).toBe(column.title);
expect(column.model.srTitle).toBe(column.srTitle);
column.title = 'title2';
column.ngOnChanges(null);
expect(column.model.title).toBe('title2');
expect(column.srTitle).toBe('Thumbnail');
});
it('should register on init', () => {

View File

@ -15,15 +15,27 @@
* limitations under the License.
*/
import { Component, OnInit, Input, OnChanges } from '@angular/core';
import { Component, OnInit, Input } from '@angular/core';
import { ContentColumnList } from './content-column-list';
import { ContentColumnModel } from './../models/content-column.model';
import { DataColumn } from 'ng2-alfresco-datatable';
@Component({
selector: 'content-column',
template: ''
})
export class ContentColumn implements OnInit, OnChanges {
export class ContentColumn implements OnInit, DataColumn {
@Input()
key: string;
@Input()
type: string = 'text';
@Input()
format: string;
@Input()
sortable: boolean = false;
@Input()
title: string = '';
@ -34,36 +46,14 @@ export class ContentColumn implements OnInit, OnChanges {
@Input('sr-title')
srTitle: string;
@Input()
source: string;
@Input('class')
cssClass: string;
@Input()
type: string = 'text';
@Input()
format: string;
model: ContentColumnModel;
constructor(private list: ContentColumnList) {
this.model = new ContentColumnModel();
}
constructor(private list: ContentColumnList) {}
ngOnInit() {
this.model = new ContentColumnModel({
title: this.title,
srTitle: this.srTitle,
source: this.source,
cssClass: this.cssClass,
type: this.type,
format: this.format
});
if (!this.model.srTitle && this.model.source === '$thumbnail') {
this.model.srTitle = 'Thumbnail';
if (!this.srTitle && this.key === '$thumbnail') {
this.srTitle = 'Thumbnail';
}
this.register();
@ -71,14 +61,8 @@ export class ContentColumn implements OnInit, OnChanges {
register(): boolean {
if (this.list) {
return this.list.registerColumn(this.model);
return this.list.registerColumn(this);
}
return false;
}
ngOnChanges(change) {
// update localizable properties
this.model.title = this.title;
this.model.srTitle = this.srTitle;
}
}

View File

@ -1,105 +0,0 @@
:host .full-width { width: 100%; }
:host .thumbnail {
width: 48px;
height: 48px;
cursor: default;
}
:host .column-header {
cursor: pointer;
user-select: none;
-webkit-user-select: none; /* Chrome/Safari/Opera */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE/Edge */
-webkit-touch-callout: none; /* iOS Safari */
}
:host .parent-folder-link { cursor: default; }
:host .parent-folder-link > td { text-align: left; }
:host .data-cell {
cursor: default;
}
:host .cell-value {}
/* Empty folder */
:host .empty-folder-content {
padding: 0 !important;
}
:host .empty-folder-content > img {
width: 100%;
}
/* Utils */
:host .non-selectable {
user-select: none;
-webkit-user-select: none; /* Chrome/Safari/Opera */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE/Edge */
-webkit-touch-callout: none; /* iOS Safari */
}
:host .sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
border: 0;
}
:host .ellipsis-cell > div
{
position: relative;
overflow: hidden;
height: 1em;
}
/* visible content */
:host .ellipsis-cell > div > span
{
display: block;
position: absolute;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1em; /* for vertical align of text */
}
/* cell stretching content */
:host .ellipsis-cell > div:after
{
content: attr(title);
overflow: hidden;
height: 0;
display: block;
}
/* small desktop */
@media all and (max-width: 1200px) {}
/* tablet */
@media all and (max-width: 1024px) {}
/* mobile phone */
@media all and (max-width: 768px) {
.desktop-only {
display: none;
}
}
@media (max-device-width: 768px){
.desktop-only {
display: none;
}
}

View File

@ -1,103 +1,15 @@
<table *ngIf="folder" class="mdl-data-table mdl-js-data-table mdl-shadow--2dp full-width">
<thead>
<tr>
<!-- Columns -->
<th class="mdl-data-table__cell--non-numeric non-selectable {{col.cssClass}}"
*ngFor="let col of columns"
[class.column-header]="col.title"
[attr.data-automation-id]="'auto_id_' + col.source"
[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'"
(click)="onColumnHeaderClick(col)">
<span *ngIf="col.srTitle" class="cell-value sr-only">{{col.srTitle}}</span>
<span *ngIf="col.title" class="cell-value">{{col.title}}</span>
</th>
<!-- Actions -->
<th>
<span class="sr-only">Actions</span>
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let content of folder.list.entries; let idx = index"
[attr.data-automation-id]="getObjectValue(content.entry, 'name')">
<!-- Columns -->
<td *ngFor="let col of columns" [ngSwitch]="col.type"
class="mdl-data-table__cell--non-numeric non-selectable data-cell {{col.cssClass}}"
(click)="onItemClick(content, $event)"
(dblclick)="onItemDblClick(content, $event)"
[context-menu]="getContextActions(content)"
[attr.data-automation-id]="col.source === '$thumbnail' ? '$thumbnail' : col.source + '_' + getObjectValue(content.entry, col.source)">
<div *ngSwitchCase="'image'" class="cell-value">
<img class="thumbnail" [src]="getCellValue(content, col)">
</div>
<div *ngSwitchCase="'date'" class="cell-value">
<span>{{ getCellValue(content, col) }}</span>
</div>
<div *ngSwitchDefault class="cell-value">
<span>{{ getCellValue(content, col) }}</span>
</div>
</td>
<!-- Actions: folder -->
<td *ngIf="content.entry.isFolder">
<!-- action buttons -->
<button class="mdl-button mdl-js-button mdl-button--icon"
*ngFor="let action of getContentActions('folder', 'button')"
(click)="executeContentAction(content, action)">
<i class="material-icons">{{action.icon}}</i>
</button>
<!-- 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"
[attr.data-automation-id]="action.title"
*ngFor="let 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="let 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"
[attr.data-automation-id]="action.title"
*ngFor="let action of getContentActions('document', 'menu')"
(click)="executeContentAction(content, action)">
{{action.title}}
</li>
</ul>
</td>
</tr>
<tr *ngIf="folder?.list?.entries?.length === 0">
<td class="mdl-data-table__cell--non-numeric empty-folder-content"
[attr.colspan]="1 + columns?.length">
<template *ngIf="emptyFolderTemplate"
ngFor [ngForOf]="[folder]"
[ngForTemplate]="emptyFolderTemplate">
<alfresco-datatable
[data]="data"
[actions]="contentActions"
[multiselect]="multiselect"
(showRowContextMenu)="onShowRowContextMenu($event)"
(showRowActionsMenu)="onShowRowActionsMenu($event)"
(executeRowAction)="onExecuteRowAction($event)"
(rowClick)="onRowClick($event)"
(rowDblClick)="onRowDblClick($event)">
<no-content-template>
<template>
<img [src]="baseComponentPath + '/img/document-list.empty-folder.png'">
</template>
<img *ngIf="!emptyFolderTemplate"
[src]="baseComponentPath + '/img/document-list.empty-folder.png'">
</td>
</tr>
</tbody>
</table>
</no-content-template>
</alfresco-datatable>

View File

@ -15,35 +15,26 @@
* limitations under the License.
*/
import {
it,
describe,
expect,
beforeEach
} from '@angular/core/testing';
import { it, describe, expect, beforeEach } from '@angular/core/testing';
import { NgZone } from '@angular/core';
import { DataColumn } from 'ng2-alfresco-datatable';
import { DocumentList } from './document-list';
import { ContentColumnModel } from '../models/content-column.model';
import { AlfrescoServiceMock } from '../assets/alfresco.service.mock';
import { DocumentListServiceMock } from './../assets/document-list.service.mock';
import { ContentActionModel } from '../models/content-action.model';
import {
PageNode,
FileNode,
FolderNode
} from '../assets/document-library.model.mock';
import { ColumnSortingModel } from '../models/column-sorting.model';
import { FileNode, FolderNode } from '../assets/document-library.model.mock';
import { MinimalNodeEntity } from '../models/document-library.model';
describe('DocumentList', () => {
let alfrescoServiceMock: AlfrescoServiceMock;
let documentListService: DocumentListServiceMock;
let documentList: DocumentList;
let eventMock: any;
let componentHandler;
beforeEach(() => {
alfrescoServiceMock = new AlfrescoServiceMock();
documentListService = new DocumentListServiceMock();
let zone = new NgZone(false);
documentList = new DocumentList(alfrescoServiceMock, zone);
documentList = new DocumentList(documentListService, zone);
eventMock = {
preventDefault: function () {
@ -63,56 +54,67 @@ describe('DocumentList', () => {
documentList.ngAfterContentInit();
expect(documentList.setupDefaultColumns).toHaveBeenCalled();
expect(documentList.columns.length).not.toBe(0);
expect(documentList.data.getColumns().length).not.toBe(0);
});
it('should use custom columns instead of default ones', () => {
let column: ContentColumnModel = {
let column = <DataColumn> {
title: 'title',
source: 'source',
key: 'source',
cssClass: 'css',
srTitle: '',
type: 'text',
format: ''
};
documentList.columns.push(column);
let columns = documentList.data.getColumns();
columns.push(column);
documentList.ngAfterContentInit();
expect(documentList.columns.length).toBe(1);
expect(documentList.columns[0]).toBe(column);
expect(columns.length).toBe(1);
expect(columns[0]).toBe(column);
});
// TODO: move to data adapter
/*
it('should fetch folder', () => {
let folder = {
'nodeRef': 'workspace://SpacesStore/8bb36efb-c26d-4d2b-9199-ab6922f53c28'
};
alfrescoServiceMock.folderToReturn = folder;
documentListService.folderToReturn = folder;
documentList.ngOnInit();
expect(documentList.folder).toBe(folder);
});
*/
// TODO: move to data adapter
/*
it('should return thumbnail url for a file when thumbnails turned on', () => {
let url = 'URL';
spyOn(alfrescoServiceMock, 'getDocumentThumbnailUrl').and.returnValue(url);
spyOn(documentListService, 'getDocumentThumbnailUrl').and.returnValue(url);
let node = new FileNode();
documentList.thumbnails = true;
let result = documentList.getThumbnailUrl(node);
expect(result).toBe(url);
expect(alfrescoServiceMock.getDocumentThumbnailUrl).toHaveBeenCalled();
expect(documentListService.getDocumentThumbnailUrl).toHaveBeenCalled();
});
*/
// TODO: move to data adapter
/*
it('should return a null thumbnail url for a null item', () => {
let url = 'URL';
spyOn(alfrescoServiceMock, 'getDocumentThumbnailUrl').and.returnValue(url);
spyOn(documentListService, 'getDocumentThumbnailUrl').and.returnValue(url);
let result = documentList.getThumbnailUrl(null);
expect(result).toBeNull();
expect(alfrescoServiceMock.getDocumentThumbnailUrl).not.toHaveBeenCalled();
expect(documentListService.getDocumentThumbnailUrl).not.toHaveBeenCalled();
});
*/
it('should execute action with node', () => {
let node = new FileNode();
@ -139,100 +141,54 @@ describe('DocumentList', () => {
expect(action.handler).not.toHaveBeenCalled();
});
it('should give no content actions for empty target', () => {
let actions = documentList.getContentActions(null, 'button');
it('should not give node actions for empty target', () => {
let actions = documentList.getNodeActions(null);
expect(actions.length).toBe(0);
});
it('should give no content actions for empty type', () => {
let actions = documentList.getContentActions('folder', null);
expect(actions.length).toBe(0);
});
it('should filter content actions for various types and targets', () => {
let folderButton = new ContentActionModel();
folderButton.target = 'folder';
folderButton.type = 'button';
it('should filter content actions for various targets', () => {
let folderMenu = new ContentActionModel();
folderMenu.target = 'folder';
folderMenu.type = 'menu';
let documentButton = new ContentActionModel();
documentButton.target = 'document';
documentButton.type = 'button';
let documentMenu = new ContentActionModel();
documentMenu.target = 'document';
documentMenu.type = 'menu';
documentList.actions = [
folderButton,
folderMenu,
documentButton,
documentMenu
];
let actions = documentList.getContentActions('folder', 'button');
expect(actions.length).toBe(1);
expect(actions[0]).toBe(folderButton);
actions = documentList.getContentActions('folder', 'menu');
let actions = documentList.getNodeActions(new FolderNode());
expect(actions.length).toBe(1);
expect(actions[0]).toBe(folderMenu);
actions = documentList.getContentActions('document', 'button');
expect(actions.length).toBe(1);
expect(actions[0]).toBe(documentButton);
actions = documentList.getContentActions('document', 'menu');
actions = documentList.getNodeActions(new FileNode());
expect(actions.length).toBe(1);
expect(actions[0]).toBe(documentMenu);
});
it('should be case insensitive when filtering content actions', () => {
let documentButton = new ContentActionModel();
documentButton.target = 'document';
documentButton.type = 'button';
documentList.actions = [documentButton];
let actions = documentList.getContentActions('DoCuMeNt', 'BUTTON');
expect(actions.length).toBe(1);
expect(actions[0]).toBe(documentButton);
});
it('should find no content actions', () => {
let documentButton = new ContentActionModel();
documentButton.target = 'document';
documentButton.type = 'button';
documentList.actions = [documentButton];
let actions = documentList.getContentActions('unknown', 'value');
expect(actions.length).toBe(0);
let node = new MinimalNodeEntity();
expect(documentList.getNodeActions(node)).toEqual([]);
node = new FileNode();
node.entry.isFile = false;
node.entry.isFolder = false;
expect(documentList.getNodeActions(node)).toEqual([]);
});
it('should emit itemClick event', (done) => {
it('should emit nodeClick event', (done) => {
let node = new FileNode();
documentList.itemClick.subscribe(e => {
documentList.nodeClick.subscribe(e => {
expect(e.value).toBe(node);
done();
});
documentList.onItemClick(node);
});
it('should prevent default item single click event', () => {
spyOn(eventMock, 'preventDefault').and.stub();
documentList.onItemClick(null, eventMock);
expect(eventMock.preventDefault).toHaveBeenCalled();
});
it('should prevent default item double click event', () => {
spyOn(eventMock, 'preventDefault').and.stub();
documentList.onItemDblClick(null, eventMock);
expect(eventMock.preventDefault).toHaveBeenCalled();
documentList.onNodeClick(node);
});
it('should display folder content on click', () => {
@ -244,7 +200,7 @@ describe('DocumentList', () => {
spyOn(documentList, 'displayFolderContent').and.stub();
documentList.navigationMode = DocumentList.SINGLE_CLICK_NAVIGATION;
documentList.onItemClick(node);
documentList.onNodeClick(node);
expect(documentList.currentFolderPath).toBe(path);
});
@ -253,7 +209,7 @@ describe('DocumentList', () => {
expect(documentList.navigate).toBe(true);
spyOn(documentList, 'displayFolderContent').and.stub();
documentList.onItemClick(null);
documentList.onNodeClick(null);
expect(documentList.displayFolderContent).not.toHaveBeenCalled();
});
@ -263,7 +219,7 @@ describe('DocumentList', () => {
spyOn(documentList, 'displayFolderContent').and.stub();
let node = new FileNode();
documentList.onItemClick(node);
documentList.onNodeClick(node);
expect(documentList.displayFolderContent).not.toHaveBeenCalled();
});
@ -273,7 +229,7 @@ describe('DocumentList', () => {
let node = new FolderNode('<display name>');
documentList.navigate = false;
documentList.onItemClick(node);
documentList.onNodeClick(node);
expect(documentList.displayFolderContent).not.toHaveBeenCalled();
});
@ -282,33 +238,6 @@ describe('DocumentList', () => {
expect(documentList.getNodePath(null)).toBe(null);
});
it('should return root object value', () => {
let target = {
key1: 'value1'
};
expect(documentList.getObjectValue(target, 'key1')).toBe('value1');
});
it('should return no object value when key is missing', () => {
let target = {
key1: 'value1'
};
expect(documentList.getObjectValue(target, 'missing')).toBeUndefined();
});
it('should return nested object value', () => {
let target = {
key1: {
key2: {
key3: 'value1'
}
}
};
expect(documentList.getObjectValue(target, 'key1.key2.key3')).toBe('value1');
});
it('should display folder content for new folder path', () => {
spyOn(documentList, 'displayFolderContent').and.stub();
let newPath = '/some/new/path';
@ -334,42 +263,44 @@ describe('DocumentList', () => {
});
it('should emit folder changed event', (done) => {
spyOn(documentList, 'displayFolderContent').and.stub();
documentList.folderChange.subscribe(e => {
done();
});
documentList.folder = new PageNode();
documentList.currentFolderPath = '/some/new/path';
});
it('should emit folder changed event with folder details', (done) => {
let folder = new PageNode();
spyOn(documentList, 'displayFolderContent').and.stub();
let path = '/path';
documentList.folderChange.subscribe(e => {
expect(e.value).toBe(folder);
expect(e.path).toBe(path);
done();
});
spyOn(documentList, 'displayFolderContent').and.stub();
documentList.currentFolderPath = path;
documentList.folder = folder;
});
it('should not emit folder changed event', () => {
let folder = new PageNode();
it('should emit folder changed event only once', () => {
spyOn(documentList, 'displayFolderContent').and.stub();
let path = '/new/path';
let calls = 0;
documentList.folderChange.subscribe(e => {
calls++;
});
documentList.folder = folder;
documentList.folder = folder;
documentList.currentFolderPath = path;
documentList.currentFolderPath = path;
documentList.currentFolderPath = path;
expect(calls).toBe(1);
});
it('should reload on binding changes', () => {
spyOn(documentList, 'reload').and.stub();
documentList.ngOnChanges(null);
documentList.ngOnChanges();
expect(documentList.reload).toHaveBeenCalled();
});
@ -396,8 +327,9 @@ describe('DocumentList', () => {
});
it('should subscribe to context action handler', () => {
let value = {};
spyOn(documentList, 'displayFolderContent').and.stub();
spyOn(documentList, 'contextActionCallback').and.stub();
let value = {};
documentList.ngOnInit();
documentList.contextActionHandler.next(value);
expect(documentList.contextActionCallback).toHaveBeenCalledWith(value);
@ -416,7 +348,7 @@ describe('DocumentList', () => {
done();
});
documentList.navigationMode = DocumentList.SINGLE_CLICK_NAVIGATION;
documentList.onItemClick(file, null);
documentList.onNodeClick(file);
});
it('should emit file preview event on double click', (done) => {
@ -426,7 +358,7 @@ describe('DocumentList', () => {
done();
});
documentList.navigationMode = DocumentList.DOUBLE_CLICK_NAVIGATION;
documentList.onItemDblClick(file, null);
documentList.onNodeDblClick(file);
});
it('should perform folder navigation on single click', () => {
@ -434,7 +366,7 @@ describe('DocumentList', () => {
spyOn(documentList, 'performNavigation').and.stub();
documentList.navigationMode = DocumentList.SINGLE_CLICK_NAVIGATION;
documentList.onItemClick(folder, null);
documentList.onNodeClick(folder);
expect(documentList.performNavigation).toHaveBeenCalled();
});
@ -443,7 +375,7 @@ describe('DocumentList', () => {
spyOn(documentList, 'performNavigation').and.stub();
documentList.navigationMode = DocumentList.DOUBLE_CLICK_NAVIGATION;
documentList.onItemDblClick(folder, null);
documentList.onNodeDblClick(folder);
expect(documentList.performNavigation).toHaveBeenCalled();
});
@ -452,7 +384,7 @@ describe('DocumentList', () => {
spyOn(documentList, 'performNavigation').and.stub();
documentList.navigationMode = DocumentList.SINGLE_CLICK_NAVIGATION;
documentList.onItemDblClick(folder, null);
documentList.onNodeDblClick(folder);
expect(documentList.performNavigation).not.toHaveBeenCalled();
});
@ -463,15 +395,16 @@ describe('DocumentList', () => {
documentList.navigate = false;
documentList.navigationMode = DocumentList.DOUBLE_CLICK_NAVIGATION;
documentList.onItemDblClick(folder, null);
documentList.onNodeDblClick(folder);
expect(documentList.performNavigation).not.toHaveBeenCalled();
});
it('should perform navigation for folder node only', () => {
spyOn(documentList, 'getNodePath').and.returnValue('/path');
let folder = new FolderNode();
let file = new FileNode();
spyOn(documentList, 'getNodePath').and.returnValue('/path');
expect(documentList.performNavigation(folder)).toBeTruthy();
expect(documentList.performNavigation(file)).toBeFalsy();
@ -488,7 +421,6 @@ describe('DocumentList', () => {
expect(documentList.getNodePath(file)).toBe('/folder1/file.txt');
});
it('should require valid node for file preview', () => {
let file = new FileNode();
file.entry = null;
@ -497,11 +429,11 @@ describe('DocumentList', () => {
documentList.navigationMode = DocumentList.SINGLE_CLICK_NAVIGATION;
documentList.preview.subscribe(val => called = true);
documentList.onItemClick(file, null);
documentList.onNodeClick(file);
expect(called).toBeFalsy();
documentList.navigationMode = DocumentList.DOUBLE_CLICK_NAVIGATION;
documentList.onItemDblClick(file, null);
documentList.onNodeDblClick(file);
expect(called).toBeFalsy();
});
@ -511,10 +443,10 @@ describe('DocumentList', () => {
spyOn(documentList, 'performNavigation').and.stub();
documentList.navigationMode = DocumentList.SINGLE_CLICK_NAVIGATION;
documentList.onItemClick(folder, null);
documentList.onNodeClick(folder);
documentList.navigationMode = DocumentList.DOUBLE_CLICK_NAVIGATION;
documentList.onItemDblClick(folder, null);
documentList.onNodeDblClick(folder);
expect(documentList.performNavigation).not.toHaveBeenCalled();
});
@ -525,6 +457,8 @@ describe('DocumentList', () => {
expect(documentList.displayFolderContent).toHaveBeenCalled();
});
// TODO: move to data adapter
/*
it('should generate thumbnail for unknown content', () => {
documentList.baseComponentPath = '/root';
let node = new FileNode();
@ -532,13 +466,19 @@ describe('DocumentList', () => {
expect(documentList.getThumbnailUrl(node)).toBe('/root/img/ft_ic_miscellaneous.svg');
});
*/
// TODO: move to data adapter
/*
it('should generate folder icon path', () => {
documentList.baseComponentPath = '/root';
let folder = new FolderNode();
expect(documentList.getThumbnailUrl(folder)).toBe('/root/img/ft_ic_folder.svg');
});
*/
// TODO: move to data adapter
/*
it('should generate file icon path based on mime type', () => {
let fileName = 'custom-icon.svg';
spyOn(alfrescoServiceMock, 'getMimeTypeIcon').and.returnValue(fileName);
@ -549,9 +489,12 @@ describe('DocumentList', () => {
expect(documentList.getThumbnailUrl(file)).toBe(`/root/img/${fileName}`);
});
*/
// TODO: move to data adapter
/*
it('should fallback to default icon for missing mime type', () => {
spyOn(alfrescoServiceMock, 'getMimeTypeIcon').and.returnValue(null);
spyOn(documentListService, 'getMimeTypeIcon').and.returnValue(null);
documentList.baseComponentPath = '/root';
let file = new FileNode();
@ -559,9 +502,12 @@ describe('DocumentList', () => {
expect(documentList.getThumbnailUrl(file)).toBe('/root/img/ft_ic_miscellaneous.svg');
});
*/
// TODO: move to data adapter
/*
it('should fallback to default icon for unknown mime type', () => {
spyOn(alfrescoServiceMock, 'getMimeTypeIcon').and.returnValue(null);
spyOn(documentListService, 'getMimeTypeIcon').and.returnValue(null);
documentList.baseComponentPath = '/root';
let file = new FileNode();
@ -569,17 +515,23 @@ describe('DocumentList', () => {
expect(documentList.getThumbnailUrl(file)).toBe('/root/img/ft_ic_miscellaneous.svg');
});
*/
// TODO: move to data adapter
/*
it('should resolve thumbnail url for a file', () => {
let url = 'http://<some url>';
spyOn(alfrescoServiceMock, 'getDocumentThumbnailUrl').and.returnValue(url);
spyOn(documentListService, 'getDocumentThumbnailUrl').and.returnValue(url);
documentList.thumbnails = true;
let file = new FileNode();
expect(documentList.getThumbnailUrl(file)).toBe(url);
});
*/
// TODO: move to data adapter
/*
it('should return no thumbnail url with missing service', () => {
let list = new DocumentList(null, null);
list.thumbnails = true;
@ -587,76 +539,10 @@ describe('DocumentList', () => {
let file = new FileNode();
expect(list.getThumbnailUrl(file)).toBeNull();
});
*/
it('should sort on column header click', () => {
let col = new ContentColumnModel();
col.source = 'id';
spyOn(documentList, 'sort').and.callThrough();
documentList.onColumnHeaderClick(col);
expect(documentList.sorting).toEqual(
jasmine.objectContaining({
key: 'id',
direction: 'asc'
})
);
expect(documentList.sort).toHaveBeenCalled();
});
it('should invert sorting on column header click', () => {
let col = new ContentColumnModel();
col.source = 'id';
spyOn(documentList, 'sort').and.callThrough();
documentList.sorting = <ColumnSortingModel> { key: 'id', direction: 'asc' };
documentList.onColumnHeaderClick(col);
expect(documentList.sorting).toEqual(
jasmine.objectContaining({
key: 'id',
direction: 'desc'
})
);
documentList.onColumnHeaderClick(col);
expect(documentList.sorting).toEqual(
jasmine.objectContaining({
key: 'id',
direction: 'asc'
})
);
expect(documentList.sort).toHaveBeenCalledTimes(2);
});
it('should use ascending direction for different column header click', () => {
let col = new ContentColumnModel();
col.source = 'id';
spyOn(documentList, 'sort').and.callThrough();
documentList.sorting = <ColumnSortingModel> { key: 'col1', direction: 'desc' };
documentList.onColumnHeaderClick(col);
expect(documentList.sorting).toEqual(
jasmine.objectContaining({
key: 'id',
direction: 'asc'
})
);
expect(documentList.sort).toHaveBeenCalled();
});
it('should not sort by column header when instance is missing', () => {
spyOn(documentList, 'sort').and.callThrough();
documentList.onColumnHeaderClick(null);
expect(documentList.sort).not.toHaveBeenCalled();
});
// TODO: move to DataTable
/*
it('should convert cell value to formatted date', () => {
let rawValue = new Date(2015, 6, 15, 21, 43, 11).toString(); // Wed Jul 15 2015 21:43:11 GMT+0100 (BST);
@ -673,7 +559,10 @@ describe('DocumentList', () => {
let value = documentList.getCellValue(file, col);
expect(value).toBe(dateValue);
});
*/
// TODO: move to DataTable
/*
it('should return date value as string', () => {
let rawValue = new Date(2015, 6, 15, 21, 43, 11).toString(); // Wed Jul 15 2015 21:43:11 GMT+0100 (BST);
@ -687,7 +576,10 @@ describe('DocumentList', () => {
let value = documentList.getCellValue(file, col);
expect(value).toBe(rawValue);
});
*/
// TODO: move to data adapter
/*
it('should convert cell value to thumbnail', () => {
let url = 'http://<address>';
spyOn(documentList, 'getThumbnailUrl').and.returnValue(url);
@ -701,14 +593,15 @@ describe('DocumentList', () => {
let value = documentList.getCellValue(file, col);
expect(value).toBe(url);
});
*/
it('should require path to display folder content', () => {
spyOn(alfrescoServiceMock, 'getFolder').and.callThrough();
spyOn(documentListService, 'getFolder').and.callThrough();
documentList.displayFolderContent(null);
documentList.displayFolderContent('');
expect(alfrescoServiceMock.getFolder).not.toHaveBeenCalled();
expect(documentListService.getFolder).not.toHaveBeenCalled();
});
it('should require node to resolve context menu actions', () => {
@ -722,12 +615,12 @@ describe('DocumentList', () => {
it('should fetch context menu actions for a file node', () => {
let actionModel = {};
spyOn(documentList, 'getContentActions').and.returnValue([actionModel]);
spyOn(documentList, 'getNodeActions').and.returnValue([actionModel]);
let file = new FileNode();
let actions = documentList.getContextActions(file);
expect(documentList.getContentActions).toHaveBeenCalledWith('document', 'menu');
expect(documentList.getNodeActions).toHaveBeenCalledWith(file);
expect(actions.length).toBe(1);
expect(actions[0].model).toBe(actionModel);
expect(actions[0].node).toBe(file);
@ -736,12 +629,12 @@ describe('DocumentList', () => {
it('should fetch context menu actions for a folder node', () => {
let actionModel = {};
spyOn(documentList, 'getContentActions').and.returnValue([actionModel]);
spyOn(documentList, 'getNodeActions').and.returnValue([actionModel]);
let folder = new FolderNode();
let actions = documentList.getContextActions(folder);
expect(documentList.getContentActions).toHaveBeenCalledWith('folder', 'menu');
expect(documentList.getNodeActions).toHaveBeenCalledWith(folder);
expect(actions.length).toBe(1);
expect(actions[0].model).toBe(actionModel);
expect(actions[0].node).toBe(folder);
@ -749,51 +642,39 @@ describe('DocumentList', () => {
});
it('should fetch no context menu actions for unknown type', () => {
spyOn(documentList, 'getContentActions').and.stub();
spyOn(documentList, 'getNodeActions').and.stub();
let node = new FileNode();
node.entry.isFile = false;
node.entry.isFolder = false;
let actions = documentList.getContextActions(node);
expect(documentList.getContentActions).not.toHaveBeenCalled();
expect(actions).toBeNull();
});
it('should return null value when no content actions found', () => {
spyOn(documentList, 'getContentActions').and.returnValue([]);
spyOn(documentList, 'getNodeActions').and.returnValue([]);
let file = new FileNode();
let actions = documentList.getContextActions(file);
expect(actions).toBeNull();
expect(documentList.getContentActions).toHaveBeenCalled();
expect(documentList.getNodeActions).toHaveBeenCalled();
});
/*
it('should update error message when folder content display fails', () => {
let error = 'My Error';
alfrescoServiceMock.getFolderReject = true;
alfrescoServiceMock.getFolderRejectError = error;
documentListService.getFolderReject = true;
documentListService.getFolderRejectError = error;
documentList.displayFolderContent('/some/path');
expect(documentList.errorMessage).toBe(error);
});
*/
it('should get object value via property path', () => {
let obj = {
name: {
firstName: '<name>'
}
};
expect(documentList.getObjectValue(obj, 'name.firstName')).toBe('<name>');
});
it('should not get object value via invalid path', () => {
expect(documentList.getObjectValue({}, 'some.missing.path')).toBeUndefined();
});
// TODO: move to data adapter
/*
it('should log error when having date conversion issues', () => {
let value = '<wrong-date>';
@ -813,7 +694,10 @@ describe('DocumentList', () => {
expect(result).toBe(value);
expect(console.error).toHaveBeenCalledWith(`DocumentList: error parsing date ${value} to format ${col.format}`);
});
*/
// TODO: move to data adapter
/*
it('should convert thumbnail if column source defined', () => {
let file = new FileNode();
let col = new ContentColumnModel({
@ -823,6 +707,7 @@ describe('DocumentList', () => {
expect(documentList.getCellValue(file, col)).toBe(file.entry.name);
});
*/
it('should require current folder path to reload', () => {
@ -839,6 +724,8 @@ describe('DocumentList', () => {
expect(documentList.displayFolderContent).not.toHaveBeenCalled();
});
// TODO: move to data adapter
/*
it('should not sort empty page', () => {
let page = new PageNode();
spyOn(page.list.entries, 'sort').and.stub();
@ -846,7 +733,10 @@ describe('DocumentList', () => {
documentList.sort(page, null);
expect(page.list.entries.sort).not.toHaveBeenCalled();
});
*/
// TODO: move to data adapter
/*
it('should put folders to top on sort', () => {
let folder = new FolderNode();
let file1 = new FileNode('file1');
@ -873,7 +763,10 @@ describe('DocumentList', () => {
expect(page.list.entries[1]).toBe(file2);
expect(page.list.entries[2]).toBe(file1);
});
*/
// TODO: move to data adapter
/*
it('should sort by dates up to ms', () => {
let file1 = new FileNode();
file1.entry['dateProp'] = new Date(2016, 6, 30, 13, 14, 1);
@ -901,5 +794,6 @@ describe('DocumentList', () => {
expect(page.list.entries[0]).toBe(file1);
expect(page.list.entries[1]).toBe(file2);
});
*/
});

View File

@ -22,19 +22,27 @@ import {
Output,
EventEmitter,
AfterContentInit,
AfterViewInit,
AfterViewChecked,
OnChanges,
TemplateRef,
NgZone
NgZone,
ViewChild
} from '@angular/core';
import { DatePipe } from '@angular/common';
import { Subject } from 'rxjs/Rx';
import { CONTEXT_MENU_DIRECTIVES } from 'ng2-alfresco-core';
import { AlfrescoService } from './../services/alfresco.service';
import { MinimalNodeEntity, NodePaging } from './../models/document-library.model';
import {
ALFRESCO_DATATABLE_DIRECTIVES,
DataRowEvent,
DataTableComponent,
ObjectDataColumn
} from 'ng2-alfresco-datatable';
import { DocumentListService } from './../services/document-list.service';
import { MinimalNodeEntity } from './../models/document-library.model';
import { ContentActionModel } from './../models/content-action.model';
import { ContentColumnModel } from './../models/content-column.model';
import { ColumnSortingModel } from './../models/column-sorting.model';
import { ShareDataTableAdapter, ShareDataRow } from './../data/share-datatable-adapter';
declare var componentHandler;
declare let __moduleName: string;
@ -44,13 +52,13 @@ declare let __moduleName: string;
selector: 'alfresco-document-list',
styleUrls: ['./document-list.css'],
templateUrl: './document-list.html',
providers: [AlfrescoService],
directives: [CONTEXT_MENU_DIRECTIVES],
providers: [DocumentListService],
directives: [CONTEXT_MENU_DIRECTIVES, ALFRESCO_DATATABLE_DIRECTIVES],
host: {
'(contextmenu)': 'onShowContextMenu($event)'
}
})
export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit, OnChanges {
export class DocumentList implements OnInit, AfterViewInit, AfterViewChecked, AfterContentInit, OnChanges {
static SINGLE_CLICK_NAVIGATION: string = 'click';
static DOUBLE_CLICK_NAVIGATION: string = 'dblclick';
@ -62,17 +70,26 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
@Input()
navigate: boolean = true;
@Input('navigation-mode')
@Input()
navigationMode: string = 'dblclick'; // click|dblclick
@Input()
thumbnails: boolean = false;
@Output()
itemClick: EventEmitter<any> = new EventEmitter();
@Input()
multiselect: boolean = false;
@Input()
contentActions: boolean = false;
@Input()
contextMenuActions: boolean = false;
@Output()
itemDblClick: EventEmitter<any> = new EventEmitter();
nodeClick: EventEmitter<any> = new EventEmitter();
@Output()
nodeDblClick: EventEmitter<any> = new EventEmitter();
@Output()
folderChange: EventEmitter<any> = new EventEmitter();
@ -80,6 +97,9 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
@Output()
preview: EventEmitter<any> = new EventEmitter();
@ViewChild(DataTableComponent)
dataTable: DataTableComponent;
private _path = this.DEFAULT_ROOT_FOLDER;
get currentFolderPath(): string {
@ -91,57 +111,28 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
if (value !== this._path) {
this._path = value || this.DEFAULT_ROOT_FOLDER;
this.displayFolderContent(this._path);
}
}
errorMessage;
actions: ContentActionModel[] = [];
columns: ContentColumnModel[] = [];
emptyFolderTemplate: TemplateRef<any>;
private _folder: NodePaging;
get folder(): NodePaging {
return this._folder;
}
set folder(value: NodePaging) {
let isChanged = this._folder !== value;
this._folder = value;
if (isChanged) {
this.folderChange.emit({
value: value,
path: this.currentFolderPath
});
}
}
sorting: ColumnSortingModel = {
key: 'name',
direction: 'asc'
};
errorMessage;
actions: ContentActionModel[] = [];
emptyFolderTemplate: TemplateRef<any>;
contextActionHandler: Subject<any> = new Subject();
data: ShareDataTableAdapter;
constructor(
private alfrescoService: AlfrescoService,
private ngZone: NgZone) {}
private documentListService: DocumentListService,
private ngZone: NgZone) {
this.data = new ShareDataTableAdapter(this.documentListService, this.baseComponentPath, []);
}
getContextActions(node: MinimalNodeEntity) {
if (node && node.entry) {
let targetType;
if (node.entry.isFolder) {
targetType = 'folder';
}
if (node.entry.isFile) {
targetType = 'document';
}
if (targetType) {
let actions = this.getContentActions(targetType, 'menu');
let actions = this.getNodeActions(node);
if (actions && actions.length > 0) {
return actions.map(a => {
return {
@ -152,7 +143,6 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
});
}
}
}
return null;
}
@ -163,20 +153,31 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
}
ngOnInit() {
this.data.thumbnails = this.thumbnails;
this.displayFolderContent(this.currentFolderPath);
this.contextActionHandler.subscribe(val => this.contextActionCallback(val));
}
ngOnChanges(change) {
ngOnChanges() {
this.reload();
}
ngAfterContentInit() {
if (!this.columns || this.columns.length === 0) {
let columns = this.data.getColumns();
if (!columns || columns.length === 0) {
this.setupDefaultColumns();
}
}
ngAfterViewInit() {
if (this.dataTable) {
if (this.emptyFolderTemplate) {
this.dataTable.noContentTemplate = this.emptyFolderTemplate;
}
}
}
ngAfterViewChecked() {
// workaround for MDL issues with dynamic components
if (componentHandler) {
@ -185,79 +186,31 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
}
/**
* 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): ContentActionModel[] {
if (target && type) {
getNodeActions(node: MinimalNodeEntity): ContentActionModel[] {
let target = null;
if (node && node.entry) {
if (node.entry.isFile) {
target = 'document';
}
if (node.entry.isFolder) {
target = 'folder';
}
if (target) {
let ltarget = target.toLowerCase();
let ltype = type.toLowerCase();
return this.actions.filter(entry => {
return entry.target.toLowerCase() === ltarget &&
entry.type.toLowerCase() === ltype;
return entry.target.toLowerCase() === ltarget;
});
}
}
return [];
}
/**
* Invoked when list row is clicked.
* @param item Underlying node item
* @param e DOM event (optional)
*/
onItemClick(item: MinimalNodeEntity, e?: Event) {
if (e) {
e.preventDefault();
}
this.itemClick.emit({
value: item
});
if (this.navigate && this.navigationMode === DocumentList.SINGLE_CLICK_NAVIGATION) {
if (item && item.entry) {
if (item.entry.isFile) {
this.preview.emit({
value: item
});
}
if (item.entry.isFolder) {
this.performNavigation(item);
}
}
}
}
onItemDblClick(item: MinimalNodeEntity, e?: Event) {
if (e) {
e.preventDefault();
}
this.itemDblClick.emit({
value: item
});
if (this.navigate && this.navigationMode === DocumentList.DOUBLE_CLICK_NAVIGATION) {
if (item && item.entry) {
if (item.entry.isFile) {
this.preview.emit({
value: item
});
}
if (item.entry.isFolder) {
this.performNavigation(item);
}
}
}
}
onShowContextMenu(e?: Event) {
if (e) {
e.preventDefault();
@ -272,41 +225,6 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
return false;
}
/**
* Gets thumbnail URL for the given node.
* @param node Node to get URL for.
* @returns {string} URL address.
*/
getThumbnailUrl(node: MinimalNodeEntity): string {
if (node && node.entry) {
let entry = node.entry;
if (entry.isFolder) {
return `${this.baseComponentPath}/img/ft_ic_folder.svg`;
}
if (entry.isFile) {
if (this.thumbnails) {
if (this.alfrescoService) {
return this.alfrescoService.getDocumentThumbnailUrl(node);
}
return null;
}
if (entry.content && entry.content.mimeType) {
let icon = this.alfrescoService.getMimeTypeIcon(entry.content.mimeType);
if (icon) {
return `${this.baseComponentPath}/img/${icon}`;
}
}
}
return `${this.baseComponentPath}/img/ft_ic_miscellaneous.svg`;
}
return null;
}
/**
* Invoked when executing content action for a document or folder.
* @param node Node to be the context of the execution.
@ -319,14 +237,7 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
}
displayFolderContent(path: string) {
if (path) {
this.alfrescoService
.getFolder(path)
.subscribe(
folder => this.folder = this.sort(folder, this.sorting),
error => this.errorMessage = <any>error
);
}
this.data.loadPath(path);
}
reload() {
@ -350,122 +261,109 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
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;
}
getCellValue(row: MinimalNodeEntity, col: ContentColumnModel): any {
let value = this.getObjectValueRaw(row.entry, col.source);
if (col.type === 'date') {
let datePipe = new DatePipe();
try {
return datePipe.transform(value, col.format);
} catch (err) {
console.error(`DocumentList: error parsing date ${value} to format ${col.format}`);
}
}
if (col.type === 'image') {
if (col.source === '$thumbnail') {
return this.getThumbnailUrl(row);
}
}
return value;
}
/**
* Creates a set of predefined columns.
*/
setupDefaultColumns(): void {
let thumbnailCol = new ContentColumnModel();
thumbnailCol.source = '$thumbnail';
thumbnailCol.type = 'image';
let colThumbnail = new ObjectDataColumn({
type: 'image',
key: '$thumbnail',
title: '',
srTitle: 'Thumbnail'
});
let nameCol = new ContentColumnModel();
nameCol.title = 'Name';
nameCol.source = 'name';
nameCol.cssClass = 'full-width name-column';
let colName = new ObjectDataColumn({
type: 'text',
key: 'name',
title: 'Name',
cssClass: 'full-width',
sortable: true
});
this.columns = [
thumbnailCol,
nameCol
];
this.data.setColumns([colThumbnail, colName]);
}
onColumnHeaderClick(column: ContentColumnModel) {
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 a.entry.isFolder ? -1 : 1;
}
let left = this.getObjectValueRaw(a.entry, options.key).toString();
let right = this.getObjectValueRaw(b.entry, options.key).toString();
return options.direction === 'asc'
? left.localeCompare(right)
: right.localeCompare(left);
onPreviewFile(node: MinimalNodeEntity) {
if (node) {
this.preview.emit({
value: node
});
}
return node;
}
private getObjectValueRaw(target: any, key: string) {
let val = this.getObjectValue(target, key);
onNodeClick(node: MinimalNodeEntity) {
this.nodeClick.emit({
value: node
});
if (val instanceof Date) {
val = val.valueOf();
if (this.navigate && this.navigationMode === DocumentList.SINGLE_CLICK_NAVIGATION) {
if (node && node.entry) {
if (node.entry.isFile) {
this.onPreviewFile(node);
}
return val;
if (node.entry.isFolder) {
this.performNavigation(node);
}
}
}
}
private hasEntries(node: NodePaging): boolean {
return (node && node.list && node.list.entries && node.list.entries.length > 0);
onRowClick(event: DataRowEvent) {
let item = (<ShareDataRow> event.value).node;
this.onNodeClick(item);
}
private isSortableColumn(column: ContentColumnModel) {
return column && column.source && !column.source.startsWith('$');
onNodeDblClick(node: MinimalNodeEntity) {
this.nodeDblClick.emit({
value: node
});
if (this.navigate && this.navigationMode === DocumentList.DOUBLE_CLICK_NAVIGATION) {
if (node && node.entry) {
if (node.entry.isFile) {
this.onPreviewFile(node);
}
if (node.entry.isFolder) {
this.performNavigation(node);
}
}
}
}
onRowDblClick(event?: DataRowEvent) {
let item = (<ShareDataRow> event.value).node;
this.onNodeDblClick(item);
}
onShowRowContextMenu(event) {
if (this.contextMenuActions) {
let args = event.args;
let node = (<ShareDataRow> args.row).node;
if (node) {
args.actions = this.getContextActions(node) || [];
}
}
}
onShowRowActionsMenu(event) {
if (this.contentActions) {
let args = event.args;
let node = (<ShareDataRow> args.row).node;
if (node) {
args.actions = this.getNodeActions(node) || [];
}
}
}
onExecuteRowAction(event) {
if (this.contentActions) {
let args = event.args;
let node = (<ShareDataRow> args.row).node;
let action = (<ContentActionModel> args.action);
this.executeContentAction(node, action);
}
}
}

View File

@ -0,0 +1,240 @@
/*!
* @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 { DatePipe } from '@angular/common';
import {
DataTableAdapter,
DataRow, DataColumn, DataSorting
} from 'ng2-alfresco-datatable';
import { NodePaging, MinimalNodeEntity } from './../models/document-library.model';
import { DocumentListService } from './../services/document-list.service';
export class ShareDataTableAdapter implements DataTableAdapter {
private sorting: DataSorting;
private rows: DataRow[];
private columns: DataColumn[];
thumbnails: boolean = false;
constructor(private documentListService: DocumentListService,
private basePath: string,
schema: DataColumn[]) {
this.rows = [];
this.columns = schema || [];
}
getRows(): Array<DataRow> {
return this.rows;
}
setRows(rows: Array<DataRow>) {
this.rows = rows || [];
this.sort();
}
getColumns(): Array<DataColumn> {
return this.columns;
}
setColumns(columns: Array<DataColumn>) {
this.columns = columns || [];
}
getValue(row: DataRow, col: DataColumn): any {
if (!row) {
throw new Error('Row not found');
}
if (!col) {
throw new Error('Column not found');
}
let value = row.getValue(col.key);
if (col.type === 'date') {
let datePipe = new DatePipe();
let format = col.format || 'medium';
try {
return datePipe.transform(value, format);
} catch (err) {
console.error(`DocumentList: error parsing date ${value} to format ${format}`);
}
}
if (col.type === 'image') {
if (col.key === '$thumbnail') {
let node = (<ShareDataRow> row).node;
if (node.entry.isFolder) {
return `${this.basePath}/img/ft_ic_folder.svg`;
}
if (node.entry.isFile) {
if (this.thumbnails) {
if (this.documentListService) {
return this.documentListService.getDocumentThumbnailUrl(node);
}
return null;
}
if (node.entry.content && node.entry.content.mimeType) {
let mimeType = node.entry.content.mimeType;
if (mimeType) {
let icon = this.documentListService.getMimeTypeIcon(mimeType);
if (icon) {
return `${this.basePath}/img/${icon}`;
}
}
}
}
return `${this.basePath}/img/ft_ic_miscellaneous.svg`;
}
}
return value;
}
getSorting(): DataSorting {
return this.sorting;
}
setSorting(sorting: DataSorting): void {
this.sorting = sorting;
if (sorting && sorting.key) {
this.rows.sort((a: ShareDataRow, b: ShareDataRow) => {
if (a.node.entry.isFolder !== b.node.entry.isFolder) {
return a.node.entry.isFolder ? -1 : 1;
}
let left = a.getValue(sorting.key);
if (left) {
left = (left instanceof Date) ? left.valueOf().toString() : left.toString();
} else {
left = '';
}
let right = b.getValue(sorting.key);
if (right) {
right = (right instanceof Date) ? right.valueOf().toString() : right.toString();
} else {
right = '';
}
return sorting.direction === 'asc'
? left.localeCompare(right)
: right.localeCompare(left);
});
}
}
sort(key?: string, direction?: string): void {
let sorting = this.sorting || new DataSorting();
if (key) {
sorting.key = key;
sorting.direction = direction || 'asc';
}
this.setSorting(sorting);
}
loadPath(path: string) {
if (path && this.documentListService) {
this.documentListService
.getFolder(path)
.subscribe(val => {
let page = <NodePaging>val;
let rows = [];
if (page && page.list) {
let data = page.list.entries;
if (data && data.length > 0) {
rows = data.map(item => new ShareDataRow(item));
// Sort by first sortable or just first column
let sortable = this.columns.filter(c => c.sortable);
if (sortable.length > 0) {
this.sort(sortable[0].key, 'asc');
} else {
this.sort(this.columns[0].key, 'asc');
}
}
}
this.rows = rows;
},
error => console.log(error));
}
}
}
export class ShareDataRow implements DataRow {
isSelected: boolean = false;
get node(): MinimalNodeEntity {
return this.obj;
}
constructor(private obj: MinimalNodeEntity) {
if (!obj) {
throw new Error('Object source not found');
}
}
/**
* 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 {
if (!target) {
return undefined;
}
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.entry, key);
}
hasValue(key: string): boolean {
return this.getValue(key) ? true : false;
}
}

View File

@ -1,31 +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 {
static DEFAULT_DIRECTION: string = 'asc';
key: string;
direction: string = ColumnSortingModel.DEFAULT_DIRECTION;
constructor(opts?: any) {
if (opts) {
this.key = opts.key;
this.direction = opts.direction || ColumnSortingModel.DEFAULT_DIRECTION;
}
}
}

View File

@ -19,7 +19,6 @@ export class ContentActionModel {
icon: string;
title: string;
handler: ContentActionHandler;
type: string;
target: string;
constructor(obj?: any) {
@ -27,7 +26,6 @@ export class ContentActionModel {
this.icon = obj.icon;
this.title = obj.title;
this.handler = obj.handler;
this.type = obj.type;
this.target = obj.target;
}
}

View File

@ -1,51 +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 ContentColumnModel {
static TYPE_TEXT: string = 'text';
static TYPE_DATE: string = 'date';
static TYPE_IMAGE: string = 'image';
// static TYPE_NUMBER: string = 'number';
title: string;
srTitle: string;
source: string;
cssClass: string;
type: string = ContentColumnModel.TYPE_TEXT;
format: string = 'medium';
constructor(obj?: any) {
if (obj) {
this.title = obj.title;
this.srTitle = obj.srTitle;
this.source = obj.source;
this.cssClass = obj.cssClass;
this.type = obj.type || ContentColumnModel.TYPE_TEXT;
this.format = obj.format;
}
}
static getSupportedTypes(): string[] {
return [
ContentColumnModel.TYPE_TEXT,
ContentColumnModel.TYPE_DATE,
ContentColumnModel.TYPE_IMAGE
// ContentColumnModel.TYPE_NUMBER
];
}
}

View File

@ -24,8 +24,8 @@ import {
import { AlfrescoContentService } from 'ng2-alfresco-core';
import { ContentActionHandler } from '../models/content-action.model';
import { DocumentActionsService } from './document-actions.service';
import { AlfrescoServiceMock } from '../assets/alfresco.service.mock';
import { AlfrescoService } from './alfresco.service';
import { DocumentListServiceMock } from '../assets/document-list.service.mock';
import { DocumentListService } from './document-list.service';
import {
FileNode,
FolderNode
@ -34,13 +34,13 @@ import {
describe('DocumentActionsService', () => {
let service: DocumentActionsService;
let alfrescoService: AlfrescoService;
let documentListService: DocumentListService;
let contentService: AlfrescoContentService;
beforeEach(() => {
alfrescoService = new AlfrescoServiceMock();
documentListService = new DocumentListServiceMock();
contentService = new AlfrescoContentService(null, null);
service = new DocumentActionsService(alfrescoService, contentService);
service = new DocumentActionsService(documentListService, contentService);
});
it('should register default download action', () => {
@ -147,7 +147,7 @@ describe('DocumentActionsService', () => {
});
it('should require content service for download action', () => {
let actionService = new DocumentActionsService(alfrescoService, null);
let actionService = new DocumentActionsService(documentListService, null);
let file = new FileNode();
let result = actionService.getHandler('download')(file);
expect(result).toBeFalsy();
@ -159,44 +159,44 @@ describe('DocumentActionsService', () => {
});
it('should delete file node', () => {
spyOn(alfrescoService, 'deleteNode').and.callThrough();
spyOn(documentListService, 'deleteNode').and.callThrough();
let file = new FileNode();
service.getHandler('delete')(file);
expect(alfrescoService.deleteNode).toHaveBeenCalledWith(file.entry.id);
expect(documentListService.deleteNode).toHaveBeenCalledWith(file.entry.id);
});
it('should support deletion only file node', () => {
spyOn(alfrescoService, 'deleteNode').and.callThrough();
spyOn(documentListService, 'deleteNode').and.callThrough();
let folder = new FolderNode();
service.getHandler('delete')(folder);
expect(alfrescoService.deleteNode).not.toHaveBeenCalled();
expect(documentListService.deleteNode).not.toHaveBeenCalled();
let file = new FileNode();
service.getHandler('delete')(file);
expect(alfrescoService.deleteNode).toHaveBeenCalled();
expect(documentListService.deleteNode).toHaveBeenCalled();
});
it('should require node id to delete', () => {
spyOn(alfrescoService, 'deleteNode').and.callThrough();
spyOn(documentListService, 'deleteNode').and.callThrough();
let file = new FileNode();
file.entry.id = null;
service.getHandler('delete')(file);
expect(alfrescoService.deleteNode).not.toHaveBeenCalled();
expect(documentListService.deleteNode).not.toHaveBeenCalled();
});
it('should reload target upon node deletion', () => {
spyOn(alfrescoService, 'deleteNode').and.callThrough();
spyOn(documentListService, 'deleteNode').and.callThrough();
let target = jasmine.createSpyObj('obj', ['reload']);
let file = new FileNode();
service.getHandler('delete')(file, target);
expect(alfrescoService.deleteNode).toHaveBeenCalled();
expect(documentListService.deleteNode).toHaveBeenCalled();
expect(target.reload).toHaveBeenCalled();
});
});

View File

@ -17,7 +17,7 @@
import { Injectable } from '@angular/core';
import { ContentActionHandler } from '../models/content-action.model';
import {AlfrescoService} from './alfresco.service';
import { DocumentListService } from './document-list.service';
import { AlfrescoContentService } from 'ng2-alfresco-core';
@Injectable()
@ -25,7 +25,7 @@ export class DocumentActionsService {
private handlers: { [id: string]: ContentActionHandler; } = {};
constructor(
private alfrescoService?: AlfrescoService,
private documentListService?: DocumentListService,
private contentService?: AlfrescoContentService
) {
this.setupActionHandlers();
@ -49,7 +49,7 @@ export class DocumentActionsService {
}
canExecuteAction(obj: any): boolean {
return this.alfrescoService && obj && obj.entry.isFile === true;
return this.documentListService && obj && obj.entry.isFile === true;
}
private setupActionHandlers() {
@ -86,7 +86,7 @@ export class DocumentActionsService {
private deleteNode(obj: any, target?: any) {
if (this.canExecuteAction(obj) && obj.entry && obj.entry.id) {
this.alfrescoService.deleteNode(obj.entry.id).subscribe(() => {
this.documentListService.deleteNode(obj.entry.id).subscribe(() => {
if (target && typeof target.reload === 'function') {
target.reload();
}

View File

@ -27,12 +27,11 @@ import {
AlfrescoContentService
} from 'ng2-alfresco-core';
import { FileNode } from '../assets/document-library.model.mock';
import { AlfrescoService } from './alfresco.service';
import { DocumentListService } from './document-list.service';
// TODO: rename to DocumentListService
describe('AlfrescoService', () => {
describe('DocumentListService', () => {
let service: AlfrescoService;
let service: DocumentListService;
let settingsService: AlfrescoSettingsService;
let authService: AlfrescoAuthenticationService;
let contentService: AlfrescoContentService;
@ -42,7 +41,7 @@ describe('AlfrescoService', () => {
settingsService = new AlfrescoSettingsService();
authService = new AlfrescoAuthenticationService(settingsService);
contentService = new AlfrescoContentService(settingsService, authService);
service = new AlfrescoService(settingsService, authService, contentService);
service = new DocumentListService(settingsService, authService, contentService);
});
it('should require node to get thumbnail url', () => {
@ -50,7 +49,7 @@ describe('AlfrescoService', () => {
});
it('should require content service to get thumbnail url', () => {
service = new AlfrescoService(settingsService, authService, null);
service = new DocumentListService(settingsService, authService, null);
let file = new FileNode();
expect(service.getDocumentThumbnailUrl(file)).toBeNull();
});
@ -72,9 +71,9 @@ describe('AlfrescoService', () => {
});
it('should resolve default icon for mime type', () => {
expect(service.getMimeTypeIcon(null)).toBe(AlfrescoService.DEFAULT_MIME_TYPE_ICON);
expect(service.getMimeTypeIcon('')).toBe(AlfrescoService.DEFAULT_MIME_TYPE_ICON);
expect(service.getMimeTypeIcon('missing/type')).toBe(AlfrescoService.DEFAULT_MIME_TYPE_ICON);
expect(service.getMimeTypeIcon(null)).toBe(DocumentListService.DEFAULT_MIME_TYPE_ICON);
expect(service.getMimeTypeIcon('')).toBe(DocumentListService.DEFAULT_MIME_TYPE_ICON);
expect(service.getMimeTypeIcon('missing/type')).toBe(DocumentListService.DEFAULT_MIME_TYPE_ICON);
});
});

View File

@ -27,12 +27,8 @@ import {
declare let AlfrescoApi: any;
// TODO: consider renaming to something like 'DocumentListService'
/**
* Internal service used by Document List component.
*/
@Injectable()
export class AlfrescoService {
export class DocumentListService {
static DEFAULT_MIME_TYPE_ICON: string = 'ft_ic_miscellaneous.svg';
@ -120,7 +116,7 @@ export class AlfrescoService {
getMimeTypeIcon(mimeType: string): string {
let icon = this.mimeTypeIcons[mimeType];
return icon || AlfrescoService.DEFAULT_MIME_TYPE_ICON;
return icon || DocumentListService.DEFAULT_MIME_TYPE_ICON;
}
private handleError(error: Response) {

View File

@ -27,17 +27,17 @@ import {
FileNode,
FolderNode
} from '../assets/document-library.model.mock';
import { AlfrescoService } from './alfresco.service';
import { AlfrescoServiceMock } from '../assets/alfresco.service.mock';
import { DocumentListService } from './document-list.service';
import { DocumentListServiceMock } from '../assets/document-list.service.mock';
describe('FolderActionsService', () => {
let service: FolderActionsService;
let alfrescoService: AlfrescoService;
let documentListService: DocumentListService;
beforeEach(() => {
alfrescoService = new AlfrescoServiceMock();
service = new FolderActionsService(alfrescoService);
documentListService = new DocumentListServiceMock();
service = new FolderActionsService(documentListService);
});
it('should register custom action handler', () => {
@ -105,44 +105,44 @@ describe('FolderActionsService', () => {
});
it('should delete folder node', () => {
spyOn(alfrescoService, 'deleteNode').and.callThrough();
spyOn(documentListService, 'deleteNode').and.callThrough();
let folder = new FolderNode();
service.getHandler('delete')(folder);
expect(alfrescoService.deleteNode).toHaveBeenCalledWith(folder.entry.id);
expect(documentListService.deleteNode).toHaveBeenCalledWith(folder.entry.id);
});
it('should support deletion only folder node', () => {
spyOn(alfrescoService, 'deleteNode').and.callThrough();
spyOn(documentListService, 'deleteNode').and.callThrough();
let file = new FileNode();
service.getHandler('delete')(file);
expect(alfrescoService.deleteNode).not.toHaveBeenCalled();
expect(documentListService.deleteNode).not.toHaveBeenCalled();
let folder = new FolderNode();
service.getHandler('delete')(folder);
expect(alfrescoService.deleteNode).toHaveBeenCalled();
expect(documentListService.deleteNode).toHaveBeenCalled();
});
it('should require node id to delete', () => {
spyOn(alfrescoService, 'deleteNode').and.callThrough();
spyOn(documentListService, 'deleteNode').and.callThrough();
let folder = new FolderNode();
folder.entry.id = null;
service.getHandler('delete')(folder);
expect(alfrescoService.deleteNode).not.toHaveBeenCalled();
expect(documentListService.deleteNode).not.toHaveBeenCalled();
});
it('should reload target upon node deletion', () => {
spyOn(alfrescoService, 'deleteNode').and.callThrough();
spyOn(documentListService, 'deleteNode').and.callThrough();
let target = jasmine.createSpyObj('obj', ['reload']);
let folder = new FolderNode();
service.getHandler('delete')(folder, target);
expect(alfrescoService.deleteNode).toHaveBeenCalled();
expect(documentListService.deleteNode).toHaveBeenCalled();
expect(target.reload).toHaveBeenCalled();
});

View File

@ -17,13 +17,13 @@
import { Injectable } from '@angular/core';
import { ContentActionHandler } from '../models/content-action.model';
import {AlfrescoService} from './alfresco.service';
import { DocumentListService } from './document-list.service';
@Injectable()
export class FolderActionsService {
private handlers: { [id: string]: ContentActionHandler; } = {};
constructor(private _alfrescoService?: AlfrescoService) {
constructor(private documentListService?: DocumentListService) {
this.setupActionHandlers();
}
@ -45,7 +45,7 @@ export class FolderActionsService {
}
canExecuteAction(obj: any): boolean {
return this._alfrescoService && obj && obj.entry.isFolder === true;
return this.documentListService && obj && obj.entry.isFolder === true;
}
private setupActionHandlers() {
@ -68,7 +68,7 @@ export class FolderActionsService {
private deleteNode(obj: any, target?: any) {
if (this.canExecuteAction(obj) && obj.entry && obj.entry.id) {
this._alfrescoService.deleteNode(obj.entry.id).subscribe(() => {
this.documentListService.deleteNode(obj.entry.id).subscribe(() => {
if (target && typeof target.reload === 'function') {
target.reload();
}