├── .gitignore ├── LICENSE ├── README.md ├── examples ├── CustomHighOrderComponentExample.js └── MaterialUiTextFieldExample.js ├── index.js ├── package.json ├── src.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | main.js 3 | .DS_Store 4 | .idea 5 | package-lock.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Vishal Daga (other_account) => https://github.com/VishalDaga 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | React Validation Framework 4 | ========================== 5 | 6 | Validating fields on UI (client side validation) a lot simpler. 7 | 8 | *** 9 | 10 | Validating fields in React is complex because of one-way binding and stores often has to be burdened. 11 | With this, you just need to wrap your field component that needs to be validated. This also supports material-ui components. 12 | 13 | 14 | 15 | 16 | ***Install*** 17 | 18 | npm i react-validation-framework --save 19 | 20 | 21 | ***Features*** 22 | - Supports components that have specific errorText prop like in case of material-ui components 23 | - Supports consolidated validation of a group of fields. This is acheived by specifying the group name as prop to the Validation component. 24 | Using ref validity of a paritcular filed at any point can be found. 25 | - If a component is not supported out of box, then support for your new/custom component can be added easily 26 | - Error Text style can be customized 27 | - Extremely lightweight ~12KB 28 | 29 | ***Basic Usage*** 30 | 31 | 32 | ```jsx 33 | } 35 | validator={} 36 | onChangeCallback={} 37 | valueProp={} 38 | defaultValueProp={} 39 | closures={} 40 | tagName="YourComponent" 41 | errorStyle={} > 42 | 43 | 44 | ``` 45 | 46 | ***Props*** 47 | 48 | - **validators (Required)**: An Array of Objects - {validator: func, errorMessage: string} defining the condition for validity. Excuted in order, 49 | 50 | - **componentTag**: Tag name of Component. When wrapping high order component you should specify the component tag name to avoid unexpected behaviour, 51 | 52 | - **onChangeCallback**: Provide the 'name' of change callback, 53 | 54 | - **valueProp**: Provide the name of 'value' prop for the component, 55 | 56 | - **defaultValueProp**: Provide the name of 'default value' prop for the component, 57 | 58 | - **group**: the name of the group in which this component belongs. Using fieldValidatorCore.checkGroup(), validity of all components in the group as single boolen true or false can be determined. False means at least one component is invalid, 59 | 60 | - **errorStyle**: Object specifying your custimized style to apply on the error message, 61 | 62 | - **tagName**: Usefull while uglifying - provide the tagName of the component to prevent failue while uglifying, 63 | 64 | - **closures**: an object set with variables, where object key is variable name, and value is the value of the variable. You must use this when component is dependent of value coming from its closure. 65 | 66 | 67 | 68 | ***Real Code Example*** 69 | ```jsx 70 | import {Validation, fieldValidatorCore} from "react-validation-framework"; 71 | import validator from "validator"; 72 | !validator.isEmpty(val), 78 | errorMessage: "Cannot be left empty" 79 | }, { 80 | validator: (val) => validator.isNumeric(val), 81 | errorMessage: "Should be a numeric number" 82 | }, { 83 | validator: (val) => { 84 | if (parseInt(val) > 100){ 85 | return false; 86 | } else { 87 | return true; 88 | } 89 | }, 90 | errorMessage: "Must be any number less than 100" 91 | } 92 | ]}> 93 | { 100 | console.log("you have typed: ", evt.target.value); 101 | console.log("this value is defined in differenet scope and hence added to closures prop", area); 102 | } 103 | }/> 104 | 105 | 106 | ``` 107 | ***Notes*** 108 | 109 | **1-** Validation accepts an array of validators functions, each with their respective error messages. 110 | The order is important and the field is validated as per the order. For validator function third-party library can be used like - [validator](http://github.com/chriso/validator.js/), 111 | as like above, which has whole bunch of well tested regex, like isEmpty, isEmail, 112 | etc or we can supply our own function for specific case to validate. 113 | 114 | **2-** “group” prop can be added to define the group in which the filed belongs. 115 | Later the group name can be used to find whether a group of filed is valid or not like this : 116 | ```jsx 117 | handleSubmit(){ 118 | let checkFieldTestResult = fieldValidatorCore.checkGroup("myGroup1"); 119 | if (checkFieldTestResult.isValid){ 120 | console.log("All fields with Gropu prop value as myGroup1 is valid" 121 | } else { 122 | console.log("Some of fields with group as "myGroup1" are invalid"); 123 | console.log("Field which are invalid are ", checkFieldTestResult.inValidComponents); 124 | console.log("Fields which are valid are ", checkFieldTestResult.validComponents 125 | } 126 | } 127 | ``` 128 | 129 | or simply add a ref to the Validation tag and call the isValid method to find if the field is valid. 130 | 131 | **3-** To add a new component (This is required just one time. It could be somewhere in your index.js) 132 | 133 | Signature - 134 | `fieldValidatorCore.addSupport(name, getValueFromChangeEvent, changeCallBackCaller, errorPropName, helperPropName)` 135 | 136 | **name** - string - Tag name of you component 137 | 138 | **getValueFromChangeEvent** - function - (arg)=>{} 139 | 140 | **changeCallBackCaller** - function - (callback, args)=>{} 141 | 142 | **errorPropName**- string - if your component has a prop for error, ex. some* *material-ui components have "error" 143 | 144 | **helperPropName**- string - if your component has a prop for helperText, ex. some* *material-ui components have "helperText" 145 | 146 | add the component before your page component mounts, like 147 | ```jsx 148 | componentWillMount(){ 149 | fieldValidatorCore.addSupport("TextField", (args)=>{ 150 | return args[0].target.value; 151 | }, (callback, args)=>{ 152 | callback(args[0]); 153 | }, "error", "helperText"); 154 | } 155 | ``` 156 | **4-** (with 2.3.0), If any other prop other than value prop of your field component has value derived from upper scope/closure, ex - 'area' in onChange, it is better to added those closures in a prop called closures, otherwise, if the value changes you field componenet will still be having old value 157 | 158 | ```jsx 159 | 160 | { 164 | fooFun(area, evt.target.value) 165 | } 166 | } 167 | onKeyUp={ 168 | (evt)=>{ 169 | if (evt.keyCode === enterKeyCode){ 170 | barFun(area, evt.target.value) 171 | } 172 | 173 | } 174 | } 175 | /> 176 | 177 | ``` 178 | **5-** (with 4.0.0), set tagName prop as the tagName of your component to avoid failures while uglifying. 179 | 180 | 181 | **Final Note** 182 | Example usages are put in the examples directory. 183 | Contributions are welcomed. 184 | 185 | 186 | -------------------------------------------------------------------------------- /examples/CustomHighOrderComponentExample.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import MyCustomHighOrderComponent from "../MyCustomHighOrderComponent"; 3 | import {Validation, fieldValidatorCore} from "react-validation-framework"; 4 | 5 | //support for MyCustomHighOrderComponent is needs just once, better place for it would be at the start of your app 6 | fieldValidatorCore.addSupport( 7 | "MyCustomHighOrderComponent", 8 | (args) => { 9 | return args[1].suggestion; 10 | }, 11 | (callback, args) => { 12 | return callback(...args); 13 | }, 14 | "error" 15 | ); 16 | 17 | 18 | class MyCustomHighOrderComponentExample extends Component { 19 | constructor(props, context){ 20 | super(props, context); 21 | } 22 | 23 | onSumbit(){ 24 | if (fieldValidatorCore.checkGroup("somegroup").isValid === true){ 25 | //proceed 26 | } 27 | //or to check a specific field 28 | if (this.refs.MyCustomHighOrderComponentREF.isValid === true){ 29 | //proceed 30 | } 31 | } 32 | 33 | render(){ 34 | return (
35 | { 43 | return !validator.isEmpty(val); 44 | }, 45 | errorPropValue: true, 46 | errorMessage: "Please enter a value" 47 | } 48 | ]}> 49 | { 53 | console.log(value); 54 | }} 55 | /> 56 | 57 |
) 58 | } 59 | } 60 | 61 | 62 | MyCustomHighOrderComponentExample.propTypes = {}; 63 | 64 | export default MyCustomHighOrderComponentExample; 65 | -------------------------------------------------------------------------------- /examples/MaterialUiTextFieldExample.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import TextField from "material-ui/TextField"; 3 | import {Validation, fieldValidatorCore} from "react-validation-framework"; 4 | 5 | //support for material-ui textfield is needs just once, better place for it would be at the start of your app 6 | fieldValidatorCore.addSupport( 7 | "TextField", 8 | (event) => event[0].target.value, 9 | (callback, args) => callback(args[0], undefined, args[0].target.value), 10 | "error" 11 | ); 12 | 13 | class UserDetailsFormComponent extends Component { 14 | constructor(props, context){ 15 | super(props, context); 16 | } 17 | 18 | onSumbit(){ 19 | if (fieldValidatorCore.checkGroup("userdetails").isValid === true){ 20 | //proceed 21 | } 22 | //or to check a specific field 23 | if (this.refs.TextFieldREF.isValid === true){ 24 | //proceed 25 | } 26 | } 27 | 28 | render(){ 29 | return (
30 | !val === false , 37 | errorPropValue: true, 38 | errorMessage: "Please enter a value" 39 | }, 40 | { 41 | validator: (val) => { 42 | return new RegExp(/^[a-zA-Z]+(([',. -][a-zA-Z ])?[a-zA-Z]*)*\\.?$/).test(val); 43 | }, 44 | errorPropValue: true, 45 | errorMessage: "" 46 | }]}> 47 | { 55 | dispatcher.publish(Actions.Action, value); 56 | }} 57 | SelectProps={{ 58 | MenuProps: { 59 | className: classes.menu, 60 | }, 61 | }} 62 | helperText="" 63 | margin="normal"> 64 | 65 | 66 |
) 67 | } 68 | } 69 | 70 | 71 | UserDetailsFormComponent.propTypes = {}; 72 | 73 | export default UserDetailsFormComponent; 74 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(require("react"), require("prop-types"), require("lodash")); 4 | else if(typeof define === 'function' && define.amd) 5 | define(["react", "prop-types", "lodash"], factory); 6 | else if(typeof exports === 'object') 7 | exports["index"] = factory(require("react"), require("prop-types"), require("lodash")); 8 | else 9 | root["index"] = factory(root["React"], root["PropTypes"], root["_"]); 10 | })(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_2__, __WEBPACK_EXTERNAL_MODULE_3__) { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | 39 | 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = ""; 48 | 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ (function(module, exports, __webpack_require__) { 56 | 57 | "use strict"; 58 | 59 | Object.defineProperty(exports, "__esModule", { 60 | value: true 61 | }); 62 | exports.fieldValidatorCore = exports.Validation = undefined; 63 | 64 | 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; }; }(); 65 | 66 | var _react = __webpack_require__(1); 67 | 68 | var _react2 = _interopRequireDefault(_react); 69 | 70 | var _propTypes = __webpack_require__(2); 71 | 72 | var _propTypes2 = _interopRequireDefault(_propTypes); 73 | 74 | var _lodash = __webpack_require__(3); 75 | 76 | var _lodash2 = _interopRequireDefault(_lodash); 77 | 78 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 79 | 80 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 81 | 82 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 83 | 84 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 85 | 86 | function generateUUID() { 87 | var basex = 16; 88 | var d = new Date().getTime(); 89 | if (window.performance && typeof window.performance.now === "function") { 90 | d += performance.now(); //use high-precision timer if available 91 | } 92 | var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { 93 | var r = (d + Math.random() * basex) % basex | 0; 94 | d = Math.floor(d / basex); 95 | var sr = 0x3; 96 | var srx = r & sr; 97 | var srx1 = 0x8; 98 | return (c === "x" ? r : srx | srx1).toString(basex); 99 | }); 100 | return uuid; 101 | } 102 | 103 | var groups = {}; 104 | var userAddedComponents = {}; 105 | 106 | var internalSupportedComponents = { 107 | TextField: { 108 | getValueFromChangeEvent: function getValueFromChangeEvent(args) { 109 | return args[0].target.value; 110 | }, 111 | changeCallBackCaller: function changeCallBackCaller(callback, args) { 112 | callback(args[0]); 113 | }, 114 | errorPropName: "errorText", 115 | helperPropName: "helperText" 116 | }, 117 | SelectField: { 118 | getValueFromChangeEvent: function getValueFromChangeEvent(args) { 119 | return args[1]; 120 | }, 121 | changeCallBackCaller: function changeCallBackCaller(callback, args) { 122 | callback(args[0], args[1], args[2]); 123 | }, 124 | errorPropName: "errorText" 125 | }, 126 | DateRangePicker: { 127 | getValueFromChangeEvent: function getValueFromChangeEvent(args) { 128 | return args[0]; 129 | }, 130 | changeCallBackCaller: function changeCallBackCaller(callback, args) { 131 | callback(args[0]); 132 | }, 133 | errorPropName: "errorText" 134 | }, 135 | DatePicker: { 136 | getValueFromChangeEvent: function getValueFromChangeEvent(args) { 137 | return args[1]; 138 | }, 139 | changeCallBackCaller: function changeCallBackCaller(callback, args) { 140 | callback(args, args[1]); 141 | } 142 | }, 143 | Select: { 144 | getValueFromChangeEvent: function getValueFromChangeEvent(args) { 145 | return args[0]; 146 | }, 147 | changeCallBackCaller: function changeCallBackCaller(callback, args) { 148 | callback(args[0]); 149 | } 150 | } 151 | }; 152 | 153 | function getAllSupportedComponent() { 154 | return Object.assign({}, internalSupportedComponents, userAddedComponents); 155 | } 156 | 157 | var fieldValidatorCore = { 158 | addSupport: function addSupport(name, getValueFromChangeEvent, changeCallBackCaller, errorPropName, helperPropName) { 159 | userAddedComponents[name] = { 160 | getValueFromChangeEvent: getValueFromChangeEvent, 161 | changeCallBackCaller: changeCallBackCaller, 162 | errorPropName: errorPropName, 163 | helperPropName: helperPropName 164 | }; 165 | }, 166 | removeSupport: function removeSupport(name) { 167 | if (userAddedComponents[name] !== undefined) { 168 | delete userAddedComponents.name; 169 | } else { 170 | console.info("Field-Validator", "removeComponent: didn't find the component"); 171 | } 172 | }, 173 | getAllCurrentlySupported: function getAllCurrentlySupported() { 174 | return getAllSupportedComponent(); 175 | }, 176 | checkGroup: function checkGroup(groupName) { 177 | var valid = true; 178 | var validityRes = { 179 | isValid: true, 180 | validCompponents: [], 181 | inValidComponents: [] 182 | }; 183 | var allCompsInGroup = groups[groupName]; 184 | if (allCompsInGroup === undefined) { 185 | valid = true; 186 | } else { 187 | allCompsInGroup.forEach(function (v1) { 188 | var v = v1.component; 189 | if (v.isValid === false) { 190 | valid = false; 191 | validityRes.inValidComponents.push(v.props.children); 192 | } else { 193 | validityRes.validCompponents.push(v.props.children); 194 | } 195 | }); 196 | } 197 | validityRes.isValid = valid; 198 | return validityRes; 199 | } 200 | }; 201 | 202 | var Validation = function (_Component) { 203 | _inherits(Validation, _Component); 204 | 205 | function Validation(props) { 206 | _classCallCheck(this, Validation); 207 | 208 | var _this = _possibleConstructorReturn(this, (Validation.__proto__ || Object.getPrototypeOf(Validation)).call(this, props)); 209 | 210 | _this.state = { 211 | childComponentToRender: null, 212 | unControlledChild: true, 213 | isValid: true, 214 | id: generateUUID() 215 | }; 216 | _this.typeOfCompnent = _this.props.componentTag ? _this.props.componentTag : _this.props.children.type.displayName ? _this.props.children.type.displayName : _this.props.children.type.name; 217 | _this.testValidity = _this.testValidity.bind(_this); 218 | return _this; 219 | } 220 | 221 | _createClass(Validation, [{ 222 | key: "componentWillReceiveProps", 223 | value: function componentWillReceiveProps(props) { 224 | var freshRendered = false; 225 | if (this.state.unsupported !== true) { 226 | if (this.state.unControlledChild === false) { 227 | var isDerivedValueComing = false; 228 | if (!_lodash2.default.isEqual(this.originalVal, props.children.props[this.props.valueProp])) { 229 | isDerivedValueComing = true; 230 | } 231 | if (this.childModified === true || isDerivedValueComing) { 232 | if (!_lodash2.default.isEqual(this.currentChildValue, props.children.props[this.props.valueProp])) { 233 | this.baseProps[this.props.valueProp] = props.children.props[this.props.valueProp]; 234 | this.currentChildValue = props.children.props[this.props.valueProp]; 235 | freshRendered = true; 236 | this.testValidity(this.currentChildValue); 237 | } 238 | } 239 | } 240 | } 241 | if (Object.keys(this.closureValues).length > 0 && freshRendered === false) { 242 | //match closures 243 | var requireRender = false; 244 | _lodash2.default.forOwn(this.closureValues, function (cVariableValue, cVariable) { 245 | if (!_lodash2.default.isEqual(cVariableValue, props.closures[cVariable])) { 246 | requireRender = true; 247 | } 248 | }); 249 | if (requireRender) { 250 | this.mountingSetup(getAllSupportedComponent()[this.typeOfCompnent].getValueFromChangeEvent, getAllSupportedComponent()[this.typeOfCompnent].changeCallBackCaller, false, props); 251 | //also test validity if closure changes -- added if any validation dependes on closure values 252 | this.testValidity(this.currentChildValue); 253 | } 254 | } 255 | } 256 | }, { 257 | key: "componentDidMount", 258 | value: function componentDidMount() { 259 | if (userAddedComponents[this.typeOfCompnent] !== undefined) { 260 | this.mountingSetup(userAddedComponents[this.typeOfCompnent].getValueFromChangeEvent, userAddedComponents[this.typeOfCompnent].changeCallBackCaller); 261 | } else { 262 | if (internalSupportedComponents[this.typeOfCompnent] !== undefined) { 263 | this.mountingSetup(internalSupportedComponents[this.typeOfCompnent].getValueFromChangeEvent, internalSupportedComponents[this.typeOfCompnent].changeCallBackCaller); 264 | } else { 265 | console.error("Field-Validator", this.typeOfCompnent + " is currently not supported by field-validator,\n Please use fieldValidatorCore.addSupport to add support for the component, For more information please refer to docs"); 266 | console.info("Field-Validator", "Ignoring " + this.typeOfCompnent + ", and it will work as if it was not wraped with Validation tag"); 267 | this.mountingSetup(null, null, true); 268 | } 269 | } 270 | if (this.props.group && this.state.unsupported !== true) { 271 | if (groups[this.props.group] === undefined) { 272 | groups[this.props.group] = []; 273 | } 274 | groups[this.props.group].push({ 275 | id: this.state.id, 276 | component: this 277 | }); 278 | } 279 | } 280 | }, { 281 | key: "mountingSetup", 282 | value: function mountingSetup(valueFromArgs, argsToPassToActualHandler, unsupportedFlag, nextProps) { 283 | var _this2 = this; 284 | 285 | var toUseProps = nextProps ? nextProps : this.props; 286 | if (unsupportedFlag === true) { 287 | this.setState({ 288 | childComponentToRender: toUseProps.children, 289 | unsupported: unsupportedFlag 290 | }); 291 | } else { 292 | this.closureValues = {}; 293 | if (Object.keys(toUseProps.closures).length > 0) { 294 | _lodash2.default.forOwn(toUseProps.closures, function (cVariableValue, cVariable) { 295 | _this2.closureValues[cVariable] = cVariableValue; 296 | }); 297 | } 298 | this.baseProps = _lodash2.default.cloneDeep(toUseProps.children.props); 299 | var isUncontrolled = true; 300 | if (this.baseProps.hasOwnProperty(toUseProps.valueProp)) { 301 | isUncontrolled = false; 302 | if (nextProps !== true) { 303 | this.originalVal = this.baseProps[toUseProps.valueProp]; 304 | } 305 | this.currentChildValue = this.baseProps[toUseProps.valueProp]; 306 | } else { 307 | //try with default prop 308 | if (this.baseProps.hasOwnProperty(toUseProps.defaultValueProp)) { 309 | if (nextProps !== true) { 310 | this.originalVal = this.baseProps[toUseProps.defaultValueProp]; 311 | } 312 | this.currentChildValue = this.baseProps[toUseProps.defaultValueProp]; 313 | } 314 | } 315 | 316 | var oldOnChange = this.baseProps[toUseProps.onChangeCallback]; 317 | this.baseProps[toUseProps.onChangeCallback] = function () { 318 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 319 | args[_key] = arguments[_key]; 320 | } 321 | 322 | var rArgs = valueFromArgs(args); 323 | _this2.childModified = true; 324 | if (!_this2.absorbing) { 325 | _this2.absorbing = true; 326 | try { 327 | _this2.baseProps[toUseProps.valueProp] = rArgs; 328 | _this2.currentChildValue = rArgs; 329 | _this2.testValidity(rArgs); 330 | if (oldOnChange) { 331 | argsToPassToActualHandler(oldOnChange, args); 332 | } 333 | } catch (er) { 334 | _this2.absorbing = false; 335 | } 336 | _this2.absorbing = false; 337 | } 338 | }; 339 | var theComponent = _react2.default.cloneElement(toUseProps.children, this.baseProps); 340 | this.setState({ 341 | childComponentToRender: theComponent, 342 | unControlledChild: isUncontrolled 343 | }); 344 | } 345 | } 346 | }, { 347 | key: "testValidity", 348 | value: function testValidity(val) { 349 | var res = { 350 | isValid: true, 351 | helperMessage: null, 352 | errorMessage: null, 353 | errorPropValue: null 354 | }; 355 | try { 356 | this.props.validators.every(function (v) { 357 | if (v.validator(val) === false) { 358 | res.isValid = false; 359 | res.helperMessage = v.errorMessage; 360 | res.errorMessage = v.errorMessage; 361 | res.errorPropValue = v.errorPropValue ? v.errorPropValue : v.errorMessage; 362 | return false; 363 | } else { 364 | return true; 365 | } 366 | }); 367 | } catch (err) { 368 | console.error(err); 369 | } 370 | if (res.isValid === false) { 371 | if (getAllSupportedComponent()[this.typeOfCompnent].errorPropName) { 372 | this.baseProps[getAllSupportedComponent()[this.typeOfCompnent].errorPropName] = res.errorPropValue; 373 | this.baseProps[getAllSupportedComponent()[this.typeOfCompnent].helperPropName] = res.helperMessage; 374 | } 375 | this.setState({ 376 | childComponentToRender: _react2.default.cloneElement(this.props.children, this.baseProps), 377 | isValid: false, 378 | errorText: res.errorMessage, 379 | helperText: res.errorMessage 380 | }); 381 | } else { 382 | if (getAllSupportedComponent()[this.typeOfCompnent].errorPropName) { 383 | this.baseProps[getAllSupportedComponent()[this.typeOfCompnent].errorPropName] = null; 384 | this.baseProps[getAllSupportedComponent()[this.typeOfCompnent].helperPropName] = null; 385 | } 386 | this.setState({ 387 | childComponentToRender: _react2.default.cloneElement(this.props.children, this.baseProps), 388 | isValid: true, 389 | errorText: null, 390 | helperText: null 391 | }); 392 | } 393 | return res; 394 | } 395 | }, { 396 | key: "componentWillUnmount", 397 | value: function componentWillUnmount() { 398 | var _this3 = this; 399 | 400 | if (this.props.group) { 401 | _lodash2.default.remove(groups[this.props.group], function (v) { 402 | return v.id === _this3.state.id; 403 | }); 404 | } 405 | } 406 | }, { 407 | key: "render", 408 | value: function render() { 409 | if (this.state.unsupported === true) { 410 | return this.props.children; 411 | } else { 412 | return _react2.default.createElement( 413 | "span", 414 | null, 415 | this.state.childComponentToRender ? this.state.childComponentToRender : "", 416 | !getAllSupportedComponent()[this.typeOfCompnent].errorPropName && this.state.isValid === false ? _react2.default.createElement( 417 | "div", 418 | { style: Object.assign({}, { color: "red", fontSize: "12px", position: "absolute" }, this.props.errorStyle) }, 419 | this.state.errorText 420 | ) : "" 421 | ); 422 | } 423 | } 424 | }, { 425 | key: "isValid", 426 | get: function get() { 427 | return this.testValidity(this.currentChildValue).isValid; 428 | } 429 | }, { 430 | key: "errorMessage", 431 | get: function get() { 432 | return this.testValidity(this.currentChildValue).errorMessage; 433 | } 434 | }, { 435 | key: "errorPropValue", 436 | get: function get() { 437 | return this.testValidity(this.currentChildValue).errorPropValue; 438 | } 439 | }, { 440 | key: "isModified", 441 | get: function get() { 442 | return this.childModified; 443 | } 444 | }]); 445 | 446 | return Validation; 447 | }(_react.Component); 448 | 449 | Validation.propTypes = { 450 | children: _propTypes2.default.oneOfType([_propTypes2.default.element]), 451 | validators: _propTypes2.default.array.isRequired, 452 | onChangeCallback: _propTypes2.default.string, 453 | group: _propTypes2.default.string, 454 | valueProp: _propTypes2.default.string, 455 | defaultValueProp: _propTypes2.default.string, 456 | errorStyle: _propTypes2.default.object, 457 | closures: _propTypes2.default.object, 458 | componentTag: _propTypes2.default.string 459 | }; 460 | 461 | Validation.defaultProps = { 462 | onChangeCallback: "onChange", 463 | valueProp: "value", 464 | defaultValueProp: "defaultValue", 465 | errorStyle: {}, 466 | closures: {} 467 | }; 468 | 469 | exports.Validation = Validation; 470 | exports.fieldValidatorCore = fieldValidatorCore; 471 | 472 | /***/ }), 473 | /* 1 */ 474 | /***/ (function(module, exports) { 475 | 476 | module.exports = __WEBPACK_EXTERNAL_MODULE_1__; 477 | 478 | /***/ }), 479 | /* 2 */ 480 | /***/ (function(module, exports) { 481 | 482 | module.exports = __WEBPACK_EXTERNAL_MODULE_2__; 483 | 484 | /***/ }), 485 | /* 3 */ 486 | /***/ (function(module, exports) { 487 | 488 | module.exports = __WEBPACK_EXTERNAL_MODULE_3__; 489 | 490 | /***/ }) 491 | /******/ ]) 492 | }); 493 | ; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-validation-framework", 3 | "version": "5.1.0", 4 | "description": "Framework to validate fields, supports all components including material-ui components", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "webpack" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/vishalvisd/react-validator.git" 13 | }, 14 | "keywords": [ 15 | "React", 16 | "Validator", 17 | "material-ui", 18 | "html" 19 | ], 20 | "author": "Vishal Daga", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/vishalvisd/react-validator/issues" 24 | }, 25 | "homepage": "https://github.com/vishalvisd/react-validator#readme", 26 | "dependencies": { 27 | "prop-types": "^15.6.1", 28 | "react": "16.1.1" 29 | }, 30 | "peerDependencies": { 31 | "lodash": "^4.17.4" 32 | }, 33 | "devDependencies": { 34 | "babel-core": "^6.21.0", 35 | "babel-loader": "^6.2.10", 36 | "babel-plugin-transform-object-rest-spread": "^6.20.2", 37 | "babel-preset-es2015": "6.18.0", 38 | "babel-preset-react": "6.16.0", 39 | "babel-preset-stage-0": "6.16.0", 40 | "webpack": "^1.14.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import _ from "lodash"; 4 | 5 | function generateUUID() { 6 | var basex = 16; 7 | var d = new Date().getTime(); 8 | if (window.performance && typeof window.performance.now === "function"){ 9 | d += performance.now(); //use high-precision timer if available 10 | } 11 | var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { 12 | var r = (d + Math.random()*basex)%basex | 0; 13 | d = Math.floor(d/basex); 14 | var sr = 0x3; 15 | var srx = r&sr; 16 | var srx1 = 0x8; 17 | return (c==="x" ? r : (srx|srx1)).toString(basex); 18 | }); 19 | return uuid; 20 | } 21 | 22 | let groups = {}; 23 | let userAddedComponents = {}; 24 | 25 | let internalSupportedComponents = { 26 | TextField: { 27 | getValueFromChangeEvent: (args)=>{ 28 | return args[0].target.value; 29 | }, 30 | changeCallBackCaller: (callback, args)=>{ 31 | callback(args[0]); 32 | }, 33 | errorPropName: "errorText", 34 | helperPropName: "helperText" 35 | }, 36 | SelectField: { 37 | getValueFromChangeEvent: (args)=>{ 38 | return args[1]; 39 | }, 40 | changeCallBackCaller: (callback, args)=>{ 41 | callback(args[0], args[1], args[2]); 42 | }, 43 | errorPropName: "errorText" 44 | }, 45 | DateRangePicker: { 46 | getValueFromChangeEvent: (args)=>{ 47 | return args[0]; 48 | }, 49 | changeCallBackCaller: (callback, args)=>{ 50 | callback(args[0]); 51 | }, 52 | errorPropName: "errorText" 53 | }, 54 | DatePicker: { 55 | getValueFromChangeEvent: (args)=>{ 56 | return args[1]; 57 | }, 58 | changeCallBackCaller: (callback, args)=>{ 59 | callback(args, args[1]); 60 | } 61 | }, 62 | Select: { 63 | getValueFromChangeEvent: (args)=>{ 64 | return args[0]; 65 | }, 66 | changeCallBackCaller: (callback, args)=>{ 67 | callback(args[0]); 68 | } 69 | } 70 | }; 71 | 72 | function getAllSupportedComponent(){ 73 | return Object.assign({}, internalSupportedComponents, userAddedComponents); 74 | } 75 | 76 | let fieldValidatorCore = { 77 | addSupport: (name, getValueFromChangeEvent, changeCallBackCaller, errorPropName, helperPropName) => { 78 | userAddedComponents[name] = { 79 | getValueFromChangeEvent: getValueFromChangeEvent, 80 | changeCallBackCaller: changeCallBackCaller, 81 | errorPropName: errorPropName, 82 | helperPropName: helperPropName 83 | }; 84 | }, 85 | removeSupport: (name) => { 86 | if (userAddedComponents[name] !== undefined){ 87 | delete userAddedComponents.name; 88 | } else { 89 | console.info("Field-Validator", "removeComponent: didn't find the component"); 90 | } 91 | }, 92 | getAllCurrentlySupported: ()=>{ 93 | return getAllSupportedComponent(); 94 | }, 95 | checkGroup: (groupName) => { 96 | let valid = true; 97 | let validityRes = { 98 | isValid: true, 99 | validCompponents: [], 100 | inValidComponents: [] 101 | }; 102 | let allCompsInGroup = groups[groupName]; 103 | if (allCompsInGroup === undefined) { 104 | valid = true; 105 | } else { 106 | allCompsInGroup.forEach((v1) => { 107 | let v = v1.component; 108 | if (v.isValid === false){ 109 | valid = false; 110 | validityRes.inValidComponents.push(v.props.children); 111 | } else { 112 | validityRes.validCompponents.push(v.props.children); 113 | } 114 | }); 115 | } 116 | validityRes.isValid = valid; 117 | return validityRes; 118 | } 119 | }; 120 | 121 | class Validation extends Component { 122 | 123 | constructor(props) { 124 | super(props); 125 | 126 | this.state = { 127 | childComponentToRender: null, 128 | unControlledChild: true, 129 | isValid: true, 130 | id: generateUUID() 131 | }; 132 | this.typeOfCompnent = this.props.componentTag ? this.props.componentTag : (this.props.children.type.displayName ? this.props.children.type.displayName : this.props.children.type.name); 133 | this.testValidity = this.testValidity.bind(this); 134 | } 135 | get isValid() { 136 | return this.testValidity(this.currentChildValue).isValid; 137 | } 138 | 139 | get errorMessage() { 140 | return this.testValidity(this.currentChildValue).errorMessage; 141 | } 142 | 143 | get errorPropValue() { 144 | return this.testValidity(this.currentChildValue).errorPropValue; 145 | } 146 | 147 | get isModified() { 148 | return this.childModified; 149 | } 150 | 151 | componentWillReceiveProps(props){ 152 | let freshRendered = false; 153 | if (this.state.unsupported !== true){ 154 | if (this.state.unControlledChild === false){ 155 | let isDerivedValueComing = false; 156 | if (!(_.isEqual(this.originalVal, props.children.props[this.props.valueProp]))){ 157 | isDerivedValueComing = true; 158 | } 159 | if (this.childModified === true || isDerivedValueComing){ 160 | if (!(_.isEqual(this.currentChildValue, props.children.props[this.props.valueProp]))){ 161 | this.baseProps[this.props.valueProp] = props.children.props[this.props.valueProp]; 162 | this.currentChildValue = props.children.props[this.props.valueProp]; 163 | freshRendered = true; 164 | this.testValidity(this.currentChildValue); 165 | } 166 | } 167 | } 168 | } 169 | if (Object.keys(this.closureValues).length > 0 && freshRendered === false){ 170 | //match closures 171 | let requireRender = false; 172 | _.forOwn(this.closureValues, (cVariableValue, cVariable)=>{ 173 | if (!(_.isEqual(cVariableValue, props.closures[cVariable]))){ 174 | requireRender = true; 175 | } 176 | }); 177 | if (requireRender){ 178 | this.mountingSetup(getAllSupportedComponent()[this.typeOfCompnent].getValueFromChangeEvent, getAllSupportedComponent()[this.typeOfCompnent].changeCallBackCaller, false, props); 179 | //also test validity if closure changes -- added if any validation dependes on closure values 180 | this.testValidity(this.currentChildValue); 181 | } 182 | } 183 | } 184 | 185 | componentDidMount(){ 186 | if (userAddedComponents[this.typeOfCompnent] !== undefined){ 187 | this.mountingSetup(userAddedComponents[this.typeOfCompnent].getValueFromChangeEvent, userAddedComponents[this.typeOfCompnent].changeCallBackCaller); 188 | } else { 189 | if (internalSupportedComponents[this.typeOfCompnent] !== undefined){ 190 | this.mountingSetup(internalSupportedComponents[this.typeOfCompnent].getValueFromChangeEvent, internalSupportedComponents[this.typeOfCompnent].changeCallBackCaller); 191 | } else { 192 | console.error("Field-Validator", 193 | `${this.typeOfCompnent} is currently not supported by field-validator, 194 | Please use fieldValidatorCore.addSupport to add support for the component, For more information please refer to docs`); 195 | console.info("Field-Validator", `Ignoring ${this.typeOfCompnent}, and it will work as if it was not wraped with Validation tag`); 196 | this.mountingSetup(null, null, true); 197 | } 198 | } 199 | if (this.props.group && this.state.unsupported !== true){ 200 | if (groups[this.props.group] === undefined){ 201 | groups[this.props.group] = []; 202 | } 203 | groups[this.props.group].push({ 204 | id: this.state.id, 205 | component: this 206 | }); 207 | } 208 | } 209 | 210 | mountingSetup(valueFromArgs, argsToPassToActualHandler, unsupportedFlag, nextProps){ 211 | let toUseProps = nextProps ? nextProps : this.props; 212 | if (unsupportedFlag === true){ 213 | this.setState({ 214 | childComponentToRender: toUseProps.children, 215 | unsupported: unsupportedFlag 216 | }); 217 | } else { 218 | this.closureValues = {}; 219 | if (Object.keys(toUseProps.closures).length > 0) { 220 | _.forOwn(toUseProps.closures, (cVariableValue, cVariable) => { 221 | this.closureValues[cVariable] = cVariableValue; 222 | }); 223 | } 224 | this.baseProps = _.cloneDeep(toUseProps.children.props); 225 | let isUncontrolled = true; 226 | if (this.baseProps.hasOwnProperty(toUseProps.valueProp)){ 227 | isUncontrolled = false; 228 | if (nextProps !== true){ 229 | this.originalVal = this.baseProps[toUseProps.valueProp]; 230 | } 231 | this.currentChildValue = this.baseProps[toUseProps.valueProp]; 232 | } else { 233 | //try with default prop 234 | if (this.baseProps.hasOwnProperty(toUseProps.defaultValueProp)){ 235 | if (nextProps !== true){ 236 | this.originalVal = this.baseProps[toUseProps.defaultValueProp]; 237 | } 238 | this.currentChildValue = this.baseProps[toUseProps.defaultValueProp]; 239 | } 240 | } 241 | 242 | let oldOnChange = this.baseProps[toUseProps.onChangeCallback]; 243 | this.baseProps[toUseProps.onChangeCallback] = (...args)=>{ 244 | let rArgs = valueFromArgs(args); 245 | this.childModified = true; 246 | if (!this.absorbing){ 247 | this.absorbing = true; 248 | try { 249 | this.baseProps[toUseProps.valueProp] = rArgs; 250 | this.currentChildValue = rArgs; 251 | this.testValidity(rArgs); 252 | if (oldOnChange) { 253 | argsToPassToActualHandler(oldOnChange, args); 254 | } 255 | } catch (er){ 256 | this.absorbing = false; 257 | } 258 | this.absorbing = false; 259 | } 260 | }; 261 | let theComponent = React.cloneElement(toUseProps.children, this.baseProps); 262 | this.setState({ 263 | childComponentToRender: theComponent, 264 | unControlledChild: isUncontrolled 265 | }); 266 | } 267 | } 268 | 269 | testValidity(val){ 270 | let res = { 271 | isValid: true, 272 | helperMessage: null, 273 | errorMessage: null, 274 | errorPropValue: null 275 | }; 276 | try { 277 | this.props.validators.every((v)=>{ 278 | if (v.validator(val) === false){ 279 | res.isValid = false; 280 | res.helperMessage = v.errorMessage; 281 | res.errorMessage = v.errorMessage; 282 | res.errorPropValue = v.errorPropValue ? v.errorPropValue : v.errorMessage; 283 | return false; 284 | } else { 285 | return true; 286 | } 287 | }); 288 | } catch (err) { 289 | console.error(err); 290 | } 291 | if (res.isValid === false){ 292 | if (getAllSupportedComponent()[this.typeOfCompnent].errorPropName){ 293 | this.baseProps[getAllSupportedComponent()[this.typeOfCompnent].errorPropName] = res.errorPropValue; 294 | this.baseProps[getAllSupportedComponent()[this.typeOfCompnent].helperPropName] = res.helperMessage; 295 | } 296 | this.setState({ 297 | childComponentToRender: React.cloneElement(this.props.children, this.baseProps), 298 | isValid: false, 299 | errorText: res.errorMessage, 300 | helperText: res.errorMessage 301 | }); 302 | } else { 303 | if (getAllSupportedComponent()[this.typeOfCompnent].errorPropName){ 304 | this.baseProps[getAllSupportedComponent()[this.typeOfCompnent].errorPropName] = null; 305 | this.baseProps[getAllSupportedComponent()[this.typeOfCompnent].helperPropName] = null; 306 | } 307 | this.setState({ 308 | childComponentToRender: React.cloneElement(this.props.children, this.baseProps), 309 | isValid: true, 310 | errorText: null, 311 | helperText: null 312 | }); 313 | } 314 | return res; 315 | } 316 | 317 | componentWillUnmount(){ 318 | if (this.props.group){ 319 | _.remove(groups[this.props.group], (v)=>{ 320 | return v.id === this.state.id; 321 | }); 322 | } 323 | } 324 | 325 | render() { 326 | if (this.state.unsupported === true){ 327 | return this.props.children; 328 | } else { 329 | return ( 330 | 331 | { 332 | this.state.childComponentToRender ? this.state.childComponentToRender : "" 333 | }{ 334 | (!(getAllSupportedComponent()[this.typeOfCompnent].errorPropName)) && this.state.isValid === false ? 335 |
336 | { 337 | this.state.errorText 338 | } 339 |
: "" 340 | } 341 |
342 | ); 343 | } 344 | } 345 | } 346 | 347 | Validation.propTypes = { 348 | children: PropTypes.oneOfType([ 349 | PropTypes.element 350 | ]), 351 | validators: PropTypes.array.isRequired, 352 | onChangeCallback: PropTypes.string, 353 | group: PropTypes.string, 354 | valueProp: PropTypes.string, 355 | defaultValueProp: PropTypes.string, 356 | errorStyle: PropTypes.object, 357 | closures: PropTypes.object, 358 | componentTag: PropTypes.string 359 | }; 360 | 361 | Validation.defaultProps = { 362 | onChangeCallback: "onChange", 363 | valueProp: "value", 364 | defaultValueProp: "defaultValue", 365 | errorStyle: {}, 366 | closures: {} 367 | }; 368 | 369 | export {Validation, fieldValidatorCore}; 370 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | module.exports = [{ 3 | entry: { 4 | index: "./src.js" 5 | }, 6 | output: { 7 | filename: "[name].js", 8 | libraryTarget: "umd", 9 | library: "[name]" 10 | }, 11 | module: { 12 | loaders: [ 13 | { 14 | test: /\.js$/, 15 | loader: "babel?presets[]=es2015,presets[]=react,presets[]=stage-0,plugins[]=transform-object-rest-spread" 16 | } 17 | ] 18 | }, 19 | externals: { 20 | lodash: { 21 | amd: "lodash", 22 | commonjs: "lodash", 23 | commonjs2: "lodash", 24 | root: "_" 25 | }, 26 | "prop-types": { 27 | amd: "prop-types", 28 | commonjs2: "prop-types", 29 | commonjs: "prop-types", 30 | root: "PropTypes" 31 | }, 32 | react: { 33 | amd: "react", 34 | commonjs2: "react", 35 | commonjs: "react", 36 | root: "React" 37 | } 38 | } 39 | }]; --------------------------------------------------------------------------------