diff --git a/demo-shell-ng2/app/app.routes.ts b/demo-shell-ng2/app/app.routes.ts index 1f88ea31cb..139c70467b 100644 --- a/demo-shell-ng2/app/app.routes.ts +++ b/demo-shell-ng2/app/app.routes.ts @@ -54,6 +54,11 @@ export const appRoutes: Routes = [ component: FilesComponent, canActivate: [AuthGuardEcm] }, + { + path: 'files/:id', + component: FilesComponent, + canActivate: [AuthGuardEcm] + }, { path: 'datatable', component: DataTableDemoComponent, diff --git a/demo-shell-ng2/app/components/files/files.component.html b/demo-shell-ng2/app/components/files/files.component.html index bbfc19e4e0..9e2f8a60d2 100644 --- a/demo-shell-ng2/app/components/files/files.component.html +++ b/demo-shell-ng2/app/components/files/files.component.html @@ -5,7 +5,7 @@ (onSuccess)="documentList.reload()"> + (pathChanged)="onBreadcrumbPathChanged($event)" *ngIf="!currentFolderId">
this.setupBpmActions(defs || []), - err => console.log(err) - ); - } else { - console.log('You are not logged in'); - } + if (this.route) { + this.route.params.forEach((params: Params) => { + this.currentFolderId = params.hasOwnProperty('id') ? params['id'] : null; + }); + } + if (this.auth.isBpmLoggedIn()) { + this.formService.getProcessDefinitions().subscribe( + defs => this.setupBpmActions(defs || []), + err => console.log(err) + ); + } else { + console.log('You are not logged in'); + } } viewActivitiForm(event?: any) { diff --git a/demo-shell-ng2/app/components/search/search-bar.component.html b/demo-shell-ng2/app/components/search/search-bar.component.html index 2533df52c2..7f66e7c78b 100644 --- a/demo-shell-ng2/app/components/search/search-bar.component.html +++ b/demo-shell-ng2/app/components/search/search-bar.component.html @@ -1,11 +1,10 @@ + (fileSelect)="onItemClicked($event)">

Search results

- + diff --git a/demo-shell-ng2/app/components/search/search.component.ts b/demo-shell-ng2/app/components/search/search.component.ts index ae8f64d019..6351a0b5b2 100644 --- a/demo-shell-ng2/app/components/search/search.component.ts +++ b/demo-shell-ng2/app/components/search/search.component.ts @@ -16,6 +16,8 @@ */ import { Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { MinimalNodeEntity } from 'alfresco-js-api'; @Component({ selector: 'search-component', @@ -48,10 +50,15 @@ export class SearchComponent { fileShowed: boolean = false; fileNodeId: string; - onFileClicked(event) { - if (event.value.entry.isFile) { - this.fileNodeId = event.value.entry.id; + constructor(public router: Router) { + } + + onNavigateItem(event: MinimalNodeEntity) { + if (event.entry.isFile) { + this.fileNodeId = event.entry.id; this.fileShowed = true; + } else if (event.entry.isFolder) { + this.router.navigate(['/files', event.entry.id]); } } } diff --git a/ng2-components/ng2-alfresco-documentlist/README.md b/ng2-components/ng2-alfresco-documentlist/README.md index 2aaf3da951..8df6a013e4 100644 --- a/ng2-components/ng2-alfresco-documentlist/README.md +++ b/ng2-components/ng2-alfresco-documentlist/README.md @@ -168,7 +168,9 @@ platformBrowserDynamic().bootstrapModule(AppModule); | Name | Type | Default | Description | | --- | --- | --- | --- | -| `rootPath` | string | -root- | Root node path, i.e. `-root-`, `-shared-`, `-my-`, etc. | +| `rootFolderId` | string | -root- | Root node ID, i.e. `-root-`, `-shared-`, `-my-`, etc. or a fixed node ID | +| `currentFolderPath` | string | null | Initial path of displayed folder below the root node, e.g. "/Sites/swsdp/documentLibrary" | +| `currentFolderId` | string | null | Initial node ID of displayed folder, if given | | `navigate` | boolean | true | Toggles navigation to folder content or file preview | | `navigationMode` | string (click\|dblclick) | dblclick | User interaction for folder navigation or file preview | | `thumbnails` | boolean | false | Show document thumbnails rather than icons | diff --git a/ng2-components/ng2-alfresco-documentlist/src/components/breadcrumb/breadcrumb.component.spec.ts b/ng2-components/ng2-alfresco-documentlist/src/components/breadcrumb/breadcrumb.component.spec.ts index 19f7a77ac5..c0c2b61793 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/components/breadcrumb/breadcrumb.component.spec.ts +++ b/ng2-components/ng2-alfresco-documentlist/src/components/breadcrumb/breadcrumb.component.spec.ts @@ -87,7 +87,7 @@ describe('DocumentListBreadcrumb', () => { it('should update document list on click', (done) => { let documentList = new DocumentList(null, null, null); - spyOn(documentList, 'displayFolderContent').and.returnValue(Promise.resolve()); + spyOn(documentList, 'loadFolderByPath').and.returnValue(Promise.resolve()); let node = { name: 'name', path: '/path' }; component.target = documentList; diff --git a/ng2-components/ng2-alfresco-documentlist/src/components/breadcrumb/breadcrumb.component.ts b/ng2-components/ng2-alfresco-documentlist/src/components/breadcrumb/breadcrumb.component.ts index 441073d3dc..20984867eb 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/components/breadcrumb/breadcrumb.component.ts +++ b/ng2-components/ng2-alfresco-documentlist/src/components/breadcrumb/breadcrumb.component.ts @@ -83,8 +83,11 @@ export class DocumentListBreadcrumb { } }); + this.currentFolderPath = route.path; + if (this.target) { this.target.currentFolderPath = route.path; + this.target.loadFolder(); } } } diff --git a/ng2-components/ng2-alfresco-documentlist/src/components/document-list.spec.ts b/ng2-components/ng2-alfresco-documentlist/src/components/document-list.spec.ts index 186f678dd8..4bbc6150d8 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/components/document-list.spec.ts +++ b/ng2-components/ng2-alfresco-documentlist/src/components/document-list.spec.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { NgZone, TemplateRef } from '@angular/core'; +import { NgZone, SimpleChange, TemplateRef } from '@angular/core'; import { DataTableComponent, DataColumn, DataRowEvent } from 'ng2-alfresco-datatable'; import { DocumentList } from './document-list'; import { DocumentListServiceMock } from './../assets/document-list.service.mock'; @@ -48,12 +48,12 @@ describe('DocumentList', () => { window['componentHandler'] = componentHandler; }); - it('should update root path', () => { + it('should update root folder ID', () => { let adapter = documentList.data; - expect(adapter.rootPath).toBe(adapter.DEFAULT_ROOT_PATH); + expect(adapter.rootFolderId).toBe(adapter.DEFAULT_ROOT_ID); - documentList.rootPath = '-shared-'; - expect(adapter.rootPath).toBe('-shared-'); + documentList.rootFolderId = '-shared-'; + expect(adapter.rootFolderId).toBe('-shared-'); }); it('should setup default columns', () => { @@ -163,7 +163,7 @@ describe('DocumentList', () => { let node = new FolderNode(''); spyOn(documentList, 'getNodePath').and.returnValue(path); - spyOn(documentList, 'displayFolderContent').and.stub(); + spyOn(documentList, 'loadFolderByPath').and.returnValue(Promise.resolve(true)); documentList.navigationMode = DocumentList.SINGLE_CLICK_NAVIGATION; documentList.onNodeClick(node); @@ -173,31 +173,31 @@ describe('DocumentList', () => { it('should not display folder content when no target node provided', () => { expect(documentList.navigate).toBe(true); - spyOn(documentList, 'displayFolderContent').and.stub(); + spyOn(documentList, 'loadFolderByPath').and.stub(); documentList.onNodeClick(null); - expect(documentList.displayFolderContent).not.toHaveBeenCalled(); + expect(documentList.loadFolderByPath).not.toHaveBeenCalled(); }); it('should display folder content only on folder node click', () => { expect(documentList.navigate).toBe(true); - spyOn(documentList, 'displayFolderContent').and.stub(); + spyOn(documentList, 'loadFolderByPath').and.stub(); let node = new FileNode(); documentList.onNodeClick(node); - expect(documentList.displayFolderContent).not.toHaveBeenCalled(); + expect(documentList.loadFolderByPath).not.toHaveBeenCalled(); }); it('should not display folder content on click when navigation is off', () => { - spyOn(documentList, 'displayFolderContent').and.stub(); + spyOn(documentList, 'loadFolderByPath').and.stub(); let node = new FolderNode(''); documentList.navigate = false; documentList.onNodeClick(node); - expect(documentList.displayFolderContent).not.toHaveBeenCalled(); + expect(documentList.loadFolderByPath).not.toHaveBeenCalled(); }); it('should require node to get path', () => { @@ -205,31 +205,35 @@ describe('DocumentList', () => { }); it('should display folder content for new folder path', () => { - spyOn(documentList, 'displayFolderContent').and.returnValue(Promise.resolve()); + spyOn(documentList, 'loadFolderByPath').and.returnValue(Promise.resolve()); let newPath = '/some/new/path'; documentList.currentFolderPath = newPath; - expect(documentList.displayFolderContent).toHaveBeenCalledWith(newPath); + documentList.ngOnChanges({currentFolderPath: new SimpleChange(null, newPath)}); + expect(documentList.loadFolderByPath).toHaveBeenCalledWith(newPath); }); it('should reset to default path', () => { - spyOn(documentList, 'displayFolderContent').and.returnValue(Promise.resolve()); + spyOn(documentList, 'loadFolderByPath').and.returnValue(Promise.resolve()); documentList.currentFolderPath = null; + documentList.ngOnChanges({currentFolderPath: new SimpleChange('', null)}); - expect(documentList.currentFolderPath).toBe(documentList.DEFAULT_ROOT_FOLDER); - expect(documentList.displayFolderContent).toHaveBeenCalledWith(documentList.DEFAULT_ROOT_FOLDER); + expect(documentList.currentFolderPath).toBe(documentList.DEFAULT_FOLDER_PATH); + expect(documentList.loadFolderByPath).toHaveBeenCalledWith(documentList.DEFAULT_FOLDER_PATH); }); it('should emit folder changed event', (done) => { - spyOn(documentList, 'displayFolderContent').and.returnValue(Promise.resolve()); + spyOn(documentList, 'loadFolderByPath').and.returnValue(Promise.resolve()); documentList.folderChange.subscribe(e => { done(); }); - documentList.currentFolderPath = '/some/new/path'; + let newPath = '/some/new/path'; + documentList.currentFolderPath = newPath; + documentList.ngOnChanges({currentFolderPath: new SimpleChange(null, newPath)}); }); it('should emit folder changed event with folder details', (done) => { - spyOn(documentList, 'displayFolderContent').and.returnValue(Promise.resolve()); + spyOn(documentList, 'loadFolderByPath').and.returnValue(Promise.resolve()); let path = '/path'; @@ -239,6 +243,7 @@ describe('DocumentList', () => { }); documentList.currentFolderPath = path; + documentList.ngOnChanges({currentFolderPath: new SimpleChange(null, path)}); }); it('should execute context action on callback', () => { @@ -259,7 +264,7 @@ describe('DocumentList', () => { }); it('should subscribe to context action handler', () => { - spyOn(documentList, 'displayFolderContent').and.stub(); + spyOn(documentList, 'loadFolderByPath').and.returnValue(Promise.resolve(true)); spyOn(documentList, 'contextActionCallback').and.stub(); let value = {}; documentList.ngOnInit(); @@ -384,16 +389,16 @@ describe('DocumentList', () => { }); it('should display folder content on reload', () => { - spyOn(documentList, 'displayFolderContent').and.callThrough(); + spyOn(documentList, 'loadFolderByPath').and.callThrough(); documentList.reload(); - expect(documentList.displayFolderContent).toHaveBeenCalled(); + expect(documentList.loadFolderByPath).toHaveBeenCalled(); }); it('should require path to display folder content', () => { spyOn(documentListService, 'getFolder').and.callThrough(); - documentList.displayFolderContent(null); - documentList.displayFolderContent(''); + documentList.loadFolderByPath(null); + documentList.loadFolderByPath(''); expect(documentListService.getFolder).not.toHaveBeenCalled(); }); @@ -464,11 +469,11 @@ describe('DocumentList', () => { }); expect(documentList.currentFolderPath).toBeNull(); - spyOn(documentList, 'displayFolderContent').and.stub(); + spyOn(documentList, 'loadFolderByPath').and.stub(); documentList.reload(); - expect(documentList.displayFolderContent).not.toHaveBeenCalled(); + expect(documentList.loadFolderByPath).not.toHaveBeenCalled(); }); it('should enforce single-click on mobile browser', () => { @@ -482,9 +487,10 @@ describe('DocumentList', () => { it('should emit error on wrong path', (done) => { let raised = false; documentList.error.subscribe(err => raised = true); - spyOn(documentList, 'displayFolderContent').and.returnValue(Promise.reject(false)); + spyOn(documentList, 'loadFolderByPath').and.returnValue(Promise.reject(false)); documentList.currentFolderPath = 'wrong-path'; + documentList.ngOnChanges({currentFolderPath: new SimpleChange(null, documentList.currentFolderPath)}); setTimeout(() => { expect(raised).toBeTruthy(); done(); @@ -506,24 +512,24 @@ describe('DocumentList', () => { expect(documentList.isEmptyTemplateDefined()).toBeFalsy(); }); - it('should set root path for underlying adapter', () => { - documentList.rootPath = 'test'; - expect(documentList.data.rootPath).toBe('test'); + it('should set root folder ID for underlying adapter', () => { + documentList.rootFolderId = 'test'; + expect(documentList.data.rootFolderId).toBe('test'); }); - it('should set default root path for underlying adapter', () => { - documentList.rootPath = null; - expect(documentList.data.rootPath).toBe(documentList.data.DEFAULT_ROOT_PATH); + it('should set default root folder ID for underlying adapter', () => { + documentList.rootFolderId = null; + expect(documentList.data.rootFolderId).toBe(documentList.data.DEFAULT_ROOT_ID); }); - it('should fetch root path from underlying adapter', () => { - documentList.data.rootPath = 'test'; - expect(documentList.rootPath).toBe('test'); + it('should fetch root folder ID from underlying adapter', () => { + documentList.data.rootFolderId = 'test'; + expect(documentList.rootFolderId).toBe('test'); }); - it('should not fetch root path when adapter missing', () => { + it('should not fetch root folder ID when adapter missing', () => { documentList.data = null; - expect(documentList.rootPath).toBeNull(); + expect(documentList.rootFolderId).toBeNull(); }); it('should set row filter for underlying adapter', () => { @@ -561,4 +567,49 @@ describe('DocumentList', () => { documentList.onRowDblClick(event); expect(documentList.onNodeDblClick).toHaveBeenCalledWith(node); }); + + describe('navigate by folder ID', () => { + + it('should load folder by ID on init', () => { + + documentList.currentFolderId = '1d26e465-dea3-42f3-b415-faa8364b9692'; + + let loadbyIdSpy: jasmine.Spy = spyOn(documentList.data, 'loadById').and.returnValue(Promise.resolve()); + + documentList.ngOnInit(); + expect(loadbyIdSpy).toHaveBeenCalled(); + expect(documentList.currentFolderPath).toBe('/'); + }); + + it('should load folder by ID on changes', () => { + + let newNodeId = '1d26e465-dea3-42f3-b415-faa8364b9692'; + + documentList.ngOnChanges({currentFolderId: new SimpleChange(null, newNodeId)}); + + let loadbyPathSpy: jasmine.Spy = spyOn(documentList.data, 'loadPath').and.returnValue(Promise.resolve()); + + documentList.ngOnInit(); + expect(loadbyPathSpy).toHaveBeenCalled(); + expect(documentList.currentFolderPath).toBe('/'); + }); + + }); + + describe('configure root folder', () => { + + it('should re-load folder when rootFolderId changed', () => { + + let newRootFolder = '-new-'; + + documentList.ngOnChanges({rootFolderId: new SimpleChange(null, newRootFolder)}); + + let loadbyPathSpy: jasmine.Spy = spyOn(documentList.data, 'loadPath').and.returnValue(Promise.resolve()); + + documentList.ngOnInit(); + expect(loadbyPathSpy).toHaveBeenCalled(); + expect(documentList.currentFolderPath).toBe('/'); + }); + + }); }); diff --git a/ng2-components/ng2-alfresco-documentlist/src/components/document-list.ts b/ng2-components/ng2-alfresco-documentlist/src/components/document-list.ts index ffdbe44cd0..11e969203a 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/components/document-list.ts +++ b/ng2-components/ng2-alfresco-documentlist/src/components/document-list.ts @@ -19,7 +19,9 @@ import { Component, OnInit, Input, + OnChanges, Output, + SimpleChanges, EventEmitter, AfterContentInit, TemplateRef, @@ -48,22 +50,25 @@ declare var module: any; styleUrls: ['./document-list.css'], templateUrl: './document-list.html' }) -export class DocumentList implements OnInit, AfterContentInit { +export class DocumentList implements OnInit, OnChanges, AfterContentInit { static SINGLE_CLICK_NAVIGATION: string = 'click'; static DOUBLE_CLICK_NAVIGATION: string = 'dblclick'; static DEFAULT_PAGE_SIZE: number = 20; - DEFAULT_ROOT_FOLDER: string = '/'; + DEFAULT_FOLDER_PATH: string = '/'; @Input() - set rootPath(value: string) { - this.data.rootPath = value || this.data.DEFAULT_ROOT_PATH; + set rootFolderId(value: string) { + this.data.rootFolderId = value || this.data.DEFAULT_ROOT_ID; } - get rootPath(): string { + @Input() + currentFolderId: string = null; + + get rootFolderId(): string { if (this.data) { - return this.data.rootPath; + return this.data.rootFolderId; } return null; } @@ -127,21 +132,11 @@ export class DocumentList implements OnInit, AfterContentInit { @ViewChild(DataTableComponent) dataTable: DataTableComponent; - private _path = this.DEFAULT_ROOT_FOLDER; + private _path = this.DEFAULT_FOLDER_PATH; @Input() set currentFolderPath(value: string) { - if (value !== this._path) { - const path = value || this.DEFAULT_ROOT_FOLDER; - this.displayFolderContent(path) - .then(() => { - this._path = path; - this.folderChange.emit({ path: path }); - }) - .catch(err => { - this.error.emit(err); - }); - } + this._path = value; } get currentFolderPath(): string { @@ -220,6 +215,8 @@ export class DocumentList implements OnInit, AfterContentInit { if (this.isMobile()) { this.navigationMode = DocumentList.SINGLE_CLICK_NAVIGATION; } + + this.loadFolder(); } ngAfterContentInit() { @@ -229,6 +226,40 @@ export class DocumentList implements OnInit, AfterContentInit { } } + ngOnChanges(changes: SimpleChanges) { + if (changes['currentFolderId'] && changes['currentFolderId'].currentValue) { + let folderId = changes['currentFolderId'].currentValue; + this.loadFolderById(folderId) + .then(() => { + this._path = null; + }) + .catch(err => { + this.error.emit(err); + }); + } else if (changes['currentFolderPath']) { + const path = changes['currentFolderPath'].currentValue || this.DEFAULT_FOLDER_PATH; + this.currentFolderPath = path; + this.loadFolderByPath(path) + .then(() => { + this._path = path; + this.folderChange.emit({ path: path }); + }) + .catch(err => { + this.error.emit(err); + }); + } else if (changes['rootFolderId']) { + // this.currentFolderPath = this.DEFAULT_FOLDER_PATH; + this.loadFolderByPath(this.currentFolderPath) + .then(() => { + this._path = this.currentFolderPath; + this.folderChange.emit({ path: this.currentFolderPath }); + }) + .catch(err => { + this.error.emit(err); + }); + } + } + isEmptyTemplateDefined() { if (this.dataTable) { if (this.emptyFolderTemplate) { @@ -277,6 +308,9 @@ export class DocumentList implements OnInit, AfterContentInit { performNavigation(node: MinimalNodeEntity): boolean { if (node && node.entry && node.entry.isFolder) { this.currentFolderPath = this.getNodePath(node); + this.currentFolderId = null; + this.loadFolder(); + this.folderChange.emit({ path: this.currentFolderPath }); return true; } return false; @@ -293,21 +327,34 @@ export class DocumentList implements OnInit, AfterContentInit { } } - displayFolderContent(path: string): Promise { + loadFolderByPath(path: string): Promise { return this.data.loadPath(path); } + loadFolderById(id: string): Promise { + return this.data.loadById(id); + } + reload() { this.ngZone.run(() => { - if (this.currentFolderPath) { - this.displayFolderContent(this.currentFolderPath) - .catch(err => { - this.error.emit(err); - }); - } + this.loadFolder(); }); } + public loadFolder() { + if (this.currentFolderId) { + this.loadFolderById(this.currentFolderId) + .catch(err => { + this.error.emit(err); + }); + } else if (this.currentFolderPath) { + this.loadFolderByPath(this.currentFolderPath) + .catch(err => { + this.error.emit(err); + }); + } + } + /** * Gets a path for a given node. * @param node diff --git a/ng2-components/ng2-alfresco-documentlist/src/data/share-datatable-adapter.ts b/ng2-components/ng2-alfresco-documentlist/src/data/share-datatable-adapter.ts index 04b3e73205..4eda393c4b 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/data/share-datatable-adapter.ts +++ b/ng2-components/ng2-alfresco-documentlist/src/data/share-datatable-adapter.ts @@ -31,7 +31,7 @@ export class ShareDataTableAdapter implements DataTableAdapter, PaginationProvid ERR_ROW_NOT_FOUND: string = 'Row not found'; ERR_COL_NOT_FOUND: string = 'Column not found'; - DEFAULT_ROOT_PATH: string = '-root-'; + DEFAULT_ROOT_ID: string = '-root-'; DEFAULT_DATE_FORMAT: string = 'medium'; DEFAULT_PAGE_SIZE: number = 20; MIN_PAGE_SIZE: number = 5; @@ -53,7 +53,7 @@ export class ShareDataTableAdapter implements DataTableAdapter, PaginationProvid thumbnails: boolean = false; dataLoaded: DataLoadedEventEmitter; - rootPath: string = this.DEFAULT_ROOT_PATH; + rootFolderId: string = this.DEFAULT_ROOT_ID; constructor(private documentListService: DocumentListService, private basePath: string, @@ -210,7 +210,7 @@ export class ShareDataTableAdapter implements DataTableAdapter, PaginationProvid .getFolder(path, { maxItems: this._maxItems, skipCount: this._skipCount, - rootPath: this.rootPath + rootFolderId: this.rootFolderId }) .subscribe(val => { this.currentPath = path; @@ -228,6 +228,30 @@ export class ShareDataTableAdapter implements DataTableAdapter, PaginationProvid } + loadById(id: string): Promise { + return new Promise((resolve, reject) => { + if (id && this.documentListService) { + this.documentListService + .getFolder(null, { + maxItems: this._maxItems, + skipCount: this._skipCount, + rootFolderId: id + }) + .subscribe(val => { + this.loadPage(val); + this.dataLoaded.emit(null); + resolve(true); + }, + error => { + reject(error); + }); + } else { + resolve(false); + } + }); + + } + setFilter(filter: RowFilter) { this.filter = filter; diff --git a/ng2-components/ng2-alfresco-documentlist/src/services/document-list.service.ts b/ng2-components/ng2-alfresco-documentlist/src/services/document-list.service.ts index 93dc0f2e84..2d51adad1c 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/services/document-list.service.ts +++ b/ng2-components/ng2-alfresco-documentlist/src/services/document-list.service.ts @@ -30,6 +30,8 @@ export class DocumentListService { static DEFAULT_MIME_TYPE_ICON: string = 'ft_ic_miscellaneous.svg'; + static ROOT_ID = '-root-'; + mimeTypeIcons: any = { 'image/png': 'ft_ic_raster_image.svg', 'image/jpeg': 'ft_ic_raster_image.svg', @@ -63,17 +65,21 @@ export class DocumentListService { } private getNodesPromise(folder: string, opts?: any): Promise { - let rootPath = '-root-'; - if (opts && opts.rootPath) { - rootPath = opts.rootPath; + let rootNodeId = DocumentListService.ROOT_ID; + if (opts && opts.rootFolderId) { + rootNodeId = opts.rootFolderId; } let params: any = { - relativePath: folder, + includeSource: true, include: ['path', 'properties'] }; + if (folder) { + params.relativePath = folder; + } + if (opts) { if (opts.maxItems) { params.maxItems = opts.maxItems; @@ -83,7 +89,7 @@ export class DocumentListService { } } - return this.apiService.getInstance().nodes.getNodeChildren(rootPath, params); + return this.apiService.getInstance().nodes.getNodeChildren(rootNodeId, params); } deleteNode(nodeId: string): Observable { @@ -105,7 +111,7 @@ export class DocumentListService { } /** - * Gets the folder node with the content. + * Gets the folder node with the specified relative name path below the root node. * @param folder Path to folder. * @param opts Options. * @returns {Observable} Folder entity. diff --git a/ng2-components/ng2-alfresco-search/README.md b/ng2-components/ng2-alfresco-search/README.md index 4092139241..c19bc899eb 100644 --- a/ng2-components/ng2-alfresco-search/README.md +++ b/ng2-components/ng2-alfresco-search/README.md @@ -225,7 +225,7 @@ platformBrowserDynamic().bootstrapModule(AppModule); | Name | Description | | --- | --- | -| `preview` | Emitted when a file result is clicked/selected | +| `navigate` | Emitted when a search result is clicked or double-clicked | | `resultsLoad` | Emitted when search results have fully loaded | #### Options @@ -237,6 +237,7 @@ platformBrowserDynamic().bootstrapModule(AppModule); | `resultType` | {boolean} | (optional) | (none) | Node type to filter search results by, e.g. 'cm:content'. | | `maxResults` | {boolean} | (optional) | 20 | Maximum number of results to show in the search. | | `resultSort` | {boolean} | (optional) | (none) | Criteria to sort search results by, must be one of "name" , "modifiedAt" or "createdAt" | +| `navigationMode` | {string} | (optional) | "dblclick" | Event used to initiate a navigation action to a specific result, one of "click" or "dblclick" | ### Build from sources diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts index 686a79fe3f..736f6ab511 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts @@ -214,7 +214,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { searchService = fixture.debugElement.injector.get(AlfrescoSearchService); }); - it('should emit file select when file item clicked', (done) => { + it('should emit fileSelect event when file item clicked', (done) => { spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.resolve(result)); @@ -231,7 +231,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { }); }); - it('should not emit preview if a non-file item is clicked', (done) => { + it('should emit fileSelect event if when folder item clicked', (done) => { spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.resolve(folderResult)); @@ -240,7 +240,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { component.resultsLoad.subscribe(() => { fixture.detectChanges(); ( element.querySelector('#result_row_0')).click(); - expect(component.fileSelect.emit).not.toHaveBeenCalled(); + expect(component.fileSelect.emit).toHaveBeenCalled(); done(); }); diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts index 94d156879d..bda5221ece 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts @@ -19,6 +19,7 @@ import { Component, ElementRef, EventEmitter, Input, OnInit, OnChanges, Output, import { AlfrescoSearchService, SearchOptions } from './../services/alfresco-search.service'; import { AlfrescoThumbnailService } from './../services/alfresco-thumbnail.service'; import { AlfrescoTranslationService } from 'ng2-alfresco-core'; +import { MinimalNodeEntity } from 'alfresco-js-api'; @Component({ moduleId: module.id, @@ -121,10 +122,12 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { * @param node Node to get URL for. * @returns {string} URL address. */ - getMimeTypeIcon(node: any): string { + getMimeTypeIcon(node: MinimalNodeEntity): string { if (node.entry.content && node.entry.content.mimeType) { let icon = this.alfrescoThumbnailService.getMimeTypeIcon(node.entry.content.mimeType); return this.resolveIconPath(icon); + } else if (node.entry.isFolder) { + return 'ft_ic_folder.svg'; } } @@ -148,7 +151,7 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { * @param node Node to get URL for. * @returns {string} URL address. */ - getMimeTypeKey(node: any): string { + getMimeTypeKey(node: MinimalNodeEntity): string { if (node.entry.content && node.entry.content.mimeType) { return 'SEARCH.ICONS.' + this.alfrescoThumbnailService.getMimeTypeKey(node.entry.content.mimeType); } else { @@ -161,13 +164,9 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { firstResult.focus(); } - onItemClick(node): void { + onItemClick(node: MinimalNodeEntity): void { if (node && node.entry) { - if (node.entry.isFile) { - this.fileSelect.emit({ - value: node - }); - } + this.fileSelect.emit(node); } } @@ -179,12 +178,10 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { this.searchFocus.emit($event); } - onRowEnter(node): void { + onRowEnter(node: MinimalNodeEntity): void { if (node && node.entry) { if (node.entry.isFile) { - this.fileSelect.emit({ - value: node - }); + this.fileSelect.emit(node); } } } diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts index c858990197..5dd0de6e0e 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts @@ -72,7 +72,7 @@ export class AlfrescoSearchControlComponent implements OnInit, OnDestroy { liveSearchRoot: string = '-root-'; @Input() - liveSearchResultType: string = 'cm:content'; + liveSearchResultType: string = null; @Input() liveSearchResultSort: string = null; @@ -171,9 +171,7 @@ export class AlfrescoSearchControlComponent implements OnInit, OnDestroy { } onFileClicked(event): void { - this.fileSelect.emit({ - value: event.value - }); + this.fileSelect.emit(event); } onSearchFocus($event): void { diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.html b/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.html index 75cfea1fb4..4b023fbfbe 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.html +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.html @@ -16,7 +16,7 @@ - + {{getMimeTypeKey(result)|translate}} {{result.entry.name}} diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.spec.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.spec.ts index 48c7ddfb24..039abc5865 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.spec.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.spec.ts @@ -15,7 +15,8 @@ * limitations under the License. */ -import { ReflectiveInjector, SimpleChange } from '@angular/core'; +import { DebugElement, ReflectiveInjector, SimpleChange } from '@angular/core'; +import { By } from '@angular/platform-browser'; import { ComponentFixture, TestBed, async } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs/Rx'; @@ -256,44 +257,91 @@ describe('AlfrescoSearchComponent', () => { }); }); - describe('search result actions', () => { + describe('search result interactions', () => { - it('should emit preview when file item clicked', (done) => { + let debugElement: DebugElement; + let searchService: AlfrescoSearchService; + let querySpy: jasmine.Spy; + let emitSpy: jasmine.Spy; + const rowSelector = '[data-automation-id="search_result_table"] tbody tr'; - let searchService = fixture.debugElement.injector.get(AlfrescoSearchService); - spyOn(searchService, 'getQueryNodesPromise') - .and.returnValue(Promise.resolve(result)); + beforeEach(() => { + debugElement = fixture.debugElement; + searchService = fixture.debugElement.injector.get(AlfrescoSearchService); + querySpy = spyOn(searchService, 'getQueryNodesPromise').and.returnValue(Promise.resolve(result)); + emitSpy = spyOn(component.navigate, 'emit'); + }); - component.resultsLoad.subscribe(() => { - fixture.detectChanges(); - ( element.querySelector('#result_row_0')).click(); + describe('click results', () => { + + beforeEach(() => { + component.navigationMode = AlfrescoSearchComponent.SINGLE_CLICK_NAVIGATION; }); - component.searchTerm = 'searchTerm'; - component.ngOnInit(); + it('should emit navigation event when file item clicked', (done) => { - component.preview.subscribe(() => { - done(); + component.resultsLoad.subscribe(() => { + fixture.detectChanges(); + debugElement.query(By.css(rowSelector)).triggerEventHandler('click', {}); + expect(emitSpy).toHaveBeenCalled(); + done(); + }); + + component.searchTerm = 'searchTerm'; + component.ngOnInit(); + }); + + it('should emit navigation event when non-file item is clicked', (done) => { + + querySpy.and.returnValue(Promise.resolve(folderResult)); + + component.resultsLoad.subscribe(() => { + fixture.detectChanges(); + debugElement.query(By.css(rowSelector)).triggerEventHandler('click', {}); + expect(emitSpy).toHaveBeenCalled(); + done(); + }); + + component.searchTerm = 'searchTerm'; + component.ngOnInit(); }); }); - it('should not emit preview when non-file item is clicked', (done) => { + describe('double click results', () => { - let searchService = fixture.debugElement.injector.get(AlfrescoSearchService); - spyOn(searchService, 'getQueryNodesPromise') - .and.returnValue(Promise.resolve(folderResult)); - - spyOn(component.preview, 'emit'); - component.resultsLoad.subscribe(() => { - fixture.detectChanges(); - ( element.querySelector('#result_row_0')).click(); - expect(component.preview.emit).not.toHaveBeenCalled(); - done(); + beforeEach(() => { + component.navigationMode = AlfrescoSearchComponent.DOUBLE_CLICK_NAVIGATION; }); - component.searchTerm = 'searchTerm'; - component.ngOnInit(); + it('should emit navigation event when file item clicked', (done) => { + + component.resultsLoad.subscribe(() => { + fixture.detectChanges(); + debugElement.query(By.css(rowSelector)).triggerEventHandler('dblclick', {}); + expect(emitSpy).toHaveBeenCalled(); + done(); + }); + + component.searchTerm = 'searchTerm'; + component.ngOnInit(); + }); + + it('should emit navigation event when non-file item is clicked', (done) => { + + querySpy.and.returnValue(Promise.resolve(folderResult)); + + component.resultsLoad.subscribe(() => { + fixture.detectChanges(); + debugElement.query(By.css(rowSelector)).triggerEventHandler('dblclick', {}); + expect(emitSpy).toHaveBeenCalled(); + done(); + }); + + component.searchTerm = 'searchTerm'; + component.ngOnInit(); + }); }); + }); }); diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.ts index 7acfc5d29e..53db492bfd 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.ts @@ -20,6 +20,7 @@ import { ActivatedRoute, Params } from '@angular/router'; import { AlfrescoSearchService, SearchOptions } from './../services/alfresco-search.service'; import { AlfrescoThumbnailService } from './../services/alfresco-thumbnail.service'; import { AlfrescoTranslationService } from 'ng2-alfresco-core'; +import { MinimalNodeEntity } from 'alfresco-js-api'; @Component({ moduleId: module.id, @@ -29,6 +30,9 @@ import { AlfrescoTranslationService } from 'ng2-alfresco-core'; }) export class AlfrescoSearchComponent implements OnChanges, OnInit { + static SINGLE_CLICK_NAVIGATION: string = 'click'; + static DOUBLE_CLICK_NAVIGATION: string = 'dblclick'; + @Input() searchTerm: string = ''; @@ -44,8 +48,11 @@ export class AlfrescoSearchComponent implements OnChanges, OnInit { @Input() resultType: string = null; + @Input() + navigationMode: string = AlfrescoSearchComponent.DOUBLE_CLICK_NAVIGATION; // click|dblclick + @Output() - preview: EventEmitter = new EventEmitter(); + navigate: EventEmitter = new EventEmitter(); @Output() resultsLoad = new EventEmitter(); @@ -92,6 +99,8 @@ export class AlfrescoSearchComponent implements OnChanges, OnInit { if (node.entry.content && node.entry.content.mimeType) { let icon = this._alfrescoThumbnailService.getMimeTypeIcon(node.entry.content.mimeType); return this.resolveIconPath(icon); + } else if (node.entry.isFolder) { + return 'ft_ic_folder.svg'; } } @@ -154,14 +163,17 @@ export class AlfrescoSearchComponent implements OnChanges, OnInit { } onItemClick(node, event?: Event): void { - if (event) { - event.preventDefault(); + if (this.navigate && this.navigationMode === AlfrescoSearchComponent.SINGLE_CLICK_NAVIGATION) { + if (node && node.entry) { + this.navigate.emit(node); + } } - if (node && node.entry) { - if (node.entry.isFile) { - this.preview.emit({ - value: node - }); + } + + onItemDblClick(node: MinimalNodeEntity) { + if (this.navigate && this.navigationMode === AlfrescoSearchComponent.DOUBLE_CLICK_NAVIGATION) { + if (node && node.entry) { + this.navigate.emit(node); } } }