UNPKG

hbp-quickfire

Version:

A library of useful user-interface components built with React on top of React Bootstrap and MobX

398 lines (288 loc) 35.7 kB
var _createClass = function () {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);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}();var _desc, _value, _class, _descriptor, _descriptor2;function _asyncToGenerator(fn) {return function () {var gen = fn.apply(this, arguments);return new Promise(function (resolve, reject) {function step(key, arg) {try {var info = gen[key](arg);var value = info.value;} catch (error) {reject(error);return;}if (info.done) {resolve(value);} else {return Promise.resolve(value).then(function (value) {step("next", value);}, function (err) {step("throw", err);});}}return step("next");});};}function _defineProperty(obj, key, value) {if (key in obj) {Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });} else {obj[key] = value;}return obj;}function _initDefineProp(target, property, descriptor, context) {if (!descriptor) return;Object.defineProperty(target, property, { enumerable: descriptor.enumerable, configurable: descriptor.configurable, writable: descriptor.writable, value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 });}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {var desc = {};Object['ke' + 'ys'](descriptor).forEach(function (key) {desc[key] = descriptor[key];});desc.enumerable = !!desc.enumerable;desc.configurable = !!desc.configurable;if ('value' in desc || desc.initializer) {desc.writable = true;}desc = decorators.slice().reverse().reduce(function (desc, decorator) {return decorator(target, property, desc) || desc;}, desc);if (context && desc.initializer !== void 0) {desc.value = desc.initializer ? desc.initializer.call(context) : void 0;desc.initializer = undefined;}if (desc.initializer === void 0) {Object['define' + 'Property'](target, property, desc);desc = null;}return desc;}function _initializerWarningHelper(descriptor, context) {throw new Error('Decorating class property failed. Please ensure that transform-class-properties is enabled.');} /* * Copyright (c) Human Brain Project * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import * as mobx from "mobx"; import { observable, action } from "mobx";import cloneDeep from "lodash/cloneDeep";import has from "lodash/has";import isString from "lodash/isString";import uniqueId from "lodash/uniqueId";import isFunction from "lodash/isFunction"; import Validator from "validatorjs"; import React from "react"; import axios from "axios"; import * as Fields from "./Fields"; import optionsStore from "./OptionsStore"; import { components } from "../Components/Field"; var typesMapping = { "CheckBox": Fields.CheckBoxField, "Toggle": Fields.ToggleField, "DropdownSelect": Fields.DropdownSelectField, "GroupSelect": Fields.GroupSelectField, "InputText": Fields.InputTextField, "InputTextMultiple": Fields.InputTextMultipleField, "Nested": Fields.NestedField, "Select": Fields.SelectField, "TextArea": Fields.TextAreaField, "TreeSelect": Fields.TreeSelectField, "Slider": Fields.Slider, "DataSheet": Fields.DataSheetField, "Default": Fields.DefaultField }; var pathNodeSeparator = "/"; /** * Mobx store to manage the Form React Component * @class FormStore * @memberof Stores * @namespace FormStore * @param {json} structure the underlying form definition */var FormStore = (_class = function () { function FormStore(providedStructure) {_classCallCheck(this, FormStore);_initDefineProp(this, "structure", _descriptor, this);_initDefineProp(this, "readMode", _descriptor2, this);this.fieldsInternalData = new WeakMap();this.generatedKeys = new WeakMap();this.axiosInstance = axios; var structure = cloneDeep(providedStructure); this.mapFields(structure.fields); this.structure = structure; }_createClass(FormStore, [{ key: "mapFields", value: function mapFields( fieldsData) {var _this = this;var basePath = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ""; Object.keys(fieldsData).forEach(function (key) { var FieldClass = typesMapping[fieldsData[key].type] || Fields.DefaultField; fieldsData[key] = new FieldClass(fieldsData[key], _this, basePath + pathNodeSeparator + key); }); } }, { key: "remapPaths", value: function remapPaths( fieldsData) {var basePath = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ""; Object.keys(fieldsData).forEach(function (key) { fieldsData[key].setPath(basePath + pathNodeSeparator + key); }); } }, { key: "setFieldInternalData", value: function setFieldInternalData( field, key, value) { if (this.fieldsInternalData.has(field)) { this.fieldsInternalData.get(field)[key] = value; } else { this.fieldsInternalData.set(field, _defineProperty({}, key, value)); } } }, { key: "getFieldInternalData", value: function getFieldInternalData( field, key) { if (this.fieldsInternalData.has(field)) { return this.fieldsInternalData.get(field)[key]; } return undefined; } }, { key: "getGeneratedKey", value: function getGeneratedKey( item, namespace) { if (this.generatedKeys.has(item)) { if (this.generatedKeys.get(item)[namespace] === undefined) { this.generatedKeys.get(item)[namespace] = uniqueId(namespace); } } else { this.generatedKeys.set(item, _defineProperty({}, namespace, uniqueId(namespace))); } return this.generatedKeys.get(item)[namespace]; } /** * Get the form field values * @memberof Stores.FormStore * @return {object} a structured object of the form field values */ }, { key: "getValues", value: function getValues( fields) {var applyMapping = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; if (fields === undefined) { fields = this.structure.fields; } var result = {}; Object.keys(fields).forEach(function (fieldKey) { // We ignore disabled fields if (fields[fieldKey].disabled) { return; } result[fieldKey] = fields[fieldKey].getValue(applyMapping); }); return result; } /** * Syntaxic shortcut accessor that calls getValues * @memberof Stores.FormStore */ }, { key: "injectValues", /** * Inject values into form fields, must be input the same format as `values`method output * @memberof Stores.FormStore * @param {object} values structured object of the form field values * @param {boolean} merge whether or not to reset the whole form or merge with the passed in values * @param {string} path base path for change */value: function injectValues( values) {var merge = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;var fields = arguments[2]; if (isString(fields)) { fields = this.getField(fields); } else if (fields === undefined) { fields = this.structure.fields; } if (!merge) { this.reset(fields); } Object.keys(values).forEach(function (fieldKey) { var field = fields[fieldKey]; if (!field) { return; } field.injectValue(values[fieldKey]); }); } /** * Syntaxic shortcut accessor that calls injectValues * @memberof Stores.FormStore * @param {object} values structured object of the form field values */ }, { key: "reset", /** * @memberof Stores.FormStore * @param {string} basePath - optional, base path to reset from * Resets the form to their default values from the base path or completely if no path is provided */value: function reset( fields) { if (isString(fields)) { fields = this.getField(fields); } else if (fields === undefined) { fields = this.structure.fields; } Object.keys(fields).forEach(function (fieldKey) { var field = fields[fieldKey]; if (!field || field.defaultValue === undefined) { return; } field.reset(); }); } }, { key: "getField", value: function getField() {var path = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""; var pathParts = path.match(new RegExp("[^" + pathNodeSeparator + "]+", "gi")) || []; var field = this.structure.fields; pathParts.forEach(function (part, index) { field = field[part]; if (index < pathParts.length - 1 && field.type === "Nested") { field = field.value; } }); return field; } }, { key: "update", /** * updates the underlying field definition * @memberof Stores.FormStore * @param {string} path the field path * @param {object} updated the updated field definition */value: function update( path, updated) { var field = this.getField(path); mobx.set(field, updated); } /** * returns the parent path for a field * @memberof Stores.FormStore * @param {(string|field)} field can be either be a field path or a field object */ }, { key: "parentPath", value: function parentPath( field) { // this accepts both a path or a field // a field has a path property var path = void 0; if (has(field, "path")) { path = field.path; } else { path = field; } return path.substr(0, path.lastIndexOf(FormStore.getPathNodeSeparator())); } /** * @memberof Stores.FormStore * @param {(string|field)} field can be either a field path or a field object * @param {string} name name of the sibling */ }, { key: "genSiblingPath", value: function genSiblingPath( field, name) { return this.parentPath(field) + FormStore.getPathNodeSeparator() + name; } }, { key: "isURL", value: function isURL( str) { var pattern = new RegExp("^(https?:\\/\\/)?" + // protocol "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|" + // domain name "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string "(\\#[-a-z\\d_]*)?$", "i"); // fragment locator return pattern.test(str); } }, { key: "resolveURL", value: function () {var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee( url) {var cacheResult = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;var storedResolve, storedPromise, _ref2, data;return regeneratorRuntime.wrap(function _callee$(_context) {while (1) {switch (_context.prev = _context.next) {case 0:if (! cacheResult) {_context.next = 12;break;}if (! optionsStore.optionsCache.has(url)) {_context.next = 5;break;}return _context.abrupt("return", optionsStore.optionsCache.get(url));case 5:if (! optionsStore.pendingPromises.has(url)) {_context.next = 9;break;}return _context.abrupt("return", optionsStore.pendingPromises.get(url).promise);case 9: storedResolve = void 0; storedPromise = new Promise(function (resolve) { storedResolve = resolve; }); optionsStore.pendingPromises.set(url, { promise: storedPromise, resolve: storedResolve });case 12:_context.prev = 12;_context.next = 15;return ( this.axiosInstance.get(url));case 15:_ref2 = _context.sent;data = _ref2.data; if (cacheResult) { optionsStore.optionsCache.set(url, data); optionsStore.pendingPromises.get(url).resolve(data); optionsStore.pendingPromises.delete(url); }return _context.abrupt("return", data);case 21:_context.prev = 21;_context.t0 = _context["catch"](12);case 23:case "end":return _context.stop();}}}, _callee, this, [[12, 21]]);}));function resolveURL(_x7) {return _ref.apply(this, arguments);}return resolveURL;}() /** * @memberof Stores.FormStore * @param {array} optionsUrls an array of URLs to fetch and put in cache */ }, { key: "validate", /** * validates all form fields at once * @memberof Stores.FormStore */value: function () {var _ref3 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {var fields, isFormValid, fieldKey, isFieldValid;return regeneratorRuntime.wrap(function _callee2$(_context2) {while (1) {switch (_context2.prev = _context2.next) {case 0: fields = this.structure.fields; isFormValid = true;_context2.t0 = regeneratorRuntime.keys( fields);case 3:if ((_context2.t1 = _context2.t0()).done) {_context2.next = 11;break;}fieldKey = _context2.t1.value;_context2.next = 7;return ( fields[fieldKey].validate(true));case 7:isFieldValid = _context2.sent; isFormValid = isFieldValid && isFormValid;_context2.next = 3;break;case 11:return _context2.abrupt("return", isFormValid);case 12:case "end":return _context2.stop();}}}, _callee2, this);}));function validate() {return _ref3.apply(this, arguments);}return validate;}() }, { key: "registerCustomValidationFunction", value: function registerCustomValidationFunction( name, func, errorMessage) { this.constructor.registerCustomValidationFunction(name, func, errorMessage, this); } /** * registers a custom validation functions that can be used in all fields * @param {string} name - a name to uniquely identify the rule * @param {function} func - The definition of the validation function. The function parameters are the field value and attribute name. Can be a sync or async function. Expected return value either boolean or promise, indication if validation was successful. * @param {string} errorMessage - The error message in case the validation fails * @memberof Stores.FormStore */ }, { key: "registerAxiosInstance", /** * registers a custom axios instance - useful for APIs requiring tokens * @param {object} axiosInstance - a valid axios instance * @memberof Stores.FormStore */value: function registerAxiosInstance( axiosInstance) { this.axiosInstance = axiosInstance; } }, { key: "toggleReadMode", /** * toggles or force readMode to display form values as pure text instead of input fields * @param {boolean} status - optional, a boolean indicating what the readMode state should be. If none is passed then the state is toggled * @memberof Stores.FormStore */value: function toggleReadMode( status) { if (status !== undefined) { this.readMode = !!status; } else { this.readMode = !this.readMode; } } }, { key: "values", get: function get() {return this.getValues();}, set: function set(values) {this.injectValues(values);} }], [{ key: "prefetchOptions", value: function () {var _ref4 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee3(optionsUrls, axiosInstance) {var responses;return regeneratorRuntime.wrap(function _callee3$(_context3) {while (1) {switch (_context3.prev = _context3.next) {case 0:_context3.next = 2;return Promise.all(optionsUrls.map(function (optionsUrl) {return (axiosInstance || axios).get(optionsUrl);}));case 2:responses = _context3.sent;return _context3.abrupt("return", responses.map(function (response, index) {optionsStore.setOptions(optionsUrls[index], response.data);return response.data;}));case 4:case "end":return _context3.stop();}}}, _callee3, this);}));function prefetchOptions(_x8, _x9) {return _ref4.apply(this, arguments);}return prefetchOptions;}() }, { key: "registerCustomValidationFunction", value: function registerCustomValidationFunction(name, func, errorMessage, formStore) {var _this2 = this;if (!func || !isFunction(func)) {throw "the second parameter \"func\" must be a function in " + name;}if (!errorMessage) {throw "you didn't provide a error message (third parameter) in " + name;} //func is wrapped to so we can accept both sync and async functions without having to use a different syntax to register them as normally done in the validatorjs plugin //https://github.com/skaterdav85/validatorjs#asynchronous-validation var callback = function () {var _ref5 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee4(value, attribute, req, passes) {var result;return regeneratorRuntime.wrap(function _callee4$(_context4) {while (1) {switch (_context4.prev = _context4.next) {case 0:_context4.prev = 0;_context4.next = 3;return func(value, attribute, formStore);case 3:result = _context4.sent; // check result in case func returns a boolean result ? passes() : passes(false, errorMessage);_context4.next = 10;break;case 7:_context4.prev = 7;_context4.t0 = _context4["catch"](0); // this is to support functions that return promises as well: if promise was rejected the await expression throws an error passes(false, errorMessage);case 10:case "end":return _context4.stop();}}}, _callee4, _this2, [[0, 7]]);}));return function callback(_x10, _x11, _x12, _x13) {return _ref5.apply(this, arguments);};}();Validator.registerAsync(name, callback);} }, { key: "setPathNodeSeparator", value: function setPathNodeSeparator(separator) {if (separator && isString(separator)) {pathNodeSeparator = separator; } else { throw "argument must be a non-empty string"; } } }, { key: "getPathNodeSeparator", value: function getPathNodeSeparator() { return pathNodeSeparator; } }, { key: "registerCustomField", value: function registerCustomField( fieldName, component, fieldStore) { if (components[fieldName] !== undefined || typesMapping[fieldName] !== undefined) { throw "Quickfire:registerCustomField: A field with that name is already registered"; } if (!(component.prototype instanceof React.Component)) { throw "Quickfire:registerCustomField: component parameter must inherit React.Component"; } if (!(fieldStore.prototype instanceof typesMapping.Default)) { throw "Quickfire:registerCustomField: fieldStore parameter must inherit Formstore.typesMapping.Default"; } components[fieldName] = component; typesMapping[fieldName] = fieldStore; } }]);return FormStore;}(), (_descriptor = _applyDecoratedDescriptor(_class.prototype, "structure", [observable], { enumerable: true, initializer: null }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, "readMode", [observable], { enumerable: true, initializer: function initializer() {return false;} }), _applyDecoratedDescriptor(_class.prototype, "injectValues", [action], Object.getOwnPropertyDescriptor(_class.prototype, "injectValues"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "reset", [action], Object.getOwnPropertyDescriptor(_class.prototype, "reset"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "update", [action], Object.getOwnPropertyDescriptor(_class.prototype, "update"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "validate", [action], Object.getOwnPropertyDescriptor(_class.prototype, "validate"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "toggleReadMode", [action], Object.getOwnPropertyDescriptor(_class.prototype, "toggleReadMode"), _class.prototype)), _class);export { FormStore as default }; FormStore.typesMapping = typesMapping;