UNPKG

formiojs

Version:

Common js library for client side interaction with <form.io>

1,758 lines (1,454 loc) • 80.2 kB
"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