Newer
Older
François Grand
committed
import { Field } from "../field";
import { SelectEntry } from "./select-entry";
import { arraysAreEqual } from "../../../util/util";
François Grand
committed
import { FormulaireNode } from "../formulaire-node";
import { ServiceFactory } from "app/services/service-factory";
import { FormulaireDefinition } from "../../definition/form-definition";
François Grand
committed
import { enumValueFromString, Nub } from "jalhyd";
François Grand
committed
export abstract class SelectField extends Field {
/**
* select options
*/
protected _entries: SelectEntry[];
/**
* currently selected option
*/
protected _selectedEntry: SelectEntry | SelectEntry[];
/** if true, user can select multiple values */
protected _multiple = false;
/** string to build the select entries IDs from */
protected _entriesBaseId: string;
/** name of the Nub property associated to this field, if any */
protected _associatedProperty: string;
/** default value (from configuration) for this field */
protected _configDefaultValue: string;
/**
* message to display below the select field when no option is selected,
* if this message is defined and not empty
*/
protected _messageWhenEmpty: string;
/**
* map id <-> select entry
*/
protected _entryMap: { [key: string]: SelectEntry } = {};
François Grand
committed
constructor(parent: FormulaireNode) {
super(parent);
this.clearEntries();
}
/**
* associated nub
*/
protected get nub(): Nub {
const parent = this.parentForm;
if (parent === undefined) {
return undefined;
}
François Grand
committed
return (this.parentForm as FormulaireDefinition).currentNub;
}
public getValue(): SelectEntry | SelectEntry[] {
return this._selectedEntry;
}
/**
* Updates _selectedEntry; notifies observers only if
* value.id has changed
*/
public setValue(v: SelectEntry | SelectEntry[]) {
// if multiple is true, value must be an array
if (this._multiple && !Array.isArray(v)) {
throw new Error("select value is not an array");
}
const previousSelectedEntry = this._selectedEntry;
this._selectedEntry = v;
// if old and new values are not both undefined
if (!(previousSelectedEntry === undefined && v === undefined)) {
// if value changed
const valueChanged = (
(previousSelectedEntry === undefined && v !== undefined)
|| (
previousSelectedEntry !== undefined && v !== undefined &&
François Grand
committed
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
!Array.isArray(previousSelectedEntry)
&& !Array.isArray(v)
&& previousSelectedEntry.id !== v.id
)
|| (
Array.isArray(previousSelectedEntry)
&& Array.isArray(v)
&& !arraysAreEqual(previousSelectedEntry, v, "id", true)
)
);
if (valueChanged) {
this.notifyValueChanged();
}
}
}
public notifyValueChanged() {
this.notifyObservers({
"action": "select",
"value": this._selectedEntry
}, this);
}
/**
* Sets value from given entry id(s); if it was not found, sets the
* first available entry as selectedValue
*/
protected setValueFromId(id: string | string[]) {
let found = false;
const entries = [];
if (Array.isArray(id)) {
for (const e of this._entries) {
if (id.includes(e.id)) {
entries.push(e);
found = true;
}
}
this.setValue(entries);
} else {
for (const e of this._entries) {
if (e.id === id) {
found = true;
this.setValue(e);
}
}
}
if (!found) {
this.findAndSetDefaultValue();
}
}
/**
François Grand
committed
* Try to find a default value to select.
* Priority order :
* - passed parameter
* - nub value for associated property
* - default value from configuration file
* - first select entry
François Grand
committed
*/
protected findAndSetDefaultValue(value?: string) {
François Grand
committed
// default to first available entry if any
if (this._entries.length > 0) {
let val: SelectEntry;
François Grand
committed
if (value !== undefined) {
val = this.getEntryFromValue(enumValueFromString(this._associatedProperty, value));
François Grand
committed
if (val === undefined) {
throw Error("invalid select default value " + value + " for " + this._associatedProperty + " property");
}
} else {
if (this.nub !== undefined) {
val = this.getEntryFromValue(this.nub.getPropValue(this._associatedProperty));
// nub may not have "this._associatedProperty" as a property
}
if (val === undefined && this._configDefaultValue !== undefined) {
val = this.getEntryFromValue(enumValueFromString(this._associatedProperty, this._configDefaultValue));
if (val === undefined) {
throw Error("invalid select default value " + this._configDefaultValue + " for " + this._associatedProperty + " property");
}
François Grand
committed
}
François Grand
committed
}
François Grand
committed
François Grand
committed
if (this._multiple) {
François Grand
committed
this.setValue([val]);
François Grand
committed
} else {
François Grand
committed
this.setValue(val);
François Grand
committed
}
} else {
// notify observers that no value is selected anymore
this.notifyValueChanged();
}
}
public parseConfig(field: {}, data?: {}) {
this._confId = field["id"];
this._entriesBaseId = this._confId + "_";
this._helpLink = field["help"];
François Grand
committed
//this.afterParseConfig(); // done in FieldSet.parse_select()
François Grand
committed
}
/**
* Once config is parsed, init original value from model
* (needs config for this._entriesBaseId to be set).
* Triggered at the end of parseConfig()
*/
François Grand
committed
public afterParseConfig() {
François Grand
committed
this.populate();
this.initSelectedValue();
}
/**
* fill select with options
*/
protected abstract populate();
/**
* initialise select (loads UI with the value held by the model)
*/
protected abstract initSelectedValue();
/**
* associated nub property
*/
public get associatedProperty(): string {
return this._associatedProperty;
}
/**
* @returns true if select field is associated to a nub property
*/
public get hasAssociatedNubProperty(): boolean {
return this._associatedProperty !== undefined;
}
François Grand
committed
/**
* default value from configuration
*/
public get configDefaultValue(): string {
return this._configDefaultValue;
}
private clearEntries() {
this._entries = [];
}
public get entries() {
return this._entries;
}
protected addEntry(e: SelectEntry) {
this._entries.push(e);
}
protected createOrGetEntry(id: string, val: any, lbl?: string): SelectEntry {
let res: SelectEntry = this._entryMap[id];
if (res === undefined) {
res = new SelectEntry(id, val, lbl);
this._entryMap[id] = res;
}
return res;
}
François Grand
committed
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
public getEntryFromValue(val: any): SelectEntry {
for (const se of this._entries) {
if (se.value === val) {
return se;
}
}
}
/**
* Reloads available entries, trying to keep the current selected
* value; does not notify observers if value did not change
*/
public updateEntries() {
// store previous selected entry
const pse = this._selectedEntry;
// empty
this.clearEntries();
// populate
this.populate();
this.updateLocalisation();
// if no entry is available anymore, unset value
if (this._entries.length === 0) {
if (this._multiple) {
this.setValue([]);
} else {
this.setValue(undefined);
}
} else {
// keep previously selected entry(ies) if possible
if (pse) {
if (!Array.isArray(pse) && pse.id) {
this.setValueFromId(pse.id);
} else if (Array.isArray(pse) && pse.length > 0) {
this.setValueFromId(pse.map((e) => e.id));
} else {
this.findAndSetDefaultValue();
}
} else {
this.findAndSetDefaultValue();
}
}
}
public get entriesBaseId(): string {
return this._entriesBaseId;
}
public get messageWhenEmpty(): string {
let msg: string;
if (this._selectedEntry === undefined && this._messageWhenEmpty) {
msg = ServiceFactory.i18nService.localizeText(this._messageWhenEmpty);
}
return msg;
}
public get multiple(): boolean {
return this._multiple;
}
public updateLocalisation() {
super.updateLocalisation();
for (const e of this._entries) {
const aId = e.id.split("_");
const trad = ServiceFactory.formulaireService.localizeText(
`${aId[1].toUpperCase()}_${aId[2]}`,
this.parentForm.currentNub.calcType
);
e.label = trad;
}
}
}