Adds the multiple autocomplete functionality on Combo Box Autocomplete.

This commit is contained in:
Diamantis Tziotzios 2019-03-26 17:09:02 +02:00
parent 02a42d22ba
commit ed03abd254
12 changed files with 145 additions and 54 deletions

View File

@ -10,12 +10,11 @@ public class AutoCompleteData extends ComboBoxData<AutoCompleteData> {
private String url;
private Option autoCompleteOptions;
private String optionsRoot;
private Boolean multiAutoComplete;
public String getOptionsRoot() {
return optionsRoot;
}
public void setOptionsRoot(String optionsRoot) {
this.optionsRoot = optionsRoot;
}
@ -23,7 +22,6 @@ public class AutoCompleteData extends ComboBoxData<AutoCompleteData> {
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@ -31,17 +29,20 @@ public class AutoCompleteData extends ComboBoxData<AutoCompleteData> {
public Option getAutoCompleteOptions() {
return autoCompleteOptions;
}
public void setAutoCompleteOptions(Option autoCompleteOptions) {
this.autoCompleteOptions = autoCompleteOptions;
}
public Boolean getMultiAutoComplete() { return multiAutoComplete; }
public void setMultiAutoComplete(Boolean multiAutoComplete) { this.multiAutoComplete = multiAutoComplete; }
@Override
public Element toXml(Document doc) {
Element root = super.toXml(doc);
root.setAttribute("url", this.url);
root.setAttribute("optionsRoot", this.optionsRoot);
root.setAttribute("multiAutoComplete", this.multiAutoComplete.toString());
Element element = doc.createElement("option");
element.setAttribute("label", this.autoCompleteOptions.getLabel());
element.setAttribute("value", autoCompleteOptions.getValue());
@ -54,6 +55,7 @@ public class AutoCompleteData extends ComboBoxData<AutoCompleteData> {
super.fromXml(item);
this.url = item.getAttribute("url");
this.optionsRoot = item.getAttribute("optionsRoot");
this.multiAutoComplete = Boolean.parseBoolean(item.getAttribute("multiAutoComplete"));
Element optionElement = (Element) item.getElementsByTagName("option").item(0);
if (optionElement != null) {
this.autoCompleteOptions = new Option();
@ -70,6 +72,7 @@ public class AutoCompleteData extends ComboBoxData<AutoCompleteData> {
if (data != null) {
this.url = (String) ((Map<String, Object>) data).get("url");
this.optionsRoot = (String) ((Map<String, Object>) data).get("optionsRoot");
this.multiAutoComplete = (Boolean) ((Map<Boolean, Object>) data).get("multiAutoComplete");
Map<String, String> options = ((Map<String, Map<String, String>>) data).get("autoCompleteOptions");
if (options != null) {
this.autoCompleteOptions.setLabel(options.get("label"));

View File

@ -5,6 +5,7 @@ import { DateTimeFormatPipe } from './pipes/date-time-format.pipe';
import { NgForLimitPipe } from './pipes/ng-for-limit.pipe';
import { TimezoneInfoDisplayPipe } from './pipes/timezone-info-display.pipe';
import { EnumUtils } from './services/utilities/enum-utils.service';
import { JsonParserPipe } from './pipes/json-parser.pipe';
//
//
@ -17,13 +18,15 @@ import { EnumUtils } from './services/utilities/enum-utils.service';
NgForLimitPipe,
TimezoneInfoDisplayPipe,
DateFormatPipe,
DateTimeFormatPipe
DateTimeFormatPipe,
JsonParserPipe
],
exports: [
NgForLimitPipe,
TimezoneInfoDisplayPipe,
DateFormatPipe,
DateTimeFormatPipe
DateTimeFormatPipe,
JsonParserPipe
],
providers: [
EnumUtils,
@ -31,7 +34,8 @@ import { EnumUtils } from './services/utilities/enum-utils.service';
NgForLimitPipe,
TimezoneInfoDisplayPipe,
DateFormatPipe,
DateTimeFormatPipe
DateTimeFormatPipe,
JsonParserPipe
]
})
export class FormattingModule { }

View File

@ -9,6 +9,7 @@ export interface AutoCompleteFieldData extends FieldData {
url: string;
optionsRoot: string;
autoCompleteOptions: FieldDataOption;
multiAutoComplete: boolean;
}
export interface CheckBoxFieldData extends FieldData {
@ -43,4 +44,4 @@ export interface FieldDataOption extends FieldData {
export interface DatePickerFieldData extends FieldData {
}
}

View File

@ -0,0 +1,16 @@
import { Pipe, PipeTransform } from "@angular/core";
@Pipe({
name: 'jsonParser'
})
export class JsonParserPipe implements PipeTransform {
transform(val) {
if (typeof val === 'string') {
return JSON.parse(val)
}
else {
return val;
}
}
}

View File

@ -3,12 +3,14 @@ import { CommonFormsModule } from '../../common/forms/common-forms.module';
import { CommonUiModule } from '../../common/ui/common-ui.module';
import { MultipleAutoCompleteComponent } from './multiple/multiple-auto-complete.component';
import { SingleAutoCompleteComponent } from './single/single-auto-complete.component';
import { FormattingModule } from '../../core/formatting.module';
@NgModule({
imports: [
CommonUiModule,
CommonFormsModule
CommonFormsModule,
FormattingModule
],
declarations: [
SingleAutoCompleteComponent,

View File

@ -20,4 +20,6 @@ export interface MultipleAutoCompleteConfiguration {
titleFn?: (item: any) => string;
// Display function for the drop down subtitle
subtitleFn?: (item: any) => string;
// Callback to intercept value assignment based on item selection
valueAssign?: (selectedItem: any) => any;
}

View File

@ -1,6 +1,6 @@
<div class="row multiple-auto-complete">
<mat-chip-list #chipList ngDefaultControl class="col multi-chip-list" [disabled]="disabled">
<mat-chip *ngFor="let selectedItem of _chipItems()" [disabled]="disabled" [selectable]="selectable" [removable]="removable" (removed)="_removeSelectedItem(selectedItem)">
<mat-chip *ngFor="let selectedItem of (_chipItems() | jsonParser)" [disabled]="disabled" [selectable]="selectable" [removable]="removable" (removed)="_removeSelectedItem(selectedItem)">
{{this._displayFn(selectedItem)}}
<mat-icon matChipRemove *ngIf="!disabled && removable">cancel</mat-icon>
</mat-chip>

View File

@ -87,16 +87,9 @@ export class MultipleAutoCompleteComponent implements OnInit, MatFormFieldContro
}
private _selectedValue;
@ViewChild('textInput') textInput: ElementRef;
@ViewChild(MatAutocompleteTrigger) autocomplete: MatAutocompleteTrigger;
constructor(
private fm: FocusMonitor,
private elRef: ElementRef,
@ -115,7 +108,6 @@ export class MultipleAutoCompleteComponent implements OnInit, MatFormFieldContro
}
ngOnInit() {
}
filter(query: string): Observable<any[]> {
@ -168,13 +160,31 @@ export class MultipleAutoCompleteComponent implements OnInit, MatFormFieldContro
return this.value || [];
}
_arraysEqual(arr1, arr2) {
if (arr1.length !== arr2.length)
return false;
for (var i = arr1.length; i--;) {
if (arr1[i].id !== arr2[i].id)
return false;
}
return true;
}
_optionSelected(event: MatAutocompleteSelectedEvent) {
const newValue = this.value || [];
newValue.push(event.option.value);
this._setValue(newValue);
this.stateChanges.next();
this.optionSelected.emit(newValue);
this.textInput.nativeElement.value = '';
if (this.configuration.valueAssign) {
const newValue = this.configuration.valueAssign(this.value) || [];
newValue.push(event.option.value);
this._setValue(this.configuration.valueAssign(newValue));
}
else {
const newValue = this.value || [];
newValue.push(event.option.value);
this._setValue(newValue);
this.stateChanges.next();
this.optionSelected.emit(newValue);
this.textInput.nativeElement.value = '';
}
}
private _setValue(value: any) {
@ -221,13 +231,19 @@ export class MultipleAutoCompleteComponent implements OnInit, MatFormFieldContro
}
_removeSelectedItem(item: any): void {
const index = this.value.indexOf(item);
if (index >= 0) {
this.value.splice(index, 1);
if (this.configuration.valueAssign) {
this.optionRemoved.emit(item);
this.textInput.nativeElement.focus();
}
else {
const index = this.value.indexOf(item);
if (index >= 0) {
this.value.splice(index, 1);
this.optionRemoved.emit(item);
}
this.textInput.nativeElement.focus();
this.pushChanges(this.value);
}
this.textInput.nativeElement.focus();
this.pushChanges(this.value);
}
_onInputClick(item: any) {

View File

@ -9,16 +9,20 @@ export class AutoCompleteFieldDataEditorModel extends FieldDataEditorModel<AutoC
public type: DatasetProfileComboBoxType = DatasetProfileComboBoxType.Autocomplete;
public url: string;
public optionsRoot: string;
public multiAutoComplete: boolean;
public autoCompleteOptions: FieldDataOptionEditorModel = new FieldDataOptionEditorModel();
//public multiAutoCompleteOptions: FieldDataOptionEditorModel = new FieldDataOptionEditorModel();
buildForm(): FormGroup {
const formGroup = this.formBuilder.group({
label: [this.label],
type: [this.type],
url: [this.url],
optionsRoot: [this.optionsRoot]
optionsRoot: [this.optionsRoot],
multiAutoComplete: [this.multiAutoComplete]
});
formGroup.addControl('autoCompleteOptions', this.autoCompleteOptions.buildForm());
return formGroup;
}
@ -27,6 +31,7 @@ export class AutoCompleteFieldDataEditorModel extends FieldDataEditorModel<AutoC
this.url = item.url;
this.label = item.label;
this.optionsRoot = item.optionsRoot;
this.multiAutoComplete = item.multiAutoComplete;
this.autoCompleteOptions = new FieldDataOptionEditorModel().fromModel(item.autoCompleteOptions);
return this;
}

View File

@ -1,15 +1,20 @@
<div class="row" *ngIf="form.get('data')">
<h5 style="font-weight: bold" class="col-12">{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-TITLE' | translate}}</h5>
<h5 style="font-weight: bold" class="col-auto">{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-TITLE' | translate}}</h5>
<mat-checkbox class="col-auto" [formControl]="this.form.get('data').get('multiAutoComplete')">
DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-MULTIPLE-AUTOCOMPLETE
</mat-checkbox>
<mat-form-field class="col-12">
<input matInput type="string" placeholder="{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-PLACEHOLDER' | translate}}" [formControl]="form.get('data').get('label')">
<input matInput type="string" placeholder="{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-PLACEHOLDER' | translate}}"
[formControl]="form.get('data').get('label')">
</mat-form-field>
<mat-form-field class="col-md-12">
<input matInput placeholder="{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-URL' | translate}}" [formControl]="this.form.get('data').get('url')">
</mat-form-field>
<mat-form-field class="col-md-4">
<input matInput placeholder="{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-OPTIONS-ROOT' | translate}}" [formControl]="this.form.get('data').get('optionsRoot')">
<input matInput placeholder="{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-OPTIONS-ROOT' | translate}}"
[formControl]="this.form.get('data').get('optionsRoot')">
</mat-form-field>
<mat-form-field class="col-md-4">
<input matInput placeholder="{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-LABEL' | translate}}" [formControl]="this.form.get('data').get('autoCompleteOptions').get('label')">

View File

@ -1,4 +1,5 @@
<div *ngIf="form && this.visibilityRulesService.checkElementVisibility(this.form.get('id').value)" [id]="this.form.get('id').value" [formGroup]="form" [ngSwitch]="this.form.get('viewStyle').value.renderStyle" class="dynamic-form-field row">
<div *ngIf="form && this.visibilityRulesService.checkElementVisibility(this.form.get('id').value)" [id]="this.form.get('id').value"
[formGroup]="form" [ngSwitch]="this.form.get('viewStyle').value.renderStyle" class="dynamic-form-field row">
<h5 *ngIf="this.form.get('title').value && !isChild">{{this.form.get('title').value}}</h5>
<mat-icon *ngIf="this.form.get('additionalInformation').value && !isChild" matTooltip="{{this.form.get('additionalInformation').value}}">info</mat-icon>
@ -6,20 +7,28 @@
<h5 *ngIf="this.form.get('description').value && !isChild" class="col-12">{{this.form.get('description').value}}
</h5>
<h5 *ngIf="this.form.get('extendedDescription').value && !isChild" class="col-12">
<i>{{this.form.get('extendedDescription').value}}</i></h5>
<i>{{this.form.get('extendedDescription').value}}</i>
</h5>
<mat-form-field *ngSwitchCase="datasetProfileFieldViewStyleEnum.FreeText" class="col-12">
<input matInput [formControl]="form.get('value')" placeholder="{{form.get('data').value.label}}" [required]="form.get('validationRequired').value">
<mat-error *ngIf="form.get('value')['errors'] && form.get('value')['errors']['required']">{{'GENERAL.VALIDATION.REQUIRED'
| translate}}</mat-error>
<mat-error *ngIf="form.get('value')['errors'] && form.get('value')['errors']['required']">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
<div *ngSwitchCase="datasetProfileFieldViewStyleEnum.ComboBox" class="col-12">
<div class="row">
<mat-form-field class="col-md-12" *ngIf="form.get('data').value.type === datasetProfileComboBoxTypeEnum.Autocomplete">
<app-single-auto-complete placeholder="{{ form.get('data').value.label | translate }}" [formControl]="form.get('value')" [configuration]="singleAutoCompleteConfiguration" [required]="form.get('validationRequired').value">
</app-single-auto-complete>
<mat-error *ngIf="form.get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
<div *ngIf="form.get('data').value.multiAutoComplete">
<app-multiple-auto-complete placeholder="{{ form.get('data').value.label | translate }}" [formControl]="form.get('value')"
[configuration]="multipleAutoCompleteConfiguration" (optionRemoved)="_optionRemove($event)">
</app-multiple-auto-complete>
</div>
<div *ngIf="!(form.get('data').value.multiAutoComplete)">
<app-single-auto-complete placeholder="{{ form.get('data').value.label | translate }}" [formControl]="form.get('value')"
[configuration]="singleAutoCompleteConfiguration" [required]="form.get('validationRequired').value">
</app-single-auto-complete>
<mat-error *ngIf="form.get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}
</mat-error>
</div>
</mat-form-field>
<mat-form-field *ngIf="form.get('data').value.type === datasetProfileComboBoxTypeEnum.WordList" class="col-md-12">
<mat-select [formControl]="form.get('value')" placeholder="{{ form.get('data').value.label | translate }}" [required]="form.get('validationRequired').value">
@ -38,12 +47,12 @@
</div>
<mat-form-field *ngSwitchCase="datasetProfileFieldViewStyleEnum.TextArea" class="col-12">
<textarea matInput [formControl]="form.get('value')" matTextareaAutosize matAutosizeMinRows="2" matAutosizeMaxRows="10" [required]="form.get('validationRequired').value" placeholder="{{ form.get('data').value.label | translate }}"></textarea>
<textarea matInput [formControl]="form.get('value')" matTextareaAutosize matAutosizeMinRows="2" matAutosizeMaxRows="10" [required]="form.get('validationRequired').value"
placeholder="{{ form.get('data').value.label | translate }}"></textarea>
<button mat-icon-button *ngIf="!form.get('value').disabled && form.get('value').value" matSuffix aria-label="Clear" (click)="this.form.patchValue({'value': ''})">
<mat-icon>close</mat-icon>
</button>
<mat-error *ngIf="form.get('value')['errors'] && form.get('value')['errors']['required']">{{'GENERAL.VALIDATION.REQUIRED'
| translate}}</mat-error>
<mat-error *ngIf="form.get('value')['errors'] && form.get('value')['errors']['required']">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
<div *ngSwitchCase="datasetProfileFieldViewStyleEnum.BooleanDecision" class="col-12">
@ -66,7 +75,8 @@
</div>
<mat-form-field *ngSwitchCase="datasetProfileFieldViewStyleEnum.DatePicker" class="col-12">
<input matInput placeholder="{{ form.get('data').value.label | translate }}" class="table-input" [matDatepicker]="date" [required]="form.get('validationRequired').value" [formControl]="form.get('value')">
<input matInput placeholder="{{ form.get('data').value.label | translate }}" class="table-input" [matDatepicker]="date" [required]="form.get('validationRequired').value"
[formControl]="form.get('value')">
<mat-datepicker-toggle matSuffix [for]="date"></mat-datepicker-toggle>
<mat-datepicker #date></mat-datepicker>
<mat-error *ngIf="form.get('value').hasError('required')">

View File

@ -9,6 +9,7 @@ import { RequestItem } from '../../../../../core/query/request-item';
import { DatasetExternalAutocompleteService } from '../../../../../core/services/dataset/dataset-external-autocomplete.service';
import { SingleAutoCompleteConfiguration } from '../../../../../library/auto-complete/single/single-auto-complete-configuration';
import { VisibilityRulesService } from '../../visibility-rules/visibility-rules.service';
import { MultipleAutoCompleteConfiguration } from '../../../../../library/auto-complete/multiple/multiple-auto-complete-configuration';
@Component({
selector: 'app-form-field',
@ -26,6 +27,7 @@ export class FormFieldComponent extends BaseComponent implements OnInit {
// trackByFn = (index, item) => item ? item['id'] : null;
public singleAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
public multipleAutoCompleteConfiguration: MultipleAutoCompleteConfiguration;
datasetProfileFieldViewStyleEnum = DatasetProfileFieldViewStyle;
datasetProfileComboBoxTypeEnum = DatasetProfileComboBoxType;
@ -41,13 +43,24 @@ export class FormFieldComponent extends BaseComponent implements OnInit {
// Setup autocomplete configuration if needed
if (this.form.get('viewStyle').value.renderStyle === DatasetProfileFieldViewStyle.ComboBox && this.form.get('data').value.type === DatasetProfileComboBoxType.Autocomplete) {
this.singleAutoCompleteConfiguration = {
filterFn: this.searchFromAutocomplete.bind(this),
initialItems: (extraData) => this.searchFromAutocomplete(''),
displayFn: (item) => (item != null && item.length > 1) ? JSON.parse(item).label : item['label'],
titleFn: (item) => item['label'],
valueAssign: (item) => JSON.stringify(item)
};
if (!(this.form.controls['data'].value.multiAutoComplete)) {
this.singleAutoCompleteConfiguration = {
filterFn: this.searchFromAutocomplete.bind(this),
initialItems: (extraData) => this.searchFromAutocomplete(''),
displayFn: (item) => (item != null && item.length > 1) ? JSON.parse(item).label : item['label'],
titleFn: (item) => item['label'],
valueAssign: (item) => JSON.stringify(item)
};
}
else {
this.multipleAutoCompleteConfiguration = {
filterFn: this.searchFromAutocomplete.bind(this),
initialItems: (extraData) => this.searchFromAutocomplete(''),
displayFn: (item) => item['label'],
titleFn: (item) => item['label'],
valueAssign: this._transformValue
}
}
}
// this.form = this.visibilityRulesService.getFormGroup(this.field.id);
@ -57,7 +70,22 @@ export class FormFieldComponent extends BaseComponent implements OnInit {
this.visibilityRulesService.updateValueAndVisibility(this.form.get('id').value, item);
});
}
// }
_optionRemove(event) {
const array = JSON.parse(this.form.get('value').value);
if (array) {
const index = array.map(x => x.id).indexOf(event.id);
if (index >= 0) {
array.splice(index, 1);
}
this.form.get('value').patchValue(JSON.stringify(array));
}
}
_transformValue(item: any) {
if (!item) return [];
return item && typeof item === 'string' ? JSON.parse(item) : JSON.stringify(item);
}
searchFromAutocomplete(query: string) {
const autocompleteRequestItem: RequestItem<DatasetExternalAutocompleteCriteria> = new RequestItem();
@ -68,5 +96,4 @@ export class FormFieldComponent extends BaseComponent implements OnInit {
autocompleteRequestItem.criteria.like = query;
return this.datasetExternalAutocompleteService.queryAutocomplete(autocompleteRequestItem);
}
}