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
JavaScript
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;