formiojs
Version:
Common js library for client side interaction with <form.io>
1,758 lines (1,454 loc) • 80.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
require("core-js/modules/es7.symbol.async-iterator");
require("core-js/modules/es6.symbol");
require("core-js/modules/es6.reflect.get");
require("core-js/modules/web.dom.iterable");
require("core-js/modules/es6.regexp.replace");
require("core-js/modules/es6.regexp.constructor");
require("core-js/modules/es6.regexp.match");
require("core-js/modules/es7.array.includes");
require("core-js/modules/es6.string.includes");
require("core-js/modules/es6.object.assign");
require("core-js/modules/es6.regexp.to-string");
require("core-js/modules/es6.regexp.split");
require("core-js/modules/es6.function.name");
var _vanillaTextMask = require("vanilla-text-mask");
var _nativePromiseOnly = _interopRequireDefault(require("native-promise-only"));
var _lodash = _interopRequireDefault(require("lodash"));
var _tooltip = _interopRequireDefault(require("tooltip.js"));
var FormioUtils = _interopRequireWildcard(require("../../utils/utils"));
var _Formio = _interopRequireDefault(require("../../Formio"));
var _Validator = _interopRequireDefault(require("../Validator"));
var _widgets = _interopRequireDefault(require("../../widgets"));
var _Component2 = _interopRequireDefault(require("../../Component"));
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); }
function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
/**
* This is the BaseComponent class which all elements within the FormioForm derive from.
*/
var BaseComponent =
/*#__PURE__*/
function (_Component) {
_inherits(BaseComponent, _Component);
_createClass(BaseComponent, null, [{
key: "schema",
value: function schema() {
for (var _len = arguments.length, sources = new Array(_len), _key = 0; _key < _len; _key++) {
sources[_key] = arguments[_key];
}
return _lodash.default.merge.apply(_lodash.default, [{
/**
* Determines if this component provides an input.
*/
input: true,
/**
* The data key for this component (how the data is stored in the database).
*/
key: '',
/**
* The input placeholder for this component.
*/
placeholder: '',
/**
* The input prefix
*/
prefix: '',
/**
* The custom CSS class to provide to this component.
*/
customClass: '',
/**
* The input suffix.
*/
suffix: '',
/**
* If this component should allow an array of values to be captured.
*/
multiple: false,
/**
* The default value of this compoennt.
*/
defaultValue: null,
/**
* If the data of this component should be protected (no GET api requests can see the data)
*/
protected: false,
/**
* Validate if the value of this component should be unique within the form.
*/
unique: false,
/**
* If the value of this component should be persisted within the backend api database.
*/
persistent: true,
/**
* Determines if the component should be within the form, but not visible.
*/
hidden: false,
/**
* If the component should be cleared when hidden.
*/
clearOnHide: true,
/**
* If this component should be included as a column within a submission table.
*/
tableView: true,
/**
* If true, will show label when component is in a datagrid.
*/
dataGridLabel: false,
/**
* The input label provided to this component.
*/
label: '',
labelPosition: 'top',
labelWidth: 30,
labelMargin: 3,
description: '',
errorLabel: '',
tooltip: '',
hideLabel: false,
tabindex: '',
disabled: false,
autofocus: false,
dbIndex: false,
customDefaultValue: '',
calculateValue: '',
allowCalculateOverride: false,
widget: null,
/**
* This will refresh this component when this field changes.
*/
refreshOn: '',
/**
* Determines if we should clear our value when a refresh occurs.
*/
clearOnRefresh: false,
/**
* This will perform the validation on either "change" or "blur" of the input element.
*/
validateOn: 'change',
/**
* The validation criteria for this component.
*/
validate: {
/**
* If this component is required.
*/
required: false,
/**
* Custom JavaScript validation.
*/
custom: '',
/**
* If the custom validation should remain private (only the backend will see it and execute it).
*/
customPrivate: false
},
/**
* The simple conditional settings for a component.
*/
conditional: {
show: null,
when: null,
eq: ''
}
}].concat(sources));
}
/**
* Provides a table view for this component. Override if you wish to do something different than using getView
* method of your instance.
*
* @param value
* @param options
*/
/* eslint-disable no-unused-vars */
}, {
key: "tableView",
value: function tableView(value, options) {}
/* eslint-enable no-unused-vars */
/**
* Initialize a new BaseComponent.
*
* @param {Object} component - The component JSON you wish to initialize.
* @param {Object} options - The options for this component.
* @param {Object} data - The global data submission object this component will belong.
*/
/* eslint-disable max-statements */
}]);
function BaseComponent(component, options, data) {
var _this;
_classCallCheck(this, BaseComponent);
_this = _possibleConstructorReturn(this, _getPrototypeOf(BaseComponent).call(this, options, component && component.id ? component.id : null));
_this.originalComponent = _lodash.default.cloneDeep(component); // Determine if we are inside a datagrid.
_this.inDataGrid = _this.options.inDataGrid;
_this.options.inDataGrid = false;
/**
* Determines if this component has a condition assigned to it.
* @type {null}
* @private
*/
_this._hasCondition = null;
/**
* The data object in which this component resides.
* @type {*}
*/
_this.data = data || {}; // Allow global override for any component JSON.
if (_this.options.components && _this.options.components[component.type]) {
_lodash.default.merge(component, _this.options.components[component.type]);
}
/**
* The Form.io component JSON schema.
* @type {*}
*/
_this.component = _lodash.default.defaultsDeep(component || {}, _this.defaultSchema); // Add the id to the component.
_this.component.id = _this.id;
/**
* The bounding HTML Element which this component is rendered.
* @type {null}
*/
_this.element = null;
/**
* The HTML Element for the table body. This is relevant for the "multiple" flag on inputs.
* @type {null}
*/
_this.tbody = null;
/**
* The HTMLElement that is assigned to the label of this component.
* @type {null}
*/
_this.labelElement = null;
/**
* The HTMLElement for which the errors are rendered for this component (usually underneath the component).
* @type {null}
*/
_this.errorElement = null;
/**
* The existing error that this component has.
* @type {string}
*/
_this.error = '';
/**
* An array of all of the input HTML Elements that have been added to this component.
* @type {Array}
*/
_this.inputs = [];
/**
* The basic component information which tells the BaseComponent how to render the input element of the components that derive from this class.
* @type {null}
*/
_this.info = null;
/**
* The row path of this component.
* @type {number}
*/
_this.row = _this.options.row;
/**
* Determines if this component is disabled, or not.
*
* @type {boolean}
*/
_this._disabled = false;
/**
* Determines if this component is visible, or not.
*/
_this._visible = true;
/**
* If this input has been input and provided value.
*
* @type {boolean}
*/
_this.pristine = true;
/**
* Points to the parent component.
*
* @type {BaseComponent}
*/
_this.parent = null;
/**
* Points to the root component, usually the FormComponent.
*
* @type {BaseComponent}
*/
_this.root = _assertThisInitialized(_assertThisInitialized(_this));
_this.options.name = _this.options.name || 'data';
/**
* The validators that are assigned to this component.
* @type {[string]}
*/
_this.validators = ['required', 'minLength', 'maxLength', 'custom', 'pattern', 'json', 'mask'];
/**
* Used to trigger a new change in this component.
* @type {function} - Call to trigger a change in this component.
*/
var _triggerChange = _lodash.default.debounce(function () {
var _this2;
if (_this.root) {
_this.root.changing = false;
}
return (_this2 = _this).onChange.apply(_this2, arguments);
}, 100);
_this.triggerChange = function () {
if (_this.root) {
_this.root.changing = true;
}
return _triggerChange.apply(void 0, arguments);
};
/**
* Used to trigger a redraw event within this component.
*
* @type {Function}
*/
_this.triggerRedraw = _lodash.default.debounce(_this.redraw.bind(_assertThisInitialized(_assertThisInitialized(_this))), 100); // To force this component to be invalid.
_this.invalid = false; // Determine if the component has been built.
_this.isBuilt = false;
if (_this.component) {
_this.type = _this.component.type;
if (_this.hasInput && _this.key) {
_this.options.name += "[".concat(_this.key, "]");
}
/**
* The element information for creating the input element.
* @type {*}
*/
_this.info = _this.elementInfo();
} // Allow anyone to hook into the component creation.
_this.hook('component');
return _this;
}
/* eslint-enable max-statements */
_createClass(BaseComponent, [{
key: "getModifiedSchema",
/**
* Returns only the schema that is different from the default.
*
* @param schema
* @param defaultSchema
*/
value: function getModifiedSchema(schema, defaultSchema) {
var _this3 = this;
var modified = {};
if (!defaultSchema) {
return schema;
}
_lodash.default.each(schema, function (val, key) {
if (!_lodash.default.isArray(val) && _lodash.default.isObject(val) && defaultSchema.hasOwnProperty(key)) {
var subModified = _this3.getModifiedSchema(val, defaultSchema[key]);
if (!_lodash.default.isEmpty(subModified)) {
modified[key] = subModified;
}
} else if (key === 'type' || key === 'key' || key === 'label' || key === 'input' || key === 'tableView' || !defaultSchema.hasOwnProperty(key) || _lodash.default.isArray(val) || val !== defaultSchema[key]) {
modified[key] = val;
}
});
return modified;
}
/**
* Returns the JSON schema for this component.
*/
}, {
key: "t",
/**
* Translate a text using the i18n system.
*
* @param {string} text - The i18n identifier.
* @param {Object} params - The i18n parameters to use for translation.
*/
value: function t(text, params) {
params = params || {};
params.data = this.root ? this.root.data : this.data;
params.row = this.data;
params.component = this.component;
return _get(_getPrototypeOf(BaseComponent.prototype), "t", this).call(this, text, params);
}
}, {
key: "performInputMapping",
value: function performInputMapping(input) {
return input;
}
}, {
key: "getBrowserLanguage",
value: function getBrowserLanguage() {
var nav = window.navigator;
var browserLanguagePropertyKeys = ['language', 'browserLanguage', 'systemLanguage', 'userLanguage'];
var language; // support for HTML 5.1 "navigator.languages"
if (Array.isArray(nav.languages)) {
for (var i = 0; i < nav.languages.length; i++) {
language = nav.languages[i];
if (language && language.length) {
return language.split(';')[0];
}
}
} // support for other well known properties in browsers
for (var _i = 0; _i < browserLanguagePropertyKeys.length; _i++) {
language = nav[browserLanguagePropertyKeys[_i]];
if (language && language.length) {
return language.split(';')[0];
}
}
return null;
}
/**
* Called before a next page is triggered allowing the components
* to perform special functions.
*
* @return {*}
*/
}, {
key: "beforeNext",
value: function beforeNext() {
return _nativePromiseOnly.default.resolve(true);
}
/**
* Called before a submission is triggered allowing the components
* to perform special async functions.
*
* @return {*}
*/
}, {
key: "beforeSubmit",
value: function beforeSubmit() {
return _nativePromiseOnly.default.resolve(true);
}
}, {
key: "build",
/**
* Builds the component.
*/
value: function build(state) {
state = state || {};
this.calculatedValue = state.calculatedValue;
if (this.viewOnly) {
this.viewOnlyBuild();
} else {
this.createElement();
var labelAtTheBottom = this.component.labelPosition === 'bottom';
if (!labelAtTheBottom) {
this.createLabel(this.element);
}
if (!this.createWrapper()) {
this.createInput(this.element);
}
if (labelAtTheBottom) {
this.createLabel(this.element);
}
this.createDescription(this.element); // Disable if needed.
if (this.shouldDisable) {
this.disabled = true;
} // Restore the value.
this.restoreValue(); // Attach the refresh on events.
this.attachRefreshOn();
this.autofocus();
}
this.attachLogic();
}
}, {
key: "attachRefreshOn",
value: function attachRefreshOn() {
var _this4 = this;
// If they wish to refresh on a value, then add that here.
if (this.component.refreshOn) {
this.on('change', function (event) {
if (_this4.component.refreshOn === 'data') {
_this4.refresh(_this4.data);
} else if (event.changed && event.changed.component && event.changed.component.key === _this4.component.refreshOn & // Make sure the changed component is not in a different "context". Solves issues where refreshOn being set
// in fields inside EditGrids could alter their state from other rows (which is bad).
_this4.inContext(event.changed.instance)) {
_this4.refresh(event.changed.value);
}
}, true);
}
}
}, {
key: "viewOnlyBuild",
value: function viewOnlyBuild() {
this.createViewOnlyElement();
this.createViewOnlyLabel(this.element);
this.createViewOnlyValue(this.element);
}
}, {
key: "createViewOnlyElement",
value: function createViewOnlyElement() {
this.element = this.ce('dl', {
id: this.id
});
if (this.element) {
// Ensure you can get the component info from the element.
this.element.component = this;
}
return this.element;
}
}, {
key: "createViewOnlyLabel",
value: function createViewOnlyLabel(container) {
if (this.labelIsHidden()) {
return;
}
this.labelElement = this.ce('dt');
this.labelElement.appendChild(this.text(this.component.label));
this.createTooltip(this.labelElement);
container.appendChild(this.labelElement);
}
}, {
key: "createViewOnlyValue",
value: function createViewOnlyValue(container) {
this.valueElement = this.ce('dd');
this.setupValueElement(this.valueElement);
container.appendChild(this.valueElement);
}
}, {
key: "setupValueElement",
value: function setupValueElement(element) {
var value = this.getValue();
value = this.isEmpty(value) ? this.defaultViewOnlyValue : this.getView(value);
element.innerHTML = value;
}
}, {
key: "getView",
value: function getView(value) {
if (!value) {
return '';
}
var widget = this.widget;
if (widget && widget.getView) {
return widget.getView(value);
}
if (Array.isArray(value)) {
return value.join(', ');
}
return value.toString();
}
}, {
key: "updateItems",
value: function updateItems() {
this.restoreValue();
this.onChange.apply(this, arguments);
}
}, {
key: "updateViewOnlyValue",
value: function updateViewOnlyValue() {
if (!this.valueElement) {
return;
}
this.setupValueElement(this.valueElement);
}
}, {
key: "createModal",
value: function createModal() {
var _this5 = this;
var modalBody = this.ce('div');
var modalOverlay = this.ce('div', {
class: 'formio-dialog-overlay'
});
var closeDialog = this.ce('button', {
class: 'formio-dialog-close pull-right btn btn-default btn-xs',
'aria-label': 'close'
});
var dialog = this.ce('div', {
class: 'formio-dialog formio-dialog-theme-default component-settings'
}, [modalOverlay, this.ce('div', {
class: 'formio-dialog-content'
}, [modalBody, closeDialog])]);
this.addEventListener(modalOverlay, 'click', function (event) {
event.preventDefault();
dialog.close();
});
this.addEventListener(closeDialog, 'click', function (event) {
event.preventDefault();
dialog.close();
});
this.addEventListener(dialog, 'close', function () {
_this5.removeChildFrom(dialog, document.body);
});
document.body.appendChild(dialog);
dialog.body = modalBody;
dialog.close = function () {
dialog.dispatchEvent(new CustomEvent('close'));
_this5.removeChildFrom(dialog, document.body);
};
return dialog;
}
/**
* Retrieves the CSS class name of this component.
* @returns {string} - The class name of this component.
*/
}, {
key: "getElement",
/**
* Returns the outside wrapping element of this component.
* @returns {HTMLElement}
*/
value: function getElement() {
return this.element;
}
/**
* Create the outside wrapping element for this component.
* @returns {HTMLElement}
*/
}, {
key: "createElement",
value: function createElement() {
// If the element is already created, don't recreate.
if (this.element) {
//update class for case when Logic changed container class (customClass)
this.element.className = this.className;
return this.element;
}
this.element = this.ce('div', {
id: this.id,
class: this.className,
style: this.customStyle
}); // Ensure you can get the component info from the element.
this.element.component = this;
this.hook('element', this.element);
return this.element;
}
/**
* Create the input wrapping element. For multiple, this may be the table wrapper for the elements.
* @returns {boolean}
*/
}, {
key: "createWrapper",
value: function createWrapper() {
if (!this.component.multiple) {
return false;
} else {
var table = this.ce('table', {
class: 'table table-bordered'
});
this.tbody = this.ce('tbody');
table.appendChild(this.tbody); // Add a default value.
var dataValue = this.dataValue;
if (!dataValue || !dataValue.length) {
this.addNewValue(this.defaultValue);
} // Build the rows.
this.buildRows();
this.setInputStyles(table); // Add the table to the element.
this.append(table);
return true;
}
}
}, {
key: "evalContext",
value: function evalContext(additional) {
return _get(_getPrototypeOf(BaseComponent.prototype), "evalContext", this).call(this, Object.assign({
component: this.component,
row: this.data,
rowIndex: this.rowIndex,
data: this.root ? this.root.data : this.data,
submission: this.root ? this.root._submission : {},
form: this.root ? this.root._form : {}
}, additional));
}
}, {
key: "setPristine",
/**
* Sets the pristine flag for this component.
*
* @param pristine {boolean} - TRUE to make pristine, FALSE not pristine.
*/
value: function setPristine(pristine) {
this.pristine = pristine;
}
/**
* Adds a new empty value to the data array.
*/
}, {
key: "addNewValue",
value: function addNewValue(value) {
if (value === undefined) {
value = this.emptyValue;
}
var dataValue = this.dataValue || [];
if (!Array.isArray(dataValue)) {
dataValue = [dataValue];
}
if (Array.isArray(value)) {
dataValue = dataValue.concat(value);
} else {
dataValue.push(value);
}
this.dataValue = dataValue;
}
/**
* Adds a new empty value to the data array, and add a new row to contain it.
*/
}, {
key: "addValue",
value: function addValue() {
this.addNewValue();
this.buildRows();
this.checkConditions(this.root ? this.root.data : this.data);
this.restoreValue();
if (this.root) {
this.root.onChange();
}
}
/**
* Removes a value out of the data array and rebuild the rows.
* @param {number} index - The index of the data element to remove.
*/
}, {
key: "removeValue",
value: function removeValue(index) {
this.splice(index);
this.buildRows();
this.restoreValue();
if (this.root) {
this.root.onChange();
}
}
/**
* Rebuild the rows to contain the values of this component.
*/
}, {
key: "buildRows",
value: function buildRows(values) {
var _this6 = this;
if (!this.tbody) {
return;
}
this.inputs = [];
this.tbody.innerHTML = '';
values = values || this.dataValue;
_lodash.default.each(values, function (value, index) {
var tr = _this6.ce('tr');
var td = _this6.ce('td');
_this6.buildInput(td, value, index);
tr.appendChild(td);
if (!_this6.shouldDisable) {
var tdAdd = _this6.ce('td');
tdAdd.appendChild(_this6.removeButton(index));
tr.appendChild(tdAdd);
}
_this6.tbody.appendChild(tr);
});
if (!this.shouldDisable) {
var tr = this.ce('tr');
var td = this.ce('td', {
colspan: '2'
});
td.appendChild(this.addButton());
tr.appendChild(td);
this.tbody.appendChild(tr);
}
if (this.shouldDisable) {
this.disabled = true;
}
}
}, {
key: "buildInput",
value: function buildInput(container, value) {
var input = this.createInput(container);
input.value = value;
}
/**
* Adds a new button to add new rows to the multiple input elements.
* @returns {HTMLElement} - The "Add New" button html element.
*/
}, {
key: "addButton",
value: function addButton(justIcon) {
var _this7 = this;
var addButton = this.ce('button', {
class: 'btn btn-primary formio-button-add-row'
});
this.addEventListener(addButton, 'click', function (event) {
event.preventDefault();
_this7.addValue();
});
var addIcon = this.ce('i', {
class: this.iconClass('plus')
});
if (justIcon) {
addButton.appendChild(addIcon);
return addButton;
} else {
addButton.appendChild(addIcon);
addButton.appendChild(this.text(this.component.addAnother || ' Add Another'));
return addButton;
}
}
/**
* The readible name for this component.
* @returns {string} - The name of the component.
*/
}, {
key: "errorMessage",
/**
* Get the error message provided a certain type of error.
* @param type
* @return {*}
*/
value: function errorMessage(type) {
return this.component.errors && this.component.errors[type] ? this.component.errors[type] : type;
}
/**
* Creates a new "remove" row button and returns the html element of that button.
* @param {number} index - The index of the row that should be removed.
* @returns {HTMLElement} - The html element of the remove button.
*/
}, {
key: "removeButton",
value: function removeButton(index) {
var _this8 = this;
var removeButton = this.ce('button', {
type: 'button',
class: 'btn btn-default btn-secondary formio-button-remove-row'
});
this.addEventListener(removeButton, 'click', function (event) {
event.preventDefault();
_this8.removeValue(index);
});
var removeIcon = this.ce('i', {
class: this.iconClass('remove-circle')
});
removeButton.appendChild(removeIcon);
return removeButton;
}
}, {
key: "labelOnTheLeft",
value: function labelOnTheLeft(position) {
return ['left-left', 'left-right'].includes(position);
}
}, {
key: "labelOnTheRight",
value: function labelOnTheRight(position) {
return ['right-left', 'right-right'].includes(position);
}
}, {
key: "rightAlignedLabel",
value: function rightAlignedLabel(position) {
return ['left-right', 'right-right'].includes(position);
}
}, {
key: "labelOnTheLeftOrRight",
value: function labelOnTheLeftOrRight(position) {
return this.labelOnTheLeft(position) || this.labelOnTheRight(position);
}
}, {
key: "getLabelWidth",
value: function getLabelWidth() {
if (!this.component.labelWidth) {
this.component.labelWidth = 30;
}
return this.component.labelWidth;
}
}, {
key: "getLabelMargin",
value: function getLabelMargin() {
if (!this.component.labelMargin) {
this.component.labelMargin = 3;
}
return this.component.labelMargin;
}
}, {
key: "setInputStyles",
value: function setInputStyles(input) {
if (this.labelIsHidden()) {
return;
}
if (this.labelOnTheLeftOrRight(this.component.labelPosition)) {
var totalLabelWidth = this.getLabelWidth() + this.getLabelMargin();
input.style.width = "".concat(100 - totalLabelWidth, "%");
if (this.labelOnTheLeft(this.component.labelPosition)) {
input.style.marginLeft = "".concat(totalLabelWidth, "%");
} else {
input.style.marginRight = "".concat(totalLabelWidth, "%");
}
}
}
}, {
key: "labelIsHidden",
value: function labelIsHidden() {
return !this.component.label || this.component.hideLabel || this.options.inputsOnly || this.inDataGrid && !this.component.dataGridLabel;
}
/**
* Create the HTML element for the label of this component.
* @param {HTMLElement} container - The containing element that will contain this label.
*/
}, {
key: "createLabel",
value: function createLabel(container) {
var isLabelHidden = this.labelIsHidden();
var className = 'control-label';
var style = '';
if (!isLabelHidden) {
var labelPosition = this.component.labelPosition; // Determine label styles/classes depending on position.
if (labelPosition === 'bottom') {
className += ' control-label--bottom';
} else if (labelPosition && labelPosition !== 'top') {
var labelWidth = this.getLabelWidth();
var labelMargin = this.getLabelMargin(); // Label is on the left or right.
if (this.labelOnTheLeft(labelPosition)) {
style += "float: left; width: ".concat(labelWidth, "%; margin-right: ").concat(labelMargin, "%; ");
} else if (this.labelOnTheRight(labelPosition)) {
style += "float: right; width: ".concat(labelWidth, "%; margin-left: ").concat(labelMargin, "%; ");
}
if (this.rightAlignedLabel(labelPosition)) {
style += 'text-align: right; ';
}
}
} else {
this.addClass(container, 'formio-component-label-hidden');
className += ' control-label--hidden';
}
if (this.hasInput && this.component.validate && this.component.validate.required) {
className += ' field-required';
}
this.labelElement = this.ce('label', {
class: className,
style: style
});
if (!isLabelHidden) {
if (this.info.attr.id) {
this.labelElement.setAttribute('for', this.info.attr.id);
}
this.labelElement.appendChild(this.text(this.component.label));
this.createTooltip(this.labelElement);
}
container.appendChild(this.labelElement);
}
}, {
key: "addShortcutToLabel",
value: function addShortcutToLabel(label, shortcut) {
if (!label) {
label = this.component.label;
}
if (!shortcut) {
shortcut = this.component.shortcut;
}
if (!shortcut || !/^[A-Za-z]$/.test(shortcut)) {
return label;
}
var match = label.match(new RegExp(shortcut, 'i'));
if (!match) {
return label;
}
var index = match.index + 1;
var lowLineCombinator = "\u0332";
return label.substring(0, index) + lowLineCombinator + label.substring(index);
}
}, {
key: "addShortcut",
value: function addShortcut(element, shortcut) {
// Avoid infinite recursion.
if (this.root === this) {
return;
}
if (!element) {
element = this.labelElement;
}
if (!shortcut) {
shortcut = this.component.shortcut;
}
this.root.addShortcut(element, shortcut);
}
}, {
key: "removeShortcut",
value: function removeShortcut(element, shortcut) {
// Avoid infinite recursion.
if (this.root === this) {
return;
}
if (!element) {
element = this.labelElement;
}
if (!shortcut) {
shortcut = this.component.shortcut;
}
this.root.removeShortcut(element, shortcut);
}
/**
* Create the HTML element for the tooltip of this component.
* @param {HTMLElement} container - The containing element that will contain this tooltip.
*/
}, {
key: "createTooltip",
value: function createTooltip(container, component, classes) {
if (this.tooltip) {
return;
}
component = component || this.component;
classes = classes || "".concat(this.iconClass('question-sign'), " text-muted");
if (!component.tooltip) {
return;
}
var ttElement = this.ce('i', {
class: classes
});
container.appendChild(this.text(' '));
container.appendChild(ttElement);
this.tooltip = new _tooltip.default(ttElement, {
trigger: 'hover click',
placement: 'right',
html: true,
title: component.tooltip.replace(/(?:\r\n|\r|\n)/g, '<br />')
});
}
/**
* Creates the description block for this input field.
* @param container
*/
}, {
key: "createDescription",
value: function createDescription(container) {
if (!this.component.description) {
return;
}
this.description = this.ce('div', {
class: 'help-block'
});
this.description.innerHTML = this.t(this.component.description);
container.appendChild(this.description);
}
/**
* Creates a new error element to hold the errors of this element.
*/
}, {
key: "createErrorElement",
value: function createErrorElement() {
if (!this.errorContainer) {
return;
}
this.errorElement = this.ce('div', {
class: 'formio-errors invalid-feedback'
});
this.errorContainer.appendChild(this.errorElement);
}
/**
* Adds a prefix html element.
*
* @param {HTMLElement} input - The input element.
* @param {HTMLElement} inputGroup - The group that will hold this prefix.
* @returns {HTMLElement} - The html element for this prefix.
*/
}, {
key: "addPrefix",
value: function addPrefix(input, inputGroup) {
var prefix = null;
if (input.widget) {
return input.widget.addPrefix(inputGroup);
}
if (this.component.prefix && typeof this.component.prefix === 'string') {
prefix = this.ce('div', {
class: 'input-group-addon input-group-prepend'
});
prefix.appendChild(this.ce('span', {
class: 'input-group-text'
}, this.text(this.component.prefix)));
inputGroup.appendChild(prefix);
}
return prefix;
}
/**
* Adds a suffix html element.
*
* @param {HTMLElement} input - The input element.
* @param {HTMLElement} inputGroup - The group that will hold this suffix.
* @returns {HTMLElement} - The html element for this suffix.
*/
}, {
key: "addSuffix",
value: function addSuffix(input, inputGroup) {
var suffix = null;
if (input.widget) {
return input.widget.addSuffix(inputGroup);
}
if (this.component.suffix && typeof this.component.suffix === 'string') {
suffix = this.ce('div', {
class: 'input-group-addon input-group-append'
});
suffix.appendChild(this.ce('span', {
class: 'input-group-text'
}, this.text(this.component.suffix)));
inputGroup.appendChild(suffix);
}
return suffix;
}
/**
* Adds a new input group to hold the input html elements.
*
* @param {HTMLElement} input - The input html element.
* @param {HTMLElement} container - The containing html element for this group.
* @returns {HTMLElement} - The input group element.
*/
}, {
key: "addInputGroup",
value: function addInputGroup(input, container) {
var inputGroup = null;
if (this.component.prefix || this.component.suffix) {
inputGroup = this.ce('div', {
class: 'input-group'
});
container.appendChild(inputGroup);
}
return inputGroup;
} // Default the mask to the component input mask.
}, {
key: "setInputMask",
value: function setInputMask(input, inputMask) {
return _get(_getPrototypeOf(BaseComponent.prototype), "setInputMask", this).call(this, input, inputMask || this.component.inputMask, !this.component.placeholder);
}
/**
* Creates a new input element.
* @param {HTMLElement} container - The container which should hold this new input element.
* @returns {HTMLElement} - Either the input or the group that contains the input.
*/
}, {
key: "createInput",
value: function createInput(container) {
var input = this.ce(this.info.type, this.info.attr);
this.setInputMask(input);
input.widget = this.createWidget();
var inputGroup = this.addInputGroup(input, container);
this.addPrefix(input, inputGroup);
this.addInput(input, inputGroup || container);
this.addSuffix(input, inputGroup);
this.errorContainer = container;
this.setInputStyles(inputGroup || input); // Attach the input to the widget.
if (input.widget) {
input.widget.attach(input);
}
return inputGroup || input;
}
/**
* Returns the instance of the widget for this component.
*
* @return {*}
*/
}, {
key: "createWidget",
/**
* Creates an instance of a widget for this component.
*
* @return {null}
*/
value: function createWidget() {
var _this9 = this;
// Return null if no widget is found.
if (!this.component.widget) {
return null;
} // Get the widget settings.
var settings = typeof this.component.widget === 'string' ? {
type: this.component.widget
} : this.component.widget; // Make sure we have a widget.
if (!_widgets.default.hasOwnProperty(settings.type)) {
return null;
} // Create the widget.
var widget = new _widgets.default[settings.type](settings, this.component);
widget.on('update', function () {
return _this9.updateValue();
}, true);
widget.on('redraw', function () {
return _this9.redraw();
}, true);
this._widget = widget;
return widget;
}
}, {
key: "redraw",
value: function redraw() {
// Don't bother if we have not built yet.
if (!this.isBuilt) {
return;
}
this.build(this.clear());
}
}, {
key: "destroyInputs",
value: function destroyInputs() {
var _this10 = this;
_lodash.default.each(this.inputs, function (input) {
input = _this10.performInputMapping(input);
if (input.mask) {
input.mask.destroy();
}
if (input.widget) {
input.widget.destroy();
}
});
if (this.tooltip) {
this.tooltip.dispose();
this.tooltip = null;
}
this.inputs = [];
}
/**
* Remove all event handlers.
*/
}, {
key: "destroy",
value: function destroy() {
var state = _get(_getPrototypeOf(BaseComponent.prototype), "destroy", this).call(this) || {};
this.destroyInputs();
state.calculatedValue = this.calculatedValue;
return state;
}
/**
* Render a template string into html.
*
* @param template
* @param data
* @param actions
*
* @return {HTMLElement} - The created element.
*/
}, {
key: "renderTemplate",
value: function renderTemplate(template, data) {
var actions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
// Create a container div.
var div = this.ce('div'); // Interpolate the template and populate
div.innerHTML = this.interpolate(template, data); // Add actions to matching elements.
actions.forEach(function (action) {
var elements = div.getElementsByClassName(action.class);
Array.prototype.forEach.call(elements, function (element) {
element.addEventListener(action.event, action.action);
});
});
return div;
}
/**
* Determines if this component has a condition defined.
*
* @return {null}
*/
}, {
key: "hasCondition",
value: function hasCondition() {
if (this._hasCondition !== null) {
return this._hasCondition;
}
this._hasCondition = FormioUtils.hasCondition(this.component);
return this._hasCondition;
}
/**
* Check if this component is conditionally visible.
*
* @param data
* @return {boolean}
*/
}, {
key: "conditionallyVisible",
value: function conditionallyVisible(data) {
if (this.options.builder || !this.hasCondition()) {
return true;
}
if (!data) {
data = this.root ? this.root.data : {};
}
return FormioUtils.checkCondition(this.component, this.data, data, this.root ? this.root._form : {}, this);
}
/**
* Check for conditionals and hide/show the element based on those conditions.
*/
}, {
key: "checkConditions",
value: function checkConditions(data) {
data = data || (this.root ? this.root.data : {}); // Check advanced conditions
var result = this.show(this.conditionallyVisible(data));
if (this.fieldLogic(data)) {
this.redraw();
}
return result;
}
}, {
key: "fieldLogic",
/**
* Check all triggers and apply necessary actions.
*
* @param data
*/
value: function fieldLogic(data) {
var _this11 = this;
var logics = this.logic; // If there aren't logic, don't go further.
if (logics.length === 0) {
return;
}
var newComponent = _lodash.default.cloneDeep(this.originalComponent);
var changed = logics.reduce(function (changed, logic) {
var result = FormioUtils.checkTrigger(newComponent, logic.trigger, _this11.data, data, _this11.root ? _this11.root._form : {}, _this11);
if (result) {
changed |= _this11.applyActions(logic.actions, result, data, newComponent);
}
return changed;
}, false); // If component definition changed, replace and mark as changed.
if (!_lodash.default.isEqual(this.component, newComponent)) {
this.component = newComponent;
changed = true;
}
return changed;
}
}, {
key: "applyActions",
value: function applyActions(actions, result, data, newComponent) {
var _this12 = this;
return actions.reduce(function (changed, action) {
switch (action.type) {
case 'property':
FormioUtils.setActionProperty(newComponent, action, _this12.data, data, newComponent, result, _this12);
break;
case 'value':
{
var oldValue = _this12.getValue();
var newValue = _this12.evaluate(action.value, {
value: _lodash.default.clone(oldValue),
data: data,
component: newComponent,
result: result
}, 'value');
if (!_lodash.default.isEqual(oldValue, newValue)) {
_this12.setValue(newValue);
changed = true;
}
break;
}
case 'validation':
// TODO
break;
}
return changed;
}, false);
}
/**
* Add a new input error to this element.
*
* @param message
* @param dirty
*/
}, {
key: "addInputError",
value: function addInputError(message, dirty) {
var _this13 = this;
if (!message) {
return;
}
if (this.errorElement) {
var errorMessage = this.ce('p', {
class: 'help-block'
});
errorMessage.appendChild(this.text(message));
this.errorElement.appendChild(errorMessage);
} // Add error classes
this.addClass(this.element, 'has-error');
this.inputs.forEach(function (input) {
return _this13.addClass(_this13.performInputMapping(input), 'is-invalid');
});
if (dirty && this.options.highlightErrors) {
this.addClass(this.element, 'alert alert-danger');
}
}
/**
* Checks to see if a separate component is in the "context" of this component. This is determined by first checking
* if they share the same "data" object. It will then walk up the parent tree and compare its parents data objects
* with the components data and returns true if they are in the same context.
*
* Different rows of the same EditGrid, for example, are in different contexts.
*
* @param component
*/
}, {
key: "inContext",
value: function inContext(component) {
if (component.data === this.data) {
return true;
}
var parent = this.parent;
while (parent) {
if (parent.data === component.data) {
return true;
}
parent = parent.parent;
}
return false;
}
/**
* Hide or Show an element.
*
* @param show
*/
}, {
key: "show",
value: function show(_show, noClear) {
if (!this.options.builder && this.options.hide && this.options.hide[this.component.key]) {
_show = false;
} else if (this.options.builder || this.options.show && this.options.show[this.component.key]) {
_show = true;
} // Execute only if visibility changes or if we are in builder mode or if hidden fields should be shown.
if (!_show === !this._visible || this.options.builder || this.options.showHiddenFields) {
if (!_show) {
this.clearOnHide(false);
}
return _show;
}
this._visible = _show;
this.showElement(_show && !this.component.hidden);
if (!noClear) {
this.clearOnHide(_show);
}
return _show;
}
/**
* Show or hide the root element of this component.
*
* @param element
* @param show
*/
}, {
key: "showElement",
value: function showElement(element, show) {
if (typeof element === 'boolean') {
show = element;
element = this.getElement();
}
if (element) {
if (show) {
element.removeAttribute('hidden');
element.style.visibility = 'visible';
element.style.position = 'relative';
} else {
element.setAttribute('hidden', true);
element.style.visibility = 'hidden';
element.style.position = 'absolute';
}
}
return show;
}
}, {
key: "clearOnHide",
value: function clearOnHide(show) {
// clearOnHide defaults to true for old forms (without the value set) so only trigger if the value is false.
if (this.component.clearOnHide !== false && !this.options.readOnly) {
if (!show) {
this.deleteValue();
} else if (!this.hasValue()) {
// If shown, ensure the default is set.
this.setValue(this.defaultValue, {
noUpdateEvent: true
});
}
}
}
}, {
key: "onChange",
value: function onChange(flags, fromRoot) {
flags = flags || {};
if (!flags.noValidate) {
this.pristine = false;
} // If we are supposed to validate on blur, then