lion/packages/field/src/registration/FormRegistrarMixin.js
Alex Ghiu 1b6c3a44c8 fix: normalization model-value-changed events
Co-authored-by: Thijs Louisse <Thijs.Louisse@ing.com>
2020-03-19 09:59:30 +01:00

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);
}
},
);