[ADF-2914] support for number range patterns (#3282)

* support for number range patterns

* fix memory leak for tag actions
This commit is contained in:
Denys Vuika
2018-05-09 09:33:11 +01:00
committed by Eugenio Romano
parent f63614e964
commit 3a3acd23ff
6 changed files with 129 additions and 15 deletions

View File

@@ -132,7 +132,8 @@
"component": { "component": {
"selector": "number-range", "selector": "number-range",
"settings": { "settings": {
"field": "cm:content.size" "field": "cm:content.size",
"format": "[{FROM} TO {TO}]"
} }
} }
}, },

View File

@@ -326,7 +326,8 @@ Provides ability to select a range between two Numbers based on the particular `
"component": { "component": {
"selector": "number-range", "selector": "number-range",
"settings": { "settings": {
"field": "cm:content.size" "field": "cm:content.size",
"format": "[{FROM} TO {TO}]"
} }
} }
} }
@@ -337,6 +338,42 @@ Provides ability to select a range between two Numbers based on the particular `
![Number Range Widget](../docassets/images/search-number-range.png) ![Number Range Widget](../docassets/images/search-number-range.png)
#### Widget Settings
| Name | Type | Description |
| --- | --- | --- |
| field | string | Field to to use |
| format | string | Value format. Uses string substitution to allow all sorts of [range queries](https://docs.alfresco.com/5.2/concepts/rm-searchsyntax-ranges.html). |
#### Range query format
For more details on the range search format please refer to the "[Search for ranges](https://docs.alfresco.com/5.2/concepts/rm-searchsyntax-ranges.html)" article.
The widget uses `{FROM}` and `{TO}` values together with the required target format of the query.
You can use any type of the query pattern, the widget automatically substitutes the values, for example:
```json
"settings": {
"field": "cm:content.size",
"format": "[{FROM} TO {TO}]"
}
```
The format above may result in the following query at runtime:
```text
cm:content.size:[0 TO 100]
```
Other format examples:
| Format | Example |
| --- | --- |
| `[{FROM} TO {TO}]` | `[0 TO 5]` |
| `<{FROM} TO {TO}]` | `<0 TO 5]` |
| `[{FROM} TO {TO}>` | `[0 TO 5>` |
| `<{FROM} TO {TO}>` | `<0 TO 5>` |
### Radio List Widget ### Radio List Widget
Provides you with a list of radio-boxes, each backed by a particular query fragment. Provides you with a list of radio-boxes, each backed by a particular query fragment.

View File

@@ -85,4 +85,43 @@ describe('SearchNumberRangeComponent', () => {
expect(context.update).toHaveBeenCalled(); expect(context.update).toHaveBeenCalled();
}); });
it('should fetch format from the settings', () => {
component.settings = {
field: 'cm:content.size',
format: '<{FROM} TO {TO}>'
};
component.ngOnInit();
expect(component.field).toEqual('cm:content.size');
expect(component.format).toEqual('<{FROM} TO {TO}>');
});
it('should use default format if not provided', () => {
component.settings = {
field: 'cm:content.size'
};
component.ngOnInit();
expect(component.field).toEqual('cm:content.size');
expect(component.format).toEqual('[{FROM} TO {TO}]');
});
it('should format value based on the current pattern', () => {
const context: any = {
queryFragments: {},
update() {}
};
component.id = 'range1';
component.settings = {
field: 'cm:content.size',
format: '<{FROM} TO {TO}>'
};
component.context = context;
component.ngOnInit();
component.apply({ from: '0', to: '100' }, true);
expect(context.queryFragments['range1']).toEqual('cm:content.size:<0 TO 100>');
});
}); });

View File

@@ -41,7 +41,16 @@ export class SearchNumberRangeComponent implements SearchWidget, OnInit {
settings?: SearchWidgetSettings; settings?: SearchWidgetSettings;
context?: SearchQueryBuilderService; context?: SearchQueryBuilderService;
field: string;
format = '[{FROM} TO {TO}]';
ngOnInit(): void { ngOnInit(): void {
if (this.settings) {
this.field = this.settings.field;
this.format = this.settings.format || '[{FROM} TO {TO}]';
}
const validators = Validators.compose([ const validators = Validators.compose([
Validators.required, Validators.required,
Validators.pattern(/^-?(0|[1-9]\d*)?$/) Validators.pattern(/^-?(0|[1-9]\d*)?$/)
@@ -57,12 +66,30 @@ export class SearchNumberRangeComponent implements SearchWidget, OnInit {
} }
apply(model: { from: string, to: string }, isValid: boolean) { apply(model: { from: string, to: string }, isValid: boolean) {
if (isValid && this.id && this.context && this.settings && this.settings.field) { if (isValid && this.id && this.context && this.field) {
this.context.queryFragments[this.id] = `${this.settings.field}:[${model.from} TO ${model.to}]`; const map = new Map<string, string>();
map.set('FROM', model.from);
map.set('TO', model.to);
const value = this.formatString(this.format, map);
const query = `${this.field}:${value}`;
this.context.queryFragments[this.id] = query;
this.context.update(); this.context.update();
} }
} }
private formatString(str: string, map: Map<string, string>): string {
let result = str;
map.forEach((value, key) => {
const expr = new RegExp('{' + key + '}', 'gm');
result = result.replace(expr, value);
});
return result;
}
reset() { reset() {
this.form.reset({ this.form.reset({
from: '', from: '',

View File

@@ -192,13 +192,14 @@ describe('TagActionsComponent', () => {
}); });
}); });
it('Add tag should be disabled by default', (done) => { it('Add tag should be disabled by default', () => {
component.nodeId = 'fake-node-id'; component.nodeId = 'fake-node-id';
component.newTagName = 'fake-tag-name'; component.newTagName = 'fake-tag-name';
fixture.detectChanges();
let addButton: any = element.querySelector('#add-tag'); let addButton: any = element.querySelector('#add-tag');
expect(addButton.disabled).toEqual(true); expect(addButton.disabled).toEqual(true);
done();
}); });
it('Add tag should return an error if the tag is already present', (done) => { it('Add tag should return an error if the tag is already present', (done) => {

View File

@@ -16,8 +16,9 @@
*/ */
import { TranslationService } from '@alfresco/adf-core'; import { TranslationService } from '@alfresco/adf-core';
import { Component, EventEmitter, Input, OnChanges, Output, ViewEncapsulation } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, Output, ViewEncapsulation, OnDestroy, OnInit } from '@angular/core';
import { TagService } from './services/tag.service'; import { TagService } from './services/tag.service';
import { Subscription } from 'rxjs/Subscription';
/** /**
* *
@@ -30,7 +31,7 @@ import { TagService } from './services/tag.service';
styleUrls: ['./tag-actions.component.scss'], styleUrls: ['./tag-actions.component.scss'],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class TagActionsComponent implements OnChanges { export class TagActionsComponent implements OnChanges, OnInit, OnDestroy {
/** The identifier of a node. */ /** The identifier of a node. */
@Input() @Input()
@@ -49,23 +50,31 @@ export class TagActionsComponent implements OnChanges {
result = new EventEmitter(); result = new EventEmitter();
newTagName: string; newTagName: string;
tagsEntries: any; tagsEntries: any;
errorMsg: string; errorMsg: string;
disableAddTag: boolean = true; disableAddTag: boolean = true;
constructor(private tagService: TagService, private translateService: TranslationService) { private subscriptions: Subscription[] = [];
constructor(private tagService: TagService, private translateService: TranslationService) {}
ngOnInit() {
this.subscriptions.push(
this.tagService.refresh.subscribe(() => { this.tagService.refresh.subscribe(() => {
this.refreshTag(); this.refreshTag();
}); })
);
} }
ngOnChanges() { ngOnChanges() {
return this.refreshTag(); return this.refreshTag();
} }
ngOnDestroy() {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
this.subscriptions = [];
}
refreshTag() { refreshTag() {
if (this.nodeId) { if (this.nodeId) {
this.tagService.getTagsByNodeId(this.nodeId).subscribe((data) => { this.tagService.getTagsByNodeId(this.nodeId).subscribe((data) => {