mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-31 17:38:48 +00:00
@@ -0,0 +1,99 @@
|
||||
<header matDialogTitle
|
||||
class="adf-content-node-selector-title"
|
||||
data-automation-id="content-node-selector-title">{{title}}</header>
|
||||
|
||||
<section matDialogContent
|
||||
class="adf-content-node-selector-content"
|
||||
(node-select)="onNodeSelect($event)">
|
||||
|
||||
<mat-form-field floatPlaceholder="never" class="adf-content-node-selector-content-input">
|
||||
<input #searchInput
|
||||
matInput
|
||||
placeholder="Search"
|
||||
(input)="search(searchInput.value)"
|
||||
[value]="searchTerm"
|
||||
data-automation-id="content-node-selector-search-input">
|
||||
|
||||
<mat-icon *ngIf="searchTerm.length > 0"
|
||||
matSuffix (click)="clear()"
|
||||
class="adf-content-node-selector-content-input-icon"
|
||||
data-automation-id="content-node-selector-search-clear">clear</mat-icon>
|
||||
|
||||
<mat-icon *ngIf="searchTerm.length === 0"
|
||||
matSuffix
|
||||
class="adf-content-node-selector-content-input-icon"
|
||||
data-automation-id="content-node-selector-search-icon">search</mat-icon>
|
||||
|
||||
</mat-form-field>
|
||||
|
||||
<adf-sites-dropdown
|
||||
(change)="siteChanged($event)"
|
||||
data-automation-id="content-node-selector-sites-combo"></adf-sites-dropdown>
|
||||
|
||||
<adf-toolbar>
|
||||
<adf-toolbar-title>
|
||||
<adf-dropdown-breadcrumb *ngIf="needBreadcrumbs()"
|
||||
class="adf-content-node-selector-content-breadcrumb"
|
||||
[target]="documentList"
|
||||
[folderNode]="breadcrumbFolderNode"
|
||||
data-automation-id="content-node-selector-content-breadcrumb">
|
||||
</adf-dropdown-breadcrumb>
|
||||
</adf-toolbar-title>
|
||||
</adf-toolbar>
|
||||
|
||||
<div class="adf-content-node-selector-content-list" data-automation-id="content-node-selector-content-list">
|
||||
<adf-document-list
|
||||
#documentList
|
||||
adf-highlight
|
||||
adf-highlight-selector=".cell-value adf-datatable-cell .adf-datatable-cell-value"
|
||||
[node]="nodes"
|
||||
[rowFilter]="rowFilter"
|
||||
[imageResolver]="imageResolver"
|
||||
[currentFolderId]="folderIdToShow"
|
||||
selectionMode="single"
|
||||
[contextMenuActions]="false"
|
||||
[contentActions]="false"
|
||||
[allowDropFiles]="false"
|
||||
[enablePagination]="!showingSearchResults"
|
||||
paginationStrategy="{{paginationStrategy}}"
|
||||
[pageSize]="pageSize"
|
||||
(folderChange)="onFolderChange()"
|
||||
(ready)="onFolderLoaded()"
|
||||
data-automation-id="content-node-selector-document-list">
|
||||
<empty-folder-content>
|
||||
<ng-template>
|
||||
<div>{{ 'NODE_SELECTOR.NO_RESULTS' | translate }}</div>
|
||||
</ng-template>
|
||||
</empty-folder-content>
|
||||
</adf-document-list>
|
||||
|
||||
<adf-infinite-pagination
|
||||
*ngIf="showingSearchResults && isSearchTermLongEnough()"
|
||||
[pagination]="pagination"
|
||||
[pageSize]="pageSize"
|
||||
[loading]="loadingSearchResults"
|
||||
(loadMore)="getNextPageOfSearch($event)"
|
||||
data-automation-id="content-node-selector-search-pagination">
|
||||
{{ 'ADF-DOCUMENT-LIST.LAYOUT.LOAD_MORE' | translate }}
|
||||
</adf-infinite-pagination>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<footer matDialogActions class="adf-content-node-selector-actions">
|
||||
|
||||
<button *ngIf="inDialog"
|
||||
mat-button
|
||||
class="adf-content-node-selector-actions-cancel"
|
||||
(click)="close()"
|
||||
data-automation-id="content-node-selector-actions-cancel">{{ 'NODE_SELECTOR.CANCEL' | translate }}
|
||||
</button>
|
||||
|
||||
<button mat-button
|
||||
[disabled]="!chosenNode"
|
||||
class="adf-content-node-selector-actions-choose"
|
||||
(click)="choose()"
|
||||
data-automation-id="content-node-selector-actions-choose">{{ 'NODE_SELECTOR.CHOOSE' | translate }}
|
||||
</button>
|
||||
|
||||
</footer>
|
@@ -0,0 +1,123 @@
|
||||
@mixin adf-content-node-selector-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
|
||||
.adf-content-node-selector-dialog {
|
||||
|
||||
.mat-dialog-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.adf-content-node-selector {
|
||||
&-title,
|
||||
&-content,
|
||||
&-actions {
|
||||
padding: 16px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&-title::first-letter {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&-content {
|
||||
padding-top: 0;
|
||||
|
||||
&-input {
|
||||
width: 100%;
|
||||
|
||||
&-icon {
|
||||
color: rgba(0, 0, 0, 0.38);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mat-input-underline .mat-input-ripple {
|
||||
height: 1px;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.adf-site-dropdown-list-element {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.mat-select-trigger {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.adf-toolbar .mat-toolbar {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&-list {
|
||||
height: 200px;
|
||||
overflow: auto;
|
||||
border: 1px solid rgba(0, 0, 0, 0.07);
|
||||
|
||||
.adf-highlight {
|
||||
color: mat-color($accent);;
|
||||
}
|
||||
|
||||
.adf-data-table {
|
||||
border: none;
|
||||
|
||||
.adf-no-content-container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.adf-data-table-cell {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
border-top: none;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
height: auto !important;
|
||||
|
||||
&:last-child {
|
||||
.adf-data-table-cell {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-actions {
|
||||
padding: 8px;
|
||||
background-color: rgb(250, 250, 250);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
color: rgb(121, 121, 121);
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
&-cancel {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
&-choose {
|
||||
font-weight: normal;
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,689 @@
|
||||
/*!
|
||||
* @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 { CUSTOM_ELEMENTS_SCHEMA, DebugElement, EventEmitter } from '@angular/core';
|
||||
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
import { ContentService, TranslationService, SearchService, SiteModel, SitesApiService } from '@alfresco/core';
|
||||
import { DataTableModule } from '@alfresco/core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { MaterialModule } from '../material.module';
|
||||
import { EmptyFolderContentDirective, DocumentListComponent, DocumentListService } from '../document-list';
|
||||
import { DropdownSitesComponent } from '../site-dropdown';
|
||||
import { DropdownBreadcrumbComponent } from '../breadcrumb';
|
||||
import { ContentNodeSelectorComponent } from './content-node-selector.component';
|
||||
import { ContentNodeSelectorService } from './content-node-selector.service';
|
||||
|
||||
const ONE_FOLDER_RESULT = {
|
||||
list: {
|
||||
entries: [
|
||||
{
|
||||
entry: {
|
||||
id: '123', name: 'MyFolder', isFile: false, isFolder: true,
|
||||
createdByUser: { displayName: 'John Doe' },
|
||||
modifiedByUser: { displayName: 'John Doe' }
|
||||
}
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
hasMoreItems: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe('ContentNodeSelectorComponent', () => {
|
||||
let component: ContentNodeSelectorComponent;
|
||||
let fixture: ComponentFixture<ContentNodeSelectorComponent>;
|
||||
let element: DebugElement;
|
||||
let data: any;
|
||||
let searchService: SearchService;
|
||||
let searchSpy: jasmine.Spy;
|
||||
|
||||
let _resolve: Function;
|
||||
let _reject: Function;
|
||||
|
||||
function typeToSearchBox(searchTerm = 'string-to-search') {
|
||||
let searchInput = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-input"]'));
|
||||
searchInput.nativeElement.value = searchTerm;
|
||||
searchInput.triggerEventHandler('input', {});
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
function respondWithSearchResults(result) {
|
||||
_resolve(result);
|
||||
}
|
||||
|
||||
function setupTestbed(plusProviders) {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
DataTableModule,
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
DocumentListComponent,
|
||||
EmptyFolderContentDirective,
|
||||
DropdownSitesComponent,
|
||||
DropdownBreadcrumbComponent,
|
||||
ContentNodeSelectorComponent
|
||||
],
|
||||
providers: [
|
||||
ContentService,
|
||||
SitesApiService,
|
||||
TranslationService,
|
||||
DocumentListService,
|
||||
SearchService,
|
||||
ContentNodeSelectorService,
|
||||
...plusProviders
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
describe('Dialog features', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
data = {
|
||||
title: 'Move along citizen...',
|
||||
select: new EventEmitter<MinimalNodeEntryEntity>(),
|
||||
rowFilter: () => {},
|
||||
imageResolver: () => 'piccolo',
|
||||
currentFolderId: 'cat-girl-nuku-nuku'
|
||||
};
|
||||
|
||||
setupTestbed([{ provide: MAT_DIALOG_DATA, useValue: data }]);
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ContentNodeSelectorComponent);
|
||||
element = fixture.debugElement;
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
describe('Data injecting with the "Material dialog way"', () => {
|
||||
|
||||
it('should show the INJECTED title', () => {
|
||||
const titleElement = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-title"]'));
|
||||
expect(titleElement).not.toBeNull();
|
||||
expect(titleElement.nativeElement.innerText).toBe('Move along citizen...');
|
||||
});
|
||||
|
||||
it('should pass through the injected currentFolderId to the documentlist', () => {
|
||||
let documentList = fixture.debugElement.query(By.directive(DocumentListComponent));
|
||||
expect(documentList).not.toBeNull('Document list should be shown');
|
||||
expect(documentList.componentInstance.currentFolderId).toBe('cat-girl-nuku-nuku');
|
||||
});
|
||||
|
||||
it('should pass through the injected rowFilter to the documentlist', () => {
|
||||
let documentList = fixture.debugElement.query(By.directive(DocumentListComponent));
|
||||
expect(documentList).not.toBeNull('Document list should be shown');
|
||||
expect(documentList.componentInstance.rowFilter).toBe(data.rowFilter);
|
||||
});
|
||||
|
||||
it('should pass through the injected imageResolver to the documentlist', () => {
|
||||
let documentList = fixture.debugElement.query(By.directive(DocumentListComponent));
|
||||
expect(documentList).not.toBeNull('Document list should be shown');
|
||||
expect(documentList.componentInstance.imageResolver).toBe(data.imageResolver);
|
||||
});
|
||||
|
||||
it('should trigger the INJECTED select event when selection has been made', (done) => {
|
||||
const expectedNode = <MinimalNodeEntryEntity> {};
|
||||
data.select.subscribe((nodes) => {
|
||||
expect(nodes.length).toBe(1);
|
||||
expect(nodes[0]).toBe(expectedNode);
|
||||
done();
|
||||
});
|
||||
|
||||
component.chosenNode = expectedNode;
|
||||
component.choose();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Cancel button', () => {
|
||||
|
||||
let dummyMdDialogRef;
|
||||
|
||||
beforeEach(() => {
|
||||
dummyMdDialogRef = <MatDialogRef<ContentNodeSelectorComponent>> { close: () => {} };
|
||||
});
|
||||
|
||||
it('should be shown if dialogRef is injected', () => {
|
||||
const componentInstance = new ContentNodeSelectorComponent(null, null, data, dummyMdDialogRef);
|
||||
expect(componentInstance.inDialog).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should should call the close method in the injected dialogRef', () => {
|
||||
spyOn(dummyMdDialogRef, 'close');
|
||||
const componentInstance = new ContentNodeSelectorComponent(null, null, data, dummyMdDialogRef);
|
||||
|
||||
componentInstance.close();
|
||||
|
||||
expect(dummyMdDialogRef.close).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('General component features', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
setupTestbed([]);
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ContentNodeSelectorComponent);
|
||||
element = fixture.debugElement;
|
||||
component = fixture.componentInstance;
|
||||
|
||||
searchService = TestBed.get(SearchService);
|
||||
searchSpy = spyOn(searchService, 'getQueryNodesPromise').and.callFake(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
_resolve = resolve;
|
||||
_reject = reject;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Parameters', () => {
|
||||
|
||||
it('should show the title', () => {
|
||||
component.title = 'Move along citizen...';
|
||||
fixture.detectChanges();
|
||||
|
||||
const titleElement = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-title"]'));
|
||||
expect(titleElement).not.toBeNull();
|
||||
expect(titleElement.nativeElement.innerText).toBe('Move along citizen...');
|
||||
});
|
||||
|
||||
it('should trigger the select event when selection has been made', (done) => {
|
||||
const expectedNode = <MinimalNodeEntryEntity> {};
|
||||
component.select.subscribe((nodes) => {
|
||||
expect(nodes.length).toBe(1);
|
||||
expect(nodes[0]).toBe(expectedNode);
|
||||
done();
|
||||
});
|
||||
|
||||
component.chosenNode = expectedNode;
|
||||
component.choose();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Breadcrumbs', () => {
|
||||
|
||||
let documentListService,
|
||||
sitesApiService,
|
||||
expectedDefaultFolderNode;
|
||||
|
||||
beforeEach(() => {
|
||||
expectedDefaultFolderNode = <MinimalNodeEntryEntity> { path: { elements: [] } };
|
||||
documentListService = TestBed.get(DocumentListService);
|
||||
sitesApiService = TestBed.get(SitesApiService);
|
||||
spyOn(documentListService, 'getFolderNode').and.returnValue(Promise.resolve(expectedDefaultFolderNode));
|
||||
spyOn(documentListService, 'getFolder').and.returnValue(Observable.throw('No results for test'));
|
||||
spyOn(sitesApiService, 'getSites').and.returnValue(Observable.of([]));
|
||||
spyOn(component.documentList, 'loadFolderNodesByFolderNodeId').and.returnValue(Promise.resolve());
|
||||
component.currentFolderId = 'cat-girl-nuku-nuku';
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the breadcrumb for the currentFolderId by default', (done) => {
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const breadcrumb = fixture.debugElement.query(By.directive(DropdownBreadcrumbComponent));
|
||||
expect(breadcrumb).not.toBeNull();
|
||||
expect(breadcrumb.componentInstance.folderNode).toBe(expectedDefaultFolderNode);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not show the breadcrumb if search was performed as last action', (done) => {
|
||||
typeToSearchBox();
|
||||
respondWithSearchResults(ONE_FOLDER_RESULT);
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const breadcrumb = fixture.debugElement.query(By.directive(DropdownBreadcrumbComponent));
|
||||
expect(breadcrumb).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show the breadcrumb again on folder navigation in the results list', (done) => {
|
||||
typeToSearchBox();
|
||||
respondWithSearchResults(ONE_FOLDER_RESULT);
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
component.onFolderChange();
|
||||
fixture.detectChanges();
|
||||
const breadcrumb = fixture.debugElement.query(By.directive(DropdownBreadcrumbComponent));
|
||||
expect(breadcrumb).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show the breadcrumb for the selected node when search results are displayed', (done) => {
|
||||
const alfrescoContentService = TestBed.get(ContentService);
|
||||
spyOn(alfrescoContentService, 'hasPermission').and.returnValue(true);
|
||||
|
||||
typeToSearchBox();
|
||||
respondWithSearchResults(ONE_FOLDER_RESULT);
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const chosenNode = <MinimalNodeEntryEntity> { path: { elements: [] } };
|
||||
component.onNodeSelect({ detail: { node: { entry: chosenNode} } });
|
||||
fixture.detectChanges();
|
||||
|
||||
const breadcrumb = fixture.debugElement.query(By.directive(DropdownBreadcrumbComponent));
|
||||
expect(breadcrumb).not.toBeNull();
|
||||
expect(breadcrumb.componentInstance.folderNode).toBe(chosenNode);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT show the breadcrumb for the selected node when not on search results list', (done) => {
|
||||
const alfrescoContentService = TestBed.get(ContentService);
|
||||
spyOn(alfrescoContentService, 'hasPermission').and.returnValue(true);
|
||||
|
||||
typeToSearchBox();
|
||||
respondWithSearchResults(ONE_FOLDER_RESULT);
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
component.onFolderChange();
|
||||
fixture.detectChanges();
|
||||
|
||||
const chosenNode = <MinimalNodeEntryEntity> { path: { elements: [] } };
|
||||
component.onNodeSelect({ detail: { node: { entry: chosenNode} } });
|
||||
fixture.detectChanges();
|
||||
|
||||
const breadcrumb = fixture.debugElement.query(By.directive(DropdownBreadcrumbComponent));
|
||||
expect(breadcrumb).not.toBeNull();
|
||||
expect(breadcrumb.componentInstance.folderNode).toBe(expectedDefaultFolderNode);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Search functionality', () => {
|
||||
|
||||
function defaultSearchOptions(rootNodeId = undefined, skipCount = 0) {
|
||||
return {
|
||||
include: ['path', 'allowableOperations'],
|
||||
skipCount,
|
||||
rootNodeId,
|
||||
nodeType: 'cm:folder',
|
||||
maxItems: 10,
|
||||
orderBy: null
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
const documentListService = TestBed.get(DocumentListService);
|
||||
const expectedDefaultFolderNode = <MinimalNodeEntryEntity> { path: { elements: [] } };
|
||||
|
||||
spyOn(documentListService, 'getFolderNode').and.returnValue(Promise.resolve(expectedDefaultFolderNode));
|
||||
spyOn(component.documentList, 'loadFolderNodesByFolderNodeId').and.returnValue(Promise.resolve());
|
||||
|
||||
component.currentFolderId = 'cat-girl-nuku-nuku';
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should load the results by calling the search api on search change', () => {
|
||||
typeToSearchBox('kakarot');
|
||||
|
||||
expect(searchSpy).toHaveBeenCalledWith('kakarot*', defaultSearchOptions());
|
||||
});
|
||||
|
||||
it('should reset the currently chosen node in case of starting a new search', () => {
|
||||
component.chosenNode = <MinimalNodeEntryEntity> {};
|
||||
typeToSearchBox('kakarot');
|
||||
|
||||
expect(component.chosenNode).toBeNull();
|
||||
});
|
||||
|
||||
it('should NOT call the search api if the searchTerm length is less than 4 characters', () => {
|
||||
typeToSearchBox('1');
|
||||
typeToSearchBox('12');
|
||||
typeToSearchBox('123');
|
||||
|
||||
expect(searchSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
xit('should debounce the search call by 500 ms', () => {
|
||||
|
||||
});
|
||||
|
||||
it('should call the search api on changing the site selectbox\'s value', () => {
|
||||
typeToSearchBox('vegeta');
|
||||
expect(searchSpy.calls.count()).toBe(1, 'Search count should be one after only one search');
|
||||
|
||||
component.siteChanged(<SiteModel> { guid: 'namek' });
|
||||
|
||||
expect(searchSpy.calls.count()).toBe(2, 'Search count should be two after the site change');
|
||||
expect(searchSpy.calls.argsFor(1)).toEqual(['vegeta*', defaultSearchOptions('namek') ]);
|
||||
});
|
||||
|
||||
it('should show the search icon by default without the X (clear) icon', () => {
|
||||
fixture.detectChanges();
|
||||
let searchIcon = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-icon"]'));
|
||||
let clearIcon = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-clear"]'));
|
||||
|
||||
expect(searchIcon).not.toBeNull('Search icon should be in the DOM');
|
||||
expect(clearIcon).toBeNull('Clear icon should NOT be in the DOM');
|
||||
});
|
||||
|
||||
it('should show the X (clear) icon without the search icon when the search contains at least one character', () => {
|
||||
fixture.detectChanges();
|
||||
typeToSearchBox('123');
|
||||
|
||||
let searchIcon = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-icon"]'));
|
||||
let clearIcon = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-clear"]'));
|
||||
|
||||
expect(searchIcon).toBeNull('Search icon should NOT be in the DOM');
|
||||
expect(clearIcon).not.toBeNull('Clear icon should be in the DOM');
|
||||
});
|
||||
|
||||
it('should clear the search field, nodes and chosenNode when clicking on the X (clear) icon', () => {
|
||||
component.chosenNode = <MinimalNodeEntryEntity> {};
|
||||
component.nodes = { list: {
|
||||
entries: [ { entry: component.chosenNode } ]
|
||||
} };
|
||||
component.searchTerm = 'piccolo';
|
||||
component.showingSearchResults = true;
|
||||
|
||||
component.clear();
|
||||
|
||||
expect(component.searchTerm).toBe('');
|
||||
expect(component.nodes).toEqual(null);
|
||||
expect(component.chosenNode).toBeNull();
|
||||
expect(component.showingSearchResults).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should show the current folder\'s content instead of search results if search was not performed', () => {
|
||||
let documentList = fixture.debugElement.query(By.directive(DocumentListComponent));
|
||||
expect(documentList).not.toBeNull('Document list should be shown');
|
||||
expect(documentList.componentInstance.currentFolderId).toBe('cat-girl-nuku-nuku');
|
||||
});
|
||||
|
||||
it('should pass through the rowFilter to the documentList', () => {
|
||||
const filter = () => {};
|
||||
component.rowFilter = filter;
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
let documentList = fixture.debugElement.query(By.directive(DocumentListComponent));
|
||||
expect(documentList).not.toBeNull('Document list should be shown');
|
||||
expect(documentList.componentInstance.rowFilter).toBe(filter);
|
||||
});
|
||||
|
||||
it('should pass through the imageResolver to the documentList', () => {
|
||||
const resolver = () => 'piccolo';
|
||||
component.imageResolver = resolver;
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
let documentList = fixture.debugElement.query(By.directive(DocumentListComponent));
|
||||
expect(documentList).not.toBeNull('Document list should be shown');
|
||||
expect(documentList.componentInstance.imageResolver).toBe(resolver);
|
||||
});
|
||||
|
||||
it('should show the result list when search was performed', async(() => {
|
||||
typeToSearchBox();
|
||||
respondWithSearchResults(ONE_FOLDER_RESULT);
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let documentList = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-document-list"]'));
|
||||
expect(documentList).not.toBeNull('Document list should be shown');
|
||||
expect(documentList.componentInstance.currentFolderId).toBeUndefined();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should highlight the results when search was performed in the next timeframe', fakeAsync(() => {
|
||||
spyOn(component.highlighter, 'highlight');
|
||||
typeToSearchBox('shenron');
|
||||
respondWithSearchResults(ONE_FOLDER_RESULT);
|
||||
|
||||
expect(component.highlighter.highlight).not.toHaveBeenCalled();
|
||||
tick();
|
||||
expect(component.highlighter.highlight).toHaveBeenCalledWith('shenron');
|
||||
}));
|
||||
|
||||
it('should show the default text instead of result list if search was cleared', async(() => {
|
||||
typeToSearchBox();
|
||||
respondWithSearchResults(ONE_FOLDER_RESULT);
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let clearButton = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-clear"]'));
|
||||
expect(clearButton).not.toBeNull('Clear button should be in DOM');
|
||||
clearButton.triggerEventHandler('click', {});
|
||||
fixture.detectChanges();
|
||||
|
||||
let documentList = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-document-list"]'));
|
||||
expect(documentList).not.toBeNull('Document list should be shown');
|
||||
expect(documentList.componentInstance.currentFolderId).toBe('cat-girl-nuku-nuku');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should reload the original documentlist when clearing the search input', async(() => {
|
||||
typeToSearchBox('shenron');
|
||||
respondWithSearchResults(ONE_FOLDER_RESULT);
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
typeToSearchBox('');
|
||||
fixture.detectChanges();
|
||||
|
||||
let documentList = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-document-list"]'));
|
||||
expect(documentList.componentInstance.currentFolderId).toBe('cat-girl-nuku-nuku');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should set the folderIdToShow to the default "currentFolderId" if siteId is undefined', () => {
|
||||
component.siteChanged(<SiteModel> { guid: 'Kame-Sennin Muten Roshi' });
|
||||
fixture.detectChanges();
|
||||
|
||||
let documentList = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-document-list"]'));
|
||||
expect(documentList.componentInstance.currentFolderId).toBe('Kame-Sennin Muten Roshi');
|
||||
|
||||
component.siteChanged(<SiteModel> { guid: undefined });
|
||||
fixture.detectChanges();
|
||||
|
||||
documentList = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-document-list"]'));
|
||||
expect(documentList.componentInstance.currentFolderId).toBe('cat-girl-nuku-nuku');
|
||||
});
|
||||
|
||||
describe('Pagination "Load more" button', () => {
|
||||
|
||||
it('should NOT be shown by default', () => {
|
||||
fixture.detectChanges();
|
||||
const pagination = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-pagination"]'));
|
||||
expect(pagination).toBeNull();
|
||||
});
|
||||
|
||||
it('should be shown when diplaying search results', async(() => {
|
||||
typeToSearchBox('shenron');
|
||||
respondWithSearchResults(ONE_FOLDER_RESULT);
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const pagination = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-pagination"]'));
|
||||
expect(pagination).not.toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should NOT be shown when modifying searchTerm to be less then 4 characters after search results have been diplayed', async(() => {
|
||||
typeToSearchBox('shenron');
|
||||
respondWithSearchResults(ONE_FOLDER_RESULT);
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
typeToSearchBox('sh');
|
||||
fixture.detectChanges();
|
||||
|
||||
const pagination = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-pagination"]'));
|
||||
expect(pagination).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('button\'s callback should load the next batch of results by calling the search api', () => {
|
||||
const skipCount = 8;
|
||||
component.searchTerm = 'kakarot';
|
||||
|
||||
component.getNextPageOfSearch({skipCount});
|
||||
|
||||
expect(searchSpy).toHaveBeenCalledWith('kakarot*', defaultSearchOptions(undefined, skipCount));
|
||||
});
|
||||
|
||||
it('should set its loading state to true after search was started', () => {
|
||||
component.showingSearchResults = true;
|
||||
component.pagination = { hasMoreItems: true };
|
||||
|
||||
typeToSearchBox('shenron');
|
||||
fixture.detectChanges();
|
||||
|
||||
const spinnerSelector = By.css('[data-automation-id="content-node-selector-search-pagination"] [data-automation-id="adf-infinite-pagination-spinner"]');
|
||||
const paginationLoading = fixture.debugElement.query(spinnerSelector);
|
||||
expect(paginationLoading).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should set its loading state to true after search was performed', async(() => {
|
||||
component.showingSearchResults = true;
|
||||
component.pagination = { hasMoreItems: true };
|
||||
|
||||
typeToSearchBox('shenron');
|
||||
fixture.detectChanges();
|
||||
respondWithSearchResults(ONE_FOLDER_RESULT);
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const spinnerSelector = By.css('[data-automation-id="content-node-selector-search-pagination"] [data-automation-id="adf-infinite-pagination-spinner"]');
|
||||
const paginationLoading = fixture.debugElement.query(spinnerSelector);
|
||||
expect(paginationLoading).toBeNull();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
xit('should trigger some kind of error when error happened during search', () => {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('Cancel button', () => {
|
||||
|
||||
it('should not be shown if dialogRef is NOT injected', () => {
|
||||
const closeButton = fixture.debugElement.query(By.css('[content-node-selector-actions-cancel]'));
|
||||
expect(closeButton).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Choose button', () => {
|
||||
|
||||
const entry: MinimalNodeEntryEntity = <MinimalNodeEntryEntity> {};
|
||||
let hasPermission;
|
||||
|
||||
beforeEach(() => {
|
||||
const alfrescoContentService = TestBed.get(ContentService);
|
||||
spyOn(alfrescoContentService, 'hasPermission').and.callFake(() => hasPermission);
|
||||
});
|
||||
|
||||
it('should be disabled by default', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let chooseButton = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-actions-choose"]'));
|
||||
expect(chooseButton.nativeElement.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should become enabled after loading node with the necessary permissions', () => {
|
||||
hasPermission = true;
|
||||
component.documentList.folderNode = entry;
|
||||
component.documentList.ready.emit();
|
||||
fixture.detectChanges();
|
||||
|
||||
let chooseButton = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-actions-choose"]'));
|
||||
expect(chooseButton.nativeElement.disabled).toBe(false);
|
||||
});
|
||||
|
||||
it('should remain disabled after loading node without the necessary permissions', () => {
|
||||
hasPermission = false;
|
||||
component.documentList.folderNode = entry;
|
||||
component.documentList.ready.emit();
|
||||
fixture.detectChanges();
|
||||
|
||||
let chooseButton = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-actions-choose"]'));
|
||||
expect(chooseButton.nativeElement.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should be enabled when clicking on a node (with the right permissions) in the list (onNodeSelect)', () => {
|
||||
hasPermission = true;
|
||||
|
||||
component.onNodeSelect({ detail: { node: { entry } } });
|
||||
fixture.detectChanges();
|
||||
|
||||
let chooseButton = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-actions-choose"]'));
|
||||
expect(chooseButton.nativeElement.disabled).toBe(false);
|
||||
});
|
||||
|
||||
it('should remain disabled when clicking on a node (with the WRONG permissions) in the list (onNodeSelect)', () => {
|
||||
hasPermission = false;
|
||||
|
||||
component.onNodeSelect({ detail: { node: { entry } } });
|
||||
fixture.detectChanges();
|
||||
|
||||
let chooseButton = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-actions-choose"]'));
|
||||
expect(chooseButton.nativeElement.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should become disabled when clicking on a node (with the WRONG permissions) after previously selecting a right node', () => {
|
||||
hasPermission = true;
|
||||
component.onNodeSelect({ detail: { node: { entry } } });
|
||||
fixture.detectChanges();
|
||||
|
||||
hasPermission = false;
|
||||
component.onNodeSelect({ detail: { node: { entry } } });
|
||||
fixture.detectChanges();
|
||||
|
||||
let chooseButton = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-actions-choose"]'));
|
||||
expect(chooseButton.nativeElement.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should be disabled when resetting the chosen node', () => {
|
||||
hasPermission = true;
|
||||
component.onNodeSelect({ detail: { node: { entry: <MinimalNodeEntryEntity> {} } } });
|
||||
fixture.detectChanges();
|
||||
|
||||
component.resetChosenNode();
|
||||
fixture.detectChanges();
|
||||
|
||||
let chooseButton = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-actions-choose"]'));
|
||||
expect(chooseButton.nativeElement.disabled).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,291 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, Inject, Input, OnInit, Optional, Output, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import { ContentService, HighlightDirective, SiteModel } from '@alfresco/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
|
||||
import { MinimalNodeEntryEntity, NodePaging, Pagination } from 'alfresco-js-api';
|
||||
import { DocumentListComponent, PaginationStrategy } from '../document-list/components/document-list.component';
|
||||
import { RowFilter } from '../document-list/data/row-filter.model';
|
||||
import { ImageResolver } from '../document-list/data/image-resolver.model';
|
||||
|
||||
import { ContentNodeSelectorService } from './content-node-selector.service';
|
||||
|
||||
export interface ContentNodeSelectorComponentData {
|
||||
title: string;
|
||||
currentFolderId?: string;
|
||||
rowFilter?: RowFilter;
|
||||
imageResolver?: ImageResolver;
|
||||
select: EventEmitter<MinimalNodeEntryEntity[]>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'adf-content-node-selector',
|
||||
styleUrls: ['./content-node-selector.component.scss'],
|
||||
templateUrl: './content-node-selector.component.html',
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class ContentNodeSelectorComponent implements OnInit {
|
||||
|
||||
nodes: NodePaging | null = null;
|
||||
siteId: null | string;
|
||||
searchTerm: string = '';
|
||||
showingSearchResults: boolean = false;
|
||||
loadingSearchResults: boolean = false;
|
||||
inDialog: boolean = false;
|
||||
chosenNode: MinimalNodeEntryEntity | null = null;
|
||||
folderIdToShow: string | null = null;
|
||||
paginationStrategy: PaginationStrategy;
|
||||
pagination: Pagination;
|
||||
skipCount: number = 0;
|
||||
|
||||
@Input()
|
||||
title: string;
|
||||
|
||||
@Input()
|
||||
currentFolderId: string | null = null;
|
||||
|
||||
@Input()
|
||||
rowFilter: RowFilter = null;
|
||||
|
||||
@Input()
|
||||
imageResolver: ImageResolver = null;
|
||||
|
||||
@Input()
|
||||
pageSize: number = 10;
|
||||
|
||||
@Output()
|
||||
select: EventEmitter<MinimalNodeEntryEntity[]> = new EventEmitter<MinimalNodeEntryEntity[]>();
|
||||
|
||||
@ViewChild(DocumentListComponent)
|
||||
documentList: DocumentListComponent;
|
||||
|
||||
@ViewChild(HighlightDirective)
|
||||
highlighter: HighlightDirective;
|
||||
|
||||
constructor(private contentNodeSelectorService: ContentNodeSelectorService,
|
||||
private contentService: ContentService,
|
||||
@Optional() @Inject(MAT_DIALOG_DATA) data?: ContentNodeSelectorComponentData,
|
||||
@Optional() private containingDialog?: MatDialogRef<ContentNodeSelectorComponent>) {
|
||||
if (data) {
|
||||
this.title = data.title;
|
||||
this.select = data.select;
|
||||
this.currentFolderId = data.currentFolderId;
|
||||
this.rowFilter = data.rowFilter;
|
||||
this.imageResolver = data.imageResolver;
|
||||
}
|
||||
|
||||
if (this.containingDialog) {
|
||||
this.inDialog = true;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.folderIdToShow = this.currentFolderId;
|
||||
this.paginationStrategy = PaginationStrategy.Infinite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the site attribute and starts a new search
|
||||
*
|
||||
* @param chosenSite Sitemodel to search within
|
||||
*/
|
||||
siteChanged(chosenSite: SiteModel): void {
|
||||
this.siteId = chosenSite.guid;
|
||||
this.updateResults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the searchTerm attribute and starts a new search
|
||||
*
|
||||
* @param searchTerm string value to search against
|
||||
*/
|
||||
search(searchTerm: string): void {
|
||||
this.searchTerm = searchTerm;
|
||||
this.updateResults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether breadcrumb has to be shown or not
|
||||
*/
|
||||
needBreadcrumbs() {
|
||||
const whenInFolderNavigation = !this.showingSearchResults,
|
||||
whenInSelectingSearchResult = this.showingSearchResults && this.chosenNode;
|
||||
|
||||
return whenInFolderNavigation || whenInSelectingSearchResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actually selected|entered folder node or null in case of searching for the breadcrumb
|
||||
*/
|
||||
get breadcrumbFolderNode(): MinimalNodeEntryEntity | null {
|
||||
if (this.showingSearchResults && this.chosenNode) {
|
||||
return this.chosenNode;
|
||||
} else {
|
||||
return this.documentList.folderNode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the search input
|
||||
*/
|
||||
clear(): void {
|
||||
this.searchTerm = '';
|
||||
this.nodes = null;
|
||||
this.skipCount = 0;
|
||||
this.chosenNode = null;
|
||||
this.showingSearchResults = false;
|
||||
this.folderIdToShow = this.currentFolderId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the result list depending on the criterias
|
||||
*/
|
||||
private updateResults(): void {
|
||||
if (this.searchTerm.length === 0) {
|
||||
this.folderIdToShow = this.siteId || this.currentFolderId;
|
||||
} else {
|
||||
this.startNewSearch();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the first page of a new search result
|
||||
*/
|
||||
private startNewSearch(): void {
|
||||
this.nodes = null;
|
||||
this.skipCount = 0;
|
||||
this.chosenNode = null;
|
||||
this.folderIdToShow = null;
|
||||
this.querySearch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the next batch of search results
|
||||
*
|
||||
* @param event Pagination object
|
||||
*/
|
||||
getNextPageOfSearch(event: Pagination): void {
|
||||
this.skipCount = event.skipCount;
|
||||
this.querySearch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the call to searchService with the proper parameters
|
||||
*/
|
||||
private querySearch(): void {
|
||||
if (this.isSearchTermLongEnough()) {
|
||||
this.loadingSearchResults = true;
|
||||
|
||||
this.contentNodeSelectorService.search(this.searchTerm, this.siteId, this.skipCount, this.pageSize)
|
||||
.subscribe(this.showSearchResults.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the results of the search
|
||||
*
|
||||
* @param results Search results
|
||||
*/
|
||||
private showSearchResults(results: NodePaging): void {
|
||||
this.showingSearchResults = true;
|
||||
this.loadingSearchResults = false;
|
||||
|
||||
// Documentlist hack, since data displaying for preloaded nodes is a little bit messy there
|
||||
if (!this.nodes) {
|
||||
this.nodes = results;
|
||||
} else {
|
||||
this.documentList.data.loadPage(results, true);
|
||||
}
|
||||
|
||||
this.pagination = results.list.pagination;
|
||||
this.highlight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicate method to decide whether searchTerm fulfills the necessary criteria
|
||||
*/
|
||||
isSearchTermLongEnough(): boolean {
|
||||
return this.searchTerm.length > 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hightlight the actual searchterm in the next frame
|
||||
*/
|
||||
highlight(): void {
|
||||
setTimeout(() => {
|
||||
this.highlighter.highlight(this.searchTerm);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when user selects a node
|
||||
*
|
||||
* @param event CustomEvent for node-select
|
||||
*/
|
||||
onNodeSelect(event: any): void {
|
||||
this.attemptNodeSelection(event.detail.node.entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets showingSearchResults state to be able to differentiate between search results or folder results
|
||||
*/
|
||||
onFolderChange(): void {
|
||||
this.showingSearchResults = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to set the currently loaded node
|
||||
*/
|
||||
onFolderLoaded(): void {
|
||||
this.attemptNodeSelection(this.documentList.folderNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects node as chosen if it has the right permission, clears the selection otherwise
|
||||
*
|
||||
* @param entry
|
||||
*/
|
||||
private attemptNodeSelection(entry: MinimalNodeEntryEntity): void {
|
||||
if (this.contentService.hasPermission(entry, 'create')) {
|
||||
this.chosenNode = entry;
|
||||
} else {
|
||||
this.resetChosenNode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the chosen node
|
||||
*/
|
||||
resetChosenNode(): void {
|
||||
this.chosenNode = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit event with the chosen node
|
||||
*/
|
||||
choose(): void {
|
||||
this.select.next([this.chosenNode]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the dialog
|
||||
*/
|
||||
close(): void {
|
||||
this.containingDialog.close();
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
/*!
|
||||
* @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 { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { MaterialModule } from '../material.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { ContentNodeSelectorComponent } from './content-node-selector.component';
|
||||
import { ContentNodeSelectorService } from './content-node-selector.service';
|
||||
import { SitesDropdownModule } from '../site-dropdown';
|
||||
import { BreadcrumbModule } from '../breadcrumb';
|
||||
import { PaginationModule, ToolbarModule } from '@alfresco/core';
|
||||
import { DocumentListModule } from '../document-list';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
MaterialModule,
|
||||
TranslateModule,
|
||||
SitesDropdownModule,
|
||||
BreadcrumbModule,
|
||||
ToolbarModule,
|
||||
DocumentListModule,
|
||||
PaginationModule
|
||||
],
|
||||
exports: [
|
||||
ContentNodeSelectorComponent
|
||||
],
|
||||
entryComponents: [
|
||||
ContentNodeSelectorComponent
|
||||
],
|
||||
declarations: [
|
||||
ContentNodeSelectorComponent
|
||||
],
|
||||
providers: [
|
||||
ContentNodeSelectorService
|
||||
]
|
||||
})
|
||||
export class ContentNodeSelectorModule {}
|
@@ -0,0 +1,54 @@
|
||||
/*!
|
||||
* @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 { SearchOptions, SearchService } from '@alfresco/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NodePaging } from 'alfresco-js-api';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
|
||||
/**
|
||||
* Internal service used by ContentNodeSelector component.
|
||||
*/
|
||||
@Injectable()
|
||||
export class ContentNodeSelectorService {
|
||||
|
||||
constructor(private searchService: SearchService) {}
|
||||
|
||||
/**
|
||||
* Performs a search for content node selection
|
||||
*
|
||||
* @param searchTerm The term to search for
|
||||
* @param skipCount From where to start the loading
|
||||
* @param rootNodeId The root is to start the search from
|
||||
* @param maxItems How many items to load
|
||||
*/
|
||||
public search(searchTerm: string, rootNodeId: string, skipCount: number, maxItems: number): Observable<NodePaging> {
|
||||
|
||||
searchTerm = searchTerm + '*';
|
||||
|
||||
let searchOpts: SearchOptions = {
|
||||
include: ['path', 'allowableOperations'],
|
||||
skipCount,
|
||||
rootNodeId,
|
||||
nodeType: 'cm:folder',
|
||||
maxItems,
|
||||
orderBy: null
|
||||
};
|
||||
|
||||
return this.searchService.getNodeQueryResults(searchTerm, searchOpts);
|
||||
}
|
||||
}
|
18
lib/content-services/content-node-selector/index.ts
Normal file
18
lib/content-services/content-node-selector/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/*!
|
||||
* @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 * from './public-api';
|
21
lib/content-services/content-node-selector/public-api.ts
Normal file
21
lib/content-services/content-node-selector/public-api.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*!
|
||||
* @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 * from './content-node-selector.component';
|
||||
export * from './content-node-selector.service';
|
||||
|
||||
export * from './content-node-selector.module';
|
Reference in New Issue
Block a user