/**
* Definitions of classes and functions that build the components required to
* generate JSON Schema forms.
*
* @module components
*/
/**
* Interface for classes that represent a form component.
*
* @interface Component
*/
/**
* Reference to the DOM object that contains the HTML structure of the
* component.
*
* @member {HTMLDivElement} module:components~Component#domElement
*/
/**
* Enables the component.
*
* @function
*
* @name module:components~Component#enable
*/
/**
* Disables the component.
*
* @function
*
* @name module:components~Component#disable
*/
/**
* Interface for classes that represent a form component that contain an input
* control.
*
* @interface InputComponent
*
* @augments module:components~Component
*/
/**
* Returns the value assigned to the component.
*
* @returns {any} The value taken by the component.
*
* @function
*
* @name module:components~InputComponent#getValue
*/
/**
* Returns the `id` of the input control element.
*
* @returns {any} The value taken by the component.
*
* @function
*
* @name module:components~InputComponent#getId
*/
/**
* An icon for a generator form element.
*
* @implements {module:components~Component}
*/
class GeneratorIcon {
/**
* @param {object} [options={}] Parameters considered to generate the
* component.
* @param {string} [options.fontAwesome] If given, it indicates the
* [Font Awesome](https://fontawesome.com/) version which the component should
* be built for.
*/
constructor(options = {}) {
this.domElement = document.createElement('div');
if (options.fontAwesome)
this.domElement
.appendChild(document.createElement('i'))
.classList.add('fas', 'fa-bullseye');
else
this.domElement.appendChild(document.createElement('h5')).innerText = '↳';
}
enable() {}
disable() {}
}
/**
* An icon for a required form element.
*
* @implements {module:components~Component}
*/
class RequiredIcon {
/**
* @param {object} [options={}] Parameters considered to generate the
* component.
* @param {string} [options.fontAwesome] If given, it indicates the
* [Font Awesome](https://fontawesome.com/) version which the component should
* be built for.
*/
constructor(options = {}) {
this.domElement = document.createElement('div');
if (options.fontAwesome)
this.domElement
.appendChild(document.createElement('i'))
.classList.add('fas', 'fa-asterisk');
else
this.domElement.appendChild(document.createElement('h5')).innerText = '*';
}
enable() {}
disable() {}
}
/**
* An icon for the root form element.
*
* @implements {module:components~Component}
*/
class RootIcon {
/**
* @param {object} [options={}] Parameters considered to generate the
* component.
* @param {string} [options.fontAwesome] If given, it indicates the
* [Font Awesome](https://fontawesome.com/) version which the component should
* be built for.
*/
constructor(options = {}) {
this.domElement = document.createElement('div');
if (options.fontAwesome)
this.domElement
.appendChild(document.createElement('i'))
.classList.add('fab', 'fa-sourcetree');
else
this.domElement.appendChild(document.createElement('h5')).innerText = '⚲';
}
enable() {}
disable() {}
}
/**
* A button that represents the action of adding a child form element to its
* associated [JSON Schema child applicator](https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.3).
*
* @implements {module:components~Component}
*/
class AddButton {
/**
* @param {Function} callback The callback function for the `click` DOM event
* generated by the button.
* @param {object} [buttonAttributes={}] The parameters to consider as
* attributes for the [HTML `<button>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button).
* @param {boolean} [buttonAttributes.disabled=false] Flag indicating whether
* the button should be disabled at initialization.
* @param {object} [options={}] Parameters considered to generate the
* component.
* @param {string} [options.bootstrap] If given, it indicates the
* [Bootstrap](https://getbootstrap.com/) version which the component should
* be built for.
* @param {string} [options.fontAwesome] If given, it indicates the
* [Font Awesome](https://fontawesome.com/) version which the component should
* be built for.
*/
constructor(callback, { disabled = false } = {}, options = {}) {
this.domElement = document.createElement('div');
/**
* Reference to the DOM object representing the `<button>` element.
*
* @type {HTMLButtonElement}
*/
this.button = this.domElement.appendChild(document.createElement('button'));
this.button.type = 'button';
this.button.disabled = !!disabled;
if (options.bootstrap)
this.button.classList.add('btn', 'btn-sm', 'btn-success');
if (options.fontAwesome)
this.button
.appendChild(document.createElement('i'))
.classList.add('fas', 'fa-plus');
else this.button.innerText = '+';
this.button.addEventListener('click', () => void callback());
}
enable() {
this.button.disabled = false;
}
disable() {
this.button.disabled = true;
}
}
/**
* A button that represents the action of removing its associated form element
* from the parent [JSON Schema child applicator](https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.3).
*
* @implements {module:components~Component}
*/
class RemoveButton {
/**
* @param {Function} callback The callback function for the `click` DOM event
* generated by the button.
* @param {object} [buttonAttributes={}] The parameters to consider as
* attributes for the [HTML `<button>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button).
* @param {boolean} [buttonAttributes.disabled=false] Flag indicating whether
* the button should be disabled at initialization.
* @param {object} [options={}] Parameters considered to generate the
* component.
* @param {string} [options.bootstrap] If given, it indicates the
* [Bootstrap](https://getbootstrap.com/) version which the component
* should be built for.
* @param {string} [options.fontAwesome] If given, it indicates the
* [Font Awesome](https://fontawesome.com/) version which the component should
* be built for.
*/
constructor(callback, { disabled = false } = {}, options = {}) {
this.domElement = document.createElement('div');
/**
* Reference to the DOM object representing the `<button>` element.
*
* @type {HTMLButtonElement}
*/
this.button = this.domElement.appendChild(document.createElement('button'));
this.button.type = 'button';
this.button.disabled = !!disabled;
if (options.bootstrap)
this.button.classList.add('btn', 'btn-sm', 'btn-danger');
if (options.fontAwesome)
this.button
.appendChild(document.createElement('i'))
.classList.add('fas', 'fa-minus');
else this.button.innerText = '-';
this.button.addEventListener('click', () => void callback());
}
enable() {
this.button.disabled = false;
}
disable() {
this.button.disabled = true;
}
}
/**
* A toggler switch that allows to enable and disable a form element.
*
* @implements {module:components~Component}
*/
class Toggler {
/**
* @param {string} formElementId The `id` of the form element.
* @param {Function} callback The callback function for the `click` DOM event
* generated by the toggler.
* @param {object} [togglerParameters={}] The parameters to initialize the
* toggler.
* @param {boolean} [togglerParameters.disabled=false] Flag indicating whether
* the toggler should be disabled at initialization.
* @param {boolean} [togglerParameters.initOff=false] Flag indicating whether
* the toggler should be initialized in "off" state.
* @param {object} [options={}] Parameters considered to generate the
* component.
* @param {string} [options.bootstrap] If given, it indicates the
* [Bootstrap](https://getbootstrap.com/) version which the component should
* be built for.
*/
constructor(
formElementId,
callback,
{ disabled = false, initOff = false } = {},
options = {}
) {
this.domElement = document.createElement('div');
/**
* Reference to the DOM object representing the `<input>` element handled
* by the toggler.
*
* @type {HTMLInputElement}
*/
this.input = document.createElement('input');
this.input.id = `${formElementId}__toggler__input`;
this.input.type = 'checkbox';
this.input.defaultChecked = !initOff;
this.input.checked = !initOff;
this.input.disabled = !!disabled;
if (options.bootstrap) {
const customSwitchDiv = this.domElement.appendChild(
document.createElement('div')
);
customSwitchDiv.appendChild(this.input);
const label = customSwitchDiv.appendChild(
document.createElement('label')
);
label.htmlFor = this.input.id;
customSwitchDiv.classList.add('custom-control', 'custom-switch');
this.input.classList.add('custom-control-input');
label.classList.add('custom-control-label');
} else this.domElement.appendChild(this.input);
this.input.addEventListener('click', () => void callback());
}
enable() {
this.input.disabled = false;
}
disable() {
this.input.disabled = true;
}
}
/**
* An input field conceived for the representation of the JSON Schema `boolean`
* type.
*
* @implements {module:components~InputComponent}
*/
class BooleanInput {
/**
* @param {string} formElementId The `id` of the form element.
* @param {object} [inputAttributes={}] The parameters to consider as
* attributes for the [HTML `<input>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
* @param {boolean} [inputAttributes.disabled=false] Flag indicating whether
* the input field should be disabled at initialization.
* @param {string} [inputAttributes.form] The `id` of the `<form>` element
* with which the input control is associated.
* @param {object} [options={}] Parameters considered to generate the
* component.
* @param {string} [options.bootstrap] If given, it indicates the
* [Bootstrap](https://getbootstrap.com/) version which the component should
* be built for.
*/
constructor(formElementId, { disabled = false, form } = {}, options = {}) {
this.domElement = document.createElement('div');
/**
* Reference to the DOM object representing the `<input>` element.
*
* @type {HTMLInputElement}
*/
this.input = document.createElement('input');
this.input.id = `${formElementId}__input`;
this.input.type = 'checkbox';
this.input.disabled = !!disabled;
if (form) this.input.setAttribute('form', form);
if (options.bootstrap) {
const customCheckboxDiv = this.domElement.appendChild(
document.createElement('div')
);
customCheckboxDiv.appendChild(this.input);
const customCheckboxLabel = customCheckboxDiv.appendChild(
document.createElement('label')
);
customCheckboxLabel.htmlFor = this.input.id;
customCheckboxDiv.classList.add('custom-control', 'custom-checkbox');
this.input.classList.add('custom-control-input');
customCheckboxLabel.classList.add('custom-control-label');
} else this.domElement.appendChild(this.input);
}
enable() {
this.input.disabled = false;
}
disable() {
this.input.disabled = true;
}
getValue() {
return this.input.checked;
}
getId() {
return this.input.id;
}
}
/**
* An input field conceived for the representation of the JSON Schema `number`
* or `integer` types.
*
* @implements {module:components~InputComponent}
*/
class NumericInput {
/**
* @param {string} formElementId The `id` of the form element.
* @param {object} [inputAttributes={}] The parameters to consider as
* attributes for the [HTML `<input>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
* @param {boolean} [inputAttributes.disabled=false] Flag indicating whether
* the input field should be disabled at initialization.
* @param {string} [inputAttributes.form] The `id` of the `<form>` element
* with which the input control is associated.
* @param {number} [inputAttributes.max] The maximum value allowed by the
* input field.
* @param {number} [inputAttributes.min] The minimum value allowed by the
* input field.
* @param {string} [inputAttributes.placeholder] Text appearing when the
* input field is empty.
* @param {number} [inputAttributes.step] The incremental step allowed by the
* input field.
* @param {object} [options={}] Parameters considered to generate the
* component.
* @param {string} [options.bootstrap] If given, it indicates the
* [Bootstrap](https://getbootstrap.com/) version which the component should
* be built for.
*/
constructor(
formElementId,
{ disabled = false, form, max, min, placeholder, step } = {},
options = {}
) {
this.domElement = document.createElement('div');
/**
* Reference to the DOM object representing the `<input>` element.
*
* @type {HTMLInputElement}
*/
this.input = this.domElement.appendChild(document.createElement('input'));
this.input.id = `${formElementId}__input`;
this.input.type = 'number';
this.input.required = true;
if (Number.isFinite(max)) this.input.max = max;
if (Number.isFinite(min)) this.input.min = min;
if (placeholder) this.input.placeholder = placeholder;
if (Number.isFinite(step)) this.input.step = step;
this.input.disabled = !!disabled;
if (form) this.input.setAttribute('form', form);
if (options.bootstrap) this.input.classList.add('form-control');
}
enable() {
this.input.disabled = false;
}
disable() {
this.input.disabled = true;
}
getValue() {
return Number.parseInt(this.input.value);
}
getId() {
return this.input.id;
}
}
/**
* An input field conceived for the representation of the JSON Schema `string`
* type.
*
* @implements {module:components~InputComponent}
*/
class TextInput {
/**
* @param {string} formElementId The `id` of the form element.
* @param {object} [inputAttributes={}] The parameters to consider as
* attributes for the [HTML `<input>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
* @param {boolean} [inputAttributes.disabled=false] Flag indicating whether
* the input field should be disabled at initialization.
* @param {string} [inputAttributes.form] The `id` of the `<form>` element
* with which the input control is associated.
* @param {number} [inputAttributes.maxlength] The maximum `string` length
* allowed as input value.
* @param {number} [inputAttributes.minlength] The minimum `string` length
* allowed as input value.
* @param {RegExp} [inputAttributes.pattern] A regular expression which the
* input value should match.
* @param {string} [inputAttributes.placeholder] Text appearing when the
* input field is empty.
* @param {object} [options={}] Parameters considered to generate the
* component.
* @param {string} [options.bootstrap] If given, it indicates the
* [Bootstrap](https://getbootstrap.com/) version which the component should
* be built for.
*/
constructor(
formElementId,
{ disabled = false, form, maxlength, minlength, pattern, placeholder } = {},
options = {}
) {
this.domElement = document.createElement('div');
/**
* Reference to the DOM object representing the `<input>` element.
*
* @type {HTMLInputElement}
*/
this.input = this.domElement.appendChild(document.createElement('input'));
this.input.id = `${formElementId}__input`;
this.input.type = 'text';
this.input.required = true;
if (!isNaN(maxlength)) this.input.maxlength = maxlength;
if (!isNaN(minlength)) this.input.minlength = minlength;
if (pattern) this.input.pattern = pattern;
if (placeholder) this.input.placeholder = placeholder;
this.input.disabled = !!disabled;
if (form) this.input.setAttribute('form', form);
if (options.bootstrap) this.input.classList.add('form-control');
}
enable() {
this.input.disabled = false;
}
disable() {
this.input.disabled = true;
}
getValue() {
return this.input.value;
}
getId() {
return this.input.id;
}
}
/**
* An input field to be used as title for form elements without predefined
* title.
*
* @implements {module:components~InputComponent}
*/
class InputTitle {
/**
* @param {string} formElementId The `id` of the form element.
* @param {object} [inputAttributes={}] The parameters to consider as
* attributes for the [HTML `<input>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
* @param {boolean} [inputAttributes.disabled=false] Flag indicating whether
* the input field should be disabled at initialization.
* @param {string} [inputAttributes.placeholder] Text appearing when the
* input field is empty.
* @param {object} [options={}] Parameters considered to generate the
* component.
* @param {string} [options.bootstrap] If given, it indicates the
* [Bootstrap](https://getbootstrap.com/) version which the component should
* be built for.
*/
constructor(
formElementId,
{ disabled = false, placeholder } = {},
options = {}
) {
this.domElement = document.createElement('div');
/**
* Reference to the DOM object representing the `<input>` element.
*
* @type {HTMLInputElement}
*/
this.input = this.domElement.appendChild(document.createElement('input'));
this.input.id = `${formElementId}__title__input`;
this.input.type = 'text';
this.input.disabled = !!disabled;
if (placeholder) this.input.placeholder = placeholder;
if (options.bootstrap) this.input.classList.add('form-control');
}
enable() {
this.input.disabled = false;
}
disable() {
this.input.disabled = true;
}
getValue() {
return this.input.value;
}
getId() {
return this.input.id;
}
}
/**
* A label to be used as form element title.
*
* @implements {module:components~Component}
*/
class LabelTitle {
/**
* @param {string} text The string to use as label.
* @param {string} description A string describing the form element.
* @param {object} [labelAttributes={}] The parameters to consider as
* attributes for the [HTML `<label>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label).
* @param {string} [labelAttributes.htmlFor] The `id` of the referred
* component.
* @param {object} [options={}] Parameters considered to generate the
* component.
* @param {string} [options.bootstrap] If given, it indicates the
* [Bootstrap](https://getbootstrap.com/) version which the component should
* be built for.
* @param {string} [options.fontAwesome] If given, it indicates the
* [Font Awesome](https://fontawesome.com/) version which the component should
* be built for.
*/
constructor(text, description, { htmlFor } = {}, options = {}) {
this.domElement = document.createElement('div');
/**
* Reference to the DOM object representing the `<label>` element.
*
* @type {HTMLLabelElement}
*/
this.label = this.domElement.appendChild(document.createElement('label'));
const h5 = this.label.appendChild(document.createElement('h5'));
h5.innerText = text;
if (htmlFor) this.label.htmlFor = htmlFor;
if (description) {
const helpIcon = h5.appendChild(document.createElement('span'));
helpIcon.classList.add('help-icon');
helpIcon.title = description;
if (options.fontAwesome)
helpIcon
.appendChild(document.createElement('i'))
.classList.add('far', 'fa-question-circle');
else helpIcon.innerText = '?';
if (options.bootstrap) {
helpIcon.dataset.toggle = 'tooltip';
helpIcon.dataset.placement = 'right';
if (window.bootstrap) window.$(helpIcon).tooltip();
helpIcon.classList.add('ml-2');
}
}
if (options.bootstrap) {
this.label.classList.add('mb-0');
h5.classList.add('mb-0');
}
}
enable() {}
disable() {}
/**
* Sets the `for` attribute of the `<label>` element.
*
* @param {string} htmlFor The `id` of the HTML element that the label should
* refer to.
*/
setLabelFor(htmlFor) {
this.label.htmlFor = htmlFor;
}
}
/**
* An input control to make a choice among multiple options.
*
* @implements {module:components~InputComponent}
*/
class Select {
/**
* @param {string} formElementId The `id` of the form element.
* @param {Array} choices The array of possible choices to show.
* @param {Function} callback The callback function for the `change` DOM event
* generated by the `<select>` element.
* @param {number} initialSelected The index of the option to appear as
* selected at initialization.
* @param {object} [selectAttributes={}] The parameters to consider as
* attributes for the [HTML `<select>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select).
* @param {boolean} selectAttributes.disabled Flag indicating whether the
* input field should be disabled at initialization.
* @param {boolean} [selectAttributes.multiple] Flag indicating whether the
* `<select>` must show multiple choice rows. The `selectAttributes.size`
* overrides its behavior if present.
* @param {number} [selectAttributes.size] The value that the `<select>`
* element would take as `size` attribute. The parameter will be ignored in
* case its value is bigger than the array length of the `options` parameter.
* @param {object} [options={}] Parameters considered to generate the
* component.
* @param {string} [options.bootstrap] If given, it indicates the
* [Bootstrap](https://getbootstrap.com/) version which the component should
* be built for.
*/
constructor(
formElementId,
choices,
callback,
initialSelected,
{ disabled, multiple, size } = {},
options = {}
) {
this.domElement = document.createElement('div');
/**
* Reference to the DOM object representing the `<select>` element.
*
* @type {HTMLSelectElement}
*/
this.select = this.domElement.appendChild(document.createElement('select'));
this.select.id = `${formElementId}__select`;
if (!isNaN(size))
this.select.size = choices.length > size ? size : choices.length;
else if (!isNaN(multiple)) this.select.multiple = true;
this.select.required = true;
this.select.disabled = !!disabled;
if (options.bootstrap) this.select.classList.add('custom-select');
for (const [i, c] of choices.entries()) {
const option = this.select.appendChild(document.createElement('option'));
option.value = i;
option.appendChild(c);
if (i === initialSelected) option.selected = true;
}
if (callback)
this.select.addEventListener(
'change',
() => void callback(this.select.value)
);
}
enable() {
this.select.disabled = false;
}
disable() {
this.select.disabled = true;
}
getValue() {
return this.select.value;
}
getId() {
return this.select.id;
}
}
/**
* A panel that contains a {@link module:components~Select} component and a
* {@link module:components~InstanceView} that shows the selected instance.
*
* @implements {module:components~InputComponent}
*/
class SelectInstancePanel {
/**
* @param {string} formElementId The `id` of the form element.
* @param {Array} instances The array of JSON instances.
* @param {string} objectSubstitute A string to be shown as choice in the case
* of an option corresponding to a JSON Schema `object` type.
* @param {string} arraySubstitute A string to be shown as choice in the case
* of an option corresponding to a JSON Schema `array` type.
* @param {Function} booleanTranslateFunction A function that maps boolean
* values into strings.
* @param {number} [initialSelected=0] The index of the option to appear as
* selected at initialization.
* @param {object} [selectAttributes={}] The parameters to consider as
* attributes for the [HTML `<select>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select).
* @param {boolean} [selectAttributes.disabled=false] Flag indicating whether
* the input field should be disabled at initialization.
* @param {boolean} [selectAttributes.multiple] Flag indicating whether the
* `<select>` must show multiple choice rows. The `selectAttributes.size`
* overrides its behavior if present.
* @param {number} [selectAttributes.size] The value that the `<select>`
* element would take as `size` attribute. The parameter will be ignored in
* case its value is bigger than the array length of the `options` parameter.
* @param {object} [options={}] Parameters considered to generate the
* component.
* @param {string} [options.bootstrap] If given, it indicates the
* [Bootstrap](https://getbootstrap.com/) version which the component should
* be built for.
* @param {string} [options.fontAwesome] If given, it indicates the
* [Font Awesome](https://fontawesome.com/) version which the component should
* be built for.
*/
constructor(
formElementId,
instances,
objectSubstitute,
arraySubstitute,
booleanTranslateFunction,
initialSelected,
{ disabled = false, multiple, size } = {},
options = {}
) {
const instanceViewDiv = document.createElement('div');
/**
* The array of selectable JSON instances.
*
* @type {Array}
*/
this.instances = instances;
const _createInstanceView = (i) => {
if (!isJsonPrimitiveType(this.instances[i]))
instanceViewDiv.appendChild(
createInstanceView(this.instances[i], booleanTranslateFunction, {
bootstrap: options.bootstrap,
})
);
};
_createInstanceView(initialSelected);
/**
* The {@link module:components~Select} component.
*
* @type {module:components~Select}
*/
this.select = new Select(
formElementId,
instances.map((instance) =>
instanceAsTitle(
instance,
arraySubstitute,
objectSubstitute,
booleanTranslateFunction
)
),
(i) => {
if (instanceViewDiv.hasChildNodes())
instanceViewDiv.lastElementChild.remove();
_createInstanceView(i);
},
initialSelected,
{
disabled,
size,
multiple,
},
{
bootstrap: options.bootstrap,
}
);
this.domElement = document.createElement('div');
this.domElement.appendChild(this.select.domElement);
this.domElement.appendChild(instanceViewDiv);
if (options.bootstrap) {
this.domElement.classList.add('row');
this.select.domElement.classList.add('col-4');
instanceViewDiv.classList.add('col-8');
}
}
enable() {
this.select.enable();
}
disable() {
this.select.disable();
}
getValue() {
return this.instances[Number.parseInt(this.select.getValue())];
}
getId() {
return this.select.getId();
}
}
/**
* A panel that contains [Bootstrap pills](https://getbootstrap.com/docs/4.5/components/navs/#pills)
* together with an instance view that allows to choose and display the selected
* instance.
*
* ❗️ This class relies on Bootstrap; if not available, the HTML result will not
* be properly displayed.
*
* @implements {module:components~InputComponent}
*/
class PillsInstancePanel {
/**
* @param {string} formElementId The `id` of the form element.
* @param {Array} instances The array of JSON instances.
* @param {string} objectSubstitute A string to be shown as choice in the case
* of an option corresponding to a JSON Schema `object` type.
* @param {string} arraySubstitute A string to be shown as choice in the case
* of an option corresponding to a JSON Schema `array` type.
* @param {Function} booleanTranslateFunction A function that maps boolean
* values into strings.
* @param {boolean} [disabled=false] Flag indicating whether the input field
* should be disabled at initialization.
* @param {object} [options={}] Parameters considered to generate the
* component.
* @param {string} [options.bootstrap] If given, it indicates the
* [Bootstrap](https://getbootstrap.com/) version which the component should
* be built for.
* @param {string} [options.fontAwesome] If given, it indicates the
* [Font Awesome](https://fontawesome.com/) version which the component should
* be built for.
*/
constructor(
formElementId,
instances,
objectSubstitute,
arraySubstitute,
booleanTranslateFunction,
disabled = false,
options = {}
) {
this.domElement = document.createElement('div');
/**
* The array of selectable JSON instances.
*
* @type {Array}
*/
this.instances = instances;
const ul = this.domElement.appendChild(document.createElement('ul'));
ul.classList.add('col-5', 'nav', 'nav-pills', 'pl-3');
ul.setAttribute('role', 'tablist');
const panesDiv = this.domElement.appendChild(document.createElement('div'));
panesDiv.classList.add('col-7', 'tab-content');
/**
* The array of anchors shown as pills.
*
* @type {Array.<HTMLAnchorElement>}
*/
this.choices = [];
for (const [i, instance] of instances.entries()) {
const li = ul.appendChild(document.createElement('li'));
li.classList.add('nav-item');
const a = li.appendChild(document.createElement('a'));
a.id = `${formElementId}__pills__${i}`;
const paneId = `${formElementId}__panes__${i}`.replace(/\//gi, '_');
a.href = `#${paneId}`;
a.classList.add('nav-link');
a.dataset.toggle = 'tab';
a.setAttribute('role', 'tab');
a.setAttribute('aria-controls', paneId);
if (disabled) a.classList.add('disabled');
a.appendChild(
instanceAsTitle(
instance,
arraySubstitute,
objectSubstitute,
booleanTranslateFunction
)
);
this.choices.push(a);
const paneDiv = panesDiv.appendChild(document.createElement('div'));
paneDiv.id = paneId;
paneDiv.classList.add('tab-pane', 'fade');
paneDiv.setAttribute('role', 'tabpanel');
paneDiv.setAttribute('aria-labelledby', a.id);
if (isJsonPrimitiveType(instance))
paneDiv.appendChild(document.createTextNode(''));
else
paneDiv.appendChild(
createInstanceView(instance, booleanTranslateFunction),
{
bootstrap: options.bootstrap,
}
);
if (i === 0) {
a.classList.add('active');
a.setAttribute('aria-selected', 'true');
paneDiv.classList.add('show', 'active');
} else a.setAttribute('aria-selected', 'false');
}
}
enable() {
for (const a of this.choices) a.classList.remove('disabled');
}
disable() {
for (const a of this.choices) a.classList.add('disabled');
}
getValue() {
return this.instances[
this.choices.findIndex((a) => a.classList.contains('active'))
];
}
getId() {
return undefined;
}
}
/**
* A panel that displays a given JSON instance.
*
* Although, strictly speaking, this component has no interactive control, it
* implements the {@link module:components~InputComponent} interface as it has
* to provide a mean to retrieve the displayed instance.
*
* @implements {module:components~InputComponent}
*/
class InstanceViewPanel {
/**
* @param {any} instance The JSON instance to be displayed.
* @param {Function} booleanTranslateFunction A function that maps boolean
* values into strings.
* @param {object} [options={}] Parameters considered to generate the
* component.
* @param {string} [options.bootstrap] If given, it indicates the
* [Bootstrap](https://getbootstrap.com/) version which the component should
* be built for.
*/
constructor(instance, booleanTranslateFunction, options = {}) {
this.domElement = document.createElement('div');
/**
* The instance to display.
*
* @type {any}
*/
this.instance = instance;
this.domElement.appendChild(
createInstanceView(instance, booleanTranslateFunction, options)
);
}
enable() {}
disable() {}
getValue() {
return this.instance;
}
getId() {
return undefined;
}
}
/**
* Creates an HTML element containing a representation of a given JSON instance.
*
* @param {any} instance The JSON instance to be displayed.
* @param {Function} booleanTranslateFunction A function that maps boolean
* values into strings.
* @param {object} [options={}] Parameters considered to generate the component.
* @param {string} [options.bootstrap] If given, it indicates the
* [Bootstrap](https://getbootstrap.com/) version which the component should be
* built for.
*
* @returns {HTMLElement} An element containing the HTML structure of the
* instance representation.
*/
function createInstanceView(instance, booleanTranslateFunction, options = {}) {
const recursion = (instance) => {
if (Array.isArray(instance)) {
const table = document.createElement('table');
if (options.bootstrap) table.classList.add('table', 'table-striped');
const tbody = table.appendChild(document.createElement('tbody'));
for (const i of instance)
tbody
.appendChild(document.createElement('tr'))
.appendChild(document.createElement('td'))
.appendChild(recursion(i));
return table;
} else if (instance instanceof Object) {
const table = document.createElement('table');
if (options.bootstrap) table.classList.add('table', 'table-striped');
const tbody = table.appendChild(document.createElement('tbody'));
for (const [key, value] of Object.entries(instance)) {
const tr = tbody.appendChild(document.createElement('tr'));
const th = tr.appendChild(document.createElement('th'));
th.scope = 'row';
th.innerText = key;
tr.appendChild(document.createElement('td')).appendChild(
recursion(value)
);
}
return table;
} else if (typeof instance === 'boolean')
return buildBooleanInstance(instance, booleanTranslateFunction);
else if (instance === null) return buildNullInstance();
else if (typeof instance === 'number') return buildNumberInstance(instance);
else if (typeof instance === 'string') return buildStringInstance(instance);
};
return recursion(instance);
}
/**
* Evaluates if a given JSON instance is of a primitive type (`boolean`, `null`,
* `number` or `string`).
*
* @param {any} instance The JSON instance to be evaluated.
*
* @returns {boolean} A boolean set to `true` if the function evaluates
* positive, `false` otherwise.
*/
function isJsonPrimitiveType(instance) {
return instance === null ||
['boolean', 'number', 'string'].includes(typeof instance)
? true
: false;
}
/**
* Generates a string from a given instance to be used as title.
*
* @param {any} instance The JSON instance from which to extract a title.
* @param {string} arraySubstitute A string to be shown as title of an instance
* corresponding to a JSON Schema `array` type.
* @param {string} objectSubstitute A string to be shown as title of an instance
* corresponding to a JSON Schema `object` type.
* @param {Function} booleanTranslateFunction A function that maps boolean
* values into strings.
*
* @returns {string} The generated title.
*/
function instanceAsTitle(
instance,
arraySubstitute,
objectSubstitute,
booleanTranslateFunction
) {
if (Array.isArray(instance)) {
const span = document.createElement('span');
const icon = span.appendChild(document.createElement('i'));
icon.classList.add('fas', 'fa-list');
span.appendChild(document.createElement('span')).classList.add('mr-2');
span.appendChild(document.createTextNode(arraySubstitute));
return span;
} else if (instance instanceof Object) {
const span = document.createElement('span');
const icon = span.appendChild(document.createElement('i'));
icon.classList.add('fas', 'fa-object-group');
span.appendChild(document.createElement('span')).classList.add('mr-2');
span.appendChild(document.createTextNode(objectSubstitute));
return span;
} else if (typeof instance === 'boolean')
return buildBooleanInstance(instance, booleanTranslateFunction);
else if (instance === null) return buildNullInstance();
else if (typeof instance === 'number') return buildNumberInstance(instance);
else if (typeof instance === 'string') return buildStringInstance(instance);
}
/**
* Builds the DOM representation of a `boolean` JSON instance.
*
* @param {boolean} instance The `boolean` JSON instance to present.
* @param {Function} [booleanTranslateFunction=(b) => b] A function that maps
* boolean values into strings.
*
* @returns {Text} An HTML text node containing the instance representation.
*/
function buildBooleanInstance(instance, booleanTranslateFunction = (b) => b) {
return document.createTextNode(booleanTranslateFunction(instance));
}
/**
* Builds the DOM representation of a `null` JSON instance.
*
* @returns {Text} An HTML text node containing the instance representation.
*/
function buildNullInstance() {
return document.createTextNode('Null');
}
/**
* Builds the DOM representation of a `number` JSON instance.
*
* @param {number} instance The `number` JSON instance to present.
*
* @returns {Text} An HTML text node containing the instance representation.
*/
function buildNumberInstance(instance) {
return document.createTextNode(instance);
}
/**
* Builds the DOM representation of a `string` JSON instance.
*
* @param {number} instance The `string` JSON instance to present.
*
* @returns {Text} An HTML text node containing the instance representation.
*/
function buildStringInstance(instance) {
const em = document.createElement('em');
em.innerText = instance;
return em;
}
export {
GeneratorIcon,
RequiredIcon,
RootIcon,
AddButton,
RemoveButton,
Toggler,
BooleanInput,
NumericInput,
TextInput,
InputTitle,
LabelTitle,
SelectInstancePanel,
PillsInstancePanel,
InstanceViewPanel,
};