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 CHROME_BIN=/usr/bin/google-chrome
- export DISPLAY=:99.0 - export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start - 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: env:
matrix: matrix:
@ -30,7 +31,7 @@ env:
- MODULE=ng2-alfresco-viewer - MODULE=ng2-alfresco-viewer
before_script: 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/ - ls -ltrh ./node_modules/
script: npm run test script: npm run test
# Send coverage data to Coveralls # Send coverage data to Coveralls

View File

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

View File

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

View File

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

View File

@ -26,17 +26,20 @@ import { ContextMenuService } from './../services/context-menu.service';
}) })
export class ContextMenuDirective { export class ContextMenuDirective {
@Input('context-menu') @Input('context-menu')
links; links: any[];
constructor( constructor(
private _contextMenuService: ContextMenuService) {} private _contextMenuService: ContextMenuService) {}
onShowContextMenu(event?: MouseEvent) { onShowContextMenu(event?: MouseEvent) {
if (this._contextMenuService) {
this._contextMenuService.show.next({event: event, obj: this.links});
}
if (event) { if (event) {
event.preventDefault(); 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 { DataTableComponent } from './src/components/datatable.component';
import { NoContentTemplateComponent } from './src/components/no-content-template.component';
// components // components
export * from './src/components/datatable.component'; export * from './src/components/datatable.component';
export * from './src/components/no-content-template.component';
// data // data
export * from './src/data/datatable-adapter'; export * from './src/data/datatable-adapter';
export * from './src/data/object-datatable-adapter'; export * from './src/data/object-datatable-adapter';
export const ALFRESCO_DATATABLE_DIRECTIVES: [any] = [ export const ALFRESCO_DATATABLE_DIRECTIVES: [any] = [
DataTableComponent DataTableComponent,
NoContentTemplateComponent
]; ];

View File

@ -9,13 +9,15 @@ var map = {
'app': 'base/dist', 'app': 'base/dist',
'rxjs': 'base/node_modules/rxjs', 'rxjs': 'base/node_modules/rxjs',
'@angular': 'base/node_modules/@angular', '@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 = { var packages = {
'app': { main: 'main.js', defaultExtension: 'js' }, 'app': { main: 'main.js', defaultExtension: 'js' },
'rxjs': { 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 = [ 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/**/*.js', included: false, watched: false},
{pattern: 'node_modules/@angular/**/*.map', 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-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}, {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", "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", "coverage": "npm run test && wsrv -o -p 9875 ./coverage/report",
"prepublish": "npm run build", "prepublish": "npm run build",
"travis": "echo 'placeholder'" "travis": "npm link ng2-alfresco-core"
}, },
"main": "./dist/index.js", "main": "./dist/index.js",
"typings": "./dist/index.d.ts", "typings": "./dist/index.d.ts",
@ -67,7 +67,9 @@
"reflect-metadata": "0.1.3", "reflect-metadata": "0.1.3",
"rxjs": "5.0.0-beta.6", "rxjs": "5.0.0-beta.6",
"zone.js": "0.6.12", "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": { "peerDependencies": {
"material-design-icons": "^2.2.3", "material-design-icons": "^2.2.3",

View File

@ -14,11 +14,58 @@
:host .data-cell { :host .data-cell {
cursor: default; cursor: default;
} }
:host .cell-value {}
:host .column-header { :host .column-header {
cursor: pointer; 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 */ /* Utils */
:host .non-selectable { :host .non-selectable {
@ -39,3 +86,22 @@
clip: rect(0,0,0,0); clip: rect(0,0,0,0);
border: 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>
<th class="mdl-data-table__cell--non-numeric non-selectable {{col.cssClass}}" <th class="mdl-data-table__cell--non-numeric non-selectable {{col.cssClass}}"
*ngFor="let col of data.getColumns()" *ngFor="let col of data.getColumns()"
[attr.data-automation-id]="'auto_id_' + col.key"
[class.column-header]="col.title" [class.column-header]="col.title"
[class.mdl-data-table__header--sorted-ascending]="isColumnSorted(col, 'asc')" [class.mdl-data-table__header--sorted-ascending]="isColumnSorted(col, 'asc')"
[class.mdl-data-table__header--sorted-descending]="isColumnSorted(col, 'desc')" [class.mdl-data-table__header--sorted-descending]="isColumnSorted(col, 'desc')"
@ -41,22 +42,50 @@
</td> </td>
<td *ngFor="let col of data.getColumns()" [ngSwitch]="col.type" <td *ngFor="let col of data.getColumns()" [ngSwitch]="col.type"
class="mdl-data-table__cell--non-numeric non-selectable data-cell {{col.cssClass}}" class="mdl-data-table__cell--non-numeric non-selectable data-cell {{col.cssClass}}"
(click)="onRowClick(row, $event)" (dblclick)="onRowDblClick(row, $event)"> (click)="onRowClick(row, $event)"
<div *ngSwitchCase="'image'"> (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> <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)}}"> <img *ngIf="!isIconValue(row, col)" class="image-cell" alt="" src="{{data.getValue(row, col)}}">
</div> </div>
<div *ngSwitchCase="'text'"> <div *ngSwitchCase="'date'" class="cell-value">
{{data.getValue(row, col)}} {{data.getValue(row, col)}}
</div> </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 --> <!-- empty cell for unknown column type -->
</span> </span>
</td> </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>
<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> </tbody>
</table> </table>

View File

@ -22,14 +22,18 @@ import {
Input, Input,
Output, Output,
EventEmitter, EventEmitter,
AfterViewChecked AfterViewChecked,
TemplateRef
} from '@angular/core'; } from '@angular/core';
import { CONTEXT_MENU_DIRECTIVES } from 'ng2-alfresco-core';
import { import {
DataTableAdapter, DataTableAdapter,
DataRow, DataRow,
DataColumn, DataColumn,
DataSorting DataSorting,
DataRowEvent
} from './../data/datatable-adapter'; } from './../data/datatable-adapter';
import { ObjectDataTableAdapter } from '../data/object-datatable-adapter'; import { ObjectDataTableAdapter } from '../data/object-datatable-adapter';
@ -40,7 +44,8 @@ declare let __moduleName: string;
moduleId: __moduleName, moduleId: __moduleName,
selector: 'alfresco-datatable', selector: 'alfresco-datatable',
styleUrls: ['./datatable.component.css'], styleUrls: ['./datatable.component.css'],
templateUrl: './datatable.component.html' templateUrl: './datatable.component.html',
directives: [CONTEXT_MENU_DIRECTIVES]
}) })
export class DataTableComponent implements OnInit, AfterViewChecked { export class DataTableComponent implements OnInit, AfterViewChecked {
@ -54,13 +59,24 @@ export class DataTableComponent implements OnInit, AfterViewChecked {
actions: boolean = false; actions: boolean = false;
@Output() @Output()
rowClick: EventEmitter<any> = new EventEmitter(); rowClick: EventEmitter<DataRowEvent> = new EventEmitter<DataRowEvent>();
@Output() @Output()
rowDblClick: EventEmitter<any> = new EventEmitter(); rowDblClick: EventEmitter<DataRowEvent> = new EventEmitter<DataRowEvent>();
noContentTemplate: TemplateRef<any>;
isSelectAllChecked: boolean = false; 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 // TODO: left for reference, will be removed during future revisions
constructor(/*private _ngZone?: NgZone*/) { constructor(/*private _ngZone?: NgZone*/) {
} }
@ -84,7 +100,8 @@ export class DataTableComponent implements OnInit, AfterViewChecked {
} }
this.rowClick.emit({ this.rowClick.emit({
value: row value: row,
event: e
}); });
} }
@ -94,7 +111,8 @@ export class DataTableComponent implements OnInit, AfterViewChecked {
} }
this.rowDblClick.emit({ this.rowDblClick.emit({
value: row value: row,
event: e
}); });
} }
@ -134,14 +152,16 @@ export class DataTableComponent implements OnInit, AfterViewChecked {
isIconValue(row: DataRow, col: DataColumn) { isIconValue(row: DataRow, col: DataColumn) {
if (row && col) { 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; return false;
} }
asIconValue(row: DataRow, col: DataColumn) { asIconValue(row: DataRow, col: DataColumn) {
if (this.isIconValue(row, col)) { 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; return null;
} }
@ -153,4 +173,21 @@ export class DataTableComponent implements OnInit, AfterViewChecked {
} }
return false; 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 { import {
it, Directive,
describe, ContentChild,
expect TemplateRef,
} from '@angular/core/testing'; 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', () => { constructor(
let model = new ContentColumnModel({}); private dataTable: DataTableComponent) {
expect(model.type).toBe(ContentColumnModel.TYPE_TEXT); }
});
it('should return supported types', () => { ngAfterContentInit() {
expect(ContentColumnModel.getSupportedTypes().length).toBeGreaterThan(0); this.dataTable.noContentTemplate = this.template;
}); }
}
});

View File

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

View File

@ -15,6 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { DatePipe } from '@angular/common';
import { import {
DataTableAdapter, DataTableAdapter,
DataRow, DataRow,
@ -78,7 +80,20 @@ export class ObjectDataTableAdapter implements DataTableAdapter {
if (!col) { if (!col) {
throw new Error('Column not found'); 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 { getSorting(): DataSorting {

View File

@ -9,7 +9,7 @@
"typings": "typings install", "typings": "typings install",
"postinstall": "npm run typings && npm run build", "postinstall": "npm run typings && npm run build",
"start": "concurrently \"npm run build:w\" \"npm run server\" ", "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": "npm run tslint && rimraf dist && tsc",
"build:w": "npm run tslint && rimraf dist && tsc -w", "build:w": "npm run tslint && rimraf dist && tsc -w",
"tslint": "npm run tslint-src && npm run tslint-root", "tslint": "npm run tslint-src && npm run tslint-root",
@ -41,7 +41,8 @@
"alfresco-js-api": "^0.1.0", "alfresco-js-api": "^0.1.0",
"ng2-alfresco-core": "^0.1.36", "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": { "devDependencies": {
"concurrently": "2.0.0", "concurrently": "2.0.0",

View File

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

View File

@ -12,6 +12,7 @@
'ng2-translate': 'node_modules/ng2-translate', 'ng2-translate': 'node_modules/ng2-translate',
'ng2-alfresco-core': 'node_modules/ng2-alfresco-core/dist', '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' 'ng2-alfresco-documentlist': 'node_modules/ng2-alfresco-documentlist/dist'
}; };
// packages tells the System loader how to load when no filename and/or no extension // packages tells the System loader how to load when no filename and/or no extension
@ -22,6 +23,7 @@
'ng2-translate': { defaultExtension: 'js' }, 'ng2-translate': { defaultExtension: 'js' },
'ng2-alfresco-core': { main: 'index.js', 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' } 'ng2-alfresco-documentlist': { main: 'index.js', defaultExtension: 'js' }
}; };
var ngPackageNames = [ 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 { FolderActionsService } from './src/services/folder-actions.service';
import { DocumentActionsService } from './src/services/document-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 // components
export * from './src/components/document-list'; 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/empty-folder-content';
export * from './src/components/document-list-breadcrumb.component'; export * from './src/components/document-list-breadcrumb.component';
// models
export * from './src/models/column-sorting.model';
// services // services
export * from './src/services/folder-actions.service'; export * from './src/services/folder-actions.service';
export * from './src/services/document-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] = [ export const DOCUMENT_LIST_DIRECTIVES: [any] = [
DocumentList, DocumentList,
@ -55,7 +52,7 @@ export const DOCUMENT_LIST_DIRECTIVES: [any] = [
]; ];
export const DOCUMENT_LIST_PROVIDERS: [any] = [ export const DOCUMENT_LIST_PROVIDERS: [any] = [
AlfrescoService, DocumentListService,
FolderActionsService, FolderActionsService,
DocumentActionsService DocumentActionsService
]; ];

View File

@ -9,15 +9,17 @@ var map = {
'app': 'base/dist', 'app': 'base/dist',
'rxjs': 'base/node_modules/rxjs', 'rxjs': 'base/node_modules/rxjs',
'@angular': 'base/node_modules/@angular', '@angular': 'base/node_modules/@angular',
'ng2-translate' : '/base/node_modules/ng2-translate',
'ng2-alfresco-core': '/base/node_modules/ng2-alfresco-core/dist', '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 = { var packages = {
'app': { main: 'main.js', defaultExtension: 'js' }, 'app': { main: 'main.js', defaultExtension: 'js' },
'rxjs': { defaultExtension: 'js' }, 'rxjs': { defaultExtension: 'js' },
'ng2-translate': { defaultExtension: 'js' },
'ng2-alfresco-core': { main: 'index.js', defaultExtension: 'js' }, 'ng2-alfresco-core': { main: 'index.js', defaultExtension: 'js' },
'ng2-translate': { defaultExtension: 'js' } 'ng2-alfresco-datatable': { main: 'index.js', defaultExtension: 'js' }
}; };
var packageNames = [ 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/**/*.js', included: false, watched: false},
{pattern: 'node_modules/@angular/**/*.map', 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-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: 'node_modules/ng2-translate/**/*.js', included: false, served: true, watched: false},
{pattern: 'karma-test-shim.js', included: true, watched: true}, {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", "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", "coverage": "npm run test && wsrv -o -p 9875 ./coverage/report",
"prepublish": "npm run build", "prepublish": "npm run build",
"travis": "npm link ng2-alfresco-core" "travis": "npm link ng2-alfresco-core ng2-alfresco-datatable"
}, },
"main": "./dist/index.js", "main": "./dist/index.js",
"typings": "./dist/index.d.ts", "typings": "./dist/index.d.ts",
@ -75,6 +75,7 @@
"rimraf": "2.5.2", "rimraf": "2.5.2",
"ng2-translate": "2.2.2", "ng2-translate": "2.2.2",
"ng2-alfresco-core": "0.2.0", "ng2-alfresco-core": "0.2.0",
"ng2-alfresco-datatable": "0.2.0",
"alfresco-js-api": "^0.1.0" "alfresco-js-api": "^0.1.0"
}, },
"peerDependencies": { "peerDependencies": {
@ -103,13 +104,7 @@
}, },
"license-check-config": { "license-check-config": {
"src": [ "src": [
"**/*.js", "./dist/**/*.js"
"**/*.ts",
"!/**/coverage/**/*",
"!/**/demo/**/*",
"!/**/node_modules/**/*",
"!/**/typings/**/*",
"!*.js"
], ],
"path": "assets/license_header.txt", "path": "assets/license_header.txt",
"blocking": true, "blocking": true,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,10 +22,10 @@ import {
beforeEach beforeEach
} from '@angular/core/testing'; } from '@angular/core/testing';
import {DocumentList} from './document-list'; import { DocumentList } from './document-list';
import {ContentColumn} from './content-column'; 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'; import { ContentColumnList } from './content-column-list';
describe('ContentColumn', () => { describe('ContentColumn', () => {
@ -33,8 +33,8 @@ describe('ContentColumn', () => {
let columnList: ContentColumnList; let columnList: ContentColumnList;
beforeEach(() => { beforeEach(() => {
let alfrescoServiceMock = new AlfrescoServiceMock(); let service = new DocumentListServiceMock();
documentList = new DocumentList(alfrescoServiceMock, null); documentList = new DocumentList(service, null);
columnList = new ContentColumnList(documentList); columnList = new ContentColumnList(documentList);
}); });
@ -45,52 +45,18 @@ describe('ContentColumn', () => {
column.ngOnInit(); column.ngOnInit();
expect(columnList.registerColumn).toHaveBeenCalled(); expect(columnList.registerColumn).toHaveBeenCalled();
});
it('should setup model properties during registration', () => { let columns = documentList.data.getColumns();
expect(columns.length).toBe(1);
let column = new ContentColumn(columnList); expect(columns[0]).toBe(column);
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);
}); });
it('should setup screen reader title for thumbnail column', () => { it('should setup screen reader title for thumbnail column', () => {
let column = new ContentColumn(columnList); let column = new ContentColumn(columnList);
column.source = '$thumbnail'; column.key = '$thumbnail';
column.ngOnInit(); column.ngOnInit();
expect(documentList.columns.length).toBe(1); expect(column.srTitle).toBe('Thumbnail');
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');
}); });
it('should register on init', () => { it('should register on init', () => {

View File

@ -15,15 +15,27 @@
* limitations under the License. * 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 { ContentColumnList } from './content-column-list';
import { ContentColumnModel } from './../models/content-column.model'; import { DataColumn } from 'ng2-alfresco-datatable';
@Component({ @Component({
selector: 'content-column', selector: 'content-column',
template: '' 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() @Input()
title: string = ''; title: string = '';
@ -34,36 +46,14 @@ export class ContentColumn implements OnInit, OnChanges {
@Input('sr-title') @Input('sr-title')
srTitle: string; srTitle: string;
@Input()
source: string;
@Input('class') @Input('class')
cssClass: string; cssClass: string;
@Input() constructor(private list: ContentColumnList) {}
type: string = 'text';
@Input()
format: string;
model: ContentColumnModel;
constructor(private list: ContentColumnList) {
this.model = new ContentColumnModel();
}
ngOnInit() { ngOnInit() {
this.model = new ContentColumnModel({ if (!this.srTitle && this.key === '$thumbnail') {
title: this.title, this.srTitle = 'Thumbnail';
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';
} }
this.register(); this.register();
@ -71,14 +61,8 @@ export class ContentColumn implements OnInit, OnChanges {
register(): boolean { register(): boolean {
if (this.list) { if (this.list) {
return this.list.registerColumn(this.model); return this.list.registerColumn(this);
} }
return false; 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"> <alfresco-datatable
<thead> [data]="data"
<tr> [actions]="contentActions"
<!-- Columns --> [multiselect]="multiselect"
<th class="mdl-data-table__cell--non-numeric non-selectable {{col.cssClass}}" (showRowContextMenu)="onShowRowContextMenu($event)"
*ngFor="let col of columns" (showRowActionsMenu)="onShowRowActionsMenu($event)"
[class.column-header]="col.title" (executeRowAction)="onExecuteRowAction($event)"
[attr.data-automation-id]="'auto_id_' + col.source" (rowClick)="onRowClick($event)"
[class.mdl-data-table__header--sorted-ascending]="sorting.key === col.source && sorting.direction === 'asc'" (rowDblClick)="onRowDblClick($event)">
[class.mdl-data-table__header--sorted-descending]="sorting.key === col.source && sorting.direction === 'desc'" <no-content-template>
(click)="onColumnHeaderClick(col)"> <template>
<span *ngIf="col.srTitle" class="cell-value sr-only">{{col.srTitle}}</span> <img [src]="baseComponentPath + '/img/document-list.empty-folder.png'">
<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">
</template> </template>
<img *ngIf="!emptyFolderTemplate" </no-content-template>
[src]="baseComponentPath + '/img/document-list.empty-folder.png'"> </alfresco-datatable>
</td>
</tr>
</tbody>
</table>

View File

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

View File

@ -22,19 +22,27 @@ import {
Output, Output,
EventEmitter, EventEmitter,
AfterContentInit, AfterContentInit,
AfterViewInit,
AfterViewChecked, AfterViewChecked,
OnChanges, OnChanges,
TemplateRef, TemplateRef,
NgZone NgZone,
ViewChild
} from '@angular/core'; } from '@angular/core';
import { DatePipe } from '@angular/common';
import { Subject } from 'rxjs/Rx'; import { Subject } from 'rxjs/Rx';
import { CONTEXT_MENU_DIRECTIVES } from 'ng2-alfresco-core'; import { CONTEXT_MENU_DIRECTIVES } from 'ng2-alfresco-core';
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 { ContentActionModel } from './../models/content-action.model';
import { ContentColumnModel } from './../models/content-column.model'; import { ShareDataTableAdapter, ShareDataRow } from './../data/share-datatable-adapter';
import { ColumnSortingModel } from './../models/column-sorting.model';
declare var componentHandler; declare var componentHandler;
declare let __moduleName: string; declare let __moduleName: string;
@ -44,13 +52,13 @@ declare let __moduleName: string;
selector: 'alfresco-document-list', selector: 'alfresco-document-list',
styleUrls: ['./document-list.css'], styleUrls: ['./document-list.css'],
templateUrl: './document-list.html', templateUrl: './document-list.html',
providers: [AlfrescoService], providers: [DocumentListService],
directives: [CONTEXT_MENU_DIRECTIVES], directives: [CONTEXT_MENU_DIRECTIVES, ALFRESCO_DATATABLE_DIRECTIVES],
host: { host: {
'(contextmenu)': 'onShowContextMenu($event)' '(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 SINGLE_CLICK_NAVIGATION: string = 'click';
static DOUBLE_CLICK_NAVIGATION: string = 'dblclick'; static DOUBLE_CLICK_NAVIGATION: string = 'dblclick';
@ -62,17 +70,26 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
@Input() @Input()
navigate: boolean = true; navigate: boolean = true;
@Input('navigation-mode') @Input()
navigationMode: string = 'dblclick'; // click|dblclick navigationMode: string = 'dblclick'; // click|dblclick
@Input() @Input()
thumbnails: boolean = false; thumbnails: boolean = false;
@Output() @Input()
itemClick: EventEmitter<any> = new EventEmitter(); multiselect: boolean = false;
@Input()
contentActions: boolean = false;
@Input()
contextMenuActions: boolean = false;
@Output() @Output()
itemDblClick: EventEmitter<any> = new EventEmitter(); nodeClick: EventEmitter<any> = new EventEmitter();
@Output()
nodeDblClick: EventEmitter<any> = new EventEmitter();
@Output() @Output()
folderChange: EventEmitter<any> = new EventEmitter(); folderChange: EventEmitter<any> = new EventEmitter();
@ -80,6 +97,9 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
@Output() @Output()
preview: EventEmitter<any> = new EventEmitter(); preview: EventEmitter<any> = new EventEmitter();
@ViewChild(DataTableComponent)
dataTable: DataTableComponent;
private _path = this.DEFAULT_ROOT_FOLDER; private _path = this.DEFAULT_ROOT_FOLDER;
get currentFolderPath(): string { get currentFolderPath(): string {
@ -91,57 +111,28 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
if (value !== this._path) { if (value !== this._path) {
this._path = value || this.DEFAULT_ROOT_FOLDER; this._path = value || this.DEFAULT_ROOT_FOLDER;
this.displayFolderContent(this._path); 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({ this.folderChange.emit({
value: value,
path: this.currentFolderPath path: this.currentFolderPath
}); });
} }
} }
sorting: ColumnSortingModel = { errorMessage;
key: 'name', actions: ContentActionModel[] = [];
direction: 'asc' emptyFolderTemplate: TemplateRef<any>;
};
contextActionHandler: Subject<any> = new Subject(); contextActionHandler: Subject<any> = new Subject();
data: ShareDataTableAdapter;
constructor( constructor(
private alfrescoService: AlfrescoService, private documentListService: DocumentListService,
private ngZone: NgZone) {} private ngZone: NgZone) {
this.data = new ShareDataTableAdapter(this.documentListService, this.baseComponentPath, []);
}
getContextActions(node: MinimalNodeEntity) { getContextActions(node: MinimalNodeEntity) {
if (node && node.entry) { if (node && node.entry) {
let targetType; let actions = this.getNodeActions(node);
if (node.entry.isFolder) {
targetType = 'folder';
}
if (node.entry.isFile) {
targetType = 'document';
}
if (targetType) {
let actions = this.getContentActions(targetType, 'menu');
if (actions && actions.length > 0) { if (actions && actions.length > 0) {
return actions.map(a => { return actions.map(a => {
return { return {
@ -152,7 +143,6 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
}); });
} }
} }
}
return null; return null;
} }
@ -163,20 +153,31 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
} }
ngOnInit() { ngOnInit() {
this.data.thumbnails = this.thumbnails;
this.displayFolderContent(this.currentFolderPath); this.displayFolderContent(this.currentFolderPath);
this.contextActionHandler.subscribe(val => this.contextActionCallback(val)); this.contextActionHandler.subscribe(val => this.contextActionCallback(val));
} }
ngOnChanges(change) { ngOnChanges() {
this.reload(); this.reload();
} }
ngAfterContentInit() { ngAfterContentInit() {
if (!this.columns || this.columns.length === 0) { let columns = this.data.getColumns();
if (!columns || columns.length === 0) {
this.setupDefaultColumns(); this.setupDefaultColumns();
} }
} }
ngAfterViewInit() {
if (this.dataTable) {
if (this.emptyFolderTemplate) {
this.dataTable.noContentTemplate = this.emptyFolderTemplate;
}
}
}
ngAfterViewChecked() { ngAfterViewChecked() {
// workaround for MDL issues with dynamic components // workaround for MDL issues with dynamic components
if (componentHandler) { if (componentHandler) {
@ -185,79 +186,31 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
} }
/** getNodeActions(node: MinimalNodeEntity): ContentActionModel[] {
* Get a list of content actions based on target and type. let target = null;
* @param target Target to filter actions by.
* @param type Type to filter actions by. if (node && node.entry) {
* @returns {ContentActionModel[]} List of actions filtered by target and type. if (node.entry.isFile) {
*/ target = 'document';
getContentActions(target: string, type: string): ContentActionModel[] { }
if (target && type) {
if (node.entry.isFolder) {
target = 'folder';
}
if (target) {
let ltarget = target.toLowerCase(); let ltarget = target.toLowerCase();
let ltype = type.toLowerCase();
return this.actions.filter(entry => { return this.actions.filter(entry => {
return entry.target.toLowerCase() === ltarget && return entry.target.toLowerCase() === ltarget;
entry.type.toLowerCase() === ltype;
}); });
} }
}
return []; 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) { onShowContextMenu(e?: Event) {
if (e) { if (e) {
e.preventDefault(); e.preventDefault();
@ -272,41 +225,6 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
return false; 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. * Invoked when executing content action for a document or folder.
* @param node Node to be the context of the execution. * @param node Node to be the context of the execution.
@ -319,14 +237,7 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
} }
displayFolderContent(path: string) { displayFolderContent(path: string) {
if (path) { this.data.loadPath(path);
this.alfrescoService
.getFolder(path)
.subscribe(
folder => this.folder = this.sort(folder, this.sorting),
error => this.errorMessage = <any>error
);
}
} }
reload() { reload() {
@ -350,122 +261,109 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
return null; 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. * Creates a set of predefined columns.
*/ */
setupDefaultColumns(): void { setupDefaultColumns(): void {
let thumbnailCol = new ContentColumnModel(); let colThumbnail = new ObjectDataColumn({
thumbnailCol.source = '$thumbnail'; type: 'image',
thumbnailCol.type = 'image'; key: '$thumbnail',
title: '',
srTitle: 'Thumbnail'
});
let nameCol = new ContentColumnModel(); let colName = new ObjectDataColumn({
nameCol.title = 'Name'; type: 'text',
nameCol.source = 'name'; key: 'name',
nameCol.cssClass = 'full-width name-column'; title: 'Name',
cssClass: 'full-width',
sortable: true
});
this.columns = [ this.data.setColumns([colThumbnail, colName]);
thumbnailCol,
nameCol
];
} }
onColumnHeaderClick(column: ContentColumnModel) { onPreviewFile(node: MinimalNodeEntity) {
if (column && this.isSortableColumn(column)) { if (node) {
if (this.sorting.key === column.source) { this.preview.emit({
this.sorting.direction = this.sorting.direction === 'asc' ? 'desc' : 'asc'; value: node
} 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);
}); });
} }
return node;
} }
private getObjectValueRaw(target: any, key: string) { onNodeClick(node: MinimalNodeEntity) {
let val = this.getObjectValue(target, key); this.nodeClick.emit({
value: node
});
if (val instanceof Date) { if (this.navigate && this.navigationMode === DocumentList.SINGLE_CLICK_NAVIGATION) {
val = val.valueOf(); 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 { onRowClick(event: DataRowEvent) {
return (node && node.list && node.list.entries && node.list.entries.length > 0); let item = (<ShareDataRow> event.value).node;
this.onNodeClick(item);
} }
private isSortableColumn(column: ContentColumnModel) { onNodeDblClick(node: MinimalNodeEntity) {
return column && column.source && !column.source.startsWith('$'); 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; icon: string;
title: string; title: string;
handler: ContentActionHandler; handler: ContentActionHandler;
type: string;
target: string; target: string;
constructor(obj?: any) { constructor(obj?: any) {
@ -27,7 +26,6 @@ export class ContentActionModel {
this.icon = obj.icon; this.icon = obj.icon;
this.title = obj.title; this.title = obj.title;
this.handler = obj.handler; this.handler = obj.handler;
this.type = obj.type;
this.target = obj.target; 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 { AlfrescoContentService } from 'ng2-alfresco-core';
import { ContentActionHandler } from '../models/content-action.model'; import { ContentActionHandler } from '../models/content-action.model';
import { DocumentActionsService } from './document-actions.service'; import { DocumentActionsService } from './document-actions.service';
import { AlfrescoServiceMock } from '../assets/alfresco.service.mock'; import { DocumentListServiceMock } from '../assets/document-list.service.mock';
import { AlfrescoService } from './alfresco.service'; import { DocumentListService } from './document-list.service';
import { import {
FileNode, FileNode,
FolderNode FolderNode
@ -34,13 +34,13 @@ import {
describe('DocumentActionsService', () => { describe('DocumentActionsService', () => {
let service: DocumentActionsService; let service: DocumentActionsService;
let alfrescoService: AlfrescoService; let documentListService: DocumentListService;
let contentService: AlfrescoContentService; let contentService: AlfrescoContentService;
beforeEach(() => { beforeEach(() => {
alfrescoService = new AlfrescoServiceMock(); documentListService = new DocumentListServiceMock();
contentService = new AlfrescoContentService(null, null); contentService = new AlfrescoContentService(null, null);
service = new DocumentActionsService(alfrescoService, contentService); service = new DocumentActionsService(documentListService, contentService);
}); });
it('should register default download action', () => { it('should register default download action', () => {
@ -147,7 +147,7 @@ describe('DocumentActionsService', () => {
}); });
it('should require content service for download action', () => { 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 file = new FileNode();
let result = actionService.getHandler('download')(file); let result = actionService.getHandler('download')(file);
expect(result).toBeFalsy(); expect(result).toBeFalsy();
@ -159,44 +159,44 @@ describe('DocumentActionsService', () => {
}); });
it('should delete file node', () => { it('should delete file node', () => {
spyOn(alfrescoService, 'deleteNode').and.callThrough(); spyOn(documentListService, 'deleteNode').and.callThrough();
let file = new FileNode(); let file = new FileNode();
service.getHandler('delete')(file); 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', () => { it('should support deletion only file node', () => {
spyOn(alfrescoService, 'deleteNode').and.callThrough(); spyOn(documentListService, 'deleteNode').and.callThrough();
let folder = new FolderNode(); let folder = new FolderNode();
service.getHandler('delete')(folder); service.getHandler('delete')(folder);
expect(alfrescoService.deleteNode).not.toHaveBeenCalled(); expect(documentListService.deleteNode).not.toHaveBeenCalled();
let file = new FileNode(); let file = new FileNode();
service.getHandler('delete')(file); service.getHandler('delete')(file);
expect(alfrescoService.deleteNode).toHaveBeenCalled(); expect(documentListService.deleteNode).toHaveBeenCalled();
}); });
it('should require node id to delete', () => { it('should require node id to delete', () => {
spyOn(alfrescoService, 'deleteNode').and.callThrough(); spyOn(documentListService, 'deleteNode').and.callThrough();
let file = new FileNode(); let file = new FileNode();
file.entry.id = null; file.entry.id = null;
service.getHandler('delete')(file); service.getHandler('delete')(file);
expect(alfrescoService.deleteNode).not.toHaveBeenCalled(); expect(documentListService.deleteNode).not.toHaveBeenCalled();
}); });
it('should reload target upon node deletion', () => { it('should reload target upon node deletion', () => {
spyOn(alfrescoService, 'deleteNode').and.callThrough(); spyOn(documentListService, 'deleteNode').and.callThrough();
let target = jasmine.createSpyObj('obj', ['reload']); let target = jasmine.createSpyObj('obj', ['reload']);
let file = new FileNode(); let file = new FileNode();
service.getHandler('delete')(file, target); service.getHandler('delete')(file, target);
expect(alfrescoService.deleteNode).toHaveBeenCalled(); expect(documentListService.deleteNode).toHaveBeenCalled();
expect(target.reload).toHaveBeenCalled(); expect(target.reload).toHaveBeenCalled();
}); });
}); });

View File

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

View File

@ -27,12 +27,11 @@ import {
AlfrescoContentService AlfrescoContentService
} from 'ng2-alfresco-core'; } from 'ng2-alfresco-core';
import { FileNode } from '../assets/document-library.model.mock'; import { FileNode } from '../assets/document-library.model.mock';
import { AlfrescoService } from './alfresco.service'; import { DocumentListService } from './document-list.service';
// TODO: rename to DocumentListService describe('DocumentListService', () => {
describe('AlfrescoService', () => {
let service: AlfrescoService; let service: DocumentListService;
let settingsService: AlfrescoSettingsService; let settingsService: AlfrescoSettingsService;
let authService: AlfrescoAuthenticationService; let authService: AlfrescoAuthenticationService;
let contentService: AlfrescoContentService; let contentService: AlfrescoContentService;
@ -42,7 +41,7 @@ describe('AlfrescoService', () => {
settingsService = new AlfrescoSettingsService(); settingsService = new AlfrescoSettingsService();
authService = new AlfrescoAuthenticationService(settingsService); authService = new AlfrescoAuthenticationService(settingsService);
contentService = new AlfrescoContentService(settingsService, authService); 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', () => { it('should require node to get thumbnail url', () => {
@ -50,7 +49,7 @@ describe('AlfrescoService', () => {
}); });
it('should require content service to get thumbnail url', () => { 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(); let file = new FileNode();
expect(service.getDocumentThumbnailUrl(file)).toBeNull(); expect(service.getDocumentThumbnailUrl(file)).toBeNull();
}); });
@ -72,9 +71,9 @@ describe('AlfrescoService', () => {
}); });
it('should resolve default icon for mime type', () => { it('should resolve default icon for mime type', () => {
expect(service.getMimeTypeIcon(null)).toBe(AlfrescoService.DEFAULT_MIME_TYPE_ICON); expect(service.getMimeTypeIcon(null)).toBe(DocumentListService.DEFAULT_MIME_TYPE_ICON);
expect(service.getMimeTypeIcon('')).toBe(AlfrescoService.DEFAULT_MIME_TYPE_ICON); expect(service.getMimeTypeIcon('')).toBe(DocumentListService.DEFAULT_MIME_TYPE_ICON);
expect(service.getMimeTypeIcon('missing/type')).toBe(AlfrescoService.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; declare let AlfrescoApi: any;
// TODO: consider renaming to something like 'DocumentListService'
/**
* Internal service used by Document List component.
*/
@Injectable() @Injectable()
export class AlfrescoService { export class DocumentListService {
static DEFAULT_MIME_TYPE_ICON: string = 'ft_ic_miscellaneous.svg'; static DEFAULT_MIME_TYPE_ICON: string = 'ft_ic_miscellaneous.svg';
@ -120,7 +116,7 @@ export class AlfrescoService {
getMimeTypeIcon(mimeType: string): string { getMimeTypeIcon(mimeType: string): string {
let icon = this.mimeTypeIcons[mimeType]; let icon = this.mimeTypeIcons[mimeType];
return icon || AlfrescoService.DEFAULT_MIME_TYPE_ICON; return icon || DocumentListService.DEFAULT_MIME_TYPE_ICON;
} }
private handleError(error: Response) { private handleError(error: Response) {

View File

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

View File

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