197 lines
6.9 KiB
JavaScript
197 lines
6.9 KiB
JavaScript
// eslint-disable-next-line max-classes-per-file
|
|
import { dedupeMixin } from '@lion/core';
|
|
import { FormRegisteringMixin } from './FormRegisteringMixin.js';
|
|
import { formRegistrarManager } from './formRegistrarManager.js';
|
|
import { FormControlsCollection } from './FormControlsCollection.js';
|
|
|
|
// TODO: rename .formElements to .formControls? (or .$controls ?)
|
|
|
|
/**
|
|
* @desc This allows an element to become the manager of a register.
|
|
* It basically keeps track of a FormControlsCollection that it stores in .formElements
|
|
* This will always be an array of all elements.
|
|
* In case of a form or fieldset(sub form), it will also act as a key based object with FormControl
|
|
* (fields, choice groups or fieldsets)as keys.
|
|
* For choice groups, the value will only stay an array.
|
|
* See FormControlsCollection for more information
|
|
*/
|
|
export const FormRegistrarMixin = dedupeMixin(
|
|
superclass =>
|
|
// eslint-disable-next-line no-shadow, no-unused-vars
|
|
class FormRegistrarMixin extends FormRegisteringMixin(superclass) {
|
|
static get properties() {
|
|
return {
|
|
/**
|
|
* @desc Flag that determines how ".formElements" should behave.
|
|
* For a regular fieldset (see LionFieldset) we expect ".formElements"
|
|
* to be accessible as an object.
|
|
* In case of a radio-group, a checkbox-group or a select/listbox,
|
|
* it should act like an array (see ChoiceGroupMixin).
|
|
* Usually, when false, we deal with a choice-group (radio-group, checkbox-group,
|
|
* (multi)select)
|
|
* @type {boolean}
|
|
*/
|
|
_isFormOrFieldset: Boolean,
|
|
};
|
|
}
|
|
|
|
constructor() {
|
|
super();
|
|
this.formElements = new FormControlsCollection();
|
|
|
|
this._isFormOrFieldset = false;
|
|
|
|
this.__readyForRegistration = false;
|
|
this.__hasBeenRendered = false;
|
|
this.registrationReady = new Promise(resolve => {
|
|
this.__resolveRegistrationReady = resolve;
|
|
});
|
|
this.registrationComplete = new Promise(resolve => {
|
|
this.__resolveRegistrationComplete = resolve;
|
|
});
|
|
|
|
this._onRequestToAddFormElement = this._onRequestToAddFormElement.bind(this);
|
|
this.addEventListener('form-element-register', this._onRequestToAddFormElement);
|
|
}
|
|
|
|
connectedCallback() {
|
|
if (super.connectedCallback) {
|
|
super.connectedCallback();
|
|
}
|
|
formRegistrarManager.add(this);
|
|
if (this.__hasBeenRendered) {
|
|
formRegistrarManager.becomesReady();
|
|
}
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
if (super.disconnectedCallback) {
|
|
super.disconnectedCallback();
|
|
}
|
|
formRegistrarManager.remove(this);
|
|
}
|
|
|
|
isRegisteredFormElement(el) {
|
|
return this.formElements.some(exitingEl => exitingEl === el);
|
|
}
|
|
|
|
firstUpdated(changedProperties) {
|
|
super.firstUpdated(changedProperties);
|
|
this.__resolveRegistrationReady();
|
|
this.__readyForRegistration = true;
|
|
|
|
// After we allow our children to register, we need to wait one tick before they
|
|
// all sent their 'form-element-register' event.
|
|
// TODO: allow developer to delay this moment, similar to LitElement.performUpdate can be
|
|
// delayed.
|
|
setTimeout(() => {
|
|
this.registrationHasCompleted = true;
|
|
this.__resolveRegistrationComplete();
|
|
});
|
|
|
|
formRegistrarManager.becomesReady();
|
|
this.__hasBeenRendered = true;
|
|
}
|
|
|
|
addFormElement(child, indexToInsertAt) {
|
|
// This is a way to let the child element (a lion-fieldset or lion-field) know, about its parent
|
|
// eslint-disable-next-line no-param-reassign
|
|
child.__parentFormGroup = this;
|
|
|
|
// 1. Add children as array element
|
|
if (indexToInsertAt > 0) {
|
|
this.formElements.splice(indexToInsertAt, 0, child);
|
|
} else {
|
|
this.formElements.push(child);
|
|
}
|
|
|
|
// 2. Add children as object key
|
|
if (this._isFormOrFieldset) {
|
|
const { name } = child;
|
|
if (!name) {
|
|
console.info('Error Node:', child); // eslint-disable-line no-console
|
|
throw new TypeError('You need to define a name');
|
|
}
|
|
if (name === this.name) {
|
|
console.info('Error Node:', child); // eslint-disable-line no-console
|
|
throw new TypeError(`You can not have the same name "${name}" as your parent`);
|
|
}
|
|
|
|
if (name.substr(-2) === '[]') {
|
|
if (!Array.isArray(this.formElements[name])) {
|
|
this.formElements[name] = new FormControlsCollection();
|
|
}
|
|
if (indexToInsertAt > 0) {
|
|
this.formElements[name].splice(indexToInsertAt, 0, child);
|
|
} else {
|
|
this.formElements[name].push(child);
|
|
}
|
|
} else if (!this.formElements[name]) {
|
|
this.formElements[name] = child;
|
|
} else {
|
|
console.info('Error Node:', child); // eslint-disable-line no-console
|
|
throw new TypeError(
|
|
`Name "${name}" is already registered - if you want an array add [] to the end`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
removeFormElement(child) {
|
|
// 1. Handle array based children
|
|
const index = this.formElements.indexOf(child);
|
|
if (index > -1) {
|
|
this.formElements.splice(index, 1);
|
|
}
|
|
|
|
// 2. Handle name based object keys
|
|
if (this._isFormOrFieldset) {
|
|
const { name } = child;
|
|
if (name.substr(-2) === '[]' && this.formElements[name]) {
|
|
const idx = this.formElements[name].indexOf(child);
|
|
if (idx > -1) {
|
|
this.formElements[name].splice(idx, 1);
|
|
}
|
|
} else if (this.formElements[name]) {
|
|
delete this.formElements[name];
|
|
}
|
|
}
|
|
}
|
|
|
|
_onRequestToAddFormElement(ev) {
|
|
const child = ev.detail.element;
|
|
if (child === this) {
|
|
// as we fire and listen - don't add ourselves
|
|
return;
|
|
}
|
|
if (this.isRegisteredFormElement(child)) {
|
|
// do not readd already existing elements
|
|
return;
|
|
}
|
|
ev.stopPropagation();
|
|
|
|
// Check for siblings to determine the right order to insert into formElements
|
|
// If there is no next sibling, index is -1
|
|
let indexToInsertAt = -1;
|
|
if (this.formElements && Array.isArray(this.formElements)) {
|
|
indexToInsertAt = this.formElements.indexOf(child.nextElementSibling);
|
|
}
|
|
this.addFormElement(child, indexToInsertAt);
|
|
}
|
|
|
|
_onRequestToRemoveFormElement(ev) {
|
|
const child = ev.detail.element;
|
|
if (child === this) {
|
|
// as we fire and listen - don't remove ourselves
|
|
return;
|
|
}
|
|
if (!this.isRegisteredFormElement(child)) {
|
|
// do not remove non existing elements
|
|
return;
|
|
}
|
|
ev.stopPropagation();
|
|
|
|
this.removeFormElement(child);
|
|
}
|
|
},
|
|
);
|