mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ADF-5219] Refactor Document list Filters (#6092)
* First commit * Second commit * Commit 4 * Add unit tests * Fix unit tests * Add documentation for breaking change * Fix rebase * Fix unit test
This commit is contained in:
@@ -222,7 +222,6 @@
|
||||
[contentActions]="true"
|
||||
[allowDropFiles]="allowDropFiles"
|
||||
[selectionMode]="selectionMode"
|
||||
[preselectNodes]="selectedNodes"
|
||||
[multiselect]="multiselect"
|
||||
[display]="displayMode"
|
||||
[node]="nodeResult"
|
||||
@@ -232,27 +231,17 @@
|
||||
[showHeader]="showHeader"
|
||||
[thumbnails]="thumbnails"
|
||||
[stickyHeader]="stickyHeader"
|
||||
[headerFilters]="headerFilters"
|
||||
[filterValue]="paramValues"
|
||||
(error)="onNavigationError($event)"
|
||||
(success)="resetError()"
|
||||
(ready)="emitReadyEvent($event)"
|
||||
(preview)="showFile($event)"
|
||||
(folderChange)="onFolderChange($event)"
|
||||
(permissionError)="handlePermissionError($event)"
|
||||
(name-click)="documentList.onNodeDblClick($event.detail?.node)">
|
||||
<adf-custom-header-filter-template *ngIf="enableCustomHeaderFilter">
|
||||
<ng-template let-col>
|
||||
<adf-search-header [col]="col"
|
||||
[value]="paramValues? paramValues[col.key] : null"
|
||||
[currentFolderNodeId]="currentFolderId"
|
||||
[sorting]="filterSorting"
|
||||
[maxItems]="pagination?.maxItems"
|
||||
[skipCount]="pagination?.skipCount"
|
||||
(update)="onFilterUpdate($event)"
|
||||
(clear)="onAllFilterCleared()"
|
||||
(selection)="onFilterSelected($event)">
|
||||
</adf-search-header>
|
||||
</ng-template>
|
||||
</adf-custom-header-filter-template>
|
||||
(name-click)="documentList.onNodeDblClick($event.detail?.node)"
|
||||
(filterSelection)="onFilterSelected($event)">
|
||||
|
||||
<adf-custom-no-permission-template *ngIf="enableCustomPermissionMessage">
|
||||
<h1>You don't have permissions</h1>
|
||||
</adf-custom-no-permission-template>
|
||||
|
@@ -39,7 +39,8 @@ import {
|
||||
UploadFilesEvent,
|
||||
ConfirmDialogComponent,
|
||||
LibraryDialogComponent,
|
||||
ContentMetadataService
|
||||
ContentMetadataService,
|
||||
FilterSearch
|
||||
} from '@alfresco/adf-content-services';
|
||||
|
||||
import { SelectAppsDialogComponent, ProcessFormRenderingService } from '@alfresco/adf-process-services';
|
||||
@@ -79,9 +80,9 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
|
||||
toolbarColor = 'default';
|
||||
|
||||
selectionModes = [
|
||||
{value: 'none', viewValue: 'None'},
|
||||
{value: 'single', viewValue: 'Single'},
|
||||
{value: 'multiple', viewValue: 'Multiple'}
|
||||
{ value: 'none', viewValue: 'None' },
|
||||
{ value: 'single', viewValue: 'Single' },
|
||||
{ value: 'multiple', viewValue: 'Multiple' }
|
||||
];
|
||||
|
||||
// The identifier of a node. You can also use one of these well-known aliases: -my- | -shared- | -root-
|
||||
@@ -165,7 +166,7 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
|
||||
navigationRoute = '/files';
|
||||
|
||||
@Input()
|
||||
enableCustomHeaderFilter = false;
|
||||
headerFilters = false;
|
||||
|
||||
@Input()
|
||||
paramValues: Map<any, any> = null;
|
||||
@@ -365,7 +366,7 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
getCurrentDocumentListNode(): MinimalNodeEntity[] {
|
||||
if (this.documentList.folderNode) {
|
||||
return [{entry: this.documentList.folderNode}];
|
||||
return [{ entry: this.documentList.folderNode }];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
@@ -464,7 +465,7 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
if (this.contentService.hasAllowableOperations(contentEntry, 'update')) {
|
||||
this.dialog.open(VersionManagerDialogAdapterComponent, {
|
||||
data: {contentEntry: contentEntry, showComments: showComments, allowDownload: allowDownload},
|
||||
data: { contentEntry: contentEntry, showComments: showComments, allowDownload: allowDownload },
|
||||
panelClass: 'adf-version-manager-dialog',
|
||||
width: '630px'
|
||||
});
|
||||
@@ -677,11 +678,30 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return '';
|
||||
}
|
||||
|
||||
onFilterUpdate(newNodePaging: NodePaging) {
|
||||
this.nodeResult = newNodePaging;
|
||||
onFilterSelected(activeFilters: FilterSearch[]) {
|
||||
if (activeFilters.length) {
|
||||
this.navigateToFilter(activeFilters);
|
||||
} else {
|
||||
this.clearFilterNavigation();
|
||||
}
|
||||
}
|
||||
|
||||
onAllFilterCleared() {
|
||||
navigateToFilter(activeFilters: FilterSearch[]) {
|
||||
const objectFromMap = {};
|
||||
activeFilters.forEach((filter: FilterSearch) => {
|
||||
let paramValue = null;
|
||||
if (filter.value && filter.value.from && filter.value.to) {
|
||||
paramValue = `${filter.value.from}||${filter.value.to}`;
|
||||
} else {
|
||||
paramValue = filter.value;
|
||||
}
|
||||
objectFromMap[filter.key] = paramValue;
|
||||
});
|
||||
|
||||
this.router.navigate([], { relativeTo: this.route, queryParams: objectFromMap });
|
||||
}
|
||||
|
||||
clearFilterNavigation() {
|
||||
this.documentList.node = null;
|
||||
if (this.currentFolderId === '-my-') {
|
||||
this.router.navigate([this.navigationRoute, '']);
|
||||
@@ -691,21 +711,6 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.documentList.reload();
|
||||
}
|
||||
|
||||
onFilterSelected(currentActiveFilters: Map<string, string>) {
|
||||
const objectFromMap = {};
|
||||
currentActiveFilters.forEach((value: any, key) => {
|
||||
let paramValue = null;
|
||||
if (value && value.from && value.to) {
|
||||
paramValue = `${value.from}||${value.to}`;
|
||||
} else {
|
||||
paramValue = value;
|
||||
}
|
||||
objectFromMap[key] = paramValue;
|
||||
});
|
||||
|
||||
this.router.navigate([], { relativeTo: this.route, queryParams: objectFromMap });
|
||||
}
|
||||
|
||||
setPreselectNodes(nodes: string) {
|
||||
this.selectedNodes = this.getArrayFromString(nodes);
|
||||
this.documentList.reload();
|
||||
|
@@ -4,8 +4,6 @@
|
||||
[showSettingsPanel]="false"
|
||||
[navigationRoute]="navigationRoute"
|
||||
[currentFolderId]="currentFolderId"
|
||||
[filterSorting]="filterSorting"
|
||||
[enableCustomHeaderFilter]="true"
|
||||
[paramValues]="queryParams"
|
||||
(sorting-changed)="onSortingChanged($event)">
|
||||
[headerFilters]="true"
|
||||
[paramValues]="queryParams">
|
||||
</app-files-component>
|
||||
|
@@ -16,13 +16,11 @@
|
||||
*/
|
||||
|
||||
import { Component, Optional } from '@angular/core';
|
||||
import { SEARCH_QUERY_SERVICE_TOKEN, SearchHeaderQueryBuilderService } from '@alfresco/adf-content-services';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-filtered-search-component',
|
||||
templateUrl: './filtered-search.component.html',
|
||||
providers: [{ provide: SEARCH_QUERY_SERVICE_TOKEN, useClass: SearchHeaderQueryBuilderService}]
|
||||
templateUrl: './filtered-search.component.html'
|
||||
})
|
||||
export class FilteredSearchComponent {
|
||||
|
||||
@@ -30,7 +28,6 @@ export class FilteredSearchComponent {
|
||||
currentFolderId = '-my-';
|
||||
|
||||
queryParams = null;
|
||||
filterSorting: string = 'name-asc';
|
||||
|
||||
constructor(@Optional() private route: ActivatedRoute) {
|
||||
|
||||
@@ -46,9 +43,4 @@ export class FilteredSearchComponent {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onSortingChanged(event) {
|
||||
this.filterSorting = event.detail.key + '-' + event.detail.direction;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -32,6 +32,7 @@ Displays the documents from a repository.
|
||||
- [Location Column](#location-column)
|
||||
- [Actions](#actions)
|
||||
- [Navigation mode](#navigation-mode)
|
||||
- [Header filters](#header-filters)
|
||||
- [Advanced usage and customization](#advanced-usage-and-customization)
|
||||
- [Image Resolver and Row Filter functions](#image-resolver-and-row-filter-functions)
|
||||
- [Custom 'empty folder' template](#custom-empty-folder-template)
|
||||
@@ -56,7 +57,7 @@ Displays the documents from a repository.
|
||||
|
||||
| Name | Type | Default value | Description |
|
||||
| ---- | ---- | ------------- | ----------- |
|
||||
| additionalSorting | `string[]` | ['isFolder DESC'] | Defines default sorting. The format is an array of strings `[key direction, otherKey otherDirection]` i.e. `['name desc', 'nodeType asc']` or `['name asc']`. Set this value if you want a base rule to be added to the sorting apart from the one driven by the header. |
|
||||
| additionalSorting | [`DataSorting`](../../../lib/core/datatable/data/data-sorting.model.ts) | Defines default sorting. The format is an array of strings `[key direction, otherKey otherDirection]` i.e. `['name desc', 'nodeType asc']` or `['name asc']`. Set this value if you want a base rule to be added to the sorting apart from the one driven by the header. |
|
||||
| allowDropFiles | `boolean` | false | When true, this enables you to drop files directly into subfolders shown as items in the list or into another file to trigger updating it's version. When false, the dropped file will be added to the current folder (ie, the one containing all the items shown in the list). See the [Upload directive](../../core/directives/upload.directive.md) for further details about how the file drop is handled. |
|
||||
| contentActions | `boolean` | false | Toggles content actions for each row |
|
||||
| contentActionsPosition | `string` | "right" | Position of the content actions dropdown menu. Can be set to "left" or "right". |
|
||||
@@ -78,13 +79,15 @@ Displays the documents from a repository.
|
||||
| rowStyleClass | `string` | | The CSS class to apply to every row |
|
||||
| selectionMode | `string` | "single" | Row selection mode. Can be null, `single` or `multiple`. For `multiple` mode, you can use Cmd (macOS) or Ctrl (Win) modifier key to toggle selection for multiple rows. |
|
||||
| showHeader | `string` | | Toggles the header |
|
||||
| sorting | `string[]` | ['name', 'asc'] | Defines default sorting. The format is an array of 2 strings `[key, direction]` i.e. `['name', 'desc']` or `['name', 'asc']`. Set this value only if you want to override the default sorting detected by the component based on columns. |
|
||||
| sorting | `string[]` \| [`DataSorting`](../../../lib/core/datatable/data/data-sorting.model.ts) | ['name', 'asc'] | Defines default sorting. The format is an array of 2 strings `[key, direction]` i.e. `['name', 'desc']` or `['name', 'asc']`. Set this value only if you want to override the default sorting detected by the component based on columns. |
|
||||
| sortingMode | `string` | "server" | Defines sorting mode. Can be either `client` (items in the list are sorted client-side) or `server` (the ordering supplied by the server is used without further client-side sorting). Note that the `server` option _does not_ request the server to sort the data before delivering it. |
|
||||
| stickyHeader | `boolean` | false | Toggles the sticky header mode. |
|
||||
| thumbnails | `boolean` | false | Show document thumbnails rather than icons |
|
||||
| where | `string` | | Filters the [`Node`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/Node.md) list using the _where_ condition of the REST API (for example, isFolder=true). See the REST API documentation for more information. |
|
||||
| currentFolderId | `string` | | The ID of the folder node to display or a reserved string alias for special sources |
|
||||
| currentFolderId | `string` | null | The ID of the folder node to display or a reserved string alias for special sources |
|
||||
| rowFilter | [`RowFilter`](../../../lib/content-services/src/lib/document-list/data/row-filter.model.ts) | | Custom function to choose whether to show or hide rows. See the [Row Filter Model](row-filter.model.md) page for more information. |
|
||||
| headerFilters | `boolean` | false | Toggles the header filters mode. |
|
||||
| filterValue | any | | Initial value for filter. |
|
||||
|
||||
### Events
|
||||
|
||||
@@ -96,7 +99,8 @@ Displays the documents from a repository.
|
||||
| nodeDblClick | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodeEntityEvent`](../../../lib/content-services/src/lib/document-list/components/node.event.ts)`>` | Emitted when the user double-clicks a list node |
|
||||
| nodeSelected | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)`[]>` | Emitted when the node selection change |
|
||||
| preview | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodeEntityEvent`](../../../lib/content-services/src/lib/document-list/components/node.event.ts)`>` | Emitted when the user acts upon files with either single or double click (depends on `navigation-mode`). Useful for integration with the [Viewer component](../../core/components/viewer.component.md). |
|
||||
| ready | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodePaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/NodePaging.md)`>` | Emitted when the Document List has loaded all items and is ready for use |
|
||||
| ready | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodePaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/NodePaging.md)`>` | Emitted when the Document List has loaded all items and is ready for use. |
|
||||
| filterSelection | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FilterSearch[]`](../../../lib/content-services/src/lib/search/filter-search.interface.ts)`>` | Emitted when a filter value is selected. |
|
||||
|
||||
## Details
|
||||
|
||||
@@ -687,6 +691,18 @@ The following example switches navigation to single clicks:
|
||||
</adf-document-list>
|
||||
```
|
||||
|
||||
### Header filters
|
||||
|
||||
You can enable Header filters in your document list simply setting to true its `headerFilters` input.
|
||||
|
||||
```html
|
||||
<adf-document-list
|
||||
currentFolderId="-my-"
|
||||
[headerFilters]="true">
|
||||
</adf-document-list>
|
||||
```
|
||||

|
||||
|
||||
## Advanced usage and customization
|
||||
|
||||
### Image Resolver and Row Filter functions
|
||||
|
@@ -1,71 +0,0 @@
|
||||
---
|
||||
Title: SearchHeader component
|
||||
Added: v3.9.0
|
||||
Status: Active
|
||||
Last reviewed: 2020-19-06
|
||||
---
|
||||
|
||||
# [SearchHeader component](../../../lib/content-services/src/lib/search/components/search-header/search-header.component.ts "Defined in search-header.component.ts")
|
||||
|
||||
Displays a button opening a menu designed to filter a document list.
|
||||
|
||||

|
||||
|
||||
## Basic usage
|
||||
|
||||
**app.component.html**
|
||||
|
||||
```html
|
||||
<adf-document-list
|
||||
...
|
||||
...>
|
||||
<adf-custom-header-filter-template>
|
||||
<ng-template let-col>
|
||||
<adf-search-header [col]="col"
|
||||
[currentFolderNodeId]="currentFolderId"
|
||||
[maxItems]="pagination?.maxItems"
|
||||
[skipCount]="pagination?.skipCount"
|
||||
(update)="onFilterUpdate($event)"
|
||||
(clear)="onAllFilterCleared()">
|
||||
</adf-search-header>
|
||||
</ng-template>
|
||||
</adf-custom-header-filter-template>
|
||||
</adf-document-list>
|
||||
```
|
||||
|
||||
**app.config.json**
|
||||
|
||||
```json
|
||||
|
||||
```
|
||||
|
||||
This component is designed to be used as transcluded inside the [document list component](../../content-services/components/document-list.component.md). With the good configurations it will allow the user to filter the data displayed by that component.
|
||||
|
||||
## Class members
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Default value | Description |
|
||||
| ---- | ---- | ------------- | ----------- |
|
||||
| col | [`DataColumn`](../../../lib/core/datatable/data/data-column.model.ts) | | The column the filter will be applied on. |
|
||||
| currentFolderNodeId | `string` | | The id of the current folder of the document list. |
|
||||
| maxItems | `number` | | Maximum number of search results to show in a page. |
|
||||
| skipCount | `number` | | The offset of the start of the page within the results list. |
|
||||
| sorting | `string` | null | The sorting to apply to the the filter. |
|
||||
| value | `any` | | (optional) Initial filter value to sort . |
|
||||
|
||||
### Events
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| clear | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` | Emitted when the last of all the filters is cleared. |
|
||||
| selection | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<Map<string, string>>` | Emitted when a filter value is selected |
|
||||
| update | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodePaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/NodePaging.md)`>` | Emitted when the result of the filter is received from the API. |
|
||||
|
||||
## See also
|
||||
|
||||
- [Document list component](document-list.component.md)
|
||||
- [Search filter component](search-filter.component.md)
|
||||
- [Search component](search.component.md)
|
||||
- [Datatable component](../../core/components/datatable.component.md)
|
||||
- [Search Query Builder service](../services/search-query-builder.service.md)
|
BIN
docs/docassets/images/header-filters.png
Normal file
BIN
docs/docassets/images/header-filters.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
62
docs/upgrade-guide/upgrade40-41.md
Normal file
62
docs/upgrade-guide/upgrade40-41.md
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
Title: Upgrading from ADF v4.0 to v4.1
|
||||
---
|
||||
|
||||
# Upgrading from ADF v4.0 to v4.1
|
||||
|
||||
This guide explains how to upgrade your ADF v4.0 project to work with v4.1.
|
||||
|
||||
Do not skip this task, if you want your application to be updated to a most recent version of ADF.
|
||||
Upgrades of multiple versions of ADF cannot be done in one step only, but should follow the chain of sequential updates.
|
||||
|
||||
**Note:** the steps described below might involve making changes
|
||||
to your code. If you are working with a versioning system then you should
|
||||
commit any changes you are currently working on. If you aren't using versioning
|
||||
then be sure to make a backup copy of your project before going ahead with the
|
||||
upgrade.
|
||||
|
||||
## Header Filters for Document List Components
|
||||
|
||||
We released a new feature called Header Filters in ADF 4.0. It would allow users to filter the content of a folder by its columns properties. While this feature was working we noticed it was hard to implement. That is way we came up with a new way of enabling this feature.
|
||||
|
||||
You will need to update your code to overcome this breaking change.
|
||||
|
||||
ADF 4.0 implementation
|
||||
```html
|
||||
<adf-document-list
|
||||
currentFolderId="-my-">
|
||||
<adf-custom-header-filter-template *ngIf="enableCustomHeaderFilter">
|
||||
<ng-template let-col>
|
||||
<adf-search-header [col]="col"
|
||||
[value]="paramValues"
|
||||
[currentFolderNodeId]="currentFolderId"
|
||||
[sorting]="filterSorting"
|
||||
[maxItems]="pagination.maxItems"
|
||||
[skipCount]="pagination.skipCount"
|
||||
(update)="onFilterUpdate($event)"
|
||||
(clear)="onAllFilterCleared()"
|
||||
(selection)="onFilterSelected($event)">
|
||||
</adf-search-header>
|
||||
</ng-template>
|
||||
</adf-custom-header-filter-template>
|
||||
</adf-document-list>
|
||||
```
|
||||
ADF 4.1 implementation
|
||||
```html
|
||||
<adf-document-list
|
||||
currentFolderId="-my-"
|
||||
[headerFilters]="true">
|
||||
</adf-document-list>
|
||||
```
|
||||
|
||||
This is all you'll need to set it up in your app. Alternatively, you can also pass an initial value to the filters and listen to filter selection changes.
|
||||
|
||||
```html
|
||||
<adf-document-list
|
||||
currentFolderId="-my-"
|
||||
[headerFilters]="true"
|
||||
[filterValue]="paramValues"
|
||||
(filterSelection)="onFilterSelected($event)">
|
||||
</adf-document-list>
|
||||
```
|
||||
Notice that for this feature in ADF 4.0 to work you also needed to overwrite the `SearchFilterQueryBuilderService` with `SEARCH_QUERY_SERVICE_TOKEN` at an app level to make it work. That is no longer the case with the new version. The component will handle everything for you.
|
@@ -31,7 +31,7 @@ describe('DropdownBreadcrumb', () => {
|
||||
let component: DropdownBreadcrumbComponent;
|
||||
let fixture: ComponentFixture<DropdownBreadcrumbComponent>;
|
||||
let documentList: DocumentListComponent;
|
||||
let documentListService: DocumentListService = jasmine.createSpyObj({'loadFolderByNodeId' : of(''), 'isCustomSourceService': false});
|
||||
let documentListService: DocumentListService = jasmine.createSpyObj({ 'loadFolderByNodeId': of(''), 'isCustomSourceService': false });
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
@@ -39,7 +39,7 @@ describe('DropdownBreadcrumb', () => {
|
||||
ContentTestingModule
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
providers : [{ provide: DocumentListService, useValue: documentListService }]
|
||||
providers: [{ provide: DocumentListService, useValue: documentListService }]
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
@@ -133,7 +133,7 @@ describe('DropdownBreadcrumb', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should update document list when clicking on an option', (done) => {
|
||||
it('should update document list when clicking on an option', (done) => {
|
||||
component.target = documentList;
|
||||
const fakeNodeWithCreatePermissionInstance = JSON.parse(JSON.stringify(fakeNodeWithCreatePermission));
|
||||
fakeNodeWithCreatePermissionInstance.path.elements = [{ id: '1', name: 'Stark Industries' }];
|
||||
@@ -144,7 +144,7 @@ describe('DropdownBreadcrumb', () => {
|
||||
fixture.whenStable().then(() => {
|
||||
clickOnTheFirstOption();
|
||||
|
||||
expect(documentListService.loadFolderByNodeId).toHaveBeenCalledWith('1', documentList.DEFAULT_PAGINATION, undefined, undefined, ['name ASC']);
|
||||
expect(documentListService.loadFolderByNodeId).toHaveBeenCalledWith('1', documentList.DEFAULT_PAGINATION, undefined, undefined, null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@@ -25,13 +25,13 @@
|
||||
(sorting-changed)="onSortingChanged($event)"
|
||||
[class.adf-datatable-gallery-thumbnails]="data.thumbnails">
|
||||
|
||||
<adf-header-filter-template>
|
||||
<ng-template let-col>
|
||||
<ng-template [ngTemplateOutlet]="customHeaderFilterTemplate?.template"
|
||||
[ngTemplateOutletContext]="{$implicit: col}">
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</adf-header-filter-template>
|
||||
<div *ngIf="headerFilters">
|
||||
<adf-filter-header
|
||||
[currentFolderId]="currentFolderId"
|
||||
[value]="filterValue"
|
||||
(filterSelection)="onFilterSelectionChange($event)">
|
||||
</adf-filter-header>
|
||||
</div>
|
||||
|
||||
<adf-no-content-template>
|
||||
<ng-template>
|
||||
|
@@ -1456,7 +1456,7 @@ describe('DocumentList', () => {
|
||||
where: undefined,
|
||||
maxItems: 25,
|
||||
skipCount: 0,
|
||||
orderBy: ['isFolder DESC', 'name asc'],
|
||||
orderBy: ['isFolder desc', 'name asc'],
|
||||
rootFolderId: 'fake-id'
|
||||
}, ['test-include']);
|
||||
});
|
||||
@@ -1472,14 +1472,14 @@ describe('DocumentList', () => {
|
||||
where: '(isFolder=true)',
|
||||
maxItems: 25,
|
||||
skipCount: 0,
|
||||
orderBy: ['isFolder DESC', 'name asc'],
|
||||
orderBy: ['isFolder desc', 'name asc'],
|
||||
rootFolderId: 'fake-id'
|
||||
}, ['test-include']);
|
||||
});
|
||||
|
||||
it('should add orderBy in the server request', () => {
|
||||
documentList.includeFields = ['test-include'];
|
||||
documentList.sorting = ['size', 'DESC'];
|
||||
documentList.sorting = ['size', 'desc'];
|
||||
documentList.where = null;
|
||||
documentList.currentFolderId = 'fake-id';
|
||||
|
||||
@@ -1489,12 +1489,13 @@ describe('DocumentList', () => {
|
||||
maxItems: 25,
|
||||
skipCount: 0,
|
||||
where: null,
|
||||
orderBy: ['isFolder DESC', 'size DESC'],
|
||||
orderBy: ['isFolder desc', 'size desc'],
|
||||
rootFolderId: 'fake-id'
|
||||
}, ['test-include']);
|
||||
});
|
||||
|
||||
it('should reset the pagination when enter in a new folder', () => {
|
||||
documentList.ngOnChanges({ currentFolderId: new SimpleChange(undefined, 'fake-id', true) });
|
||||
const folder = new FolderNode();
|
||||
documentList.navigationMode = DocumentListComponent.SINGLE_CLICK_NAVIGATION;
|
||||
documentList.updatePagination({
|
||||
@@ -1505,7 +1506,7 @@ describe('DocumentList', () => {
|
||||
expect(documentListService.getFolder).toHaveBeenCalledWith(null, Object({
|
||||
maxItems: 10,
|
||||
skipCount: 10,
|
||||
orderBy: ['name ASC'],
|
||||
orderBy: ['isFolder desc', 'name asc'],
|
||||
rootFolderId: 'no-node',
|
||||
where: undefined
|
||||
}), undefined);
|
||||
@@ -1515,7 +1516,7 @@ describe('DocumentList', () => {
|
||||
expect(documentListService.getFolder).toHaveBeenCalledWith(null, Object({
|
||||
maxItems: 25,
|
||||
skipCount: 0,
|
||||
orderBy: ['name ASC'],
|
||||
orderBy: ['isFolder desc', 'name asc'],
|
||||
rootFolderId: 'folder-id',
|
||||
where: undefined
|
||||
}), undefined);
|
||||
|
@@ -41,7 +41,6 @@ import {
|
||||
CustomLoadingContentTemplateDirective,
|
||||
CustomNoPermissionTemplateDirective,
|
||||
CustomEmptyContentTemplateDirective,
|
||||
CustomHeaderFilterTemplateDirective,
|
||||
RequestPaginationModel,
|
||||
AlfrescoApiService,
|
||||
UserPreferenceValues,
|
||||
@@ -58,6 +57,7 @@ import { ContentActionModel } from './../models/content-action.model';
|
||||
import { PermissionStyleModel } from './../models/permissions-style.model';
|
||||
import { NodeEntityEvent, NodeEntryEvent } from './node.event';
|
||||
import { NavigableComponentInterface } from '../../breadcrumb/navigable-component.interface';
|
||||
import { FilterSearch } from './../../search/filter-search.interface';
|
||||
import { RowFilter } from '../data/row-filter.model';
|
||||
import { DocumentListService } from '../services/document-list.service';
|
||||
import { DocumentLoaderNode } from '../models/document-folder.model';
|
||||
@@ -68,7 +68,7 @@ import { takeUntil } from 'rxjs/operators';
|
||||
styleUrls: ['./document-list.component.scss'],
|
||||
templateUrl: './document-list.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: {class: 'adf-document-list'}
|
||||
host: { class: 'adf-document-list' }
|
||||
})
|
||||
export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, AfterContentInit, PaginatedComponent, NavigableComponentInterface {
|
||||
|
||||
@@ -82,6 +82,11 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
totalItems: 0
|
||||
});
|
||||
|
||||
DEFAULT_SORTING: DataSorting[] = [
|
||||
new DataSorting('name', 'asc'),
|
||||
new DataSorting('isFolder', 'desc')
|
||||
];
|
||||
|
||||
@ContentChild(DataColumnListComponent)
|
||||
columnList: DataColumnListComponent;
|
||||
|
||||
@@ -94,9 +99,6 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
@ContentChild(CustomEmptyContentTemplateDirective)
|
||||
customNoContentTemplate: CustomEmptyContentTemplateDirective;
|
||||
|
||||
@ContentChild(CustomHeaderFilterTemplateDirective)
|
||||
customHeaderFilterTemplate: CustomHeaderFilterTemplateDirective;
|
||||
|
||||
/** Include additional information about the node in the server request. For example: association, isLink, isLocked and others. */
|
||||
@Input()
|
||||
includeFields: string[];
|
||||
@@ -183,14 +185,14 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
* override the default sorting detected by the component based on columns.
|
||||
*/
|
||||
@Input()
|
||||
sorting = ['name', 'asc'];
|
||||
sorting: string[] | DataSorting = ['name', 'asc'];
|
||||
|
||||
/** Defines default sorting. The format is an array of strings `[key direction, otherKey otherDirection]`
|
||||
* i.e. `['name desc', 'nodeType asc']` or `['name asc']`. Set this value if you want a base
|
||||
* rule to be added to the sorting apart from the one driven by the header.
|
||||
*/
|
||||
@Input()
|
||||
additionalSorting = ['isFolder DESC'];
|
||||
additionalSorting: DataSorting = new DataSorting('isFolder', 'desc');
|
||||
|
||||
/** Defines sorting mode. Can be either `client` (items in the list
|
||||
* are sorted client-side) or `server` (the ordering supplied by the
|
||||
@@ -255,6 +257,14 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
@Input()
|
||||
stickyHeader: boolean = false;
|
||||
|
||||
/** Toggles the header filters mode. */
|
||||
@Input()
|
||||
headerFilters: boolean = false;
|
||||
|
||||
/** Initial value for filter. */
|
||||
@Input()
|
||||
filterValue: any;
|
||||
|
||||
/** The ID of the folder node to display or a reserved string alias for special sources */
|
||||
@Input()
|
||||
currentFolderId: string = null;
|
||||
@@ -305,6 +315,10 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
@Output()
|
||||
nodeSelected: EventEmitter<NodeEntry[]> = new EventEmitter<NodeEntry[]>();
|
||||
|
||||
/** Emitted when a filter value is selected */
|
||||
@Output()
|
||||
filterSelection: EventEmitter<FilterSearch[]> = new EventEmitter();
|
||||
|
||||
@ViewChild('dataTable', { static: true })
|
||||
dataTable: DataTableComponent;
|
||||
|
||||
@@ -315,13 +329,14 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
selection = new Array<NodeEntry>();
|
||||
$folderNode: Subject<Node> = new Subject<Node>();
|
||||
allowFiltering: boolean = true;
|
||||
orderBy: string[] = ['name ASC'];
|
||||
orderBy: string[] = null;
|
||||
|
||||
// @deprecated 3.0.0
|
||||
folderNode: Node;
|
||||
|
||||
private _pagination: PaginationModel = this.DEFAULT_PAGINATION;
|
||||
pagination: BehaviorSubject<PaginationModel> = new BehaviorSubject<PaginationModel>(this.DEFAULT_PAGINATION);
|
||||
sortingSubject: BehaviorSubject<DataSorting[]> = new BehaviorSubject<DataSorting[]>(this.DEFAULT_SORTING);
|
||||
|
||||
private layoutPresets = {};
|
||||
private rowMenuCache: { [key: string]: ContentActionModel[] } = {};
|
||||
@@ -367,10 +382,13 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
|
||||
private getDefaultSorting(): DataSorting {
|
||||
let defaultSorting: DataSorting;
|
||||
if (this.sorting) {
|
||||
if (Array.isArray(this.sorting)) {
|
||||
const [key, direction] = this.sorting;
|
||||
defaultSorting = new DataSorting(key, direction);
|
||||
} else {
|
||||
defaultSorting = new DataSorting(this.sorting.key, this.sorting.direction);
|
||||
}
|
||||
|
||||
return defaultSorting;
|
||||
}
|
||||
|
||||
@@ -440,9 +458,11 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
this.resetSelection();
|
||||
|
||||
if (this.sorting) {
|
||||
if (Array.isArray(this.sorting)) {
|
||||
const [key, direction] = this.sorting;
|
||||
this.orderBy = this.buildOrderByArray(key, direction);
|
||||
} else {
|
||||
this.orderBy = this.buildOrderByArray(this.sorting.key, this.sorting.direction);
|
||||
}
|
||||
|
||||
if (this.data) {
|
||||
@@ -493,6 +513,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
if (this.node) {
|
||||
this.data.loadPage(this.node, this._pagination.merge, null, this.getPreselectNodesBasedOnSelectionMode());
|
||||
this.onPreselectNodes();
|
||||
this.syncPagination();
|
||||
this.onDataReady(this.node);
|
||||
} else {
|
||||
this.loadFolder();
|
||||
@@ -586,14 +607,14 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
if (typeof node === 'string') {
|
||||
this.resetNewFolderPagination();
|
||||
this.currentFolderId = node;
|
||||
this.folderChange.emit(new NodeEntryEvent(<Node> {id: node}));
|
||||
this.folderChange.emit(new NodeEntryEvent(<Node> { id: node }));
|
||||
this.reload();
|
||||
return true;
|
||||
} else {
|
||||
if (this.canNavigateFolder(node)) {
|
||||
this.resetNewFolderPagination();
|
||||
this.currentFolderId = this.getNodeFolderDestinationId(node);
|
||||
this.folderChange.emit(new NodeEntryEvent(<Node> {id: this.currentFolderId}));
|
||||
this.folderChange.emit(new NodeEntryEvent(<Node> { id: this.currentFolderId }));
|
||||
this.reload();
|
||||
return true;
|
||||
}
|
||||
@@ -690,14 +711,16 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
}
|
||||
|
||||
onSortingChanged(event: CustomEvent) {
|
||||
this.orderBy = this.buildOrderByArray(event.detail.sortingKey, event.detail.direction);
|
||||
this.orderBy = this.buildOrderByArray(event.detail.key, event.detail.direction);
|
||||
this.reload();
|
||||
this.sortingSubject.next([this.additionalSorting, event.detail]);
|
||||
}
|
||||
|
||||
private buildOrderByArray(currentKey: string, currentDirection: string ): string[] {
|
||||
const orderArray = [...this.additionalSorting];
|
||||
orderArray.push(''.concat(currentKey, ' ', currentDirection));
|
||||
return orderArray;
|
||||
private buildOrderByArray(currentKey: string, currentDirection: string): string[] {
|
||||
return [
|
||||
`${this.additionalSorting.key} ${this.additionalSorting.direction}`,
|
||||
`${currentKey} ${currentDirection}`
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -874,6 +897,15 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
this.reload();
|
||||
}
|
||||
|
||||
private syncPagination() {
|
||||
this.node.list.pagination.maxItems = this._pagination.maxItems;
|
||||
this.node.list.pagination.skipCount = this._pagination.skipCount;
|
||||
}
|
||||
|
||||
onFilterSelectionChange(activeFilters: FilterSearch[]) {
|
||||
this.filterSelection.emit(activeFilters);
|
||||
}
|
||||
|
||||
private resetNewFolderPagination() {
|
||||
this._pagination.skipCount = 0;
|
||||
this._pagination.maxItems = this.maxItems;
|
||||
|
@@ -0,0 +1,10 @@
|
||||
<div *ngIf="isFilterServiceActive">
|
||||
<adf-header-filter-template>
|
||||
<ng-template let-col>
|
||||
<adf-search-filter-container [col]="col"
|
||||
[value]="value"
|
||||
(filterChange)="onFilterSelectionChange()">
|
||||
</adf-search-filter-container>
|
||||
</ng-template>
|
||||
</adf-header-filter-template>
|
||||
</div>
|
@@ -0,0 +1,151 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { Subject, BehaviorSubject } from 'rxjs';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SearchService, setupTestBed, DataTableComponent, DataSorting } from '@alfresco/adf-core';
|
||||
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { SearchFilterQueryBuilderService } from './../../../search/search-filter-query-builder.service';
|
||||
import { SEARCH_QUERY_SERVICE_TOKEN } from './../../../search/search-query-service.token';
|
||||
import { DocumentListComponent } from './../document-list.component';
|
||||
import { FilterHeaderComponent } from './filter-header.component';
|
||||
import { Pagination } from '@alfresco/js-api';
|
||||
|
||||
describe('FilterHeaderComponent', () => {
|
||||
let fixture: ComponentFixture<FilterHeaderComponent>;
|
||||
let component: FilterHeaderComponent;
|
||||
let queryBuilder: SearchFilterQueryBuilderService;
|
||||
|
||||
const searchMock: any = {
|
||||
dataLoaded: new Subject()
|
||||
};
|
||||
|
||||
const paginationMock = <Pagination> { maxItems: 10, skipCount: 0 };
|
||||
|
||||
const documentListMock = {
|
||||
node: 'my-node',
|
||||
sorting: ['name', 'asc'],
|
||||
pagination: new BehaviorSubject<Pagination>(paginationMock),
|
||||
sortingSubject: new BehaviorSubject<DataSorting[]>([]),
|
||||
reload: () => jasmine.createSpy('reload')
|
||||
};
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
ContentTestingModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: SearchService, useValue: searchMock },
|
||||
{ provide: SEARCH_QUERY_SERVICE_TOKEN, useClass: SearchFilterQueryBuilderService },
|
||||
{ provide: DocumentListComponent, useValue: documentListMock },
|
||||
DataTableComponent
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FilterHeaderComponent);
|
||||
component = fixture.componentInstance;
|
||||
queryBuilder = fixture.componentInstance['searchFilterQueryBuilder'];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should subscribe to changes in document list pagination', async () => {
|
||||
const setupCurrentPaginationSpy = spyOn(queryBuilder, 'setupCurrentPagination');
|
||||
|
||||
const currentFolderNodeIdChange = new SimpleChange('current-node-id', 'next-node-id', true);
|
||||
component.ngOnChanges({ currentFolderId: currentFolderNodeIdChange });
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(setupCurrentPaginationSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should subscribe to changes in document list sorting', async () => {
|
||||
const setSortingSpy = spyOn(queryBuilder, 'setSorting');
|
||||
|
||||
const currentFolderNodeIdChange = new SimpleChange('current-node-id', 'next-node-id', true);
|
||||
component.ngOnChanges({ currentFolderId: currentFolderNodeIdChange });
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(setSortingSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reset filters after changing the folder node', async () => {
|
||||
const resetActiveFiltersSpy = spyOn(queryBuilder, 'resetActiveFilters');
|
||||
spyOn(queryBuilder, 'isCustomSourceNode').and.returnValue(false);
|
||||
|
||||
const currentFolderNodeIdChange = new SimpleChange('current-node-id', 'next-node-id', true);
|
||||
component.ngOnChanges({ currentFolderId: currentFolderNodeIdChange });
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(resetActiveFiltersSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should init filters after changing the folder node', async () => {
|
||||
const setCurrentRootFolderIdSpy = spyOn(queryBuilder, 'setCurrentRootFolderId');
|
||||
spyOn(queryBuilder, 'isCustomSourceNode').and.returnValue(false);
|
||||
const currentFolderNodeIdChange = new SimpleChange('current-node-id', 'next-node-id', true);
|
||||
component.ngOnChanges({ currentFolderId: currentFolderNodeIdChange });
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
expect(setCurrentRootFolderIdSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set active filters when an initial value is set', async () => {
|
||||
spyOn(queryBuilder, 'setCurrentRootFolderId');
|
||||
spyOn(queryBuilder, 'isCustomSourceNode').and.returnValue(false);
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
expect(queryBuilder.getActiveFilters().length).toBe(0);
|
||||
|
||||
const initialFilterValue = { name: 'pinocchio'};
|
||||
component.value = initialFilterValue;
|
||||
const currentFolderNodeIdChange = new SimpleChange('current-node-id', 'next-node-id', true);
|
||||
component.ngOnChanges({ currentFolderId: currentFolderNodeIdChange });
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(queryBuilder.getActiveFilters().length).toBe(1);
|
||||
expect(queryBuilder.getActiveFilters()[0].key).toBe('name');
|
||||
expect(queryBuilder.getActiveFilters()[0].value).toBe('pinocchio');
|
||||
});
|
||||
|
||||
it('should emit filterSelection when a filter is changed', async (done) => {
|
||||
spyOn(queryBuilder, 'getActiveFilters').and.returnValue([{ key: 'name', value: 'pinocchio' }]);
|
||||
|
||||
component.filterSelection.subscribe((selectedFilters) => {
|
||||
expect(selectedFilters.length).toBe(1);
|
||||
expect(selectedFilters[0].key).toBe('name');
|
||||
expect(selectedFilters[0].value).toBe('pinocchio');
|
||||
done();
|
||||
});
|
||||
|
||||
component.onFilterSelectionChange();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,126 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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, Inject, OnInit, OnChanges, SimpleChanges, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { PaginationModel, DataSorting } from '@alfresco/adf-core';
|
||||
import { DocumentListComponent } from '../document-list.component';
|
||||
import { SEARCH_QUERY_SERVICE_TOKEN } from '../../../search/search-query-service.token';
|
||||
import { SearchFilterQueryBuilderService } from '../../../search/search-filter-query-builder.service';
|
||||
import { FilterSearch } from './../../../search/filter-search.interface';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { NodePaging, MinimalNode } from '@alfresco/js-api';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-filter-header',
|
||||
templateUrl: './filter-header.component.html',
|
||||
providers: [{ provide: SEARCH_QUERY_SERVICE_TOKEN, useClass: SearchFilterQueryBuilderService}]
|
||||
})
|
||||
export class FilterHeaderComponent implements OnInit, OnChanges {
|
||||
|
||||
/** (optional) Initial filter value to sort . */
|
||||
@Input()
|
||||
value: any = {};
|
||||
|
||||
/** The id of the current folder of the document list. */
|
||||
@Input()
|
||||
currentFolderId: string;
|
||||
|
||||
/** Emitted when a filter value is selected */
|
||||
@Output()
|
||||
filterSelection: EventEmitter<FilterSearch[]> = new EventEmitter();
|
||||
|
||||
isFilterServiceActive: boolean;
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
|
||||
constructor(@Inject(DocumentListComponent) private documentList: DocumentListComponent,
|
||||
@Inject(SEARCH_QUERY_SERVICE_TOKEN) private searchFilterQueryBuilder: SearchFilterQueryBuilderService) {
|
||||
this.isFilterServiceActive = this.searchFilterQueryBuilder.isFilterServiceActive();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.searchFilterQueryBuilder.executed
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe((newNodePaging: NodePaging) => {
|
||||
this.documentList.node = newNodePaging;
|
||||
this.documentList.reload();
|
||||
});
|
||||
|
||||
this.initDataPagination();
|
||||
this.initDataSorting();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['currentFolderId'] && changes['currentFolderId'].currentValue) {
|
||||
this.resetFilterHeader();
|
||||
this.configureSearchParent(changes['currentFolderId'].currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
onFilterSelectionChange() {
|
||||
this.filterSelection.emit(this.searchFilterQueryBuilder.getActiveFilters());
|
||||
if (this.searchFilterQueryBuilder.isNoFilterActive()) {
|
||||
this.documentList.node = null;
|
||||
this.documentList.reload();
|
||||
}
|
||||
}
|
||||
|
||||
resetFilterHeader() {
|
||||
this.searchFilterQueryBuilder.resetActiveFilters();
|
||||
}
|
||||
|
||||
initDataPagination() {
|
||||
this.documentList.pagination
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe((newPagination: PaginationModel) => {
|
||||
this.searchFilterQueryBuilder.setupCurrentPagination(newPagination.maxItems, newPagination.skipCount);
|
||||
});
|
||||
}
|
||||
|
||||
initDataSorting() {
|
||||
this.documentList.sortingSubject
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe((sorting: DataSorting[]) => {
|
||||
this.searchFilterQueryBuilder.setSorting(sorting);
|
||||
});
|
||||
}
|
||||
|
||||
private configureSearchParent(currentFolderId: string) {
|
||||
if (this.searchFilterQueryBuilder.isCustomSourceNode(currentFolderId)) {
|
||||
this.searchFilterQueryBuilder.getNodeIdForCustomSource(currentFolderId).subscribe((node: MinimalNode) => {
|
||||
this.initSearchHeader(node.id);
|
||||
});
|
||||
} else {
|
||||
this.initSearchHeader(currentFolderId);
|
||||
}
|
||||
}
|
||||
|
||||
private initSearchHeader(currentFolderId: string) {
|
||||
this.searchFilterQueryBuilder.setCurrentRootFolderId(currentFolderId);
|
||||
if (this.value) {
|
||||
Object.keys(this.value).forEach((columnKey) => {
|
||||
this.searchFilterQueryBuilder.setActiveFilter(columnKey, this.value[columnKey]);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
}
|
@@ -32,6 +32,8 @@ import { LibraryStatusColumnComponent } from './components/library-status-column
|
||||
import { LibraryRoleColumnComponent } from './components/library-role-column/library-role-column.component';
|
||||
import { LibraryNameColumnComponent } from './components/library-name-column/library-name-column.component';
|
||||
import { NameColumnComponent } from './components/name-column/name-column.component';
|
||||
import { FilterHeaderComponent } from './components/filter-header/filter-header.component';
|
||||
import { SearchModule } from './../search/search.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -40,7 +42,8 @@ import { NameColumnComponent } from './components/name-column/name-column.compon
|
||||
FlexLayoutModule,
|
||||
MaterialModule,
|
||||
UploadModule,
|
||||
EditJsonDialogModule
|
||||
EditJsonDialogModule,
|
||||
SearchModule
|
||||
],
|
||||
declarations: [
|
||||
DocumentListComponent,
|
||||
@@ -50,7 +53,8 @@ import { NameColumnComponent } from './components/name-column/name-column.compon
|
||||
LibraryNameColumnComponent,
|
||||
NameColumnComponent,
|
||||
ContentActionComponent,
|
||||
ContentActionListComponent
|
||||
ContentActionListComponent,
|
||||
FilterHeaderComponent
|
||||
],
|
||||
exports: [
|
||||
DocumentListComponent,
|
||||
@@ -60,7 +64,8 @@ import { NameColumnComponent } from './components/name-column/name-column.compon
|
||||
LibraryNameColumnComponent,
|
||||
NameColumnComponent,
|
||||
ContentActionComponent,
|
||||
ContentActionListComponent
|
||||
ContentActionListComponent,
|
||||
FilterHeaderComponent
|
||||
]
|
||||
})
|
||||
export class DocumentListModule {}
|
||||
|
@@ -46,14 +46,14 @@ export abstract class BaseQueryBuilderService {
|
||||
executed = new Subject<ResultSetPaging>();
|
||||
error = new Subject();
|
||||
|
||||
categories: Array<SearchCategory> = [];
|
||||
categories: SearchCategory[] = [];
|
||||
queryFragments: { [id: string]: string } = {};
|
||||
filterQueries: FilterQuery[] = [];
|
||||
paging: { maxItems?: number; skipCount?: number } = null;
|
||||
sorting: Array<SearchSortingDefinition> = [];
|
||||
sortingOptions: Array<SearchSortingDefinition> = [];
|
||||
sorting: SearchSortingDefinition[] = [];
|
||||
sortingOptions: SearchSortingDefinition[] = [];
|
||||
|
||||
protected userFacetBuckets: { [key: string]: Array<FacetFieldBucket> } = {};
|
||||
protected userFacetBuckets: { [key: string]: FacetFieldBucket[] } = {};
|
||||
|
||||
get userQuery(): string {
|
||||
return this._userQuery;
|
||||
|
@@ -0,0 +1,49 @@
|
||||
<div *ngIf="!!category"
|
||||
class="adf-filter">
|
||||
<button mat-icon-button
|
||||
[matMenuTriggerFor]="filter"
|
||||
id="filter-menu-button"
|
||||
#menuTrigger="matMenuTrigger"
|
||||
(click)="$event.stopPropagation()"
|
||||
(menuOpened)="onMenuOpen()"
|
||||
(keyup.enter)="$event.stopPropagation()"
|
||||
class="adf-filter-button"
|
||||
[matTooltip]="getTooltipTranslation(col?.title)">
|
||||
<adf-icon value="adf:filter"
|
||||
[ngClass]="{ 'adf-icon-active': isActive() || menuTrigger.menuOpen }"
|
||||
matBadge="filter"
|
||||
matBadgeColor="warn"
|
||||
[matBadgeHidden]="!isActive()">
|
||||
</adf-icon>
|
||||
</button>
|
||||
|
||||
<mat-menu #filter="matMenu"
|
||||
class="adf-filter-menu"
|
||||
(closed)="onClosed()">
|
||||
<div #filterContainer
|
||||
(keydown.tab)="$event.stopPropagation();">
|
||||
<div (click)="$event.stopPropagation()"
|
||||
class="adf-filter-container">
|
||||
<div class="adf-filter-title">{{ category?.name | translate }}</div>
|
||||
<adf-search-widget-container (keypress)="onKeyPressed($event, menuTrigger)"
|
||||
[id]="category?.id"
|
||||
[selector]="category?.component?.selector"
|
||||
[settings]="category?.component?.settings"
|
||||
[value]="initialValue">
|
||||
</adf-search-widget-container>
|
||||
</div>
|
||||
<mat-dialog-actions class="adf-filter-actions">
|
||||
<button mat-button
|
||||
id="clear-filter-button"
|
||||
(click)="onClearButtonClick($event)">{{ 'SEARCH.SEARCH_HEADER.CLEAR' | translate | uppercase }}
|
||||
</button>
|
||||
<button mat-button
|
||||
color="primary"
|
||||
id="apply-filter-button"
|
||||
class="adf-filter-apply-button"
|
||||
(click)="onApply()">{{ 'SEARCH.SEARCH_HEADER.APPLY' | translate | uppercase }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
</div>
|
||||
</mat-menu>
|
||||
</div>
|
@@ -18,16 +18,16 @@ import { Subject } from 'rxjs';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SearchService, setupTestBed, AlfrescoApiService } from '@alfresco/adf-core';
|
||||
import { SearchHeaderComponent } from './search-header.component';
|
||||
import { SearchHeaderQueryBuilderService } from '../../search-header-query-builder.service';
|
||||
import { SearchFilterQueryBuilderService } from '../../search-filter-query-builder.service';
|
||||
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
||||
import { fakeNodePaging } from '../../../mock';
|
||||
import { fakeNodePaging } from './../../../mock/document-list.component.mock';
|
||||
import { SEARCH_QUERY_SERVICE_TOKEN } from '../../search-query-service.token';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { SearchFilterContainerComponent } from './search-filter-container.component';
|
||||
import { MatMenuTrigger } from '@angular/material/menu';
|
||||
import { SearchCategory } from '../../search-category.interface';
|
||||
|
||||
const mockCategory: any = {
|
||||
const mockCategory: SearchCategory = {
|
||||
'id': 'queryName',
|
||||
'name': 'Name',
|
||||
'columnKey': 'name',
|
||||
@@ -43,10 +43,10 @@ const mockCategory: any = {
|
||||
}
|
||||
};
|
||||
|
||||
describe('SearchHeaderComponent', () => {
|
||||
let fixture: ComponentFixture<SearchHeaderComponent>;
|
||||
let component: SearchHeaderComponent;
|
||||
let queryBuilder: SearchHeaderQueryBuilderService;
|
||||
describe('SearchFilterContainerComponent', () => {
|
||||
let fixture: ComponentFixture<SearchFilterContainerComponent>;
|
||||
let component: SearchFilterContainerComponent;
|
||||
let queryBuilder: SearchFilterQueryBuilderService;
|
||||
let alfrescoApiService: AlfrescoApiService;
|
||||
|
||||
const searchMock: any = {
|
||||
@@ -60,14 +60,14 @@ describe('SearchHeaderComponent', () => {
|
||||
],
|
||||
providers: [
|
||||
{ provide: SearchService, useValue: searchMock },
|
||||
{ provide: SEARCH_QUERY_SERVICE_TOKEN, useClass: SearchHeaderQueryBuilderService }
|
||||
{ provide: SEARCH_QUERY_SERVICE_TOKEN, useClass: SearchFilterQueryBuilderService }
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SearchHeaderComponent);
|
||||
fixture = TestBed.createComponent(SearchFilterContainerComponent);
|
||||
component = fixture.componentInstance;
|
||||
queryBuilder = fixture.componentInstance['searchHeaderQueryBuilder'];
|
||||
queryBuilder = fixture.componentInstance['searchFilterQueryBuilder'];
|
||||
alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
||||
component.col = {key: '123', type: 'text'};
|
||||
spyOn(queryBuilder, 'getCategoryForColumn').and.returnValue(mockCategory);
|
||||
@@ -79,17 +79,33 @@ describe('SearchHeaderComponent', () => {
|
||||
});
|
||||
|
||||
it('should show the filter when a category is found', async () => {
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
expect(queryBuilder.isFilterServiceActive()).toBe(true);
|
||||
const element = fixture.nativeElement.querySelector('.adf-filter');
|
||||
expect(element).not.toBeNull();
|
||||
expect(element).not.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should emit the node paging received from the queryBuilder after the Apply button is clicked', async (done) => {
|
||||
it('should set new active filter after the Apply button is clicked', async () => {
|
||||
const menuButton: HTMLButtonElement = fixture.nativeElement.querySelector('#filter-menu-button');
|
||||
menuButton.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
component.widgetContainer.componentRef.instance.value = 'searchText';
|
||||
const applyButton = fixture.debugElement.query(By.css('#apply-filter-button'));
|
||||
applyButton.triggerEventHandler('click', {});
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
expect(queryBuilder.getActiveFilters().length).toBe(1);
|
||||
expect(queryBuilder.getActiveFilters()[0].key).toBe('name');
|
||||
expect(queryBuilder.getActiveFilters()[0].value).toBe('searchText');
|
||||
});
|
||||
|
||||
it('should emit filterChange after the Apply button is clicked', async (done) => {
|
||||
spyOn(alfrescoApiService.searchApi, 'search').and.returnValue(Promise.resolve(fakeNodePaging));
|
||||
spyOn(queryBuilder, 'buildQuery').and.returnValue({});
|
||||
component.update.subscribe((newNodePaging) => {
|
||||
expect(newNodePaging).toBe(fakeNodePaging);
|
||||
component.filterChange.subscribe(() => {
|
||||
done();
|
||||
});
|
||||
const menuButton: HTMLButtonElement = fixture.nativeElement.querySelector('#filter-menu-button');
|
||||
@@ -103,11 +119,42 @@ describe('SearchHeaderComponent', () => {
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should emit the node paging received from the queryBuilder after the Enter key is pressed', async (done) => {
|
||||
it('should remove active filter after the Clear button is clicked', async () => {
|
||||
queryBuilder.setActiveFilter('name', 'searchText');
|
||||
const menuButton: HTMLButtonElement = fixture.nativeElement.querySelector('#filter-menu-button');
|
||||
menuButton.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
component.widgetContainer.componentRef.instance.value = 'searchText';
|
||||
const fakeEvent = jasmine.createSpyObj('event', ['stopPropagation']);
|
||||
const clearButton = fixture.debugElement.query(By.css('#clear-filter-button'));
|
||||
clearButton.triggerEventHandler('click', fakeEvent);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
expect(queryBuilder.getActiveFilters().length).toBe(0);
|
||||
});
|
||||
|
||||
it('should emit filterChange after the Clear button is clicked', async (done) => {
|
||||
spyOn(alfrescoApiService.searchApi, 'search').and.returnValue(Promise.resolve(fakeNodePaging));
|
||||
spyOn(queryBuilder, 'buildQuery').and.returnValue({});
|
||||
component.update.subscribe((newNodePaging) => {
|
||||
expect(newNodePaging).toBe(fakeNodePaging);
|
||||
component.filterChange.subscribe(() => {
|
||||
done();
|
||||
});
|
||||
const menuButton: HTMLButtonElement = fixture.nativeElement.querySelector('#filter-menu-button');
|
||||
menuButton.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
component.widgetContainer.componentRef.instance.value = 'searchText';
|
||||
const fakeEvent = jasmine.createSpyObj('event', ['stopPropagation']);
|
||||
const clearButton = fixture.debugElement.query(By.css('#clear-filter-button'));
|
||||
clearButton.triggerEventHandler('click', fakeEvent);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should emit filterChange after the Enter key is pressed', async (done) => {
|
||||
spyOn(queryBuilder, 'buildQuery').and.returnValue({});
|
||||
component.filterChange.subscribe(() => {
|
||||
done();
|
||||
});
|
||||
const menuButton: HTMLButtonElement = fixture.nativeElement.querySelector('#filter-menu-button');
|
||||
@@ -121,134 +168,6 @@ describe('SearchHeaderComponent', () => {
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should execute a new query when the page size is changed', async (done) => {
|
||||
spyOn(alfrescoApiService.searchApi, 'search').and.returnValue(Promise.resolve(fakeNodePaging));
|
||||
spyOn(queryBuilder, 'buildQuery').and.returnValue({});
|
||||
component.update.subscribe((newNodePaging) => {
|
||||
expect(newNodePaging).toBe(fakeNodePaging);
|
||||
done();
|
||||
});
|
||||
|
||||
const maxItem = new SimpleChange(10, 20, false);
|
||||
component.ngOnChanges({ 'maxItems': maxItem });
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should execute a new query when a new page is requested', async (done) => {
|
||||
spyOn(alfrescoApiService.searchApi, 'search').and.returnValue(Promise.resolve(fakeNodePaging));
|
||||
spyOn(queryBuilder, 'buildQuery').and.returnValue({});
|
||||
component.update.subscribe((newNodePaging) => {
|
||||
expect(newNodePaging).toBe(fakeNodePaging);
|
||||
done();
|
||||
});
|
||||
|
||||
const skipCount = new SimpleChange(0, 10, false);
|
||||
component.ngOnChanges({ 'skipCount': skipCount });
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should execute a new query when a new sorting is requested', async (done) => {
|
||||
spyOn(alfrescoApiService.searchApi, 'search').and.returnValue(Promise.resolve(fakeNodePaging));
|
||||
spyOn(queryBuilder, 'buildQuery').and.returnValue({});
|
||||
component.update.subscribe((newNodePaging) => {
|
||||
expect(newNodePaging).toBe(fakeNodePaging);
|
||||
done();
|
||||
});
|
||||
|
||||
const skipCount = new SimpleChange(null, '123-asc', false);
|
||||
component.ngOnChanges({ 'sorting': skipCount });
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should emit the clear event when no filter has been selected', async (done) => {
|
||||
spyOn(queryBuilder, 'isNoFilterActive').and.returnValue(true);
|
||||
spyOn(alfrescoApiService.searchApi, 'search').and.returnValue(Promise.resolve(fakeNodePaging));
|
||||
spyOn(queryBuilder, 'buildQuery').and.returnValue({});
|
||||
spyOn(component.widgetContainer, 'resetInnerWidget').and.stub();
|
||||
spyOn(component, 'isActive').and.returnValue(true);
|
||||
const fakeEvent = jasmine.createSpyObj('event', ['stopPropagation']);
|
||||
component.clear.subscribe(() => {
|
||||
done();
|
||||
});
|
||||
|
||||
const menuButton: HTMLButtonElement = fixture.nativeElement.querySelector('#filter-menu-button');
|
||||
menuButton.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const clearButton = fixture.debugElement.query(By.css('#clear-filter-button'));
|
||||
clearButton.triggerEventHandler('click', fakeEvent);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should execute the query again if there are more filter actives after a clear', async (done) => {
|
||||
spyOn(queryBuilder, 'isNoFilterActive').and.returnValue(false);
|
||||
spyOn(alfrescoApiService.searchApi, 'search').and.returnValue(Promise.resolve(fakeNodePaging));
|
||||
spyOn(queryBuilder, 'buildQuery').and.returnValue({});
|
||||
spyOn(component, 'isActive').and.returnValue(true);
|
||||
queryBuilder.queryFragments['fake'] = 'test';
|
||||
spyOn(component.widgetContainer, 'resetInnerWidget').and.callThrough();
|
||||
const fakeEvent = jasmine.createSpyObj('event', ['stopPropagation']);
|
||||
const menuButton: HTMLButtonElement = fixture.nativeElement.querySelector('#filter-menu-button');
|
||||
component.update.subscribe((newNodePaging) => {
|
||||
expect(newNodePaging).toBe(fakeNodePaging);
|
||||
done();
|
||||
});
|
||||
|
||||
menuButton.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const clearButton = fixture.debugElement.query(By.css('#clear-filter-button'));
|
||||
clearButton.triggerEventHandler('click', fakeEvent);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should emit the clear event when no filter has valued applied', async (done) => {
|
||||
spyOn(queryBuilder, 'isNoFilterActive').and.returnValue(true);
|
||||
spyOn(alfrescoApiService.searchApi, 'search').and.returnValue(Promise.resolve(fakeNodePaging));
|
||||
spyOn(queryBuilder, 'buildQuery').and.returnValue({});
|
||||
spyOn(component, 'isActive').and.returnValue(true);
|
||||
spyOn(component.widgetContainer, 'resetInnerWidget').and.stub();
|
||||
component.widgetContainer.componentRef.instance.value = '';
|
||||
const fakeEvent = jasmine.createSpyObj('event', ['stopPropagation']);
|
||||
component.clear.subscribe(() => {
|
||||
done();
|
||||
});
|
||||
|
||||
const menuButton: HTMLButtonElement = fixture.nativeElement.querySelector('#filter-menu-button');
|
||||
menuButton.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const applyButton = fixture.debugElement.query(By.css('#apply-filter-button'));
|
||||
applyButton.triggerEventHandler('click', fakeEvent);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should not emit clear event when currentFolderNodeId changes and no filter was applied', async () => {
|
||||
const currentFolderNodeIdChange = new SimpleChange('current-node-id', 'next-node-id', true);
|
||||
spyOn(component, 'isActive').and.returnValue(false);
|
||||
spyOn(component.clear, 'emit');
|
||||
|
||||
component.ngOnChanges({ currentFolderNodeId: currentFolderNodeIdChange });
|
||||
fixture.detectChanges();
|
||||
expect(component.clear.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit clear event when currentFolderNodeId changes and filter was applied', async () => {
|
||||
const currentFolderNodeIdChange = new SimpleChange('current-node-id', 'next-node-id', true);
|
||||
spyOn(component.clear, 'emit');
|
||||
spyOn(component, 'isActive').and.returnValue(true);
|
||||
|
||||
component.ngOnChanges({ currentFolderNodeId: currentFolderNodeIdChange });
|
||||
fixture.detectChanges();
|
||||
expect(component.clear.emit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
|
||||
it('should set up a focus trap on the filter when the menu is opened', async () => {
|
@@ -0,0 +1,136 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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,
|
||||
Input,
|
||||
Output,
|
||||
OnInit,
|
||||
EventEmitter,
|
||||
ViewEncapsulation,
|
||||
ViewChild,
|
||||
Inject,
|
||||
OnDestroy,
|
||||
ElementRef
|
||||
} from '@angular/core';
|
||||
import { ConfigurableFocusTrapFactory, ConfigurableFocusTrap } from '@angular/cdk/a11y';
|
||||
import { DataColumn, TranslationService } from '@alfresco/adf-core';
|
||||
import { SearchWidgetContainerComponent } from '../search-widget-container/search-widget-container.component';
|
||||
import { SearchFilterQueryBuilderService } from '../../search-filter-query-builder.service';
|
||||
import { SearchCategory } from '../../search-category.interface';
|
||||
import { SEARCH_QUERY_SERVICE_TOKEN } from '../../search-query-service.token';
|
||||
import { Subject } from 'rxjs';
|
||||
import { MatMenuTrigger } from '@angular/material/menu';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search-filter-container',
|
||||
templateUrl: './search-filter-container.component.html',
|
||||
styleUrls: ['./search-filter-container.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class SearchFilterContainerComponent implements OnInit, OnDestroy {
|
||||
|
||||
/** The column the filter will be applied on. */
|
||||
@Input()
|
||||
col: DataColumn;
|
||||
|
||||
/** The column the filter will be applied on. */
|
||||
@Input()
|
||||
value: any;
|
||||
|
||||
/** Emitted when a filter value is selected */
|
||||
@Output()
|
||||
filterChange: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
@ViewChild(SearchWidgetContainerComponent)
|
||||
widgetContainer: SearchWidgetContainerComponent;
|
||||
|
||||
@ViewChild('filterContainer')
|
||||
filterContainer: ElementRef;
|
||||
|
||||
category: SearchCategory;
|
||||
focusTrap: ConfigurableFocusTrap;
|
||||
initialValue: any;
|
||||
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
|
||||
constructor(@Inject(SEARCH_QUERY_SERVICE_TOKEN) private searchFilterQueryBuilder: SearchFilterQueryBuilderService,
|
||||
private translationService: TranslationService,
|
||||
private focusTrapFactory: ConfigurableFocusTrapFactory) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.category = this.searchFilterQueryBuilder.getCategoryForColumn(this.col.key);
|
||||
this.initialValue = this.value && this.value[this.col.key] ? this.value[this.col.key] : undefined;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
onKeyPressed(event: KeyboardEvent, menuTrigger: MatMenuTrigger) {
|
||||
if (event.key === 'Enter' && this.widgetContainer.selector !== 'check-list') {
|
||||
this.onApply();
|
||||
menuTrigger.closeMenu();
|
||||
}
|
||||
}
|
||||
|
||||
onApply() {
|
||||
if (this.widgetContainer.hasValueSelected()) {
|
||||
this.searchFilterQueryBuilder.setActiveFilter(this.category.columnKey, this.widgetContainer.getCurrentValue());
|
||||
this.filterChange.emit();
|
||||
this.widgetContainer.applyInnerWidget();
|
||||
} else {
|
||||
this.resetSearchFilter();
|
||||
}
|
||||
}
|
||||
|
||||
onClearButtonClick(event: Event) {
|
||||
event.stopPropagation();
|
||||
this.resetSearchFilter();
|
||||
}
|
||||
|
||||
resetSearchFilter() {
|
||||
this.widgetContainer.resetInnerWidget();
|
||||
this.searchFilterQueryBuilder.removeActiveFilter(this.category.columnKey);
|
||||
this.filterChange.emit();
|
||||
}
|
||||
|
||||
getTooltipTranslation(columnTitle: string): string {
|
||||
if (!columnTitle) {
|
||||
columnTitle = 'SEARCH.SEARCH_HEADER.TYPE';
|
||||
}
|
||||
return this.translationService.instant('SEARCH.SEARCH_HEADER.FILTER_BY', { category: this.translationService.instant(columnTitle) });
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.widgetContainer && this.widgetContainer.componentRef && this.widgetContainer.componentRef.instance.isActive;
|
||||
}
|
||||
|
||||
onMenuOpen() {
|
||||
if (this.filterContainer && !this.focusTrap) {
|
||||
this.focusTrap = this.focusTrapFactory.create(this.filterContainer.nativeElement);
|
||||
this.focusTrap.focusInitialElement();
|
||||
}
|
||||
}
|
||||
|
||||
onClosed() {
|
||||
this.focusTrap.destroy();
|
||||
this.focusTrap = null;
|
||||
}
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
<div *ngIf="isFilterServiceActive">
|
||||
<div *ngIf="!!category" class="adf-filter">
|
||||
<button mat-icon-button [matMenuTriggerFor]="filter"
|
||||
id="filter-menu-button"
|
||||
#menuTrigger="matMenuTrigger"
|
||||
(click)="$event.stopPropagation()"
|
||||
(menuOpened)="onMenuOpen()"
|
||||
(keyup.enter)="$event.stopPropagation()"
|
||||
class="adf-filter-button"
|
||||
[matTooltip]="getTooltipTranslation(col?.title)">
|
||||
<adf-icon value="adf:filter"
|
||||
[ngClass]="{ 'adf-icon-active': isActive() || menuTrigger.menuOpen }"
|
||||
matBadge="filter"
|
||||
matBadgeColor="warn"
|
||||
[matBadgeHidden]="!isActive()"></adf-icon>
|
||||
</button>
|
||||
|
||||
<mat-menu #filter="matMenu" class="adf-filter-menu" (closed)="onClosed()">
|
||||
<div #filterContainer (keydown.tab)="$event.stopPropagation();">
|
||||
<div (click)="$event.stopPropagation()" class="adf-filter-container">
|
||||
<div class="adf-filter-title">{{ category?.name | translate }}</div>
|
||||
<adf-search-widget-container
|
||||
(keypress)="onKeyPressed($event, menuTrigger)"
|
||||
[id]="category?.id"
|
||||
[selector]="category?.component?.selector"
|
||||
[settings]="category?.component?.settings"
|
||||
[value]="initialValue">
|
||||
</adf-search-widget-container>
|
||||
</div>
|
||||
<mat-dialog-actions class="adf-filter-actions">
|
||||
<button mat-button id="clear-filter-button"
|
||||
(click)="onClearButtonClick($event)">{{ 'SEARCH.SEARCH_HEADER.CLEAR' | translate | uppercase }}
|
||||
</button>
|
||||
<button mat-button color="primary"
|
||||
id="apply-filter-button"
|
||||
class="adf-filter-apply-button"
|
||||
(click)="onApply()">{{ 'SEARCH.SEARCH_HEADER.APPLY' | translate | uppercase }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
</div>
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
@@ -1,225 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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,
|
||||
Input,
|
||||
Output,
|
||||
OnInit,
|
||||
OnChanges,
|
||||
EventEmitter,
|
||||
SimpleChanges,
|
||||
ViewEncapsulation,
|
||||
ViewChild,
|
||||
Inject,
|
||||
OnDestroy,
|
||||
ElementRef
|
||||
} from '@angular/core';
|
||||
import { ConfigurableFocusTrapFactory, ConfigurableFocusTrap } from '@angular/cdk/a11y';
|
||||
import { DataColumn, TranslationService } from '@alfresco/adf-core';
|
||||
import { SearchWidgetContainerComponent } from '../search-widget-container/search-widget-container.component';
|
||||
import { SearchHeaderQueryBuilderService } from '../../search-header-query-builder.service';
|
||||
import { NodePaging, MinimalNode } from '@alfresco/js-api';
|
||||
import { SearchCategory } from '../../search-category.interface';
|
||||
import { SEARCH_QUERY_SERVICE_TOKEN } from '../../search-query-service.token';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { MatMenuTrigger } from '@angular/material/menu';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search-header',
|
||||
templateUrl: './search-header.component.html',
|
||||
styleUrls: ['./search-header.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class SearchHeaderComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
/** The column the filter will be applied on. */
|
||||
@Input()
|
||||
col: DataColumn;
|
||||
|
||||
/** (optional) Initial filter value to sort . */
|
||||
@Input()
|
||||
value: any;
|
||||
|
||||
/** The id of the current folder of the document list. */
|
||||
@Input()
|
||||
currentFolderNodeId: string;
|
||||
|
||||
/** Maximum number of search results to show in a page. */
|
||||
@Input()
|
||||
maxItems: number;
|
||||
|
||||
/** The offset of the start of the page within the results list. */
|
||||
@Input()
|
||||
skipCount: number;
|
||||
|
||||
/** The sorting to apply to the the filter. */
|
||||
@Input()
|
||||
sorting: string = null;
|
||||
|
||||
/** Emitted when the result of the filter is received from the API. */
|
||||
@Output()
|
||||
update: EventEmitter<NodePaging> = new EventEmitter();
|
||||
|
||||
/** Emitted when the last of all the filters is cleared. */
|
||||
@Output()
|
||||
clear: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
/** Emitted when a filter value is selected */
|
||||
@Output()
|
||||
selection: EventEmitter<Map<string, string>> = new EventEmitter();
|
||||
|
||||
@ViewChild(SearchWidgetContainerComponent)
|
||||
widgetContainer: SearchWidgetContainerComponent;
|
||||
|
||||
@ViewChild('filterContainer')
|
||||
filterContainer: ElementRef;
|
||||
|
||||
category: SearchCategory;
|
||||
isFilterServiceActive: boolean;
|
||||
initialValue: any;
|
||||
focusTrap: ConfigurableFocusTrap;
|
||||
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
|
||||
constructor(@Inject(SEARCH_QUERY_SERVICE_TOKEN) private searchHeaderQueryBuilder: SearchHeaderQueryBuilderService,
|
||||
private translationService: TranslationService,
|
||||
private focusTrapFactory: ConfigurableFocusTrapFactory) {
|
||||
this.isFilterServiceActive = this.searchHeaderQueryBuilder.isFilterServiceActive();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.category = this.searchHeaderQueryBuilder.getCategoryForColumn(
|
||||
this.col.key
|
||||
);
|
||||
|
||||
this.searchHeaderQueryBuilder.executed
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe((newNodePaging: NodePaging) => {
|
||||
this.update.emit(newNodePaging);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['currentFolderNodeId'] && changes['currentFolderNodeId'].currentValue) {
|
||||
this.clearHeader();
|
||||
this.configureSearchParent(changes['currentFolderNodeId'].currentValue);
|
||||
}
|
||||
|
||||
if (changes['maxItems'] || changes['skipCount']) {
|
||||
let actualMaxItems = this.maxItems;
|
||||
let actualSkipCount = this.skipCount;
|
||||
|
||||
if (changes['maxItems'] && changes['maxItems'].currentValue) {
|
||||
actualMaxItems = changes['maxItems'].currentValue;
|
||||
}
|
||||
if (changes['skipCount'] && changes['skipCount'].currentValue) {
|
||||
actualSkipCount = changes['skipCount'].currentValue;
|
||||
}
|
||||
|
||||
this.searchHeaderQueryBuilder.setupCurrentPagination(actualMaxItems, actualSkipCount);
|
||||
}
|
||||
|
||||
if (changes['sorting'] && changes['sorting'].currentValue) {
|
||||
const [key, value] = changes['sorting'].currentValue.split('-');
|
||||
if (key === this.col.key) {
|
||||
this.searchHeaderQueryBuilder.setSorting(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
onKeyPressed(event: KeyboardEvent, menuTrigger: MatMenuTrigger) {
|
||||
if (event.key === 'Enter' && this.widgetContainer.selector !== 'check-list') {
|
||||
this.onApply();
|
||||
menuTrigger.closeMenu();
|
||||
}
|
||||
}
|
||||
|
||||
onApply() {
|
||||
if (this.widgetContainer.hasValueSelected()) {
|
||||
this.widgetContainer.applyInnerWidget();
|
||||
this.searchHeaderQueryBuilder.setActiveFilter(this.category.columnKey, this.widgetContainer.getCurrentValue());
|
||||
this.selection.emit(this.searchHeaderQueryBuilder.getActiveFilters());
|
||||
} else {
|
||||
this.clearHeader();
|
||||
}
|
||||
}
|
||||
|
||||
onClearButtonClick(event: Event) {
|
||||
event.stopPropagation();
|
||||
this.clearHeader();
|
||||
}
|
||||
|
||||
clearHeader() {
|
||||
if (this.widgetContainer && this.isActive()) {
|
||||
this.widgetContainer.resetInnerWidget();
|
||||
this.searchHeaderQueryBuilder.removeActiveFilter(this.category.columnKey);
|
||||
this.selection.emit(this.searchHeaderQueryBuilder.getActiveFilters());
|
||||
if (this.searchHeaderQueryBuilder.isNoFilterActive()) {
|
||||
this.clear.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getTooltipTranslation(columnTitle: string): string {
|
||||
if (!columnTitle) {
|
||||
columnTitle = 'SEARCH.SEARCH_HEADER.TYPE';
|
||||
}
|
||||
return this.translationService.instant('SEARCH.SEARCH_HEADER.FILTER_BY', { category: this.translationService.instant(columnTitle) });
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.widgetContainer && this.widgetContainer.componentRef && this.widgetContainer.componentRef.instance.isActive;
|
||||
}
|
||||
|
||||
private configureSearchParent(currentFolderNodeId: string) {
|
||||
if (this.searchHeaderQueryBuilder.isCustomSourceNode(currentFolderNodeId)) {
|
||||
this.searchHeaderQueryBuilder.getNodeIdForCustomSource(currentFolderNodeId).subscribe((node: MinimalNode) => {
|
||||
this.initSearchHeader(node.id);
|
||||
});
|
||||
} else {
|
||||
this.initSearchHeader(currentFolderNodeId);
|
||||
}
|
||||
}
|
||||
|
||||
private initSearchHeader(currentFolderId: string) {
|
||||
this.searchHeaderQueryBuilder.setCurrentRootFolderId(currentFolderId);
|
||||
if (this.value) {
|
||||
this.searchHeaderQueryBuilder.setActiveFilter(this.category.columnKey, this.initialValue);
|
||||
this.initialValue = this.value;
|
||||
}
|
||||
}
|
||||
|
||||
onMenuOpen() {
|
||||
if (this.filterContainer && !this.focusTrap) {
|
||||
this.focusTrap = this.focusTrapFactory.create(this.filterContainer.nativeElement);
|
||||
this.focusTrap.focusInitialElement();
|
||||
}
|
||||
}
|
||||
|
||||
onClosed() {
|
||||
this.focusTrap.destroy();
|
||||
this.focusTrap = null;
|
||||
}
|
||||
}
|
@@ -15,14 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Directive, ContentChild, TemplateRef } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: 'adf-custom-header-filter-template'
|
||||
})
|
||||
export class CustomHeaderFilterTemplateDirective {
|
||||
|
||||
@ContentChild(TemplateRef)
|
||||
template: any;
|
||||
|
||||
export interface FilterSearch {
|
||||
key: string;
|
||||
value: any;
|
||||
}
|
@@ -19,6 +19,7 @@ export * from './facet-field-bucket.interface';
|
||||
export * from './facet-field.interface';
|
||||
export * from './facet-query.interface';
|
||||
export * from './filter-query.interface';
|
||||
export * from './filter-search.interface';
|
||||
export * from './search-category.interface';
|
||||
export * from './search-widget-settings.interface';
|
||||
export * from './search-widget.interface';
|
||||
@@ -26,7 +27,7 @@ export * from './search-configuration.interface';
|
||||
export * from './search-query-builder.service';
|
||||
export * from './search-range.interface';
|
||||
export * from './search-query-service.token';
|
||||
export * from './search-header-query-builder.service';
|
||||
export * from './search-filter-query-builder.service';
|
||||
|
||||
export * from './components/search.component';
|
||||
export * from './components/search-control.component';
|
||||
@@ -38,7 +39,7 @@ export * from './components/search-chip-list/search-chip-list.component';
|
||||
export * from './components/search-date-range/search-date-range.component';
|
||||
export * from './components/search-filter/search-filter.component';
|
||||
export * from './components/search-filter/search-filter.service';
|
||||
export * from './components/search-header/search-header.component';
|
||||
export * from './components/search-filter-container/search-filter-container.component';
|
||||
export * from './components/search-number-range/search-number-range.component';
|
||||
export * from './components/search-radio/search-radio.component';
|
||||
export * from './components/search-slider/search-slider.component';
|
||||
|
@@ -17,9 +17,9 @@
|
||||
|
||||
import { SearchConfiguration } from './search-configuration.interface';
|
||||
import { AppConfigService } from '@alfresco/adf-core';
|
||||
import { SearchHeaderQueryBuilderService } from './search-header-query-builder.service';
|
||||
import { SearchFilterQueryBuilderService } from './search-filter-query-builder.service';
|
||||
|
||||
describe('SearchHeaderQueryBuilder', () => {
|
||||
describe('SearchFilterQueryBuilderService', () => {
|
||||
|
||||
const buildConfig = (searchSettings): AppConfigService => {
|
||||
const config = new AppConfigService(null);
|
||||
@@ -36,7 +36,7 @@ describe('SearchHeaderQueryBuilder', () => {
|
||||
filterQueries: [{ query: 'query1' }, { query: 'query2' }]
|
||||
};
|
||||
|
||||
const builder = new SearchHeaderQueryBuilderService(
|
||||
const builder = new SearchFilterQueryBuilderService(
|
||||
buildConfig(config),
|
||||
null,
|
||||
null
|
||||
@@ -63,7 +63,7 @@ describe('SearchHeaderQueryBuilder', () => {
|
||||
filterQueries: [{ query: 'query1' }, { query: 'query2' }]
|
||||
};
|
||||
|
||||
const service = new SearchHeaderQueryBuilderService(
|
||||
const service = new SearchFilterQueryBuilderService(
|
||||
buildConfig(config),
|
||||
null,
|
||||
null
|
||||
@@ -76,7 +76,7 @@ describe('SearchHeaderQueryBuilder', () => {
|
||||
});
|
||||
|
||||
it('should have empty user query by default', () => {
|
||||
const builder = new SearchHeaderQueryBuilderService(
|
||||
const builder = new SearchFilterQueryBuilderService(
|
||||
buildConfig({}),
|
||||
null,
|
||||
null
|
||||
@@ -97,7 +97,7 @@ describe('SearchHeaderQueryBuilder', () => {
|
||||
{ query: 'PARENT:"workspace://SpacesStore/fake-node-id"' }
|
||||
];
|
||||
|
||||
const searchHeaderService = new SearchHeaderQueryBuilderService(
|
||||
const searchHeaderService = new SearchFilterQueryBuilderService(
|
||||
buildConfig(config),
|
||||
null,
|
||||
null
|
||||
@@ -122,7 +122,7 @@ describe('SearchHeaderQueryBuilder', () => {
|
||||
filterQueries: expectedResult
|
||||
};
|
||||
|
||||
const searchHeaderService = new SearchHeaderQueryBuilderService(
|
||||
const searchHeaderService = new SearchFilterQueryBuilderService(
|
||||
buildConfig(config),
|
||||
null,
|
||||
null
|
||||
@@ -148,17 +148,17 @@ describe('SearchHeaderQueryBuilder', () => {
|
||||
]
|
||||
};
|
||||
|
||||
const searchHeaderService = new SearchHeaderQueryBuilderService(
|
||||
const searchHeaderService = new SearchFilterQueryBuilderService(
|
||||
buildConfig(config),
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
expect(searchHeaderService.activeFilters.size).toBe(0);
|
||||
expect(searchHeaderService.activeFilters.length).toBe(0);
|
||||
|
||||
searchHeaderService.setActiveFilter(activeFilter, 'fake-value');
|
||||
searchHeaderService.setActiveFilter(activeFilter, 'fake-value');
|
||||
|
||||
expect(searchHeaderService.activeFilters.size).toBe(1);
|
||||
expect(searchHeaderService.activeFilters.length).toBe(1);
|
||||
});
|
||||
});
|
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AlfrescoApiService, AppConfigService, NodesApiService } from '@alfresco/adf-core';
|
||||
import { AlfrescoApiService, AppConfigService, NodesApiService, DataSorting } from '@alfresco/adf-core';
|
||||
import { SearchConfiguration } from './search-configuration.interface';
|
||||
import { BaseQueryBuilderService } from './base-query-builder.service';
|
||||
import { SearchCategory } from './search-category.interface';
|
||||
@@ -24,23 +24,26 @@ import { MinimalNode, QueryBody } from '@alfresco/js-api';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { Observable } from 'rxjs';
|
||||
import { SearchSortingDefinition } from './search-sorting-definition.interface';
|
||||
import { FilterSearch } from './filter-search.interface';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SearchHeaderQueryBuilderService extends BaseQueryBuilderService {
|
||||
export class SearchFilterQueryBuilderService extends BaseQueryBuilderService {
|
||||
|
||||
private customSources = ['-trashcan-', '-sharedlinks-', '-sites-', '-mysites-', '-favorites-', '-recent-', '-my-'];
|
||||
|
||||
activeFilters: Map<string, string> = new Map();
|
||||
activeFilters: FilterSearch[] = [];
|
||||
|
||||
constructor(appConfig: AppConfigService, alfrescoApiService: AlfrescoApiService, private nodeApiService: NodesApiService) {
|
||||
constructor(appConfig: AppConfigService,
|
||||
alfrescoApiService: AlfrescoApiService,
|
||||
private nodeApiService: NodesApiService) {
|
||||
super(appConfig, alfrescoApiService);
|
||||
|
||||
this.updated.pipe(
|
||||
filter((query: QueryBody) => !!query)).subscribe(() => {
|
||||
this.execute();
|
||||
});
|
||||
this.execute();
|
||||
});
|
||||
}
|
||||
|
||||
public isFilterServiceActive(): boolean {
|
||||
@@ -61,35 +64,53 @@ export class SearchHeaderQueryBuilderService extends BaseQueryBuilderService {
|
||||
}
|
||||
|
||||
setActiveFilter(columnActivated: string, filterValue: string) {
|
||||
this.activeFilters.set(columnActivated, filterValue);
|
||||
const filterIndex = this.activeFilters.find((activeFilter) => activeFilter.key === columnActivated);
|
||||
if (!filterIndex) {
|
||||
this.activeFilters.push(<FilterSearch> {
|
||||
key: columnActivated,
|
||||
value: filterValue
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
getActiveFilters(): Map<string, string> {
|
||||
resetActiveFilters() {
|
||||
this.activeFilters = [];
|
||||
}
|
||||
|
||||
getActiveFilters(): FilterSearch[] {
|
||||
return this.activeFilters;
|
||||
}
|
||||
|
||||
isNoFilterActive(): boolean {
|
||||
return this.activeFilters.size === 0;
|
||||
return this.activeFilters.length === 0;
|
||||
}
|
||||
|
||||
removeActiveFilter(columnRemoved: string) {
|
||||
if (this.activeFilters.get(columnRemoved) !== null) {
|
||||
this.activeFilters.delete(columnRemoved);
|
||||
const filterIndex = this.activeFilters.map((activeFilter) => activeFilter.key).indexOf(columnRemoved);
|
||||
if (filterIndex >= 0) {
|
||||
this.activeFilters.splice(filterIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
setSorting(column: string, direction: string) {
|
||||
const optionAscending = direction.toLocaleLowerCase() === 'asc' ? true : false;
|
||||
const fieldValue = this.getSortingFieldFromColumnName(column);
|
||||
const currentSort: SearchSortingDefinition = { key: column, label: 'current', type: 'FIELD', field: fieldValue, ascending: optionAscending};
|
||||
this.sorting = [currentSort];
|
||||
setSorting(dataSorting: DataSorting[]) {
|
||||
this.sorting = [];
|
||||
dataSorting.forEach((columnSorting: DataSorting) => {
|
||||
const fieldValue = this.getSortingFieldFromColumnName(columnSorting.key);
|
||||
if (fieldValue) {
|
||||
const optionAscending = columnSorting.direction.toLocaleLowerCase() === 'asc' ? true : false;
|
||||
const currentSort: SearchSortingDefinition = { key: columnSorting.key, label: 'current', type: 'FIELD', field: fieldValue, ascending: optionAscending };
|
||||
this.sorting.push(currentSort);
|
||||
}
|
||||
});
|
||||
|
||||
this.execute();
|
||||
}
|
||||
|
||||
private getSortingFieldFromColumnName(columnName: string) {
|
||||
if (this.sortingOptions.length > 0) {
|
||||
const sortOption: SearchSortingDefinition = this.sortingOptions.find((option: SearchSortingDefinition) => option.key === columnName);
|
||||
return sortOption.field;
|
||||
return sortOption ? sortOption.field : '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -116,6 +137,8 @@ export class SearchHeaderQueryBuilderService extends BaseQueryBuilderService {
|
||||
this.filterQueries = [{
|
||||
query: `PARENT:"workspace://SpacesStore/${currentFolderId}"`
|
||||
}];
|
||||
|
||||
this.execute();
|
||||
}
|
||||
|
||||
isCustomSourceNode(currentNodeId: string): boolean {
|
@@ -35,9 +35,9 @@ import { SearchNumberRangeComponent } from './components/search-number-range/sea
|
||||
import { SearchCheckListComponent } from './components/search-check-list/search-check-list.component';
|
||||
import { SearchDateRangeComponent } from './components/search-date-range/search-date-range.component';
|
||||
import { SearchSortingPickerComponent } from './components/search-sorting-picker/search-sorting-picker.component';
|
||||
import { SearchHeaderComponent } from './components/search-header/search-header.component';
|
||||
import { SEARCH_QUERY_SERVICE_TOKEN } from './search-query-service.token';
|
||||
import { SearchQueryBuilderService } from './search-query-builder.service';
|
||||
import { SearchFilterContainerComponent } from './components/search-filter-container/search-filter-container.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -61,7 +61,7 @@ import { SearchQueryBuilderService } from './search-query-builder.service';
|
||||
SearchCheckListComponent,
|
||||
SearchDateRangeComponent,
|
||||
SearchSortingPickerComponent,
|
||||
SearchHeaderComponent
|
||||
SearchFilterContainerComponent
|
||||
],
|
||||
exports: [
|
||||
SearchComponent,
|
||||
@@ -77,7 +77,7 @@ import { SearchQueryBuilderService } from './search-query-builder.service';
|
||||
SearchCheckListComponent,
|
||||
SearchDateRangeComponent,
|
||||
SearchSortingPickerComponent,
|
||||
SearchHeaderComponent
|
||||
SearchFilterContainerComponent
|
||||
],
|
||||
providers: [
|
||||
{ provide: SEARCH_QUERY_SERVICE_TOKEN, useExisting: SearchQueryBuilderService },
|
||||
|
@@ -13,7 +13,7 @@
|
||||
@import '../search/components/search-sorting-picker/search-sorting-picker.component';
|
||||
@import '../search/components/search-filter/search-filter.component';
|
||||
@import '../search/components/search-chip-list/search-chip-list.component';
|
||||
@import '../search/components/search-header/search-header.component';
|
||||
@import '../search/components/search-filter-container/search-filter-container.component';
|
||||
|
||||
@import '../dialogs/folder.dialog';
|
||||
|
||||
|
@@ -42,7 +42,6 @@ import { HeaderFilterTemplateDirective } from './directives/header-filter-templa
|
||||
import { CustomEmptyContentTemplateDirective } from './directives/custom-empty-content-template.directive';
|
||||
import { CustomLoadingContentTemplateDirective } from './directives/custom-loading-template.directive';
|
||||
import { CustomNoPermissionTemplateDirective } from './directives/custom-no-permission-template.directive';
|
||||
import { CustomHeaderFilterTemplateDirective } from './directives/custom-header-filter-template.directive';
|
||||
import { JsonCellComponent } from './components/json-cell/json-cell.component';
|
||||
import { ClipboardModule } from '../clipboard/clipboard.module';
|
||||
import { DropZoneDirective } from './directives/drop-zone.directive';
|
||||
@@ -79,7 +78,6 @@ import { DataColumnModule } from '../data-column/data-column.module';
|
||||
CustomEmptyContentTemplateDirective,
|
||||
CustomLoadingContentTemplateDirective,
|
||||
CustomNoPermissionTemplateDirective,
|
||||
CustomHeaderFilterTemplateDirective,
|
||||
DropZoneDirective
|
||||
],
|
||||
exports: [
|
||||
@@ -101,7 +99,6 @@ import { DataColumnModule } from '../data-column/data-column.module';
|
||||
CustomEmptyContentTemplateDirective,
|
||||
CustomLoadingContentTemplateDirective,
|
||||
CustomNoPermissionTemplateDirective,
|
||||
CustomHeaderFilterTemplateDirective,
|
||||
DropZoneDirective
|
||||
]
|
||||
|
||||
|
@@ -47,6 +47,5 @@ export * from './directives/header-filter-template.directive';
|
||||
export * from './directives/custom-empty-content-template.directive';
|
||||
export * from './directives/custom-loading-template.directive';
|
||||
export * from './directives/custom-no-permission-template.directive';
|
||||
export * from './directives/custom-header-filter-template.directive';
|
||||
|
||||
export * from './datatable.module';
|
||||
|
Reference in New Issue
Block a user