BREAKING: _isPrefilled was removed in favor of _isEmpty. We used to have both, but we decided to align, because they basically do the same thing but opposite. If you were using _isPrefilled, switch to using _isEmpty and just use it in reverse. This change also makes _isEmpty available to all things that implement FormControlMixin. Lastly, filled is available now on all fields that implement InteractionStateMixin
189 lines
6.4 KiB
JavaScript
189 lines
6.4 KiB
JavaScript
import { dedupeMixin } from '@lion/core';
|
|
import { FormRegistrarMixin, InteractionStateMixin } from '@lion/field';
|
|
|
|
export const ChoiceGroupMixin = dedupeMixin(
|
|
superclass =>
|
|
// eslint-disable-next-line
|
|
class ChoiceGroupMixin extends FormRegistrarMixin(InteractionStateMixin(superclass)) {
|
|
static get properties() {
|
|
return {
|
|
/**
|
|
* @desc When false (default), modelValue and serializedValue will reflect the
|
|
* currently selected choice (usually a string). When true, modelValue will and
|
|
* serializedValue will be an array of strings.
|
|
* @type {boolean}
|
|
*/
|
|
multipleChoice: {
|
|
type: Boolean,
|
|
attribute: 'multiple-choice',
|
|
},
|
|
};
|
|
}
|
|
|
|
get modelValue() {
|
|
const elems = this._getCheckedElements();
|
|
if (this.multipleChoice) {
|
|
return elems.map(el => el.modelValue.value);
|
|
}
|
|
return elems[0] ? elems[0].modelValue.value : '';
|
|
}
|
|
|
|
set modelValue(value) {
|
|
this._setCheckedElements(value, (el, val) => el.modelValue.value === val);
|
|
}
|
|
|
|
get serializedValue() {
|
|
// We want to filter out disabled values out by default:
|
|
// The goal of serializing values could either be submitting state to a backend
|
|
// ot storing state in a backend. For this, only values that are entered by the end
|
|
// user are relevant, choice values are always defined by the Application Developer
|
|
// and known by the backend.
|
|
|
|
// Assuming values are always defined as strings, modelValues and serializedValues
|
|
// are the same.
|
|
const elems = this._getCheckedElements();
|
|
if (this.multipleChoice) {
|
|
return elems.map(el => el.serializedValue.value);
|
|
}
|
|
return elems[0] ? elems[0].serializedValue.value : '';
|
|
}
|
|
|
|
set serializedValue(value) {
|
|
this._setCheckedElements(value, (el, val) => el.serializedValue.value === val);
|
|
}
|
|
|
|
constructor() {
|
|
super();
|
|
this.multipleChoice = false;
|
|
this._repropagationRole = 'choice-group'; // configures event propagation logic of FormControlMixin
|
|
}
|
|
|
|
/**
|
|
* @override from FormRegistrarMixin
|
|
*/
|
|
addFormElement(child, indexToInsertAt) {
|
|
this._throwWhenInvalidChildModelValue(child);
|
|
this.__delegateNameAttribute(child);
|
|
super.addFormElement(child, indexToInsertAt);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
_getFromAllFormElements(property, filterCondition = () => true) {
|
|
// For modelValue and serializedValue, an exception should be made,
|
|
// The reset can be requested from children
|
|
if (property === 'modelValue' || property === 'serializedValue') {
|
|
return this[property];
|
|
}
|
|
return this.formElements.filter(filterCondition).map(el => el.property);
|
|
}
|
|
|
|
_throwWhenInvalidChildModelValue(child) {
|
|
if (
|
|
typeof child.modelValue.checked !== 'boolean' ||
|
|
!Object.prototype.hasOwnProperty.call(child.modelValue, 'value')
|
|
) {
|
|
throw new Error(
|
|
`The ${this.tagName.toLowerCase()} name="${
|
|
this.name
|
|
}" does not allow to register ${child.tagName.toLowerCase()} with .modelValue="${
|
|
child.modelValue
|
|
}" - The modelValue should represent an Object { value: "foo", checked: false }`,
|
|
);
|
|
}
|
|
}
|
|
|
|
_isEmpty() {
|
|
if (this.multipleChoice) {
|
|
return this.modelValue.length === 0;
|
|
}
|
|
|
|
if (typeof this.modelValue === 'string' && this.modelValue === '') {
|
|
return true;
|
|
}
|
|
if (this.modelValue === undefined || this.modelValue === null) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
_checkSingleChoiceElements(ev) {
|
|
const { target } = ev;
|
|
if (target.checked === false) return;
|
|
|
|
const groupName = target.name;
|
|
this.formElements
|
|
.filter(i => i.name === groupName)
|
|
.forEach(choice => {
|
|
if (choice !== target) {
|
|
choice.checked = false; // eslint-disable-line no-param-reassign
|
|
}
|
|
});
|
|
this.__triggerCheckedValueChanged();
|
|
}
|
|
|
|
_getCheckedElements() {
|
|
// We want to filter out disabled values out by default
|
|
return this.formElements.filter(el => el.checked && !el.disabled);
|
|
}
|
|
|
|
async _setCheckedElements(value, check) {
|
|
if (!this.__readyForRegistration) {
|
|
await this.registrationReady;
|
|
}
|
|
|
|
for (let i = 0; i < this.formElements.length; i += 1) {
|
|
if (this.multipleChoice) {
|
|
this.formElements[i].checked = value.includes(this.formElements[i].value);
|
|
} else if (check(this.formElements[i], value)) {
|
|
// Allows checking against custom values e.g. formattedValue or serializedValue
|
|
this.formElements[i].checked = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
__setChoiceGroupTouched() {
|
|
const value = this.modelValue;
|
|
if (value != null && value !== this.__previousCheckedValue) {
|
|
// TODO: what happens here exactly? Needs to be based on user interaction (?)
|
|
this.touched = true;
|
|
this.__previousCheckedValue = value;
|
|
}
|
|
}
|
|
|
|
__delegateNameAttribute(child) {
|
|
if (!child.name || child.name === this.name) {
|
|
// eslint-disable-next-line no-param-reassign
|
|
child.name = this.name;
|
|
} else {
|
|
throw new Error(
|
|
`The ${this.tagName.toLowerCase()} name="${
|
|
this.name
|
|
}" does not allow to register ${child.tagName.toLowerCase()} with custom names (name="${
|
|
child.name
|
|
}" given)`,
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @override FormControlMixin
|
|
*/
|
|
_onBeforeRepropagateChildrenValues(ev) {
|
|
// Normalize target, since we might receive 'portal events' (from children in a modal,
|
|
// see select-rich)
|
|
const target = (ev.detail && ev.detail.element) || ev.target;
|
|
if (this.multipleChoice || !target.checked) {
|
|
return;
|
|
}
|
|
this.formElements.forEach(option => {
|
|
if (target.choiceValue !== option.choiceValue) {
|
|
option.checked = false; // eslint-disable-line no-param-reassign
|
|
}
|
|
});
|
|
this.__setChoiceGroupTouched();
|
|
this.requestUpdate('modelValue');
|
|
}
|
|
},
|
|
);
|