// ---------------------------------------------------------------------------
// ValueEditorControl
// ---------------------------------------------------------------------------
class ValueEditorControl {
constructor(valueEditor, options) {
this._valueEditor = valueEditor;
let needsBg = false;
switch (options["controlType"]) {
case "text-area":
case "text-field":
case "date-field":
case "file-name-field":
case "password-field":
case "number-field":
// Text fields only.
// Needed because in some cases, it's an error to have an empty
// field (empty => no text displayed using error-color).
needsBg = true;
break;
}
let opts = Object.assign({ "missing-color": "#008080", // Cyan.
"missing-background-color":
needsBg? "#F0FFFF" : null,
"error-color": "#C00000", // Red.
"error-background-color":
needsBg? "#FFF0F0" : null },
options);
this._missingForeground = opts["missing-color"];
this._missingBackground = opts["missing-background-color"];
this._errorForeground = opts["error-color"];
this._errorBackground = opts["error-background-color"];
this._element = null;
this._value = this._state = undefined;
}
static setControlStyle(elem, ...classes) {
elem.classList.add("xxe-valed-ctrl", ...classes);
}
init(elem, input, needsFocusHandler) {
this._element = elem;
if (input !== null && !input.disabled) {
if (needsFocusHandler) {
input.addEventListener("focus", this.onFocus.bind(this));
}
// Otherwise focus handler not needed. Clicking sets focus and
// triggers a change event, which commits the changed value after
// selecting the element.
// Note that for input type=text, textarea, in addition to "blur",
// pressing "Enter" also triggers a "change" event.
input.addEventListener("change", this.onChange.bind(this));
input.addEventListener("keydown", this.onTabOut.bind(this));
}
}
static labelsFromValues(labels, values) {
if (labels === null ||
!Array.isArray(labels) || labels.length !== values.length) {
labels = values;
}
if (labels.indexOf("") >= 0) {
labels = labels.map((l) => ((l.length === 0)? "(empty string)" : l));
}
return labels;
}
get valueEditor() {
return this._valueEditor;
}
get element() {
return this._element;
}
// Must be overriden.
getValue() {
return this._value;
}
// Must be overriden.
setValue(value) {
this._value = value;
}
getState() {
return this._state;
}
setState(state) {
if (state !== this._state) {
this._state = state;
switch (state) {
case ValueEditorControl.STATE_NORMAL:
if (this._missingForeground) {
this._element.style.removeProperty("color");
}
if (this._missingBackground) {
this._element.style.removeProperty("background-color");
}
break;
case ValueEditorControl.STATE_MISSING:
if (this._missingForeground) {
this._element.style.color = this._missingForeground;
}
if (this._missingBackground) {
this._element.style.backgroundColor =
this._missingBackground;
}
break;
case ValueEditorControl.STATE_ERROR:
if (this._errorForeground) {
this._element.style.color = this._errorForeground;
}
if (this._errorBackground) {
this._element.style.backgroundColor = this._errorBackground;
}
break;
}
}
}
// -----------------------------------
// Event handlers
// -----------------------------------
onFocus(event) {
XUI.Util.consumeEvent(event);
this.valueEditor.selectEditedElement();
}
onChange(event) {
XUI.Util.consumeEvent(event);
let state;
if (this.checkValue()) {
state = ValueEditorControl.STATE_NORMAL;
} else {
state = ValueEditorControl.STATE_ERROR;
}
this.setState(state);
this.valueEditor.commitValue(this.getValue(), state);
}
// Must be overriden by all editors validating typed value
// on the client side.
checkValue() {
return true;
}
commitValueFailed() {
this.setState(ValueEditorControl.STATE_ERROR);
}
onTabOut(event) {
// FIREFOX BUG: Does not work with input type=color.
// Clicking input type=color gives it the focus and displays
// a color chooser dialog box. Closing the dialog box
// does not return the focus to input type=color.
//
// FIREFOX BUG: Does not always work with input type=number.
// Clicking an arrow without first clicking inside the field
// does not gives the focus to the input type=number.
if (event.key === "Tab" && !event.ctrlKey && !event.metaKey) {
XUI.Util.consumeEvent(event);
this.valueEditor.selectEditedElement()
.then((selected) => {
if (selected) {
let param;
if (event.shiftKey) {
param = `(preceding::text()|preceding::comment()|\
preceding::processing-instruction())[last()]`;
} else {
param = `(following::text()|following::comment()|\
following::processing-instruction())[1]`;
}
this.valueEditor.documentView.executeCommand(
EXECUTE_HELPER, "xpathSearch", param);
}
});
}
}
}
ValueEditorControl.STATE_NORMAL = 0;
ValueEditorControl.STATE_MISSING = 1;
ValueEditorControl.STATE_ERROR = 2;
// -----------------------------------
// DummyVCE
// -----------------------------------
class DummyVCE extends ValueEditorControl {
constructor(valueEditor, options) {
super(valueEditor, options);
const span = document.createElement("span");
ValueEditorControl.setControlStyle(span);
span.setAttribute("style", "padding: 0.125em 0.25em");
span.textContent = options["controlType"];
this.init(span, null, false);
}
}
// ---------------------------------------------------------------------------
// CheckBoxVCE
// ---------------------------------------------------------------------------
class CheckBoxVCE extends ValueEditorControl {
constructor(valueEditor, options) {
super(valueEditor, options);
let opts = Object.assign({ "label": null,
"unchecked-value": null,
"checked-value": null,
"allow-empty-checked-value": false,
"remove-value": false }, options);
this._uncheckedValue = opts["unchecked-value"];
this._checkedValue = opts["checked-value"];
this._allowEmptyCheckedValue = opts["allow-empty-checked-value"];
this._removeValue = opts["remove-value"];
this._toggle = document.createElement("input");
this._toggle.setAttribute("type", "checkbox");
ValueEditorControl.setControlStyle(this._toggle);
let element = this._toggle;
if (opts["label"]) {
let label = document.createElement("label");
element = label;
ValueEditorControl.setControlStyle(label);
label.appendChild(this._toggle);
label.appendChild(document.createTextNode(opts["label"]));
}
// Check the consistency of the options ---
this._disabled = true;
if (this._removeValue) {
if ((this._uncheckedValue === null &&
this._checkedValue !== null) ||
(this._checkedValue === null &&
this._uncheckedValue !== null)) {
this._disabled = false;
}
} else {
if (this._checkedValue !== null &&
this._uncheckedValue !== null) {
this._disabled = false;
}
}
if (!this._disabled &&
this._allowEmptyCheckedValue &&
this._checkedValue === null) {
this._disabled = true;
}
this._toggle.disabled = this._disabled;
this.init(element, this._toggle, /*needsFocusHandler*/ false);
}
getValue() {
if (this._disabled) {
return super.getValue();
}
// May return null. Means: no value.
return this._toggle.checked? this._checkedValue : this._uncheckedValue;
}
setValue(value) {
if (this._disabled) {
super.setValue(value);
return;
}
// Value may be null. Means: no value.
this._toggle.checked =
(value === this._checkedValue ||
(value !== null && value.length === 0 &&
this._checkedValue !== null && this._allowEmptyCheckedValue));
}
}
// ---------------------------------------------------------------------------
// ColorChooserVCE
// ---------------------------------------------------------------------------
class ColorChooserVCE extends ValueEditorControl {
constructor(valueEditor, options) {
super(valueEditor, options);
const input = document.createElement("input");
input.setAttribute("type", "color");
ValueEditorControl.setControlStyle(input, "xxe-valed-ctrl2",
"xxe-valed-clrchsr");
let w = Number(options["swatch-width"]);
if (w >= 5 && w < 1000) {
input.style.width = `${w}px`;
}
let h = Number(options["swatch-height"]);
if (h >= 5 && h < 500) {
input.style.height = `${h}px`;
}
this.init(input, input, true);
}
getValue() {
return this._element.value; // Always lower-case; same in
// ColorChooserRenderer.
}
setValue(value) {
this._element.value = (value === null)? "#000000" : value;
}
}
// ---------------------------------------------------------------------------
// DateTimePickerVCE
// ---------------------------------------------------------------------------
class DateTimePickerVCE extends ValueEditorControl {
constructor(valueEditor, options, type="datetime-local") {
super(valueEditor, options);
// "format", "pattern", "language", "country", "variant", "columns"
// are ignored on the client-side.
const input = document.createElement("input");
input.setAttribute("type", type);
if (type !== "date") {
input.setAttribute("step", "1"); // We want ":ss" too.
}
ValueEditorControl.setControlStyle(input);
this.init(input, input, true);
}
getValue() {
return this._element.value;
}
setValue(value) {
this._element.value = (value === null)? "" : value;
}
}
// -----------------------------------
// DatePickerVCE
// -----------------------------------
class DatePickerVCE extends DateTimePickerVCE {
constructor(valueEditor, options) {
super(valueEditor, options, "date");
}
}
// -----------------------------------
// TimePickerVCE
// -----------------------------------
class TimePickerVCE extends DateTimePickerVCE {
constructor(valueEditor, options) {
super(valueEditor, options, "time");
}
}
// ---------------------------------------------------------------------------
// GaugeVCE
// ---------------------------------------------------------------------------
class GaugeVCE extends ValueEditorControl {
constructor(valueEditor, options) {
super(valueEditor, options);
// low, high, optimum, low-color, high-color, optimum-color are ignored.
let opts = Object.assign({ "min": 0, "max": 1, "step": -1,
"thumb": false }, options);
let minValue = opts["min"];
let maxValue = opts["max"];
let stepValue = opts["step"];
this._disabled = false;
if (minValue >= maxValue) {
this._disabled = true;
} else {
if (stepValue > 0 &&
stepValue > (maxValue - minValue)) {
this._disabled = true;
}
}
if (this._disabled) {
minValue = 0;
maxValue = 1;
stepValue = -1;
}
const input = document.createElement("input");
input.setAttribute("type", "range");
this._minValue = String(minValue);
input.setAttribute("min", this._minValue);
input.setAttribute("max", String(maxValue));
input.setAttribute("step", (stepValue <= 0)? "any" : String(stepValue));
ValueEditorControl.setControlStyle(input, "xxe-valed-ctrl2",
opts["thumb"]? "xxe-valed-gauge" :
"xxe-valed-meter");
let w = Number(opts["width"]);
if (w >= 60 && w < 1000) {
input.style.width = `${w}px`;
}
let h = Number(opts["height"]);
if (h >= 10 && h < 500) {
input.style.height = `${h}px`;
}
this.init(input, input, true);
}
getValue() {
if (this._disabled) {
return super.getValue();
}
return this._element.value;
}
setValue(value) {
if (this._disabled) {
super.setValue(value);
return;
}
this._element.value = (value === null)? this._minValue : value;
}
}
// ---------------------------------------------------------------------------
// ComboBoxVCE
// ---------------------------------------------------------------------------
class ComboBoxVCE extends ValueEditorControl {
constructor(valueEditor, options) {
super(valueEditor, options);
let opts = Object.assign({ "labels": null, "values": [] }, options);
let values = opts["values"];
if (!Array.isArray(values)) {
values = [];
}
const valueCount = values.length;
let labels = ValueEditorControl.labelsFromValues(opts["labels"],values);
let select = document.createElement("select");
ValueEditorControl.setControlStyle(select, "xxe-valed-ctrl2");
for (let i = 0; i < valueCount; ++i) {
const label = labels[i];
const value = values[i];
const option = document.createElement("option");
option.setAttribute("value", value);
option.appendChild(document.createTextNode(label));
select.appendChild(option);
}
this.init(select, select, true);
}
getValue() {
return this._element.value;
}
setValue(value) {
this._element.value = (value === null)? "" : value;
}
}
// -----------------------------------
// ListVCE
// -----------------------------------
class ListVCE extends ComboBoxVCE {
constructor(valueEditor, options) {
super(valueEditor, options);
let opts = Object.assign({ "rows": 5,
"selection": "single",
"separator": " " }, options);
this._multiSelection = (opts["selection"] === "multiple");
let rows = opts["rows"];
if (typeof rows !== "number") {
rows = 5;
} else {
if (rows < 2) {
rows = 2;
} else if (rows > 10) {
rows = 10;
}
}
this._separator = opts["separator"];
if ((typeof this._separator !== "string") ||
this._separator.length !== 1) {
this._separator = " ";
}
this._element.classList.remove("xxe-valed-ctrl2");
if (this._multiSelection) {
this._element.setAttribute("multiple", "multiple");
}
this._element.setAttribute("size", String(rows));
}
getValue() {
if (!this._multiSelection) {
return super.getValue();
}
const options = this._element.selectedOptions;
const count = options.length;
// This value editor may be used to remove a value.
let value = null;
if (count > 0) {
value = "";
for (let i = 0; i < count; ++i) {
if (i > 0) {
value += this._separator;
}
value += options[i].value;
}
}
return value;
}
setValue(value) {
if (!this._multiSelection) {
super.setValue(value);
return;
}
if (value === null) {
value = "";
}
if (this._separator === " ") {
value = value.trim();
}
let items;
if (value.length === 0) {
items = [];
} else {
if (this._separator === " ") {
items = value.split(/\s+/);
} else {
items = value.split(this._separator);
}
}
const options = this._element.options;
const count = options.length;
for (let i = 0; i < count; ++i) {
options[i].selected = (items.indexOf(options[i].value) >= 0);
}
}
}
// ---------------------------------------------------------------------------
// RadioButtonsVCE
// ---------------------------------------------------------------------------
class RadioButtonsVCE extends ValueEditorControl {
constructor(valueEditor, options) {
super(valueEditor, options);
let opts = Object.assign({ "labels": null, "values": [],
"selection": "single",
"separator": " " }, options);
this._multiSelection = (opts["selection"] === "multiple");
let values = opts["values"];
if (!Array.isArray(values)) {
values = [];
}
const valueCount = values.length;
let labels = ValueEditorControl.labelsFromValues(opts["labels"],values);
this._separator = opts["separator"];
if ((typeof this._separator !== "string") ||
this._separator.length !== 1) {
this._separator = " ";
}
let columns = Number(opts["columns"]);
let rows = Number(opts["rows"]);
let cols;
if (columns > 0) {
cols = columns;
rows = -1;
} else {
if (rows > 0) {
cols = -1;
} else {
cols = valueCount;
if (cols > 10) {
cols = 10;
}
rows = -1;
}
}
const div = document.createElement("div");
ValueEditorControl.setControlStyle(div, "xxe-valed-rbtns");
const changeHandler = this.onChange.bind(this);
const tabOutHandler = this.onTabOut.bind(this);
const radioButtonName = this.valueEditor.id + "-rbtn";
let x = 0;
let y = 0;
for (let i = 0; i < valueCount; ++i) {
const label = labels[i];
const value = values[i];
const toggle = document.createElement("input");
toggle.setAttribute("data-value", value);
if (this._multiSelection) {
toggle.setAttribute("type", "checkbox");
} else {
toggle.setAttribute("type", "radio");
toggle.setAttribute("name", radioButtonName);
}
ValueEditorControl.setControlStyle(toggle);
const toggleLabel = document.createElement("label");
ValueEditorControl.setControlStyle(toggleLabel);
toggleLabel.setAttribute(
"style",
`grid-column: ${1+x} / span 1; grid-row: ${1+y} / span 1;`);
toggleLabel.appendChild(toggle);
toggleLabel.appendChild(document.createTextNode(label));
div.appendChild(toggleLabel);
if (cols > 0) {
++x;
if (x === cols) {
++y;
x = 0;
}
} else {
++y;
if (y === rows) {
++x;
y = 0;
}
}
// Focus handler not needed.
toggle.addEventListener("change", changeHandler);
toggle.addEventListener("keydown", tabOutHandler);
}
this.init(div, null, false);
}
getValue() {
// This value editor may be used to remove a value.
let value = null;
let toggleLabel = this._element.firstElementChild;
while (toggleLabel !== null) {
const toggle = toggleLabel.firstElementChild;
const toggleValue = toggle.getAttribute("data-value");
if (this._multiSelection) {
if (toggle.checked) {
if (value === null) {
value = toggleValue;
} else {
value += this._separator + toggleValue;
}
}
} else {
if (toggle.checked) {
value = toggleValue;
break;
}
}
toggleLabel = toggleLabel.nextElementSibling;
}
return value;
}
setValue(value) {
let valueList;
if (this._multiSelection) {
if (value === null) {
value = "";
}
if (this._separator === " ") {
value = value.trim();
}
if (value.length === 0) {
valueList = [];
} else {
if (this._separator === " ") {
valueList = value.split(/\s+/);
} else {
valueList = value.split(this._separator);
}
}
} else {
if (value === null) {
valueList = [];
} else {
valueList = [ value ];
}
}
let toggleLabel = this._element.firstElementChild;
while (toggleLabel !== null) {
const toggle = toggleLabel.firstElementChild;
const toggleValue = toggle.getAttribute("data-value");
toggle.checked = (valueList.indexOf(toggleValue) >= 0);
toggleLabel = toggleLabel.nextElementSibling;
}
}
}
// ---------------------------------------------------------------------------
// SpinnerVCE
// ---------------------------------------------------------------------------
class SpinnerVCE extends ValueEditorControl {
constructor(valueEditor, options) {
super(valueEditor, options);
// "pattern", "language", "country", "variant", "columns" are ignored:
// input type=number always displays/parses plain JavaScript Numbers.
let opts = Object.assign({ "step": 1,
"data-type": "double" }, options);
let minValue = Number(opts["min"]);
let maxValue = Number(opts["max"]);
// Ensure that "byte", "short", "int" have both min and max.
let isInteger = false;
switch (opts["data-type"]) {
case "byte":
isInteger = true;
if (Number.isNaN(maxValue) ||
maxValue < -128 || maxValue > 127) {
maxValue = 127;
}
if (Number.isNaN(minValue) ||
minValue < -128 || minValue > maxValue) {
minValue = -128;
}
break;
case "short":
isInteger = true;
if (Number.isNaN(maxValue) ||
maxValue < -32768 || maxValue > 32767) {
maxValue = 32767;
}
if (Number.isNaN(minValue) ||
minValue < -32768 || minValue > maxValue) {
minValue = -32768;
}
break;
case "int":
isInteger = true;
if (Number.isNaN(maxValue) ||
maxValue < -2147483648 || maxValue > 2147483647) {
maxValue = 2147483647;
}
if (Number.isNaN(minValue) ||
minValue < -2147483648 || minValue > maxValue) {
minValue = -2147483648;
}
break;
case "long":
isInteger = true;
//FALLTHROUGH
default:
if (!Number.isNaN(minValue) && !Number.isNaN(maxValue) &&
minValue > maxValue) {
minValue = maxValue = Number.NaN;
}
break;
}
// Prefer 0 as the displayed value when the element or attribute has
// no value and has no default value.
let fallbackValue;
if (Number.isNaN(minValue)) {
if (Number.isNaN(maxValue)) {
// The full range of any number always includes 0.
fallbackValue = 0;
} else {
// Has just an upper bound.
if (maxValue >= 0) {
fallbackValue = 0;
} else {
fallbackValue = maxValue;
}
}
} else {
// Has a least a lower bound.
if (minValue <= 0) {
fallbackValue = 0;
} else {
fallbackValue = minValue;
}
}
this._fallbackValue = fallbackValue;
let stepValue = opts["step"];
if (stepValue <= 0) {
stepValue = 1;
}
if (isInteger && !Number.isInteger(stepValue)) {
stepValue = Math.ceil(stepValue);
}
const input = document.createElement("input");
input.setAttribute("type", "number");
if (!Number.isNaN(minValue)) {
input.setAttribute("min", String(minValue));
}
if (!Number.isNaN(maxValue)) {
input.setAttribute("max", String(maxValue));
}
input.setAttribute("step", String(stepValue));
ValueEditorControl.setControlStyle(input);
this.init(input, input, true);
}
getValue() {
return this._element.value;
}
setValue(value) {
this._element.value = (value === null)? this._fallbackValue : value;
}
}
// ---------------------------------------------------------------------------
// TextAreaVCE
// ---------------------------------------------------------------------------
class TextAreaVCE extends ValueEditorControl {
constructor(valueEditor, options) {
super(valueEditor, options);
let opts = Object.assign({ "columns": 40,
"rows": 3,
"wrap": "none" }, options);
const textarea = document.createElement("textarea");
textarea.setAttribute("cols", String(opts["columns"]));
textarea.setAttribute("rows", String(opts["rows"]));
textarea.setAttribute("wrap",
(opts["wrap"] === "line" ||
opts["wrap"] === "word")? "soft" : "off");
textarea.setAttribute("autocomplete", "off");
textarea.setAttribute("spellcheck", "false");
ValueEditorControl.setControlStyle(textarea);
this.init(textarea, textarea, true);
}
getValue() {
// This never returns null, hence this value editor may not be used to
// remove a value.
return this._element.value;
}
setValue(value) {
this._element.value = (value === null)? "" : value;
}
}
// ---------------------------------------------------------------------------
// TextFieldVCE
// ---------------------------------------------------------------------------
class TextFieldVCE extends ValueEditorControl {
constructor(valueEditor, options, type="text", cols=20) {
super(valueEditor, options);
let opts = Object.assign({ "columns": cols }, options);
const input = document.createElement("input");
input.setAttribute("type", type);
input.setAttribute("size", String(opts["columns"]));
input.setAttribute("autocomplete", "off");
input.setAttribute("spellcheck", "false");
ValueEditorControl.setControlStyle(input);
this.init(input, input, true);
}
getValue() {
// This never returns null, hence this value editor may not be used to
// remove a value.
return this._element.value;
}
setValue(value) {
this._element.value = (value === null)? "" : value;
}
}
// -----------------------------------
// DateFieldVCE
// -----------------------------------
class DateFieldVCE extends TextFieldVCE {
constructor(valueEditor, options) {
super(valueEditor, options, "text", 30);
}
}
// -----------------------------------
// FileNameFieldVCE
// -----------------------------------
class FileNameFieldVCE extends TextFieldVCE {
constructor(valueEditor, options) {
super(valueEditor, options, "text", 40);
}
}
// -----------------------------------
// PasswordFieldVCE
// -----------------------------------
class PasswordFieldVCE extends TextFieldVCE {
constructor(valueEditor, options) {
super(valueEditor, options, "password");
}
}
// -----------------------------------
// NumberFieldVCE
// -----------------------------------
class NumberFieldVCE extends TextFieldVCE {
constructor(valueEditor, options) {
super(valueEditor, options, "text", 20);
}
}
// ---------------------------------------------------------------------------
/**
* Client-side implementation of CSS proprietary extensions
* <code>text-field()</code>, <code>check-box()</code>, etc.
* This is specified as part of the generated
* content and it inserts one or more controls which can be used to
* set, remove or modify the value of an attribute or a "data-only" element.
*/
class ValueEditor extends HTMLElement {
constructor() {
super();
this._docView = null;
this._attributeName = null; // "-" means no attribute.
this._control = null;
this._commitedValue = this._commitedState = undefined;
}
get documentView() {
return this._docView;
}
// -----------------------------------------------------------------------
// Custom element
// -----------------------------------------------------------------------
connectedCallback() {
this._docView = DOMUtil.lookupAncestorByTag(this, "xxe-document-view");
if (this._docView === null) {
// Should not happen.
return;
}
// ---
let opts = {};
let optsVal = this.getAttribute("options");
this.removeAttribute("options"); // No longer useful.
if (optsVal !== null) {
try {
opts = JSON.parse(optsVal);
} catch {}
}
let options =
Object.assign({ "controlType": null,
"attributeName": null,
"initialValue": null,
"initialState": ValueEditorControl.STATE_MISSING },
opts);
let controlType = options["controlType"];
this._attributeName = options["attributeName"]; // "-" if no attribute.
if (!controlType || !this._attributeName) {
console.error(`"${optsVal}", invalid "options" attribute`);
return;
}
switch (controlType) {
case "check-box":
this._control = new CheckBoxVCE(this, options);
break;
case "color-chooser":
this._control = new ColorChooserVCE(this, options);
break;
case "combo-box":
this._control = new ComboBoxVCE(this, options);
break;
case "date-picker":
this._control = new DatePickerVCE(this, options);
break;
case "date-time-picker":
this._control = new DateTimePickerVCE(this, options);
break;
case "time-picker":
this._control = new TimePickerVCE(this, options);
break;
case "gauge":
this._control = new GaugeVCE(this, options);
break;
case "list":
this._control = new ListVCE(this, options);
break;
case "radio-buttons":
this._control = new RadioButtonsVCE(this, options);
break;
case "spinner":
this._control = new SpinnerVCE(this, options);
break;
case "text-area":
this._control = new TextAreaVCE(this, options);
break;
case "text-field":
this._control = new TextFieldVCE(this, options);
break;
case "date-field":
this._control = new DateFieldVCE(this, options);
break;
case "file-name-field":
this._control = new FileNameFieldVCE(this, options);
break;
case "password-field":
this._control = new PasswordFieldVCE(this, options);
break;
case "number-field":
this._control = new NumberFieldVCE(this, options);
break;
default:
// Should not happen.
this._control = new DummyVCE(this, options);
break;
}
this.appendChild(this._control.element);
this.valueChanged(options["initialValue"], options["initialState"]);
}
// -----------------------------------------------------------------------
// API used by ValueEditorControls
// -----------------------------------------------------------------------
commitValue(value, state) {
const commitedValue = this._commitedValue;
const commitedState = this._commitedState;
if (value !== undefined &&
state !== ValueEditorControl.STATE_ERROR &&
(value !== commitedValue || state !== commitedState)) {
this.selectEditedElement()
.then((selected) => {
if (!selected) {
return Promise.resolve(CommandResult.FAILED);
} else {
const id = this.getAttribute("id");
let param = id? `[${id}]`: "";
if (this._attributeName !== "-") {
param += this._attributeName;
}
if (value !== null) {
param += "=" + value;
}
return this._docView.executeCommand(
EXECUTE_HELPER, "commitValue", param);
}
})
.then((result) => {
if (!CommandResult.isDone(result)) {
// This also means that we will not receive
// DocumentViewChangedEvent, CHANGE_UPDATE_CUSTOM_PART,
// valueChanged().
this._control.commitValueFailed();
} else {
if (result.value === "noop" &&
commitedValue !== undefined) {
this.valueChanged(commitedValue, commitedState);
}
}
});
}
}
selectEditedElement() {
if (this._docView === null) {
// Should not happen.
return Promise.resolve(false);
}
let view = NodeView.lookupView(this);
if (view === null || NodeView.uidIfElementView(view) === null) {
// Not an element view. Should not happen.
return Promise.resolve(false);
}
let selectElem;
if (!Object.is(this._docView.selected, view) ||
this._docView.selected2 !== null) {
selectElem = this._docView.selectNode(view, /*show*/ false);
} else {
selectElem = Promise.resolve(true);
}
return selectElem;
}
// -----------------------------------------------------------------------
// DocumentViewChangedEvent, CHANGE_UPDATE_CUSTOM_PART handler
// -----------------------------------------------------------------------
valueChanged(value, state) {
/*
console.log(`valueChanged value=${value} state=${state}`);
*/
this._commitedValue = value;
this._commitedState = state;
this._control.setValue(value);
this._control.setState(state);
}
}
window.customElements.define("xxe-value-editor", ValueEditor);