mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ADF-2131] Search sorting (#3334)
* sorting configuration * detect primary sorting and use with document list * search results sorting * docs update * unit tests and code updates * update code * update code * generic sorting picker, test updates * ability to switch off client side sorting * update docs for document list
This commit is contained in:
committed by
Eugenio Romano
parent
73bc62ae8f
commit
07440731fa
@@ -55,6 +55,16 @@
|
|||||||
],
|
],
|
||||||
"search": {
|
"search": {
|
||||||
"include": ["path", "allowableOperations"],
|
"include": ["path", "allowableOperations"],
|
||||||
|
"sorting": {
|
||||||
|
"options": [
|
||||||
|
{ "key": "name", "label": "Name", "type": "FIELD", "field": "cm:name", "ascending": true },
|
||||||
|
{ "key": "content.sizeInBytes", "label": "Size", "type": "FIELD", "field": "content.size", "ascending": true },
|
||||||
|
{ "key": "description", "label": "Description", "type": "FIELD", "field": "cm:description", "ascending": true }
|
||||||
|
],
|
||||||
|
"defaults": [
|
||||||
|
{ "key": "name", "type": "FIELD", "field": "cm:name", "ascending": true }
|
||||||
|
]
|
||||||
|
},
|
||||||
"filterQueries": [
|
"filterQueries": [
|
||||||
{ "query": "TYPE:'cm:folder' OR TYPE:'cm:content'" },
|
{ "query": "TYPE:'cm:folder' OR TYPE:'cm:content'" },
|
||||||
{ "query": "NOT cm:creator:System" }
|
{ "query": "NOT cm:creator:System" }
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
<mat-accordion class="adf-container-recent">
|
<mat-accordion *ngIf="showRecentFiles" class="adf-container-recent">
|
||||||
<mat-expansion-panel hideToggle="true">
|
<mat-expansion-panel hideToggle="true">
|
||||||
<mat-expansion-panel-header >
|
<mat-expansion-panel-header >
|
||||||
<mat-panel-title>
|
<mat-panel-title>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
</mat-accordion>
|
</mat-accordion>
|
||||||
|
|
||||||
<div class="adf-site-container-style" id="site-container">
|
<div *ngIf="showSitePicker" class="adf-site-container-style" id="site-container">
|
||||||
<adf-sites-dropdown (change)="onSiteChange($event)" [hideMyFiles]="false" [relations]="'members'">
|
<adf-sites-dropdown (change)="onSiteChange($event)" [hideMyFiles]="false" [relations]="'members'">
|
||||||
</adf-sites-dropdown>
|
</adf-sites-dropdown>
|
||||||
</div>
|
</div>
|
||||||
@@ -194,6 +194,9 @@
|
|||||||
[display]="displayMode"
|
[display]="displayMode"
|
||||||
[node]="nodeResult"
|
[node]="nodeResult"
|
||||||
[includeFields]="includeFields"
|
[includeFields]="includeFields"
|
||||||
|
[sorting]="sorting"
|
||||||
|
[sortingMode]="sortingMode"
|
||||||
|
[showHeader]="showHeader"
|
||||||
(error)="onNavigationError($event)"
|
(error)="onNavigationError($event)"
|
||||||
(success)="resetError()"
|
(success)="resetError()"
|
||||||
(ready)="emitReadyEvent($event)"
|
(ready)="emitReadyEvent($event)"
|
||||||
@@ -416,7 +419,7 @@
|
|||||||
</adf-start-process>
|
</adf-start-process>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="adf-content-service-settings">
|
<div *ngIf="showSettingsPanel" class="adf-content-service-settings">
|
||||||
|
|
||||||
<p>Current folder ID: {{ documentList.currentFolderId }}</p>
|
<p>Current folder ID: {{ documentList.currentFolderId }}</p>
|
||||||
|
|
||||||
|
@@ -74,6 +74,24 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
processId;
|
processId;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
sorting = ['name', 'asc'];
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
sortingMode = 'client';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
showRecentFiles = true;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
showSitePicker = true;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
showSettingsPanel = true;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
showHeader = true;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
selectionMode = 'multiple';
|
selectionMode = 'multiple';
|
||||||
|
|
||||||
|
@@ -14,17 +14,26 @@
|
|||||||
<adf-search-filter #searchFilter></adf-search-filter>
|
<adf-search-filter #searchFilter></adf-search-filter>
|
||||||
|
|
||||||
<div class="adf-search-results__content">
|
<div class="adf-search-results__content">
|
||||||
|
<div class="adf-search-results__sorting">
|
||||||
|
<adf-search-sorting-picker></adf-search-sorting-picker>
|
||||||
|
</div>
|
||||||
<app-files-component
|
<app-files-component
|
||||||
[currentFolderId]="null"
|
[showHeader]="false"
|
||||||
[nodeResult]="resultNodePageList"
|
[sorting]="sorting"
|
||||||
[disableDragArea]="true"
|
[sortingMode]="'server'"
|
||||||
[pagination]="pagination"
|
[showRecentFiles]="false"
|
||||||
(changedPageSize)="onRefreshPagination($event)"
|
[showSitePicker]="false"
|
||||||
(changedPageNumber)="onRefreshPagination($event)"
|
[showSettingsPanel]="false"
|
||||||
(turnedNextPage)="onRefreshPagination($event)"
|
[currentFolderId]="null"
|
||||||
(loadNext)="onRefreshPagination($event)"
|
[nodeResult]="resultNodePageList"
|
||||||
(turnedPreviousPage)="onRefreshPagination($event)"
|
[disableDragArea]="true"
|
||||||
(deleteElementSuccess)="onDeleteElementSuccess($event)">
|
[pagination]="pagination"
|
||||||
|
(changedPageSize)="onRefreshPagination($event)"
|
||||||
|
(changedPageNumber)="onRefreshPagination($event)"
|
||||||
|
(turnedNextPage)="onRefreshPagination($event)"
|
||||||
|
(loadNext)="onRefreshPagination($event)"
|
||||||
|
(turnedPreviousPage)="onRefreshPagination($event)"
|
||||||
|
(deleteElementSuccess)="onDeleteElementSuccess($event)">
|
||||||
</app-files-component>
|
</app-files-component>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -14,6 +14,10 @@
|
|||||||
&__content {
|
&__content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__sorting {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div.search-results-container {
|
div.search-results-container {
|
||||||
|
@@ -39,6 +39,8 @@ export class SearchResultComponent implements OnInit {
|
|||||||
maxItems: number;
|
maxItems: number;
|
||||||
skipCount = 0;
|
skipCount = 0;
|
||||||
|
|
||||||
|
sorting = ['name', 'asc'];
|
||||||
|
|
||||||
constructor(public router: Router,
|
constructor(public router: Router,
|
||||||
private preferences: UserPreferencesService,
|
private preferences: UserPreferencesService,
|
||||||
private queryBuilder: SearchQueryBuilderService,
|
private queryBuilder: SearchQueryBuilderService,
|
||||||
@@ -51,6 +53,13 @@ export class SearchResultComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
||||||
|
this.sorting = this.getSorting();
|
||||||
|
|
||||||
|
this.queryBuilder.updated.subscribe(() => {
|
||||||
|
this.sorting = this.getSorting();
|
||||||
|
});
|
||||||
|
|
||||||
if (this.route) {
|
if (this.route) {
|
||||||
this.route.params.forEach((params: Params) => {
|
this.route.params.forEach((params: Params) => {
|
||||||
this.searchedWord = params.hasOwnProperty(this.queryParamName) ? params[this.queryParamName] : null;
|
this.searchedWord = params.hasOwnProperty(this.queryParamName) ? params[this.queryParamName] : null;
|
||||||
@@ -79,4 +88,14 @@ export class SearchResultComponent implements OnInit {
|
|||||||
onDeleteElementSuccess(element: any) {
|
onDeleteElementSuccess(element: any) {
|
||||||
this.searchResult.reload();
|
this.searchResult.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getSorting(): string[] {
|
||||||
|
const primary = this.queryBuilder.getPrimarySorting();
|
||||||
|
|
||||||
|
if (primary) {
|
||||||
|
return [primary.key, primary.ascending ? 'asc' : 'desc'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['name', 'asc'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -88,6 +88,7 @@ Displays the documents from a repository.
|
|||||||
| skipCount | `number` | 0 | Number of elements to skip over for pagination purposes |
|
| skipCount | `number` | 0 | Number of elements to skip over for pagination purposes |
|
||||||
| sorting | `string[]` | | 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[]` | | 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. |
|
||||||
| thumbnails | `boolean` | false | Show document thumbnails rather than icons |
|
| thumbnails | `boolean` | false | Show document thumbnails rather than icons |
|
||||||
|
| sortingMode | `string` | `client` | Defines sorting mode. Can be either `client` or `server`. |
|
||||||
|
|
||||||
### Events
|
### Events
|
||||||
|
|
||||||
|
@@ -34,6 +34,16 @@ Below is an example configuration:
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"search": {
|
"search": {
|
||||||
|
"sorting": {
|
||||||
|
"options": [
|
||||||
|
{ "key": "name", "label": "Name", "type": "FIELD", "field": "cm:name", "ascending": true },
|
||||||
|
{ "key": "content.sizeInBytes", "label": "Size", "type": "FIELD", "field": "content.size", "ascending": true },
|
||||||
|
{ "key": "description", "label": "Description", "type": "FIELD", "field": "cm:description", "ascending": true }
|
||||||
|
],
|
||||||
|
"defaults": [
|
||||||
|
{ "key": "name", "type": "FIELD", "field": "cm:name", "ascending": true }
|
||||||
|
]
|
||||||
|
},
|
||||||
"filterQueries": [
|
"filterQueries": [
|
||||||
{ "query": "TYPE:'cm:folder' OR TYPE:'cm:content'" },
|
{ "query": "TYPE:'cm:folder' OR TYPE:'cm:content'" },
|
||||||
{ "query": "NOT cm:creator:System" }
|
{ "query": "NOT cm:creator:System" }
|
||||||
@@ -107,6 +117,42 @@ In addition, it is also possible to provide a set of queries that are always exe
|
|||||||
|
|
||||||
Note that the entries of the `filterQueries` array are joined using the `AND` operator.
|
Note that the entries of the `filterQueries` array are joined using the `AND` operator.
|
||||||
|
|
||||||
|
### Sorting
|
||||||
|
|
||||||
|
The Sorting configuration section consists of two blocks:
|
||||||
|
`options` that holds a list of items that users can select from,
|
||||||
|
and `defaults` that contains predefined sorting to use by default.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"search": {
|
||||||
|
"sorting": {
|
||||||
|
"options": [
|
||||||
|
{ "key": "name", "label": "Name", "type": "FIELD", "field": "cm:name", "ascending": true },
|
||||||
|
{ "key": "content.sizeInBytes", "label": "Size", "type": "FIELD", "field": "content.size", "ascending": true },
|
||||||
|
{ "key": "description", "label": "Description", "type": "FIELD", "field": "cm:description", "ascending": true }
|
||||||
|
],
|
||||||
|
"defaults": [
|
||||||
|
{ "key": "name", "type": "FIELD", "field": "cm:name", "ascending": true }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Sorting Definition Attributes
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| key | string | Unique key to identify the entry, can also be used to map DataColumn instances. |
|
||||||
|
| label | string | Display text, can also be an i18n resource key. |
|
||||||
|
| type | string | This specifies how to order - either by using a field or based on the position of the document in the index, or by score/relevance. |
|
||||||
|
| field | string | The name of the field. |
|
||||||
|
| ascending | boolean | The sorting order. |
|
||||||
|
|
||||||
|
See also:
|
||||||
|
* Alfresco Content Services API Reference / Search Api / [Sort](https://docs.alfresco.com/5.2/concepts/search-api-sort.html)
|
||||||
|
|
||||||
### Categories
|
### Categories
|
||||||
|
|
||||||
The Search Settings component and Query Builder require a `categories` section provided within the configuration.
|
The Search Settings component and Query Builder require a `categories` section provided within the configuration.
|
||||||
@@ -647,4 +693,6 @@ and pass custom attributes in case your component supports them:
|
|||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
- [Search Query Builder service](search-query-builder.service.md)
|
- [Search Query Builder service](search-query-builder.service.md)
|
||||||
|
- [Search Chip List Component](search-chip-list.component.md)
|
||||||
|
- [Search Sorting Picker Component](search-sorting-picker.component.md)
|
||||||
|
13
docs/content-services/search-sorting-picker.component.md
Normal file
13
docs/content-services/search-sorting-picker.component.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
Added: v2.3.0
|
||||||
|
Status: Active
|
||||||
|
---
|
||||||
|
|
||||||
|
# Search Sorting Picker Component
|
||||||
|
|
||||||
|
Provides an ability to select one of the predefined sorting definitions for search results:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<adf-search-sorting-picker></adf-search-sorting-picker>
|
||||||
|
<adf-search-filter></adf-search-filter>
|
||||||
|
```
|
43
docs/core/sorting-picker.component.md
Normal file
43
docs/core/sorting-picker.component.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
Added: v2.4.0
|
||||||
|
Status: Active
|
||||||
|
---
|
||||||
|
|
||||||
|
# Sorting Picker Component
|
||||||
|
|
||||||
|
Provides an ability to pick one of the predefined sorting definitions and define sorting direction:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<adf-sorting-picker
|
||||||
|
[options]="options"
|
||||||
|
[selected]="value"
|
||||||
|
[ascending]="ascending"
|
||||||
|
(change)="onChanged($event)">
|
||||||
|
</adf-sorting-picker>
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Options format
|
||||||
|
|
||||||
|
You can bind a collection of any objects that expose the following properties:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Name | Type | Default Value | Description |
|
||||||
|
| options | `Array<{key: string, label: string}>` | `[]` | Available sorting options. |
|
||||||
|
| selected | `string` | | Currently selected option key. |
|
||||||
|
| ascending | `boolean` | true | Current sorting direction. |
|
||||||
|
|
||||||
|
## Events
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| change | `EventEmitter<{ key: string, ascending: boolean }>` | Raised each time sorting key or direction gets changed. |
|
BIN
docs/docassets/images/sorting-picker.png
Normal file
BIN
docs/docassets/images/sorting-picker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
@@ -138,6 +138,10 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
@Input()
|
@Input()
|
||||||
sorting = ['name', 'asc'];
|
sorting = ['name', 'asc'];
|
||||||
|
|
||||||
|
/** Defines sorting mode. Can be either `client` or `server`. */
|
||||||
|
@Input()
|
||||||
|
sortingMode = 'client';
|
||||||
|
|
||||||
/** The inline style to apply to every row. See
|
/** The inline style to apply to every row. See
|
||||||
* the Angular NgStyle
|
* the Angular NgStyle
|
||||||
* docs for more details and usage examples.
|
* docs for more details and usage examples.
|
||||||
@@ -329,7 +333,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.loadLayoutPresets();
|
this.loadLayoutPresets();
|
||||||
this.data = new ShareDataTableAdapter(this.documentListService, null, this.getDefaultSorting());
|
this.data = new ShareDataTableAdapter(this.documentListService, null, this.getDefaultSorting(), this.sortingMode);
|
||||||
this.data.thumbnails = this.thumbnails;
|
this.data.thumbnails = this.thumbnails;
|
||||||
this.data.permissionsStyle = this.permissionsStyle;
|
this.data.permissionsStyle = this.permissionsStyle;
|
||||||
|
|
||||||
@@ -367,7 +371,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.data) {
|
if (!this.data) {
|
||||||
this.data = new ShareDataTableAdapter(this.documentListService, schema, this.getDefaultSorting());
|
this.data = new ShareDataTableAdapter(this.documentListService, schema, this.getDefaultSorting(), this.sortingMode);
|
||||||
} else if (schema && schema.length > 0) {
|
} else if (schema && schema.length > 0) {
|
||||||
this.data.setColumns(schema);
|
this.data.setColumns(schema);
|
||||||
}
|
}
|
||||||
@@ -381,6 +385,18 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
this.resetSelection();
|
this.resetSelection();
|
||||||
|
|
||||||
|
if (changes.sortingMode && !changes.sortingMode.firstChange && this.data) {
|
||||||
|
this.data.sortingMode = changes.sortingMode.currentValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes.sorting && !changes.sorting.firstChange && this.data) {
|
||||||
|
const newValue = changes.sorting.currentValue;
|
||||||
|
if (newValue && newValue.length > 0) {
|
||||||
|
const [key, direction] = newValue;
|
||||||
|
this.data.setSorting(new DataSorting(key, direction));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (changes.folderNode && changes.folderNode.currentValue) {
|
if (changes.folderNode && changes.folderNode.currentValue) {
|
||||||
this.currentFolderId = changes.folderNode.currentValue.id;
|
this.currentFolderId = changes.folderNode.currentValue.id;
|
||||||
this.resetNewFolderPagination();
|
this.resetNewFolderPagination();
|
||||||
|
@@ -32,6 +32,31 @@ describe('ShareDataTableAdapter', () => {
|
|||||||
spyOn(documentListService, 'getDocumentThumbnailUrl').and.returnValue(imageUrl);
|
spyOn(documentListService, 'getDocumentThumbnailUrl').and.returnValue(imageUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should use client sorting by default', () => {
|
||||||
|
const adapter = new ShareDataTableAdapter(documentListService, []);
|
||||||
|
expect(adapter.sortingMode).toBe('client');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be case sensitive for sorting mode value', () => {
|
||||||
|
const adapter = new ShareDataTableAdapter(documentListService, []);
|
||||||
|
|
||||||
|
adapter.sortingMode = 'CLIENT';
|
||||||
|
expect(adapter.sortingMode).toBe('client');
|
||||||
|
|
||||||
|
adapter.sortingMode = 'SeRvEr';
|
||||||
|
expect(adapter.sortingMode).toBe('server');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fallback to client sorting for unknown values', () => {
|
||||||
|
const adapter = new ShareDataTableAdapter(documentListService, []);
|
||||||
|
|
||||||
|
adapter.sortingMode = 'SeRvEr';
|
||||||
|
expect(adapter.sortingMode).toBe('server');
|
||||||
|
|
||||||
|
adapter.sortingMode = 'quantum';
|
||||||
|
expect(adapter.sortingMode).toBe('client');
|
||||||
|
});
|
||||||
|
|
||||||
it('should setup rows and columns with constructor', () => {
|
it('should setup rows and columns with constructor', () => {
|
||||||
let schema = [<DataColumn> {}];
|
let schema = [<DataColumn> {}];
|
||||||
let adapter = new ShareDataTableAdapter(documentListService, schema);
|
let adapter = new ShareDataTableAdapter(documentListService, schema);
|
||||||
|
@@ -26,6 +26,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
|
|||||||
ERR_ROW_NOT_FOUND: string = 'Row not found';
|
ERR_ROW_NOT_FOUND: string = 'Row not found';
|
||||||
ERR_COL_NOT_FOUND: string = 'Column not found';
|
ERR_COL_NOT_FOUND: string = 'Column not found';
|
||||||
|
|
||||||
|
private _sortingMode: string;
|
||||||
private sorting: DataSorting;
|
private sorting: DataSorting;
|
||||||
private rows: DataRow[];
|
private rows: DataRow[];
|
||||||
private columns: DataColumn[];
|
private columns: DataColumn[];
|
||||||
@@ -37,12 +38,26 @@ export class ShareDataTableAdapter implements DataTableAdapter {
|
|||||||
permissionsStyle: PermissionStyleModel[];
|
permissionsStyle: PermissionStyleModel[];
|
||||||
selectedRow: DataRow;
|
selectedRow: DataRow;
|
||||||
|
|
||||||
|
set sortingMode(value: string) {
|
||||||
|
let newValue = (value || 'client').toLowerCase();
|
||||||
|
if (newValue !== 'client' && newValue !== 'server') {
|
||||||
|
newValue = 'client';
|
||||||
|
}
|
||||||
|
this._sortingMode = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
get sortingMode(): string {
|
||||||
|
return this._sortingMode;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(private documentListService: DocumentListService,
|
constructor(private documentListService: DocumentListService,
|
||||||
schema: DataColumn[] = [],
|
schema: DataColumn[] = [],
|
||||||
sorting?: DataSorting) {
|
sorting?: DataSorting,
|
||||||
|
sortingMode: string = 'client') {
|
||||||
this.rows = [];
|
this.rows = [];
|
||||||
this.columns = schema || [];
|
this.columns = schema || [];
|
||||||
this.sorting = sorting;
|
this.sorting = sorting;
|
||||||
|
this.sortingMode = sortingMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
getRows(): Array<DataRow> {
|
getRows(): Array<DataRow> {
|
||||||
@@ -148,6 +163,10 @@ export class ShareDataTableAdapter implements DataTableAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private sortRows(rows: DataRow[], sorting: DataSorting) {
|
private sortRows(rows: DataRow[], sorting: DataSorting) {
|
||||||
|
if (this.sortingMode === 'server') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const options: Intl.CollatorOptions = {};
|
const options: Intl.CollatorOptions = {};
|
||||||
|
|
||||||
if (sorting && sorting.key && rows && rows.length > 0) {
|
if (sorting && sorting.key && rows && rows.length > 0) {
|
||||||
@@ -194,17 +213,19 @@ export class ShareDataTableAdapter implements DataTableAdapter {
|
|||||||
rows = rows.filter(this.filter);
|
rows = rows.filter(this.filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by first sortable or just first column
|
if (this.sortingMode !== 'server') {
|
||||||
if (this.columns && this.columns.length > 0) {
|
// Sort by first sortable or just first column
|
||||||
let sorting = this.getSorting();
|
if (this.columns && this.columns.length > 0) {
|
||||||
if (sorting) {
|
let sorting = this.getSorting();
|
||||||
this.sortRows(rows, sorting);
|
if (sorting) {
|
||||||
} else {
|
this.sortRows(rows, sorting);
|
||||||
let sortable = this.columns.filter(c => c.sortable);
|
|
||||||
if (sortable.length > 0) {
|
|
||||||
this.sort(sortable[0].key, 'asc');
|
|
||||||
} else {
|
} else {
|
||||||
this.sort(this.columns[0].key, 'asc');
|
let sortable = this.columns.filter(c => c.sortable);
|
||||||
|
if (sortable.length > 0) {
|
||||||
|
this.sort(sortable[0].key, 'asc');
|
||||||
|
} else {
|
||||||
|
this.sort(this.columns[0].key, 'asc');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
<adf-sorting-picker
|
||||||
|
[options]="options"
|
||||||
|
[selected]="value"
|
||||||
|
[ascending]="ascending"
|
||||||
|
(change)="onChanged($event)">
|
||||||
|
</adf-sorting-picker>
|
@@ -0,0 +1,81 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { SearchSortingPickerComponent } from './search-sorting-picker.component';
|
||||||
|
import { SearchQueryBuilderService } from '../../search-query-builder.service';
|
||||||
|
import { AppConfigService } from '@alfresco/adf-core';
|
||||||
|
import { SearchConfiguration } from '../../search-configuration.interface';
|
||||||
|
|
||||||
|
describe('SearchSortingPickerComponent', () => {
|
||||||
|
|
||||||
|
let queryBuilder: SearchQueryBuilderService;
|
||||||
|
let component: SearchSortingPickerComponent;
|
||||||
|
|
||||||
|
const buildConfig = (searchSettings): AppConfigService => {
|
||||||
|
const config = new AppConfigService(null);
|
||||||
|
config.config.search = searchSettings;
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const config: SearchConfiguration = {
|
||||||
|
sorting: {
|
||||||
|
options: [
|
||||||
|
<any> { 'key': 'name', 'label': 'Name', 'type': 'FIELD', 'field': 'cm:name', 'ascending': true },
|
||||||
|
<any> { 'key': 'content.sizeInBytes', 'label': 'Size', 'type': 'FIELD', 'field': 'content.size', 'ascending': true },
|
||||||
|
<any> { 'key': 'description', 'label': 'Description', 'type': 'FIELD', 'field': 'cm:description', 'ascending': true }
|
||||||
|
],
|
||||||
|
defaults: [
|
||||||
|
<any> { 'key': 'name', 'type': 'FIELD', 'field': 'cm:name', 'ascending': true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
categories: [
|
||||||
|
<any> { id: 'cat1', enabled: true }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
queryBuilder = new SearchQueryBuilderService(buildConfig(config), null);
|
||||||
|
component = new SearchSortingPickerComponent(queryBuilder);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load options from query builder', () => {
|
||||||
|
component.ngOnInit();
|
||||||
|
|
||||||
|
expect(component.options.length).toBe(3);
|
||||||
|
expect(component.options[0].key).toEqual('name');
|
||||||
|
expect(component.options[1].key).toEqual('content.sizeInBytes');
|
||||||
|
expect(component.options[2].key).toEqual('description');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pre-select the primary sorting definition', () => {
|
||||||
|
component.ngOnInit();
|
||||||
|
|
||||||
|
expect(component.value).toEqual('name');
|
||||||
|
expect(component.ascending).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update query builder each time selection is changed', () => {
|
||||||
|
spyOn(queryBuilder, 'update').and.stub();
|
||||||
|
|
||||||
|
component.ngOnInit();
|
||||||
|
component.onChanged({ key: 'description', ascending: false });
|
||||||
|
|
||||||
|
expect(queryBuilder.update).toHaveBeenCalled();
|
||||||
|
expect(queryBuilder.sorting.length).toBe(1);
|
||||||
|
expect(queryBuilder.sorting[0].key).toEqual('description');
|
||||||
|
expect(queryBuilder.sorting[0].ascending).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,70 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
|
import { SearchQueryBuilderService } from '../../search-query-builder.service';
|
||||||
|
import { SearchSortingDefinition } from '../../search-sorting-definition.interface';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'adf-search-sorting-picker',
|
||||||
|
templateUrl: './search-sorting-picker.component.html',
|
||||||
|
encapsulation: ViewEncapsulation.None,
|
||||||
|
host: { class: 'adf-search-sorting-picker' }
|
||||||
|
})
|
||||||
|
export class SearchSortingPickerComponent implements OnInit {
|
||||||
|
|
||||||
|
options: SearchSortingDefinition[] = [];
|
||||||
|
value: string;
|
||||||
|
ascending: boolean;
|
||||||
|
|
||||||
|
constructor(private queryBuilder: SearchQueryBuilderService) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.options = this.queryBuilder.getSortingOptions();
|
||||||
|
|
||||||
|
const primary = this.queryBuilder.getPrimarySorting();
|
||||||
|
if (primary) {
|
||||||
|
this.value = primary.key;
|
||||||
|
this.ascending = primary.ascending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onChanged(sorting: { key: string, ascending: boolean }) {
|
||||||
|
this.value = sorting.key;
|
||||||
|
this.ascending = sorting.ascending;
|
||||||
|
this.applySorting();
|
||||||
|
}
|
||||||
|
|
||||||
|
private findOptionByKey(key: string): SearchSortingDefinition {
|
||||||
|
if (key) {
|
||||||
|
return this.options.find(opt => opt.key === key);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private applySorting() {
|
||||||
|
const option = this.findOptionByKey(this.value);
|
||||||
|
if (option) {
|
||||||
|
this.queryBuilder.sorting = [{
|
||||||
|
...option,
|
||||||
|
ascending: this.ascending
|
||||||
|
}];
|
||||||
|
this.queryBuilder.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -34,5 +34,6 @@ export * from './components/search-trigger.directive';
|
|||||||
export * from './components/empty-search-result.component';
|
export * from './components/empty-search-result.component';
|
||||||
export * from './components/search-filter/search-filter.component';
|
export * from './components/search-filter/search-filter.component';
|
||||||
export * from './components/search-chip-list/search-chip-list.component';
|
export * from './components/search-chip-list/search-chip-list.component';
|
||||||
|
export * from './components/search-sorting-picker/search-sorting-picker.component';
|
||||||
|
|
||||||
export * from './search.module';
|
export * from './search.module';
|
||||||
|
@@ -19,6 +19,7 @@ import { FilterQuery } from './filter-query.interface';
|
|||||||
import { FacetQuery } from './facet-query.interface';
|
import { FacetQuery } from './facet-query.interface';
|
||||||
import { FacetField } from './facet-field.interface';
|
import { FacetField } from './facet-field.interface';
|
||||||
import { SearchCategory } from './search-category.interface';
|
import { SearchCategory } from './search-category.interface';
|
||||||
|
import { SearchSortingDefinition } from './search-sorting-definition.interface';
|
||||||
|
|
||||||
export interface SearchConfiguration {
|
export interface SearchConfiguration {
|
||||||
include?: Array<string>;
|
include?: Array<string>;
|
||||||
@@ -32,4 +33,8 @@ export interface SearchConfiguration {
|
|||||||
queries: Array<FacetQuery>;
|
queries: Array<FacetQuery>;
|
||||||
};
|
};
|
||||||
facetFields?: Array<FacetField>;
|
facetFields?: Array<FacetField>;
|
||||||
|
sorting?: {
|
||||||
|
options: Array<SearchSortingDefinition>;
|
||||||
|
defaults: Array<SearchSortingDefinition>;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@@ -299,6 +299,24 @@ describe('SearchQueryBuilder', () => {
|
|||||||
expect(compiled.facetFields.facets).toEqual(jasmine.objectContaining(config.facetFields));
|
expect(compiled.facetFields.facets).toEqual(jasmine.objectContaining(config.facetFields));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should build query with sorting', () => {
|
||||||
|
const config: SearchConfiguration = {
|
||||||
|
fields: [],
|
||||||
|
categories: [
|
||||||
|
<any> { id: 'cat1', enabled: true },
|
||||||
|
<any> { id: 'cat2', enabled: true }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
const builder = new SearchQueryBuilderService(buildConfig(config), null);
|
||||||
|
const sorting: any = { type: 'FIELD', field: 'cm:name', ascending: true };
|
||||||
|
builder.sorting = [sorting];
|
||||||
|
|
||||||
|
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||||
|
|
||||||
|
const compiled = builder.buildQuery();
|
||||||
|
expect(compiled.sort[0]).toEqual(jasmine.objectContaining(sorting));
|
||||||
|
});
|
||||||
|
|
||||||
it('should use pagination settings', () => {
|
it('should use pagination settings', () => {
|
||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
categories: [
|
categories: [
|
||||||
|
@@ -18,12 +18,13 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Subject } from 'rxjs/Subject';
|
import { Subject } from 'rxjs/Subject';
|
||||||
import { AlfrescoApiService, AppConfigService } from '@alfresco/adf-core';
|
import { AlfrescoApiService, AppConfigService } from '@alfresco/adf-core';
|
||||||
import { QueryBody, RequestFacetFields, RequestFacetField } from 'alfresco-js-api';
|
import { QueryBody, RequestFacetFields, RequestFacetField, RequestSortDefinitionInner } from 'alfresco-js-api';
|
||||||
import { SearchCategory } from './search-category.interface';
|
import { SearchCategory } from './search-category.interface';
|
||||||
import { FilterQuery } from './filter-query.interface';
|
import { FilterQuery } from './filter-query.interface';
|
||||||
import { SearchRange } from './search-range.interface';
|
import { SearchRange } from './search-range.interface';
|
||||||
import { SearchConfiguration } from './search-configuration.interface';
|
import { SearchConfiguration } from './search-configuration.interface';
|
||||||
import { FacetQuery } from './facet-query.interface';
|
import { FacetQuery } from './facet-query.interface';
|
||||||
|
import { SearchSortingDefinition } from './search-sorting-definition.interface';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SearchQueryBuilderService {
|
export class SearchQueryBuilderService {
|
||||||
@@ -34,17 +35,24 @@ export class SearchQueryBuilderService {
|
|||||||
categories: Array<SearchCategory> = [];
|
categories: Array<SearchCategory> = [];
|
||||||
queryFragments: { [id: string]: string } = {};
|
queryFragments: { [id: string]: string } = {};
|
||||||
filterQueries: FilterQuery[] = [];
|
filterQueries: FilterQuery[] = [];
|
||||||
ranges: { [id: string]: SearchRange } = {};
|
|
||||||
paging: { maxItems?: number; skipCount?: number } = null;
|
paging: { maxItems?: number; skipCount?: number } = null;
|
||||||
|
sorting: Array<SearchSortingDefinition> = [];
|
||||||
|
|
||||||
config: SearchConfiguration;
|
config: SearchConfiguration;
|
||||||
|
|
||||||
|
// TODO: to be supported in future iterations
|
||||||
|
ranges: { [id: string]: SearchRange } = {};
|
||||||
|
|
||||||
constructor(appConfig: AppConfigService, private alfrescoApiService: AlfrescoApiService) {
|
constructor(appConfig: AppConfigService, private alfrescoApiService: AlfrescoApiService) {
|
||||||
this.config = appConfig.get<SearchConfiguration>('search');
|
this.config = appConfig.get<SearchConfiguration>('search');
|
||||||
|
|
||||||
if (this.config) {
|
if (this.config) {
|
||||||
this.categories = (this.config.categories || []).filter(f => f.enabled);
|
this.categories = (this.config.categories || []).filter(f => f.enabled);
|
||||||
this.filterQueries = this.config.filterQueries || [];
|
this.filterQueries = this.config.filterQueries || [];
|
||||||
|
|
||||||
|
if (this.config.sorting) {
|
||||||
|
this.sorting = this.config.sorting.defaults || [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +120,8 @@ export class SearchQueryBuilderService {
|
|||||||
fields: this.config.fields,
|
fields: this.config.fields,
|
||||||
filterQueries: this.filterQueries,
|
filterQueries: this.filterQueries,
|
||||||
facetQueries: this.facetQueries,
|
facetQueries: this.facetQueries,
|
||||||
facetFields: this.facetFields
|
facetFields: this.facetFields,
|
||||||
|
sort: this.sort
|
||||||
};
|
};
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -121,6 +130,36 @@ export class SearchQueryBuilderService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns primary sorting definition.
|
||||||
|
*/
|
||||||
|
getPrimarySorting(): SearchSortingDefinition {
|
||||||
|
if (this.sorting && this.sorting.length > 0) {
|
||||||
|
return this.sorting[0];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all pre-configured sorting options that users can choose from.
|
||||||
|
*/
|
||||||
|
getSortingOptions(): SearchSortingDefinition[] {
|
||||||
|
if (this.config && this.config.sorting) {
|
||||||
|
return this.config.sorting.options || [];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private get sort(): RequestSortDefinitionInner[] {
|
||||||
|
return this.sorting.map(def => {
|
||||||
|
return {
|
||||||
|
type: def.type,
|
||||||
|
field: def.field,
|
||||||
|
ascending: def.ascending
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private get facetQueries(): FacetQuery[] {
|
private get facetQueries(): FacetQuery[] {
|
||||||
const config = this.config.facetQueries;
|
const config = this.config.facetQueries;
|
||||||
|
|
||||||
|
@@ -0,0 +1,24 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface SearchSortingDefinition {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
type: string;
|
||||||
|
field: string;
|
||||||
|
ascending: boolean;
|
||||||
|
}
|
@@ -21,7 +21,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { MaterialModule } from '../material.module';
|
import { MaterialModule } from '../material.module';
|
||||||
|
|
||||||
import { PipeModule } from '@alfresco/adf-core';
|
import { PipeModule, CoreModule } from '@alfresco/adf-core';
|
||||||
|
|
||||||
import { SearchTriggerDirective } from './components/search-trigger.directive';
|
import { SearchTriggerDirective } from './components/search-trigger.directive';
|
||||||
|
|
||||||
@@ -37,6 +37,7 @@ import { SearchSliderComponent } from './components/search-slider/search-slider.
|
|||||||
import { SearchNumberRangeComponent } from './components/search-number-range/search-number-range.component';
|
import { SearchNumberRangeComponent } from './components/search-number-range/search-number-range.component';
|
||||||
import { SearchCheckListComponent } from './components/search-check-list/search-check-list.component';
|
import { SearchCheckListComponent } from './components/search-check-list/search-check-list.component';
|
||||||
import { SearchDateRangeComponent } from './components/search-date-range/search-date-range.component';
|
import { SearchDateRangeComponent } from './components/search-date-range/search-date-range.component';
|
||||||
|
import { SearchSortingPickerComponent } from './components/search-sorting-picker/search-sorting-picker.component';
|
||||||
|
|
||||||
export const ALFRESCO_SEARCH_DIRECTIVES: any[] = [
|
export const ALFRESCO_SEARCH_DIRECTIVES: any[] = [
|
||||||
SearchComponent,
|
SearchComponent,
|
||||||
@@ -49,6 +50,7 @@ export const ALFRESCO_SEARCH_DIRECTIVES: any[] = [
|
|||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
CoreModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
@@ -64,7 +66,8 @@ export const ALFRESCO_SEARCH_DIRECTIVES: any[] = [
|
|||||||
SearchSliderComponent,
|
SearchSliderComponent,
|
||||||
SearchNumberRangeComponent,
|
SearchNumberRangeComponent,
|
||||||
SearchCheckListComponent,
|
SearchCheckListComponent,
|
||||||
SearchDateRangeComponent
|
SearchDateRangeComponent,
|
||||||
|
SearchSortingPickerComponent
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
...ALFRESCO_SEARCH_DIRECTIVES,
|
...ALFRESCO_SEARCH_DIRECTIVES,
|
||||||
@@ -74,7 +77,8 @@ export const ALFRESCO_SEARCH_DIRECTIVES: any[] = [
|
|||||||
SearchSliderComponent,
|
SearchSliderComponent,
|
||||||
SearchNumberRangeComponent,
|
SearchNumberRangeComponent,
|
||||||
SearchCheckListComponent,
|
SearchCheckListComponent,
|
||||||
SearchDateRangeComponent
|
SearchDateRangeComponent,
|
||||||
|
SearchSortingPickerComponent
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
SearchWidgetContainerComponent,
|
SearchWidgetContainerComponent,
|
||||||
|
@@ -599,6 +599,46 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sorting": {
|
||||||
|
"description": "Sorting options and defaults",
|
||||||
|
"required": ["options"],
|
||||||
|
"properties": {
|
||||||
|
"options": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"description": "Sorting options available for users to choose from",
|
||||||
|
"type": "object",
|
||||||
|
"required": ["key", "label", "type", "field", "ascending"],
|
||||||
|
"properties": {
|
||||||
|
"key": { "type": "string" },
|
||||||
|
"label": { "type": "string" },
|
||||||
|
"type": { "type": "string" },
|
||||||
|
"field": { "type": "string" },
|
||||||
|
"ascending": { "type": "boolean" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaults": {
|
||||||
|
"description": "Predefined sorting to execute by default",
|
||||||
|
"options": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["key", "label", "type", "field", "ascending"],
|
||||||
|
"properties": {
|
||||||
|
"key": { "type": "string" },
|
||||||
|
"label": { "type": "string" },
|
||||||
|
"type": { "type": "string" },
|
||||||
|
"field": { "type": "string" },
|
||||||
|
"ascending": { "type": "boolean" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
<mat-form-field>
|
||||||
|
<mat-select [(value)]="selected" (selectionChange)="onChanged($event)">
|
||||||
|
<mat-option *ngFor="let option of options" [value]="option.key">
|
||||||
|
{{ option.label | translate }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<button *ngIf="selected" mat-icon-button (click)="toggleSortDirection()">
|
||||||
|
<mat-icon *ngIf="ascending">arrow_upward</mat-icon>
|
||||||
|
<mat-icon *ngIf="!ascending">arrow_downward</mat-icon>
|
||||||
|
</button>
|
@@ -0,0 +1,52 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { SortingPickerComponent } from './sorting-picker.component';
|
||||||
|
|
||||||
|
describe('SortingPickerComponent', () => {
|
||||||
|
|
||||||
|
let component: SortingPickerComponent;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
component = new SortingPickerComponent();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should raise changed event on changing value', (done) => {
|
||||||
|
component.selected = 'key1';
|
||||||
|
component.ascending = false;
|
||||||
|
|
||||||
|
component.change.subscribe((event: { key: string, ascending: boolean }) => {
|
||||||
|
expect(event.key).toBe('key2');
|
||||||
|
expect(event.ascending).toBeFalsy();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
component.onChanged(<any> { value: 'key2' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should raise changed event on changing direction', (done) => {
|
||||||
|
component.selected = 'key1';
|
||||||
|
component.ascending = false;
|
||||||
|
|
||||||
|
component.change.subscribe((event: { key: string, ascending: boolean }) => {
|
||||||
|
expect(event.key).toBe('key1');
|
||||||
|
expect(event.ascending).toBeTruthy();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
component.toggleSortDirection();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,61 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component, ViewEncapsulation, Input, EventEmitter, Output } from '@angular/core';
|
||||||
|
import { MatSelectChange } from '@angular/material';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'adf-sorting-picker',
|
||||||
|
templateUrl: './sorting-picker.component.html',
|
||||||
|
encapsulation: ViewEncapsulation.None,
|
||||||
|
host: { class: 'adf-sorting-picker' }
|
||||||
|
})
|
||||||
|
export class SortingPickerComponent {
|
||||||
|
|
||||||
|
/** Available sorting options */
|
||||||
|
@Input()
|
||||||
|
options: Array<{key: string, label: string}> = [];
|
||||||
|
|
||||||
|
/** Currently selected option key */
|
||||||
|
@Input()
|
||||||
|
selected: string;
|
||||||
|
|
||||||
|
/** Current sorting direction */
|
||||||
|
@Input()
|
||||||
|
ascending = true;
|
||||||
|
|
||||||
|
/** Raised each time sorting key or direction gets changed. */
|
||||||
|
@Output()
|
||||||
|
change = new EventEmitter<{ key: string, ascending: boolean }>();
|
||||||
|
|
||||||
|
onChanged(event: MatSelectChange) {
|
||||||
|
this.selected = event.value;
|
||||||
|
this.raiseChangedEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSortDirection() {
|
||||||
|
this.ascending = !this.ascending;
|
||||||
|
this.raiseChangedEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private raiseChangedEvent() {
|
||||||
|
this.change.emit({
|
||||||
|
key: this.selected,
|
||||||
|
ascending: this.ascending
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -81,6 +81,7 @@ import { UserPreferencesService } from './services/user-preferences.service';
|
|||||||
import { SearchConfigurationService } from './services/search-configuration.service';
|
import { SearchConfigurationService } from './services/search-configuration.service';
|
||||||
import { startupServiceFactory } from './services/startup-service-factory';
|
import { startupServiceFactory } from './services/startup-service-factory';
|
||||||
import { EmptyContentComponent } from './components/empty-content/empty-content.component';
|
import { EmptyContentComponent } from './components/empty-content/empty-content.component';
|
||||||
|
import { SortingPickerComponent } from './components/sorting-picker/sorting-picker.component';
|
||||||
|
|
||||||
export function createTranslateLoader(http: HttpClient, logService: LogService) {
|
export function createTranslateLoader(http: HttpClient, logService: LogService) {
|
||||||
return new TranslateLoaderService(http, logService);
|
return new TranslateLoaderService(http, logService);
|
||||||
@@ -232,7 +233,8 @@ export class CoreModuleLazy {
|
|||||||
})
|
})
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
EmptyContentComponent
|
EmptyContentComponent,
|
||||||
|
SortingPickerComponent
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
AboutModule,
|
AboutModule,
|
||||||
@@ -262,7 +264,8 @@ export class CoreModuleLazy {
|
|||||||
DataTableModule,
|
DataTableModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
ButtonsMenuModule,
|
ButtonsMenuModule,
|
||||||
EmptyContentComponent
|
EmptyContentComponent,
|
||||||
|
SortingPickerComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
...providers(),
|
...providers(),
|
||||||
|
@@ -36,6 +36,7 @@ export * from './comments/index';
|
|||||||
export * from './buttons-menu/index';
|
export * from './buttons-menu/index';
|
||||||
|
|
||||||
export * from './components/empty-content/empty-content.component';
|
export * from './components/empty-content/empty-content.component';
|
||||||
|
export * from './components/sorting-picker/sorting-picker.component';
|
||||||
|
|
||||||
export * from './pipes/index';
|
export * from './pipes/index';
|
||||||
export * from './services/index';
|
export * from './services/index';
|
||||||
|
Reference in New Issue
Block a user