├── lib ├── .npmignore ├── src │ ├── consts.ts │ ├── index.ts │ ├── rules │ │ ├── length.ts │ │ ├── index.ts │ │ ├── email.ts │ │ ├── required.ts │ │ ├── minLength.ts │ │ ├── maxLength.ts │ │ ├── pattern.ts │ │ └── spec │ │ │ ├── email.spec.ts │ │ │ ├── maxLength.spec.ts │ │ │ ├── pattern.spec.ts │ │ │ ├── minLength.spec.ts │ │ │ └── required.spec.ts │ ├── fieldValidationEventFilter.ts │ ├── entities.ts │ ├── validationsResultBuilder.ts │ ├── baseFormValidation.ts │ └── validationsDispatcher.ts ├── .babelrc ├── test │ └── test_index.js ├── tsconfig.json ├── webpack.config.js ├── karma.conf.js ├── lc-form-validation.d.ts └── package.json ├── .editorconfig ├── samples ├── jquery │ └── 00 ShoppingForm │ │ ├── src │ │ ├── index.js │ │ ├── modules │ │ │ └── app │ │ │ │ ├── validation │ │ │ │ ├── formProductValidationService.js │ │ │ │ └── rules │ │ │ │ │ └── nifValidator.js │ │ │ │ └── app.js │ │ ├── services │ │ │ └── productsService.js │ │ └── index.html │ │ ├── .babelrc │ │ ├── package.json │ │ └── webpack.config.js ├── vuejs │ └── typescript │ │ └── 00 Custom Validator │ │ ├── .babelrc │ │ ├── src │ │ ├── pages │ │ │ └── recipe │ │ │ │ └── edit │ │ │ │ ├── components │ │ │ │ ├── index.ts │ │ │ │ ├── ingredientRow.tsx │ │ │ │ ├── ingredientList.tsx │ │ │ │ └── form.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── validations │ │ │ │ └── editFormValidation.ts │ │ │ │ ├── page.tsx │ │ │ │ └── pageContainer.tsx │ │ ├── model │ │ │ ├── index.ts │ │ │ ├── recipe.ts │ │ │ └── recipeError.ts │ │ ├── app.tsx │ │ ├── main.tsx │ │ ├── common │ │ │ ├── components │ │ │ │ └── form │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── validation.tsx │ │ │ │ │ ├── input.tsx │ │ │ │ │ └── inputButton.tsx │ │ │ └── validations │ │ │ │ └── arrayValidation.ts │ │ ├── api │ │ │ └── recipe │ │ │ │ ├── mockData.ts │ │ │ │ └── index.ts │ │ ├── index.html │ │ └── router.ts │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── webpack.config.js └── react │ ├── typescript │ ├── 00 SimpleForm │ │ ├── .babelrc │ │ ├── src │ │ │ ├── reducers │ │ │ │ ├── index.ts │ │ │ │ └── customer.ts │ │ │ ├── entity │ │ │ │ ├── customerErrors.ts │ │ │ │ └── customerEntity.ts │ │ │ ├── index.tsx │ │ │ ├── index.html │ │ │ ├── actions │ │ │ │ ├── actionsDef.ts │ │ │ │ ├── customerSaveCompleted.ts │ │ │ │ ├── customerUIInputStart.ts │ │ │ │ ├── customerUInputCompleted.ts │ │ │ │ └── customerSaveStart.ts │ │ │ ├── components │ │ │ │ ├── sampleForm │ │ │ │ │ ├── validations │ │ │ │ │ │ └── customerFormValidation.ts │ │ │ │ │ ├── sampleForm.container.ts │ │ │ │ │ └── sampleForm.tsx │ │ │ │ ├── app.tsx │ │ │ │ └── common │ │ │ │ │ └── input.tsx │ │ │ └── css │ │ │ │ └── site.css │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── webpack.config.js │ ├── 01 SignupForm │ │ ├── .babelrc │ │ ├── src │ │ │ ├── reducers │ │ │ │ ├── index.ts │ │ │ │ └── signup.ts │ │ │ ├── index.html │ │ │ ├── index.tsx │ │ │ ├── actions │ │ │ │ ├── actionsDef.ts │ │ │ │ ├── signupRequestCompleted.ts │ │ │ │ ├── signupRequestStart.ts │ │ │ │ ├── signupUIOnInteractionStart.ts │ │ │ │ └── signupUIOnInteractionCompleted.ts │ │ │ ├── entity │ │ │ │ ├── signupErrors.ts │ │ │ │ └── signupEntity.ts │ │ │ ├── css │ │ │ │ └── site.css │ │ │ ├── components │ │ │ │ ├── app.tsx │ │ │ │ ├── sampleSignupForm │ │ │ │ │ ├── sampleSignupForm.container.ts │ │ │ │ │ ├── validations │ │ │ │ │ │ └── signupFormValidation.ts │ │ │ │ │ └── sampleSignupForm.tsx │ │ │ │ └── common │ │ │ │ │ └── input.tsx │ │ │ └── api │ │ │ │ └── gitHub.ts │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── webpack.config.js │ └── 02 QuizForm │ │ ├── .babelrc │ │ ├── src │ │ ├── reducers │ │ │ ├── index.ts │ │ │ └── quiz.ts │ │ ├── index.html │ │ ├── index.tsx │ │ ├── actions │ │ │ ├── actionsDef.ts │ │ │ ├── resetQuizResolveCompleted.ts │ │ │ ├── quizUIInputCompleted.ts │ │ │ ├── quizResolveCompleted.ts │ │ │ └── quizResolveStart.ts │ │ ├── entity │ │ │ └── quizEntity.ts │ │ ├── css │ │ │ └── site.css │ │ └── components │ │ │ ├── app.tsx │ │ │ ├── common │ │ │ └── question.tsx │ │ │ └── quizForm │ │ │ ├── validations │ │ │ └── quizFormValidation.ts │ │ │ ├── quizForm.container.ts │ │ │ └── quizForm.tsx │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── webpack.config.js │ └── es6 │ ├── 00 SimpleForm │ ├── src │ │ ├── entity │ │ │ └── customerEntity.js │ │ ├── reducers │ │ │ ├── index.js │ │ │ └── customer.js │ │ ├── index.jsx │ │ ├── index.html │ │ ├── actions │ │ │ ├── customerSaveCompleted.js │ │ │ ├── actionsDef.js │ │ │ ├── customerUInputCompleted.js │ │ │ ├── customerUIInputStart.js │ │ │ └── customerSaveStart.js │ │ ├── components │ │ │ ├── sampleForm │ │ │ │ ├── validations │ │ │ │ │ └── customerFormValidation.js │ │ │ │ ├── sampleForm.container.js │ │ │ │ └── sampleForm.jsx │ │ │ ├── app.jsx │ │ │ └── common │ │ │ │ └── input.jsx │ │ └── css │ │ │ └── site.css │ ├── .babelrc │ ├── package.json │ └── webpack.config.js │ ├── 02 QuizForm │ ├── src │ │ ├── reducers │ │ │ ├── index.js │ │ │ └── quiz.js │ │ ├── index.html │ │ ├── index.jsx │ │ ├── actions │ │ │ ├── resetQuizResolveCompleted.js │ │ │ ├── actionsDef.js │ │ │ ├── quizResolveCompleted.js │ │ │ ├── quizUIInputCompleted.js │ │ │ └── quizResolveStart.js │ │ ├── entity │ │ │ └── quizEntity.js │ │ ├── css │ │ │ └── site.css │ │ └── components │ │ │ ├── app.jsx │ │ │ ├── common │ │ │ └── question.jsx │ │ │ └── quizForm │ │ │ ├── validations │ │ │ └── quizFormValidation.js │ │ │ ├── quizForm.container.js │ │ │ └── quizForm.jsx │ ├── .babelrc │ ├── package.json │ └── webpack.config.js │ └── 01 SignupForm │ ├── .babelrc │ ├── src │ ├── entity │ │ └── signupEntity.js │ ├── reducers │ │ ├── index.js │ │ └── signup.js │ ├── index.html │ ├── index.jsx │ ├── actions │ │ ├── actionsDef.js │ │ ├── signupRequestCompleted.js │ │ ├── signupUIOnInteractionCompleted.js │ │ ├── signupRequestStart.js │ │ └── signupUIOnInteractionStart.js │ ├── css │ │ └── site.css │ ├── components │ │ ├── app.jsx │ │ ├── sampleSignupForm │ │ │ ├── sampleSignupForm.container.js │ │ │ ├── validations │ │ │ │ └── signupFormValidation.js │ │ │ └── sampleSignupForm.jsx │ │ └── common │ │ │ └── input.jsx │ └── api │ │ └── gitHub.js │ ├── package.json │ └── webpack.config.js ├── ReadmeResources └── loginForm.png ├── .travis.yml ├── CHANGELOG.md ├── .gitignore └── LICENSE /lib/.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | src/ 3 | test/ 4 | tsd/ 5 | typings/ 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 -------------------------------------------------------------------------------- /samples/jquery/00 ShoppingForm/src/index.js: -------------------------------------------------------------------------------- 1 | import { App } from './modules/app/app'; 2 | const app = new App(); 3 | -------------------------------------------------------------------------------- /ReadmeResources/loginForm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/lcFormValidation/HEAD/ReadmeResources/loginForm.png -------------------------------------------------------------------------------- /lib/src/consts.ts: -------------------------------------------------------------------------------- 1 | export const consts = { 2 | globalFormValidationId: "_GLOBAL_FORM_", 3 | defaultFilter: { onChange: true } 4 | } 5 | -------------------------------------------------------------------------------- /lib/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env" 4 | ], 5 | "plugins": [ 6 | "transform-vue-jsx" 7 | ] 8 | } -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/pages/recipe/edit/components/index.ts: -------------------------------------------------------------------------------- 1 | import {FormComponent} from './form'; 2 | 3 | export { 4 | FormComponent, 5 | } -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/pages/recipe/edit/index.ts: -------------------------------------------------------------------------------- 1 | import {EditRecipeContainer} from './pageContainer'; 2 | 3 | export { 4 | EditRecipeContainer 5 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "5.4.0" 4 | script: cd lib && npm install && npm test 5 | after_script: 6 | - codeclimate-test-reporter < coverage/tests/lcov.info 7 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /samples/jquery/00 ShoppingForm/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false 7 | } 8 | ], 9 | "stage-3" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/src/entity/customerEntity.js: -------------------------------------------------------------------------------- 1 | export class CustomerEntity { 2 | constructor() { 3 | this.id = -1; 4 | this.fullname = ''; 5 | this.password = ''; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { quizReducer as quiz } from './quiz'; 3 | 4 | export default combineReducers({ 5 | quiz 6 | }); 7 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/model/index.ts: -------------------------------------------------------------------------------- 1 | import {RecipeEntity} from './recipe'; 2 | import {RecipeError} from './recipeError'; 3 | 4 | export { 5 | RecipeEntity, 6 | RecipeError, 7 | } -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false 7 | } 8 | ], 9 | "react", 10 | "stage-2" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false 7 | } 8 | ], 9 | "react", 10 | "stage-2" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/src/entity/signupEntity.js: -------------------------------------------------------------------------------- 1 | export class SignupEntity { 2 | constructor() { 3 | this.login = ''; 4 | this.password = ''; 5 | this.confirmPassword = ''; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { signupReducer as signup } from './signup'; 3 | 4 | export default combineReducers({ 5 | signup 6 | }); 7 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false 7 | } 8 | ], 9 | "react", 10 | "stage-2" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/src/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { quizReducer as quiz } from './quiz'; 3 | 4 | export default combineReducers({ 5 | quiz 6 | }); 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | This project follows [Semantic Versioning](http://semver.org) system. 4 | Every release is documented on the [Releases](https://github.com/Lemoncode/lcFormValidation/releases) section. 5 | -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { customerReducer as customer } from './customer'; 3 | 4 | export default combineReducers({ 5 | customer 6 | }); 7 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { signupReducer as signup } from './signup'; 3 | 4 | export default combineReducers({ 5 | signup 6 | }); 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist/ 4 | typings/ 5 | *.orig 6 | .idea/ 7 | lib/README.md 8 | lib/ReadmeResources/ 9 | lib/coverage/ 10 | yarn.lock 11 | .vscode/ 12 | npm-debug.log 13 | package-lock\.json 14 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/src/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { customerReducer as customer } from './customer'; 3 | 4 | export default combineReducers({ 5 | customer 6 | }); 7 | -------------------------------------------------------------------------------- /lib/test/test_index.js: -------------------------------------------------------------------------------- 1 | // require all modules ending in ".spec" from the 2 | // current directory and all subdirectories 3 | 4 | var testsContext = require.context("../src", true, /.spec$/); 5 | testsContext.keys().forEach(testsContext); 6 | -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './components/app'; 4 | 5 | ReactDOM.render( 6 | , 7 | document.getElementById('root'), 8 | ); 9 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | 11 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './components/app'; 4 | 5 | ReactDOM.render( 6 | , 7 | document.getElementById('root'), 8 | ); 9 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | 11 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './components/app'; 4 | 5 | ReactDOM.render( 6 | , 7 | document.getElementById('root'), 8 | ); 9 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | 11 | -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | 11 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/src/entity/customerErrors.ts: -------------------------------------------------------------------------------- 1 | import { FieldValidationResult } from 'lc-form-validation'; 2 | 3 | export class CustomerErrors { 4 | fullname: FieldValidationResult; 5 | password: FieldValidationResult; 6 | } 7 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import App from './components/app'; 4 | 5 | ReactDOM.render( 6 | , 7 | document.getElementById('root'), 8 | ); 9 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import App from './components/app'; 4 | 5 | ReactDOM.render( 6 | , 7 | document.getElementById('root'), 8 | ); 9 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import App from './components/app'; 4 | 5 | ReactDOM.render( 6 | , 7 | document.getElementById('root'), 8 | ); 9 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/src/actions/resetQuizResolveCompleted.js: -------------------------------------------------------------------------------- 1 | import { actionsDef } from './actionsDef'; 2 | 3 | export const resetQuizResolveCompleted = () => { 4 | return { 5 | type: actionsDef.quiz.RESET_QUIZ_RESOLVE_COMPLETED 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/src/actions/actionsDef.js: -------------------------------------------------------------------------------- 1 | export const actionsDef = { 2 | signup: { 3 | SIGNUP_PROCESS_UI_INTERACTION_COMPLETED: 'SIGNUP_PROCESS_UI_INTERACTION_COMPLETED', 4 | SIGNUP_REQUEST_COMPLETED: 'SIGNUP_REQUEST_COMPLETED' 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/actions/actionsDef.ts: -------------------------------------------------------------------------------- 1 | export const actionsDef = { 2 | signup: { 3 | SIGNUP_PROCESS_UI_INTERACTION_COMPLETED: 'SIGNUP_PROCESS_UI_INTERACTION_COMPLETED', 4 | SIGNUP_REQUEST_COMPLETED: 'SIGNUP_REQUEST_COMPLETED' 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/src/actions/actionsDef.js: -------------------------------------------------------------------------------- 1 | export const actionsDef = { 2 | quiz: { 3 | UI_INPUT_CHANGE: 'UI_INPUT_CHANGE', 4 | QUIZ_RESOLVE_COMPLETED: 'QUIZ_RESOLVE_COMPLETED', 5 | RESET_QUIZ_RESOLVE_COMPLETED: 'RESET_QUIZ_RESOLVE_COMPLETED' 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/model/recipe.ts: -------------------------------------------------------------------------------- 1 | export class RecipeEntity { 2 | id: number; 3 | name: string; 4 | ingredients: string[]; 5 | 6 | constructor() { 7 | this.id = 0; 8 | this.name = ''; 9 | this.ingredients = []; 10 | } 11 | } -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/entity/signupErrors.ts: -------------------------------------------------------------------------------- 1 | import { FieldValidationResult } from 'lc-form-validation'; 2 | 3 | export class SignupErrors { 4 | login: FieldValidationResult; 5 | password: FieldValidationResult; 6 | confirmPassword: FieldValidationResult; 7 | } 8 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/src/actions/actionsDef.ts: -------------------------------------------------------------------------------- 1 | export const actionsDef = { 2 | quiz: { 3 | UI_INPUT_CHANGE: 'UI_INPUT_CHANGE', 4 | QUIZ_RESOLVE_COMPLETED: 'QUIZ_RESOLVE_COMPLETED', 5 | RESET_QUIZ_RESOLVE_COMPLETED: 'RESET_QUIZ_RESOLVE_COMPLETED' 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/src/actions/quizResolveCompleted.js: -------------------------------------------------------------------------------- 1 | import { actionsDef } from './actionsDef'; 2 | 3 | export const quizResolveCompleted = (formValidationResult) => { 4 | return { 5 | type: actionsDef.quiz.QUIZ_RESOLVE_COMPLETED, 6 | formValidationResult 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/app.tsx: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | export const App = Vue.extend({ 4 | render: function(h) { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | }, 11 | }); -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/src/actions/customerSaveCompleted.js: -------------------------------------------------------------------------------- 1 | import { actionsDef } from './actionsDef'; 2 | 3 | export const customerSaveCompleted = (formValidationResult) => { 4 | return { 5 | type: actionsDef.customer.CUSTOMER_SAVE_COMPLETED, 6 | formValidationResult 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/src/actions/signupRequestCompleted.js: -------------------------------------------------------------------------------- 1 | import { actionsDef } from './actionsDef'; 2 | 3 | export const signupRequestCompleted = (formValidationResult) => { 4 | return { 5 | type: actionsDef.signup.SIGNUP_REQUEST_COMPLETED, 6 | formValidationResult 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/src/actions/quizUIInputCompleted.js: -------------------------------------------------------------------------------- 1 | import { actionsDef } from './actionsDef'; 2 | 3 | export const quizUIInputCompleted = (questionId, value) => { 4 | return { 5 | type: actionsDef.quiz.UI_INPUT_CHANGE, 6 | questionId, 7 | value 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/src/entity/customerEntity.ts: -------------------------------------------------------------------------------- 1 | export class CustomerEntity { 2 | id: number; 3 | fullname: string; 4 | password: string; 5 | 6 | public constructor() { 7 | this.id = -1; 8 | this.fullname = ''; 9 | this.password = ''; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/main.tsx: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import {router} from './router'; 4 | import {App} from './app'; 5 | 6 | Vue.use(Router); 7 | 8 | new Vue({ 9 | render: (h) => h(App), 10 | router, 11 | }).$mount('#root'); -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/entity/signupEntity.ts: -------------------------------------------------------------------------------- 1 | export class SignupEntity { 2 | login: string; 3 | password: string; 4 | confirmPassword: string; 5 | 6 | constructor() { 7 | this.login = ''; 8 | this.password = ''; 9 | this.confirmPassword = ''; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/common/components/form/index.ts: -------------------------------------------------------------------------------- 1 | import {InputComponent} from './input'; 2 | import {ValidationComponent} from './validation'; 3 | import {InputButtonComponent} from './inputButton'; 4 | 5 | export { 6 | InputComponent, 7 | ValidationComponent, 8 | InputButtonComponent, 9 | } -------------------------------------------------------------------------------- /lib/src/index.ts: -------------------------------------------------------------------------------- 1 | import { FormValidationResult, FieldValidationResult } from "./entities"; 2 | import { createFormValidation } from './baseFormValidation'; 3 | import { Validators } from './rules'; 4 | 5 | export { 6 | FormValidationResult, 7 | FieldValidationResult, 8 | createFormValidation, 9 | Validators, 10 | } 11 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/api/recipe/mockData.ts: -------------------------------------------------------------------------------- 1 | import {RecipeEntity} from '../../model'; 2 | 3 | export const mockRecipes: RecipeEntity[] = [ 4 | { 5 | id: 1, 6 | name: 'Omelette', 7 | ingredients: [ 8 | '2 eggs', 9 | 'cheese', 10 | 'salt', 11 | 'black pepper', 12 | ], 13 | }, 14 | ] -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/src/actions/actionsDef.js: -------------------------------------------------------------------------------- 1 | export const actionsDef = { 2 | customer: { 3 | CUSTOMER_PROCESS_UI_INPUT_START: 'CUSTOMER_UI_INPUT_START', 4 | CUSTOMER_PROCESS_UI_INPUT_COMPLETED: 'CUSTOMER_UI_INPUT_COMPLETED', 5 | CUSTOMER_SAVE_START: 'CUSTOMER_SAVE_START', 6 | CUSTOMER_SAVE_COMPLETED: 'CUSTOMER_SAVE_COMPLETED' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/src/actions/actionsDef.ts: -------------------------------------------------------------------------------- 1 | export const actionsDef = { 2 | customer: { 3 | CUSTOMER_PROCESS_UI_INPUT_START: 'CUSTOMER_UI_INPUT_START', 4 | CUSTOMER_PROCESS_UI_INPUT_COMPLETED: 'CUSTOMER_UI_INPUT_COMPLETED', 5 | CUSTOMER_SAVE_START: 'CUSTOMER_SAVE_START', 6 | CUSTOMER_SAVE_COMPLETED: 'CUSTOMER_SAVE_COMPLETED' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/src/actions/customerUInputCompleted.js: -------------------------------------------------------------------------------- 1 | import { actionsDef } from "./actionsDef"; 2 | 3 | export const customerUIInputCompleted = (fieldName, value, fieldValidationResult) => { 4 | return { 5 | type: actionsDef.customer.CUSTOMER_PROCESS_UI_INPUT_COMPLETED, 6 | fieldName, 7 | value, 8 | fieldValidationResult 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/src/entity/quizEntity.js: -------------------------------------------------------------------------------- 1 | export class QuizEntity { 2 | constructor() { 3 | this.questionA = new Question(false); 4 | this.questionB = new Question(false); 5 | this.questionC = new Question(false); 6 | } 7 | } 8 | 9 | export class Question { 10 | constructor(isSelected) { 11 | this.isSelected = isSelected; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vue.js by sample 7 | 8 | 9 |
10 |
11 | 12 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/src/actions/signupUIOnInteractionCompleted.js: -------------------------------------------------------------------------------- 1 | import { actionsDef } from './actionsDef'; 2 | 3 | export const signupUIOnInteractionCompleted = (fieldName, value, fieldValidationResult) => { 4 | return { 5 | type: actionsDef.signup.SIGNUP_PROCESS_UI_INTERACTION_COMPLETED, 6 | fieldName, 7 | value, 8 | fieldValidationResult 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/src/actions/resetQuizResolveCompleted.ts: -------------------------------------------------------------------------------- 1 | import { actionsDef } from './actionsDef'; 2 | 3 | export interface IQuizResetQuizResolveCompletedAction { 4 | type: string; 5 | } 6 | 7 | export const resetQuizResolveCompleted = (): IQuizResetQuizResolveCompletedAction => { 8 | return { 9 | type: actionsDef.quiz.RESET_QUIZ_RESOLVE_COMPLETED 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/router.ts: -------------------------------------------------------------------------------- 1 | import Router, {RouteConfig} from 'vue-router'; 2 | import {EditRecipeContainer} from './pages/recipe/edit'; 3 | 4 | const routes: RouteConfig[] = [ 5 | { path: '/', redirect: '/recipe/1' }, 6 | { path: '/recipe/:id', component: EditRecipeContainer, props: true }, 7 | ]; 8 | 9 | export const router = new Router({ 10 | routes 11 | }); -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/common/validations/arrayValidation.ts: -------------------------------------------------------------------------------- 1 | import {FieldValidationResult} from 'lc-form-validation'; 2 | 3 | export const hasItems = (message) => (value: any[]): FieldValidationResult => { 4 | const isValid = value.length > 0; 5 | return { 6 | type: 'ARRAY_HAS_ITEMS', 7 | succeeded: isValid, 8 | errorMessage: isValid ? '' : message, 9 | } 10 | } -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/api/recipe/index.ts: -------------------------------------------------------------------------------- 1 | import {RecipeEntity} from '../../model'; 2 | import {mockRecipes} from './mockData'; 3 | 4 | let recipes = mockRecipes; 5 | 6 | const fetchRecipeById = (id: number): Promise => { 7 | const recipe = recipes.find((r) => r.id === id); 8 | return Promise.resolve(recipe); 9 | } 10 | 11 | export const recipeAPI = { 12 | fetchRecipeById, 13 | } -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/model/recipeError.ts: -------------------------------------------------------------------------------- 1 | import {FieldValidationResult} from 'lc-form-validation'; 2 | 3 | export class RecipeError { 4 | name: FieldValidationResult; 5 | ingredients: FieldValidationResult; 6 | 7 | constructor() { 8 | this.name = new FieldValidationResult(); 9 | this.name.succeeded = true; 10 | 11 | this.ingredients = new FieldValidationResult(); 12 | this.ingredients.succeeded = true; 13 | } 14 | } -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/src/components/sampleForm/validations/customerFormValidation.js: -------------------------------------------------------------------------------- 1 | import { Validators, createFormValidation } from 'lc-form-validation'; 2 | 3 | const customerValidationConstraints = { 4 | fields: { 5 | fullname: [ 6 | { validator: Validators.required } 7 | ], 8 | password: [ 9 | { validator: Validators.required } 10 | ], 11 | } 12 | }; 13 | 14 | export const customerFormValidation = createFormValidation(customerValidationConstraints); 15 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/src/entity/quizEntity.ts: -------------------------------------------------------------------------------- 1 | export class QuizEntity { 2 | questionA: Question; 3 | questionB: Question; 4 | questionC: Question; 5 | 6 | public constructor() { 7 | this.questionA = new Question(false); 8 | this.questionB = new Question(false); 9 | this.questionC = new Question(false); 10 | } 11 | } 12 | 13 | export class Question { 14 | isSelected: boolean; 15 | 16 | constructor(isSelected: boolean) { 17 | this.isSelected = isSelected; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/src/actions/quizUIInputCompleted.ts: -------------------------------------------------------------------------------- 1 | import { actionsDef } from './actionsDef'; 2 | 3 | interface IQuizUIInputCompletedAction { 4 | type: string; 5 | questionId: string; 6 | value: any; 7 | } 8 | 9 | const quizUIInputCompleted = (questionId: string, value: any): IQuizUIInputCompletedAction => { 10 | return { 11 | type: actionsDef.quiz.UI_INPUT_CHANGE, 12 | questionId, 13 | value 14 | }; 15 | }; 16 | 17 | export { 18 | IQuizUIInputCompletedAction, 19 | quizUIInputCompleted 20 | } 21 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/src/actions/quizResolveCompleted.ts: -------------------------------------------------------------------------------- 1 | import { actionsDef } from './actionsDef'; 2 | import { FormValidationResult } from 'lc-form-validation'; 3 | 4 | export interface IQuizResolveCompletedAction { 5 | type: string; 6 | formValidationResult: FormValidationResult; 7 | } 8 | 9 | export const quizResolveCompleted = (formValidationResult: FormValidationResult): IQuizResolveCompletedAction => { 10 | return { 11 | type: actionsDef.quiz.QUIZ_RESOLVE_COMPLETED, 12 | formValidationResult 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/src/actions/quizResolveStart.js: -------------------------------------------------------------------------------- 1 | import { actionsDef } from './actionsDef'; 2 | import { quizFormValidation } from '../components/quizForm/validations/quizFormValidation'; 3 | import { quizResolveCompleted } from './quizResolveCompleted'; 4 | 5 | export function quizResolveStart(viewModel) { 6 | return (dispatcher) => { 7 | quizFormValidation.validateForm(viewModel).then( 8 | (formValidationResult) => { 9 | dispatcher(quizResolveCompleted(formValidationResult)); 10 | } 11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "sourceMap": true, 9 | "noLib": false, 10 | "suppressImplicitAnyIndexErrors": true, 11 | "allowSyntheticDefaultImports": true, 12 | "jsx": "preserve", 13 | "types": [ 14 | "webpack-env" 15 | ] 16 | }, 17 | "compileOnSave": false, 18 | "exclude": [ 19 | "node_modules" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/src/actions/customerUIInputStart.js: -------------------------------------------------------------------------------- 1 | import { customerUIInputCompleted } from './customerUInputCompleted'; 2 | import { customerFormValidation } from '../components/sampleForm/validations/customerFormValidation'; 3 | 4 | export function customerUIInputStart(viewModel, fieldName, value) { 5 | return (dispatcher) => { 6 | customerFormValidation.validateField(viewModel, fieldName, value).then( 7 | (fieldValidationResult) => { 8 | dispatcher(customerUIInputCompleted(fieldName, value, fieldValidationResult)); 9 | } 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "moduleResolution": "node", 5 | "declaration": false, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "sourceMap": true, 9 | "jsx": "react", 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "preserveConstEnums": true, 13 | "suppressImplicitAnyIndexErrors": true 14 | }, 15 | "compileOnSave": false, 16 | "exclude": [ 17 | "node_modules" 18 | ], 19 | "atom": { 20 | "rewriteTsconfig": false 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/pages/recipe/edit/validations/editFormValidation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ValidationConstraints, createFormValidation, Validators 3 | } from 'lc-form-validation'; 4 | import {hasItems} from '../../../../common/validations/arrayValidation'; 5 | 6 | const constraints: ValidationConstraints = { 7 | fields: { 8 | name: [ 9 | { validator: Validators.required } 10 | ], 11 | ingredients: [ 12 | { validator: hasItems('Should have at least one ingredient')} 13 | ] 14 | } 15 | }; 16 | 17 | export const editFormValidation = createFormValidation(constraints); -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/src/actions/customerSaveCompleted.ts: -------------------------------------------------------------------------------- 1 | import { actionsDef } from './actionsDef'; 2 | import { FormValidationResult } from 'lc-form-validation'; 3 | 4 | interface ICustomerSaveCompletedAction { 5 | type: string; 6 | formValidationResult: FormValidationResult; 7 | } 8 | 9 | const customerSaveCompleted = (formValidationResult: FormValidationResult): ICustomerSaveCompletedAction => { 10 | return { 11 | type: actionsDef.customer.CUSTOMER_SAVE_COMPLETED, 12 | formValidationResult 13 | } 14 | }; 15 | 16 | export { 17 | ICustomerSaveCompletedAction, 18 | customerSaveCompleted 19 | } 20 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "moduleResolution": "node", 5 | "declaration": false, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "sourceMap": true, 9 | "jsx": "react", 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "preserveConstEnums": true, 13 | "suppressImplicitAnyIndexErrors": true, 14 | "skipLibCheck": true 15 | }, 16 | "compileOnSave": false, 17 | "exclude": [ 18 | "node_modules" 19 | ], 20 | "atom": { 21 | "rewriteTsconfig": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/actions/signupRequestCompleted.ts: -------------------------------------------------------------------------------- 1 | import { actionsDef } from './actionsDef'; 2 | import { FormValidationResult } from 'lc-form-validation'; 3 | 4 | interface ISignupRequestCompletedAction { 5 | type: string; 6 | formValidationResult: FormValidationResult; 7 | } 8 | 9 | const signupRequestCompleted = (formValidationResult: FormValidationResult): ISignupRequestCompletedAction => { 10 | return { 11 | type: actionsDef.signup.SIGNUP_REQUEST_COMPLETED, 12 | formValidationResult 13 | } 14 | }; 15 | 16 | export { 17 | ISignupRequestCompletedAction, 18 | signupRequestCompleted 19 | } 20 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "moduleResolution": "node", 5 | "declaration": false, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "sourceMap": true, 9 | "jsx": "react", 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "preserveConstEnums": true, 13 | "suppressImplicitAnyIndexErrors": true, 14 | "skipLibCheck": true 15 | }, 16 | "compileOnSave": false, 17 | "exclude": [ 18 | "node_modules" 19 | ], 20 | "atom": { 21 | "rewriteTsconfig": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/src/actions/signupRequestStart.js: -------------------------------------------------------------------------------- 1 | import { signupRequestCompleted } from './signupRequestCompleted'; 2 | import { signupFormValidation } from '../components/sampleSignupForm/validations/signupFormValidation'; 3 | 4 | export function signupRequestStart(viewModel) { 5 | return (dispatcher) => { 6 | signupFormValidation.validateForm(viewModel).then( 7 | (formValidationResult) => { 8 | if (formValidationResult.succeeded) { 9 | console.log("Sign up completed"); 10 | } 11 | dispatcher(signupRequestCompleted(formValidationResult)); 12 | } 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/src/components/sampleForm/validations/customerFormValidation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ValidationConstraints, 3 | Validators, 4 | createFormValidation, 5 | RequiredParams, 6 | } from 'lc-form-validation'; 7 | 8 | const customerValidationConstraints: ValidationConstraints = { 9 | fields: { 10 | fullname: [ 11 | { validator: Validators.required } 12 | ], 13 | password: [ 14 | { validator: Validators.required } 15 | ], 16 | } 17 | }; 18 | const customerFormValidation = createFormValidation(customerValidationConstraints); 19 | 20 | export { 21 | customerFormValidation 22 | }; 23 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/src/actions/quizResolveStart.ts: -------------------------------------------------------------------------------- 1 | import { actionsDef } from './actionsDef'; 2 | import { FormValidationResult } from 'lc-form-validation'; 3 | import { quizFormValidation } from '../components/quizForm/validations/quizFormValidation'; 4 | import { quizResolveCompleted } from './quizResolveCompleted'; 5 | 6 | export function quizResolveStart(viewModel: any) { 7 | return (dispatcher) => { 8 | quizFormValidation.validateForm(viewModel).then( 9 | (formValidationResult: FormValidationResult) => { 10 | dispatcher(quizResolveCompleted(formValidationResult)); 11 | } 12 | ); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/src/actions/signupUIOnInteractionStart.js: -------------------------------------------------------------------------------- 1 | import { signupUIOnInteractionCompleted } from './signupUIOnInteractionCompleted'; 2 | import { signupFormValidation } from '../components/sampleSignupForm/validations/signupFormValidation'; 3 | 4 | export function signupUIOnInteractionStart(viewModel, fieldName, value, eventsFilter) { 5 | return (dispatcher) => { 6 | signupFormValidation.validateField(viewModel, fieldName, value, eventsFilter).then( 7 | (fieldValidationResult) => { 8 | dispatcher(signupUIOnInteractionCompleted(fieldName, value, fieldValidationResult)); 9 | } 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/src/css/site.css: -------------------------------------------------------------------------------- 1 | .top-buffer{ 2 | margin-top: 20px; 3 | } 4 | 5 | .about-page{ 6 | position: relative; 7 | top: -20px; 8 | } 9 | 10 | .about-page .jumbotron{ 11 | margin: 0; 12 | background:rgba(9,69,95,0.8); 13 | color: white; 14 | border-radius: 0 !important; 15 | } 16 | 17 | /*React apply activeClassName to element, but Bootstrap active class is over
  • element*/ 18 | .navbar .nav .active, .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus{ 19 | background: #e7e7e7 !important; 20 | color: #333 !important; 21 | } 22 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/src/css/site.css: -------------------------------------------------------------------------------- 1 | .top-buffer{ 2 | margin-top: 20px; 3 | } 4 | 5 | .about-page{ 6 | position: relative; 7 | top: -20px; 8 | } 9 | 10 | .about-page .jumbotron{ 11 | margin: 0; 12 | background:rgba(9,69,95,0.8); 13 | color: white; 14 | border-radius: 0 !important; 15 | } 16 | 17 | /*React apply activeClassName to element, but Bootstrap active class is over
  • element*/ 18 | .navbar .nav .active, .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus{ 19 | background: #e7e7e7 !important; 20 | color: #333 !important; 21 | } 22 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/src/css/site.css: -------------------------------------------------------------------------------- 1 | .top-buffer{ 2 | margin-top: 20px; 3 | } 4 | 5 | .about-page{ 6 | position: relative; 7 | top: -20px; 8 | } 9 | 10 | .about-page .jumbotron{ 11 | margin: 0; 12 | background:rgba(9,69,95,0.8); 13 | color: white; 14 | border-radius: 0 !important; 15 | } 16 | 17 | /*React apply activeClassName to element, but Bootstrap active class is over
  • element*/ 18 | .navbar .nav .active, .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus{ 19 | background: #e7e7e7 !important; 20 | color: #333 !important; 21 | } 22 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/src/css/site.css: -------------------------------------------------------------------------------- 1 | .top-buffer{ 2 | margin-top: 20px; 3 | } 4 | 5 | .about-page{ 6 | position: relative; 7 | top: -20px; 8 | } 9 | 10 | .about-page .jumbotron{ 11 | margin: 0; 12 | background:rgba(9,69,95,0.8); 13 | color: white; 14 | border-radius: 0 !important; 15 | } 16 | 17 | /*React apply activeClassName to element, but Bootstrap active class is over
  • element*/ 18 | .navbar .nav .active, .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus{ 19 | background: #e7e7e7 !important; 20 | color: #333 !important; 21 | } 22 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/src/css/site.css: -------------------------------------------------------------------------------- 1 | .top-buffer{ 2 | margin-top: 20px; 3 | } 4 | 5 | .about-page{ 6 | position: relative; 7 | top: -20px; 8 | } 9 | 10 | .about-page .jumbotron{ 11 | margin: 0; 12 | background:rgba(9,69,95,0.8); 13 | color: white; 14 | border-radius: 0 !important; 15 | } 16 | 17 | /*React apply activeClassName to element, but Bootstrap active class is over
  • element*/ 18 | .navbar .nav .active, .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus{ 19 | background: #e7e7e7 !important; 20 | color: #333 !important; 21 | } 22 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/css/site.css: -------------------------------------------------------------------------------- 1 | .top-buffer{ 2 | margin-top: 20px; 3 | } 4 | 5 | .about-page{ 6 | position: relative; 7 | top: -20px; 8 | } 9 | 10 | .about-page .jumbotron{ 11 | margin: 0; 12 | background:rgba(9,69,95,0.8); 13 | color: white; 14 | border-radius: 0 !important; 15 | } 16 | 17 | /*React apply activeClassName to element, but Bootstrap active class is over
  • element*/ 18 | .navbar .nav .active, .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus{ 19 | background: #e7e7e7 !important; 20 | color: #333 !important; 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/rules/length.ts: -------------------------------------------------------------------------------- 1 | export interface LengthParams { 2 | length: number; 3 | } 4 | 5 | export function parseLengthParams(customParams: LengthParams, errorMessage: string) { 6 | const length = customParams.length === null ? NaN : Number(customParams.length); 7 | if (isNaN(length)) { 8 | throw new Error(errorMessage); 9 | } 10 | 11 | return length; 12 | } 13 | 14 | export function isLengthValid( 15 | value, 16 | length: number, 17 | validatorFn: (value: string, length: number) => boolean 18 | ): boolean { 19 | // Don't try to validate non string values 20 | return typeof value === 'string' ? 21 | validatorFn(value, length) : 22 | true; 23 | } 24 | -------------------------------------------------------------------------------- /lib/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "moduleResolution": "node", 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "sourceMap": true, 8 | "jsx": "react", 9 | "experimentalDecorators": true, 10 | "emitDecoratorMetadata": false, 11 | "noLib": false, 12 | "preserveConstEnums": true, 13 | "suppressImplicitAnyIndexErrors": true, 14 | "types": [ 15 | "mocha", 16 | "chai-as-promised", 17 | "karma-chai-sinon" 18 | ] 19 | }, 20 | "compileOnSave": false, 21 | "exclude": [ 22 | "node_modules" 23 | ], 24 | "atom": { 25 | "rewriteTsconfig": false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/rules/index.ts: -------------------------------------------------------------------------------- 1 | import { required } from './required'; 2 | import { minLength } from './minLength'; 3 | import { maxLength } from './maxLength'; 4 | import { email } from './email'; 5 | import { pattern } from './pattern'; 6 | import { FieldValidationFunction } from '../entities'; 7 | 8 | 9 | interface ValidatorFunctions { 10 | required: FieldValidationFunction; 11 | minLength: FieldValidationFunction; 12 | maxLength: FieldValidationFunction; 13 | email: FieldValidationFunction; 14 | pattern: FieldValidationFunction; 15 | } 16 | 17 | export const Validators: ValidatorFunctions = { 18 | required, 19 | minLength, 20 | maxLength, 21 | email, 22 | pattern, 23 | }; 24 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/src/components/app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReduxThunk from 'redux-thunk'; 3 | import { createStore, applyMiddleware } from 'redux'; 4 | import { Provider } from 'react-redux'; 5 | import reducers from '../reducers'; 6 | import { ContainerQuizForm } from './quizForm/quizForm.container'; 7 | 8 | const store = createStore( 9 | reducers, 10 | applyMiddleware(ReduxThunk), 11 | ); 12 | 13 | export default class App extends React.Component { 14 | render() { 15 | return ( 16 | 17 |
    18 | 19 |
    20 |
    21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/src/components/app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReduxThunk from 'redux-thunk'; 3 | import { createStore, applyMiddleware } from 'redux'; 4 | import { Provider } from 'react-redux'; 5 | import reducers from '../reducers' 6 | import { ContainerSampleForm } from './sampleForm/sampleForm.container'; 7 | 8 | const store = createStore( 9 | reducers, 10 | applyMiddleware(ReduxThunk) 11 | ); 12 | 13 | export default class App extends React.Component { 14 | render() { 15 | return ( 16 | 17 |
    18 | 19 |
    20 |
    21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/src/components/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import ReduxThunk from 'redux-thunk'; 3 | import { createStore, applyMiddleware } from 'redux'; 4 | import { Provider } from 'react-redux'; 5 | import reducers from '../reducers'; 6 | import { ContainerQuizForm } from './quizForm/quizForm.container'; 7 | 8 | const store = createStore( 9 | reducers, 10 | applyMiddleware(ReduxThunk), 11 | ); 12 | 13 | export default class App extends React.Component<{}, {}> { 14 | render() { 15 | return ( 16 | 17 |
    18 | 19 |
    20 |
    21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/src/components/app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReduxThunk from 'redux-thunk'; 3 | import { createStore, applyMiddleware } from 'redux'; 4 | import { Provider } from 'react-redux'; 5 | import reducers from '../reducers'; 6 | import { SampleSignupContainer } from './sampleSignupForm/sampleSignupForm.container'; 7 | 8 | const store = createStore( 9 | reducers, 10 | applyMiddleware(ReduxThunk) 11 | ); 12 | 13 | export default class App extends React.Component { 14 | render() { 15 | return ( 16 | 17 |
    18 | 19 |
    20 |
    21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/src/components/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import ReduxThunk from 'redux-thunk'; 3 | import { createStore, applyMiddleware } from 'redux'; 4 | import { Provider } from 'react-redux'; 5 | import reducers from '../reducers' 6 | import { ContainerSampleForm } from './sampleForm/sampleForm.container'; 7 | 8 | const store = createStore( 9 | reducers, 10 | applyMiddleware(ReduxThunk) 11 | ); 12 | 13 | export default class App extends React.Component<{}, {}> { 14 | render() { 15 | return ( 16 | 17 |
    18 | 19 |
    20 |
    21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/actions/signupRequestStart.ts: -------------------------------------------------------------------------------- 1 | import { FormValidationResult } from 'lc-form-validation'; 2 | import { signupRequestCompleted } from './signupRequestCompleted'; 3 | import { signupFormValidation } from '../components/sampleSignupForm/validations/signupFormValidation'; 4 | 5 | export function signupRequestStart(viewModel: any) { 6 | return (dispatcher) => { 7 | signupFormValidation.validateForm(viewModel).then( 8 | (formValidationResult: FormValidationResult) => { 9 | if (formValidationResult.succeeded) { 10 | console.log("Sign up completed"); 11 | } 12 | dispatcher(signupRequestCompleted(formValidationResult)); 13 | } 14 | ); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/src/actions/customerUIInputStart.ts: -------------------------------------------------------------------------------- 1 | import { actionsDef } from './actionsDef'; 2 | import { FieldValidationResult } from 'lc-form-validation'; 3 | import { customerUIInputCompleted } from './customerUInputCompleted'; 4 | import { customerFormValidation } from '../components/sampleForm/validations/customerFormValidation'; 5 | 6 | export function customerUIInputStart(viewModel: any, fieldName: string, value: any) { 7 | return (dispatcher) => { 8 | customerFormValidation.validateField(viewModel, fieldName, value).then( 9 | (fieldValidationResult: FieldValidationResult) => { 10 | dispatcher(customerUIInputCompleted(fieldName, value, fieldValidationResult)); 11 | } 12 | ); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/components/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import ReduxThunk from 'redux-thunk'; 3 | import { createStore, applyMiddleware } from 'redux'; 4 | import { Provider } from 'react-redux'; 5 | import reducers from '../reducers'; 6 | import { SampleSignupContainer } from './sampleSignupForm/sampleSignupForm.container'; 7 | 8 | const store = createStore( 9 | reducers, 10 | applyMiddleware(ReduxThunk) 11 | ); 12 | 13 | export default class App extends React.Component<{}, {}> { 14 | render() { 15 | return ( 16 | 17 |
    18 | 19 |
    20 |
    21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/src/actions/customerSaveStart.js: -------------------------------------------------------------------------------- 1 | import { customerSaveCompleted } from './customerSaveCompleted'; 2 | import { customerFormValidation } from '../components/sampleForm/validations/customerFormValidation'; 3 | 4 | export function customerSaveStart(viewModel) { 5 | 6 | return (dispatcher) => { 7 | customerFormValidation.validateForm(viewModel).then( 8 | (formValidationResult) => { 9 | if (formValidationResult.succeeded) { 10 | // Call here the async call to save 11 | // additional logic or actions to be added on a real case 12 | console.log("Save Completed"); 13 | } 14 | dispatcher(customerSaveCompleted(formValidationResult)); 15 | } 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/src/actions/customerUInputCompleted.ts: -------------------------------------------------------------------------------- 1 | import { actionsDef } from "./actionsDef"; 2 | import { FieldValidationResult } from 'lc-form-validation'; 3 | 4 | interface ICustomerUIInputCompletedAction { 5 | type: string; 6 | fieldName: string; 7 | value: any; 8 | fieldValidationResult: FieldValidationResult; 9 | } 10 | 11 | const customerUIInputCompleted = (fieldName: string, value: any, fieldValidationResult: FieldValidationResult): ICustomerUIInputCompletedAction => { 12 | return { 13 | type: actionsDef.customer.CUSTOMER_PROCESS_UI_INPUT_COMPLETED, 14 | fieldName, 15 | value, 16 | fieldValidationResult 17 | }; 18 | } 19 | 20 | export { 21 | ICustomerUIInputCompletedAction, 22 | customerUIInputCompleted 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/rules/email.ts: -------------------------------------------------------------------------------- 1 | import { FieldValidationResult, FieldValidationFunction } from '../entities'; 2 | import { isValidPattern } from './pattern'; 3 | 4 | // RegExp from http://emailregex.com 5 | const EMAIL_PATTERN = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 6 | 7 | export const email: FieldValidationFunction = (value: string) => { 8 | const validationResult = new FieldValidationResult(); 9 | const isValid = isValidPattern(value, EMAIL_PATTERN); 10 | 11 | validationResult.succeeded = isValid; 12 | validationResult.type = 'EMAIL'; 13 | validationResult.errorMessage = isValid ? '' : 'Please enter a valid email address.'; 14 | return validationResult; 15 | }; 16 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/actions/signupUIOnInteractionStart.ts: -------------------------------------------------------------------------------- 1 | import { FieldValidationResult, ValidationEventsFilter } from 'lc-form-validation'; 2 | import { signupUIOnInteractionCompleted } from './signupUIOnInteractionCompleted'; 3 | import { signupFormValidation } from '../components/sampleSignupForm/validations/signupFormValidation'; 4 | 5 | export function signupUIOnInteractionStart(viewModel: any, fieldName: string, value: any, eventsFilter?: ValidationEventsFilter) { 6 | return (dispatcher) => { 7 | signupFormValidation.validateField(viewModel, fieldName, value, eventsFilter).then( 8 | (fieldValidationResult: FieldValidationResult) => { 9 | dispatcher(signupUIOnInteractionCompleted(fieldName, value, fieldValidationResult)); 10 | } 11 | ); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/actions/signupUIOnInteractionCompleted.ts: -------------------------------------------------------------------------------- 1 | import { actionsDef } from './actionsDef'; 2 | import { FieldValidationResult } from 'lc-form-validation'; 3 | 4 | interface ISignupUIOnInteractionCompletedAction { 5 | type: string; 6 | fieldName: string; 7 | value: any; 8 | fieldValidationResult: FieldValidationResult; 9 | } 10 | 11 | const signupUIOnInteractionCompleted = (fieldName: string, value: any, fieldValidationResult: FieldValidationResult): ISignupUIOnInteractionCompletedAction => { 12 | return { 13 | type: actionsDef.signup.SIGNUP_PROCESS_UI_INTERACTION_COMPLETED, 14 | fieldName, 15 | value, 16 | fieldValidationResult 17 | }; 18 | }; 19 | 20 | export { 21 | ISignupUIOnInteractionCompletedAction, 22 | signupUIOnInteractionCompleted 23 | } 24 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/pages/recipe/edit/page.tsx: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import {FormComponent} from './components'; 3 | 4 | export const EditRecipePage = Vue.extend({ 5 | props: [ 6 | 'recipe', 7 | 'recipeError', 8 | 'updateRecipe', 9 | 'addIngredient', 10 | 'removeIngredient', 11 | 'save', 12 | ], 13 | render: function(h) { 14 | return ( 15 |
    16 |

    Recipe: {this.recipe.name}

    17 | 25 |
    26 | ); 27 | } 28 | }); -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/src/components/common/question.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface Props { 4 | text: string, 5 | name: string, 6 | isSelected: boolean, 7 | onChange(event): void; 8 | } 9 | 10 | export class Question extends React.Component{ 11 | constructor(props: Props) { 12 | super(props); 13 | } 14 | 15 | render() { 16 | return ( 17 |
    18 | 27 |
    28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/common/components/form/validation.tsx: -------------------------------------------------------------------------------- 1 | import Vue, { ComponentOptions } from 'vue'; 2 | 3 | interface Props extends Vue { 4 | hasError: boolean; 5 | errorMessage: string; 6 | className: string; 7 | } 8 | 9 | export const ValidationComponent = Vue.extend({ 10 | props: [ 11 | 'hasError', 12 | 'errorMessage', 13 | 'className', 14 | ], 15 | render: function(h) { 16 | let wrapperClass = `${this.className}`; 17 | 18 | if (this.hasError) { 19 | wrapperClass = `${wrapperClass} has-error` 20 | } 21 | 22 | return ( 23 |
    24 | {this.$slots.default} 25 |
    26 | {this.errorMessage} 27 |
    28 |
    29 | ); 30 | }, 31 | } as ComponentOptions); 32 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/pages/recipe/edit/components/ingredientRow.tsx: -------------------------------------------------------------------------------- 1 | import Vue, {ComponentOptions} from 'vue'; 2 | 3 | interface Props extends Vue { 4 | ingredient: string; 5 | removeIngredient: (ingredient) => void; 6 | } 7 | 8 | export const IngredientRowComponent = Vue.extend({ 9 | props: [ 10 | 'ingredient', 11 | 'removeIngredient', 12 | ], 13 | render: function(h) { 14 | return ( 15 |
    16 | 19 | this.removeIngredient(this.ingredient)} 22 | > 23 | 24 | 25 |
    26 | ); 27 | }, 28 | } as ComponentOptions); -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/src/actions/customerSaveStart.ts: -------------------------------------------------------------------------------- 1 | import { actionsDef } from './actionsDef'; 2 | import { FormValidationResult } from 'lc-form-validation'; 3 | import { customerSaveCompleted } from './customerSaveCompleted'; 4 | import { customerFormValidation } from '../components/sampleForm/validations/customerFormValidation'; 5 | 6 | export function customerSaveStart(viewModel: any) { 7 | return (dispatcher) => { 8 | customerFormValidation.validateForm(viewModel).then( 9 | (formValidationResult: FormValidationResult) => { 10 | if (formValidationResult.succeeded) { 11 | // Call here the async call to save 12 | // additional logic or actins to be added on a real case 13 | console.log("Save Completed"); 14 | } 15 | dispatcher(customerSaveCompleted(formValidationResult)); 16 | } 17 | ); 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/src/components/common/question.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export class Question extends React.Component { 4 | 5 | static propTypes = { 6 | text: React.PropTypes.string.isRequired, 7 | name: React.PropTypes.string.isRequired, 8 | isSelected: React.PropTypes.bool.isRequired, 9 | onChange: React.PropTypes.func.isRequired, 10 | }; 11 | 12 | constructor(props) { 13 | super(props); 14 | } 15 | 16 | render() { 17 | return ( 18 |
    19 | 28 |
    29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/fieldValidationEventFilter.ts: -------------------------------------------------------------------------------- 1 | import { FieldValidation, ValidationEventsFilter } from './entities'; 2 | 3 | class FieldValidationEventFilter { 4 | 5 | public filter(fieldValidations: FieldValidation[], eventsFilter: ValidationEventsFilter): FieldValidation[] { 6 | let result: FieldValidation[] = []; 7 | 8 | if (eventsFilter) { 9 | result = fieldValidations.filter((fieldValidation) => 10 | this.hasAnyFilter(fieldValidation, eventsFilter) 11 | ); 12 | } else { 13 | result = fieldValidations; 14 | } 15 | 16 | return result; 17 | } 18 | 19 | private hasAnyFilter(fieldValidation: FieldValidation, eventsFilter: ValidationEventsFilter) { 20 | return Object.keys(eventsFilter).some(filter => 21 | eventsFilter[filter] === fieldValidation.eventsFilter[filter] 22 | ); 23 | } 24 | } 25 | 26 | export const fieldValidationEventFilter = new FieldValidationEventFilter(); 27 | -------------------------------------------------------------------------------- /samples/jquery/00 ShoppingForm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jqueryboilerplate", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "webpack-dev-server" 7 | }, 8 | "author": "Braulio Diez", 9 | "license": "ISC", 10 | "dependencies": { 11 | "bootstrap": "^3.3.7", 12 | "core-js": "^2.4.1", 13 | "jquery": "^3.2.0", 14 | "lc-form-validation": "file:../../../lib/" 15 | }, 16 | "devDependencies": { 17 | "babel-core": "^6.24.0", 18 | "babel-loader": "^6.4.1", 19 | "babel-preset-env": "^1.2.2", 20 | "babel-preset-stage-3": "^6.22.0", 21 | "css-loader": "^0.27.3", 22 | "extract-text-webpack-plugin": "^2.1.0", 23 | "file-loader": "^0.10.1", 24 | "html-webpack-plugin": "^2.28.0", 25 | "style-loader": "^0.14.1", 26 | "typescript": "^2.2.1", 27 | "url-loader": "^0.5.8", 28 | "webpack": "^2.2.1", 29 | "webpack-dev-server": "^2.4.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/src/components/sampleForm/sampleForm.container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { SampleForm } from './sampleForm'; 3 | import { customerUIInputStart } from '../../actions/customerUIInputStart'; 4 | import { customerSaveStart } from '../../actions/customerSaveStart'; 5 | 6 | const mapStateToProps = (state) => { 7 | return { 8 | customer: state.customer.customer, 9 | errors: state.customer.customerErrors 10 | } 11 | }; 12 | 13 | const mapDispatchToProps = (dispatch) => { 14 | return { 15 | fireValidationFieldValueChanged: (viewModel, fieldName, value) => { 16 | return dispatch(customerUIInputStart(viewModel, fieldName, value)); 17 | }, 18 | saveCustomer: (customer) => { 19 | return dispatch(customerSaveStart(customer)); 20 | } 21 | } 22 | }; 23 | 24 | export const ContainerSampleForm = connect( 25 | mapStateToProps, 26 | mapDispatchToProps 27 | )(SampleForm); 28 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/src/components/quizForm/validations/quizFormValidation.js: -------------------------------------------------------------------------------- 1 | import { FieldValidationResult, createFormValidation } from 'lc-form-validation'; 2 | import { QuizEntity, Question } from '../../../entity/quizEntity'; 3 | 4 | function isAnyQuestionSelected(quiz) { 5 | return Object.keys(quiz).some(question => quiz[question].isSelected); 6 | } 7 | 8 | function quizValidation(quiz) { 9 | const isQuizPassed = isAnyQuestionSelected(quiz); 10 | const errorInfo = (isQuizPassed) ? '' : 'Failed'; 11 | const fieldValidationResult = new FieldValidationResult(); 12 | 13 | fieldValidationResult.type = 'QUIZ_VALIDATION'; 14 | fieldValidationResult.succeeded = isQuizPassed; 15 | fieldValidationResult.errorMessage = errorInfo; 16 | return fieldValidationResult; 17 | } 18 | 19 | const quizValidationConstraints = { 20 | global: [ 21 | quizValidation 22 | ] 23 | }; 24 | 25 | export const quizFormValidation = createFormValidation(quizValidationConstraints); 26 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/pages/recipe/edit/components/ingredientList.tsx: -------------------------------------------------------------------------------- 1 | import Vue, {ComponentOptions} from 'vue'; 2 | import {IngredientRowComponent} from './ingredientRow'; 3 | 4 | interface Props extends Vue { 5 | ingredients: string[]; 6 | removeIngredient: (ingredient) => void; 7 | } 8 | 9 | export const IngredientListComponent = Vue.extend({ 10 | props: [ 11 | 'ingredients', 12 | 'removeIngredient', 13 | ], 14 | render: function(h) { 15 | return ( 16 |
    17 |
    18 | { 19 | this.ingredients.map((ingredient, index) => 20 | 25 | ) 26 | } 27 |
    28 |
    29 | ); 30 | }, 31 | } as ComponentOptions); -------------------------------------------------------------------------------- /samples/jquery/00 ShoppingForm/src/modules/app/validation/formProductValidationService.js: -------------------------------------------------------------------------------- 1 | import { 2 | FieldValidationResult, 3 | Validators, 4 | createFormValidation, 5 | } from 'lc-form-validation'; 6 | import { nifValidator } from './rules/nifValidator'; 7 | 8 | const DISCOUNT_REGEXP = /^[A-Z\d]{3}\-[A-Z\d]{4}[A-Z\d]{3}$/; 9 | 10 | const validationConstraints = { 11 | fields: { 12 | product: [ 13 | { 14 | validator: Validators.required, 15 | }, 16 | ], 17 | version: [ 18 | { 19 | validator: Validators.required, 20 | }, 21 | ], 22 | discountCode: [ 23 | { 24 | validator: Validators.pattern, 25 | customParams: { pattern: DISCOUNT_REGEXP }, 26 | } 27 | ], 28 | nif: [ 29 | { 30 | validator: Validators.required, 31 | }, 32 | { 33 | validator: nifValidator, 34 | } 35 | ] 36 | } 37 | }; 38 | 39 | export const productsFormValidation = createFormValidation(validationConstraints); 40 | -------------------------------------------------------------------------------- /lib/src/rules/required.ts: -------------------------------------------------------------------------------- 1 | import { FieldValidationResult, FieldValidationFunction } from '../entities'; 2 | export interface RequiredParams { 3 | trim: boolean; 4 | } 5 | 6 | const DEFAULT_PARAMS: RequiredParams = { trim: true }; 7 | 8 | export const required: FieldValidationFunction = (value, vm, customParams: RequiredParams = DEFAULT_PARAMS) => { 9 | const validationResult = new FieldValidationResult(); 10 | const isValid = isValidField(value, Boolean(customParams.trim)); 11 | validationResult.errorMessage = isValid ? '' : 'Please fill in this mandatory field.'; 12 | validationResult.succeeded = isValid; 13 | validationResult.type = 'REQUIRED'; 14 | return validationResult; 15 | } 16 | 17 | function isValidField(value, trim: boolean): boolean { 18 | return typeof value === 'string' ? 19 | isStringValid(value, trim) : 20 | value === true; 21 | } 22 | 23 | function isStringValid(value: string, trim: boolean): boolean { 24 | return trim ? 25 | value.trim().length > 0 : 26 | value.length > 0; 27 | } 28 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/src/api/gitHub.js: -------------------------------------------------------------------------------- 1 | export class GitHub { 2 | doesLoginExists(login) { 3 | const baseGitHubUsersUrl = 'https://api.github.com/users/'; 4 | const fetchGitHubUserUrl = baseGitHubUsersUrl + login; 5 | 6 | return fetch(fetchGitHubUserUrl) 7 | .then((response) => this.checkStatus(response)) 8 | .then((response) => this.parseJSON(response)) 9 | .then((data) => this.resolveLoginFound(data)) 10 | .catch((error) => this.resolveLoginNotFound(error)); 11 | } 12 | 13 | checkStatus(response) { 14 | if (response.status >= 200 && response.status < 300) { 15 | return Promise.resolve(response); 16 | } else { 17 | throw new Error(response.statusText); 18 | } 19 | } 20 | 21 | parseJSON(response) { 22 | return response.json(); 23 | } 24 | 25 | resolveLoginFound(data) { 26 | return Promise.resolve(true); 27 | } 28 | 29 | resolveLoginNotFound(error) { 30 | return Promise.resolve(false); 31 | } 32 | } 33 | 34 | export const gitHub = new GitHub(); 35 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/common/components/form/input.tsx: -------------------------------------------------------------------------------- 1 | import Vue, {ComponentOptions} from 'vue'; 2 | 3 | interface Props extends Vue { 4 | className: string; 5 | placeholder: string; 6 | type: string; 7 | value: any; 8 | inputHandler: (event) => void; 9 | label: string; 10 | name: string; 11 | } 12 | 13 | export const InputComponent = Vue.extend({ 14 | props: [ 15 | 'className', 16 | 'placeholder', 17 | 'type', 18 | 'value', 19 | 'inputHandler', 20 | 'label', 21 | 'name', 22 | ], 23 | render: function(h) { 24 | return ( 25 |
    26 | 29 | 37 |
    38 | ); 39 | }, 40 | } as ComponentOptions); -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/src/components/sampleForm/sampleForm.container.ts: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { SampleForm } from './sampleForm'; 3 | import { customerUIInputStart } from '../../actions/customerUIInputStart'; 4 | import { customerSaveStart } from '../../actions/customerSaveStart'; 5 | import { CustomerEntity } from '../../entity/customerEntity'; 6 | 7 | const mapStateToProps = (state) => { 8 | return { 9 | customer: state.customer.customer, 10 | errors: state.customer.customerErrors 11 | } 12 | }; 13 | 14 | const mapDispatchToProps = (dispatch) => { 15 | return { 16 | fireValidationFieldValueChanged: (viewModel: any, fieldName: string, value: any) => { 17 | return dispatch(customerUIInputStart(viewModel, fieldName, value)); 18 | }, 19 | saveCustomer: (customer: CustomerEntity) => { 20 | return dispatch(customerSaveStart(customer)); 21 | } 22 | } 23 | }; 24 | 25 | export const ContainerSampleForm = connect( 26 | mapStateToProps, 27 | mapDispatchToProps 28 | )(SampleForm); 29 | -------------------------------------------------------------------------------- /lib/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var libraryName = 'lc-form-validation'; 3 | var CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | 5 | var basePath = __dirname; 6 | 7 | var config = { 8 | context: path.join(basePath, "src"), 9 | 10 | resolve: { 11 | extensions: ['.js', '.ts'] 12 | }, 13 | 14 | entry: [ 15 | 'es6-promise', 16 | './index.ts', 17 | ], 18 | 19 | output: { 20 | library: libraryName, 21 | libraryTarget: 'umd', 22 | umdNamedDefine: true 23 | }, 24 | 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.(ts|tsx)$/, 29 | exclude: /node_modules/, 30 | use: 31 | { 32 | loader: 'awesome-typescript-loader', 33 | options: { 34 | useBabel: true 35 | } 36 | } 37 | } 38 | ] 39 | }, 40 | 41 | plugins: [ 42 | new CopyWebpackPlugin([ 43 | { from: '../../README.md', to: 'README.md' }, 44 | { from: '../../ReadmeResources', to: 'ReadmeResources' } 45 | ]) 46 | ] 47 | }; 48 | 49 | module.exports = config; 50 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/src/components/quizForm/validations/quizFormValidation.ts: -------------------------------------------------------------------------------- 1 | import { FieldValidationResult, createFormValidation } from 'lc-form-validation'; 2 | import { QuizEntity, Question } from '../../../entity/quizEntity'; 3 | 4 | function isAnyQuestionSelected(quiz: QuizEntity) { 5 | return Object.keys(quiz).some(question => (quiz[question] as Question).isSelected); 6 | } 7 | 8 | function quizValidation(quiz: QuizEntity) { 9 | const isQuizPassed = isAnyQuestionSelected(quiz); 10 | const errorInfo = (isQuizPassed) ? '' : 'Failed'; 11 | const fieldValidationResult: FieldValidationResult = new FieldValidationResult(); 12 | 13 | fieldValidationResult.type = 'QUIZ_VALIDATION'; 14 | fieldValidationResult.succeeded = isQuizPassed; 15 | fieldValidationResult.errorMessage = errorInfo; 16 | return fieldValidationResult; 17 | } 18 | 19 | const quizValidationConstraints = { 20 | global: [ 21 | quizValidation 22 | ] 23 | }; 24 | 25 | const quizFormValidation = createFormValidation(quizValidationConstraints); 26 | 27 | export { 28 | quizFormValidation 29 | } 30 | -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactboiler", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "webpack-dev-server" 7 | }, 8 | "author": "Braulio Diez", 9 | "license": "ISC", 10 | "dependencies": { 11 | "bootstrap": "^3.3.7", 12 | "core-js": "^2.4.1", 13 | "jquery": "^3.2.0", 14 | "lc-form-validation": "file:../../../../lib/", 15 | "react": "^15.4.2", 16 | "react-dom": "^15.4.2", 17 | "react-redux": "^5.0.3", 18 | "redux": "^3.6.0", 19 | "redux-thunk": "^2.2.0" 20 | }, 21 | "devDependencies": { 22 | "babel-core": "^6.24.0", 23 | "babel-loader": "^6.4.1", 24 | "babel-preset-env": "^1.2.2", 25 | "babel-preset-react": "^6.23.0", 26 | "babel-preset-stage-2": "^6.22.0", 27 | "css-loader": "^0.27.3", 28 | "extract-text-webpack-plugin": "^2.1.0", 29 | "file-loader": "^0.10.1", 30 | "html-webpack-plugin": "^2.28.0", 31 | "style-loader": "^0.14.1", 32 | "url-loader": "^0.5.8", 33 | "webpack": "^2.2.1", 34 | "webpack-dev-server": "^2.4.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactboiler", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "webpack-dev-server" 7 | }, 8 | "author": "Braulio Diez", 9 | "license": "ISC", 10 | "dependencies": { 11 | "bootstrap": "^3.3.7", 12 | "core-js": "^2.4.1", 13 | "jquery": "^3.2.0", 14 | "lc-form-validation": "file:../../../../lib/", 15 | "react": "^15.4.2", 16 | "react-dom": "^15.4.2", 17 | "react-redux": "^5.0.3", 18 | "redux": "^3.6.0", 19 | "redux-thunk": "^2.2.0" 20 | }, 21 | "devDependencies": { 22 | "babel-core": "^6.24.0", 23 | "babel-loader": "^6.4.1", 24 | "babel-preset-env": "^1.2.2", 25 | "babel-preset-react": "^6.23.0", 26 | "babel-preset-stage-2": "^6.22.0", 27 | "css-loader": "^0.27.3", 28 | "extract-text-webpack-plugin": "^2.1.0", 29 | "file-loader": "^0.10.1", 30 | "html-webpack-plugin": "^2.28.0", 31 | "style-loader": "^0.14.1", 32 | "url-loader": "^0.5.8", 33 | "webpack": "^2.2.1", 34 | "webpack-dev-server": "^2.4.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/rules/minLength.ts: -------------------------------------------------------------------------------- 1 | import { FieldValidationResult, FieldValidationFunction } from '../entities'; 2 | import { 3 | LengthParams, 4 | parseLengthParams, 5 | isLengthValid, 6 | } from './length'; 7 | 8 | const DEFAULT_PARAMS = { length: undefined }; 9 | const BAD_PARAMETER = 'FieldValidationError: Parameter "length" for minLength in customParams is mandatory and should be a valid number. Example: { length: 4 }.'; 10 | 11 | export const minLength: FieldValidationFunction = (value: string, vm, customParams: LengthParams = DEFAULT_PARAMS) => { 12 | const length = parseLengthParams(customParams, BAD_PARAMETER); 13 | const isValid = isLengthValid(value, length, isStringLengthValid); 14 | const validationResult = new FieldValidationResult(); 15 | 16 | validationResult.errorMessage = isValid ? '' : `The value provided must have at least ${length} characters.`; 17 | validationResult.succeeded = isValid; 18 | validationResult.type = 'MIN_LENGTH'; 19 | return validationResult; 20 | } 21 | 22 | function isStringLengthValid(value: string, length: number): boolean { 23 | return value.length >= length; 24 | } 25 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactboiler", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "webpack-dev-server" 7 | }, 8 | "author": "Braulio Diez", 9 | "license": "ISC", 10 | "dependencies": { 11 | "bootstrap": "^3.3.7", 12 | "core-js": "^2.4.1", 13 | "jquery": "^3.2.0", 14 | "lc-form-validation": "file:../../../../lib/", 15 | "react": "^15.4.2", 16 | "react-dom": "^15.4.2", 17 | "react-redux": "^5.0.3", 18 | "redux": "^3.6.0", 19 | "redux-thunk": "^2.2.0", 20 | "toastr": "^2.1.2" 21 | }, 22 | "devDependencies": { 23 | "babel-core": "^6.24.0", 24 | "babel-loader": "^6.4.1", 25 | "babel-preset-env": "^1.2.2", 26 | "babel-preset-react": "^6.23.0", 27 | "babel-preset-stage-2": "^6.22.0", 28 | "css-loader": "^0.27.3", 29 | "extract-text-webpack-plugin": "^2.1.0", 30 | "file-loader": "^0.10.1", 31 | "html-webpack-plugin": "^2.28.0", 32 | "style-loader": "^0.14.1", 33 | "url-loader": "^0.5.8", 34 | "webpack": "^2.2.1", 35 | "webpack-dev-server": "^2.4.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/src/components/sampleSignupForm/sampleSignupForm.container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { SampleSignupForm } from './sampleSignupForm'; 3 | import { SignupEntity } from '../../entity/signupEntity'; 4 | import { signupRequestStart } from '../../actions/signupRequestStart'; 5 | import { signupUIOnInteractionStart } from '../../actions/signupUIOnInteractionStart'; 6 | import { ValidationEventsFilter } from 'lc-form-validation'; 7 | 8 | const mapStateToProps = (state) => { 9 | return { 10 | signup: state.signup.signup, // ViewModel 11 | errors: state.signup.signupErrors 12 | } 13 | }; 14 | 15 | const mapDispatchToProps = (dispatch) => { 16 | return { 17 | fireValidationField: (viewModel, fieldName, value, filter) => { 18 | return dispatch(signupUIOnInteractionStart(viewModel, fieldName, value, filter)); 19 | }, 20 | performSignup: (signup) => { 21 | return dispatch(signupRequestStart(signup)); 22 | } 23 | } 24 | }; 25 | 26 | export const SampleSignupContainer = connect( 27 | mapStateToProps, 28 | mapDispatchToProps 29 | )(SampleSignupForm); 30 | -------------------------------------------------------------------------------- /lib/src/rules/maxLength.ts: -------------------------------------------------------------------------------- 1 | import { FieldValidationResult, FieldValidationFunction } from '../entities'; 2 | import { 3 | LengthParams, 4 | parseLengthParams, 5 | isLengthValid, 6 | } from './length'; 7 | 8 | const DEFAULT_PARAMS = { length: undefined }; 9 | const BAD_PARAMETER = 'FieldValidationError: Parameter "length" for maxLength in customParams is mandatory and should be a valid number. Example: { length: 4 }.'; 10 | 11 | export function maxLength(value: string, vm, customParams: LengthParams = DEFAULT_PARAMS) { 12 | const length = parseLengthParams(customParams, BAD_PARAMETER); 13 | const isValid = isLengthValid(value, length, isStringLengthValid); 14 | const validationResult = new FieldValidationResult(); 15 | 16 | validationResult.succeeded = isValid; 17 | validationResult.type = 'MAX_LENGTH'; 18 | validationResult.errorMessage = isValid ? '' : `The value provided is too long. Length must not exceed ${length} characters.`; 19 | return validationResult; 20 | } 21 | 22 | function isStringLengthValid(value: string, length: number): boolean { 23 | return isNaN(length) ? 24 | false : 25 | value.length <= length; 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Braulio Diez 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 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactboiler", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "webpack-dev-server" 7 | }, 8 | "author": "Braulio Diez", 9 | "license": "ISC", 10 | "dependencies": { 11 | "bootstrap": "^3.3.7", 12 | "core-js": "^2.4.1", 13 | "jquery": "^3.2.0", 14 | "lc-form-validation": "file:../../../../lib/", 15 | "react": "^15.4.2", 16 | "react-dom": "^15.4.2", 17 | "react-redux": "^5.0.3", 18 | "redux": "^3.6.0", 19 | "redux-thunk": "^2.2.0" 20 | }, 21 | "devDependencies": { 22 | "@types/react": "^15.0.16", 23 | "@types/react-dom": "^0.14.23", 24 | "@types/react-redux": "^4.4.37", 25 | "awesome-typescript-loader": "^3.1.2", 26 | "babel-core": "^6.24.0", 27 | "babel-preset-env": "^1.2.2", 28 | "css-loader": "^0.27.3", 29 | "extract-text-webpack-plugin": "^2.1.0", 30 | "file-loader": "^0.10.1", 31 | "html-webpack-plugin": "^2.28.0", 32 | "style-loader": "^0.14.1", 33 | "typescript": "^2.2.1", 34 | "url-loader": "^0.5.8", 35 | "webpack": "^2.2.1", 36 | "webpack-dev-server": "^2.4.2" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactboiler", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "webpack-dev-server" 7 | }, 8 | "author": "Braulio Diez", 9 | "license": "ISC", 10 | "dependencies": { 11 | "bootstrap": "^3.3.7", 12 | "core-js": "^2.4.1", 13 | "jquery": "^3.2.0", 14 | "lc-form-validation": "file:../../../../lib/", 15 | "react": "^15.4.2", 16 | "react-dom": "^15.4.2", 17 | "react-redux": "^5.0.3", 18 | "redux": "^3.6.0", 19 | "redux-thunk": "^2.2.0" 20 | }, 21 | "devDependencies": { 22 | "@types/react": "^15.0.16", 23 | "@types/react-dom": "^0.14.23", 24 | "@types/react-redux": "^4.4.37", 25 | "awesome-typescript-loader": "^3.1.2", 26 | "babel-core": "^6.24.0", 27 | "babel-preset-env": "^1.2.2", 28 | "css-loader": "^0.27.3", 29 | "extract-text-webpack-plugin": "^2.1.0", 30 | "file-loader": "^0.10.1", 31 | "html-webpack-plugin": "^2.28.0", 32 | "style-loader": "^0.14.1", 33 | "typescript": "^2.2.1", 34 | "url-loader": "^0.5.8", 35 | "webpack": "^2.2.1", 36 | "webpack-dev-server": "^2.4.2" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/components/sampleSignupForm/sampleSignupForm.container.ts: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { SampleSignupForm } from './sampleSignupForm'; 3 | import { SignupEntity } from '../../entity/signupEntity'; 4 | import { signupRequestStart } from '../../actions/signupRequestStart'; 5 | import { signupUIOnInteractionStart } from '../../actions/signupUIOnInteractionStart'; 6 | import { ValidationEventsFilter } from 'lc-form-validation'; 7 | 8 | const mapStateToProps = (state) => { 9 | return { 10 | signup: state.signup.signup, // ViewModel 11 | errors: state.signup.signupErrors 12 | } 13 | }; 14 | 15 | const mapDispatchToProps = (dispatch) => { 16 | return { 17 | fireValidationField: (viewModel: any, fieldName: string, value: any, filter?: ValidationEventsFilter) => { 18 | return dispatch(signupUIOnInteractionStart(viewModel, fieldName, value, filter)); 19 | }, 20 | performSignup: (signup: SignupEntity) => { 21 | return dispatch(signupRequestStart(signup)); 22 | } 23 | }; 24 | }; 25 | 26 | export const SampleSignupContainer = connect( 27 | mapStateToProps, 28 | mapDispatchToProps 29 | )(SampleSignupForm); 30 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/src/components/quizForm/quizForm.container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { QuizForm } from './quizForm'; 3 | import { quizUIInputCompleted } from '../../actions/quizUIInputCompleted'; 4 | import { QuizEntity } from '../../entity/quizEntity'; 5 | import { quizResolveStart } from '../../actions/quizResolveStart'; 6 | import { resetQuizResolveCompleted } from '../../actions/resetQuizResolveCompleted'; 7 | 8 | const mapStateToProps = (state) => { 9 | return { 10 | quiz: state.quiz.quiz, 11 | quizResult: state.quiz.quizResult, 12 | quizResolveCompleted: state.quiz.quizResolveCompleted 13 | } 14 | }; 15 | 16 | const mapDispatchToProps = (dispatch) => { 17 | return { 18 | onSelectedQuestionHandler: (questionId, value) => { 19 | return dispatch(quizUIInputCompleted(questionId, value)); 20 | }, 21 | quizResolve: (quiz) => { 22 | return dispatch(quizResolveStart(quiz)); 23 | }, 24 | resetQuizResolveCompleted: () => { 25 | return dispatch(resetQuizResolveCompleted()); 26 | } 27 | } 28 | }; 29 | 30 | export const ContainerQuizForm = connect( 31 | mapStateToProps, 32 | mapDispatchToProps 33 | )(QuizForm); 34 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/api/gitHub.ts: -------------------------------------------------------------------------------- 1 | class GitHub { 2 | doesLoginExists(login: string): Promise { 3 | const baseGitHubUsersUrl: string = 'https://api.github.com/users/'; 4 | const fetchGitHubUserUrl: string = baseGitHubUsersUrl + login; 5 | 6 | return fetch(fetchGitHubUserUrl) 7 | .then((response) => this.checkStatus(response)) 8 | .then((response) => this.parseJSON(response)) 9 | .then((data) => this.resolveLoginFound(data)) 10 | .catch((error) => this.resolveLoginNotFound(error)); 11 | } 12 | 13 | private checkStatus(response: Response): Promise { 14 | if (response.status >= 200 && response.status < 300) { 15 | return Promise.resolve(response); 16 | } else { 17 | throw new Error(response.statusText); 18 | } 19 | } 20 | 21 | private parseJSON(response: Response): any { 22 | return response.json(); 23 | } 24 | 25 | private resolveLoginFound(data: any): Promise { 26 | return Promise.resolve(true); 27 | } 28 | 29 | private resolveLoginNotFound(error: Error): Promise { 30 | return Promise.resolve(false); 31 | } 32 | } 33 | 34 | export const gitHub = new GitHub(); 35 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/src/components/quizForm/quizForm.container.ts: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { QuizForm } from './quizForm'; 3 | import { quizUIInputCompleted } from '../../actions/quizUIInputCompleted'; 4 | import { QuizEntity } from '../../entity/quizEntity'; 5 | import { quizResolveStart } from '../../actions/quizResolveStart'; 6 | import { resetQuizResolveCompleted } from '../../actions/resetQuizResolveCompleted'; 7 | 8 | const mapStateToProps = (state) => { 9 | return { 10 | quiz: state.quiz.quiz, 11 | quizResult: state.quiz.quizResult, 12 | quizResolveCompleted: state.quiz.quizResolveCompleted 13 | } 14 | }; 15 | 16 | const mapDispatchToProps = (dispatch) => { 17 | return { 18 | onSelectedQuestionHandler: (questionId: string, value: boolean) => { 19 | return dispatch(quizUIInputCompleted(questionId, value)); 20 | }, 21 | quizResolve: (quiz: QuizEntity) => { 22 | return dispatch(quizResolveStart(quiz)); 23 | }, 24 | resetQuizResolveCompleted: () => { 25 | return dispatch(resetQuizResolveCompleted()); 26 | } 27 | } 28 | }; 29 | 30 | export const ContainerQuizForm = connect( 31 | mapStateToProps, 32 | mapDispatchToProps 33 | )(QuizForm); 34 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactboiler", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "webpack-dev-server" 7 | }, 8 | "author": "Braulio Diez", 9 | "license": "ISC", 10 | "dependencies": { 11 | "bootstrap": "^3.3.7", 12 | "core-js": "^2.4.1", 13 | "jquery": "^3.2.0", 14 | "lc-form-validation": "file:../../../../lib/", 15 | "react": "^15.4.2", 16 | "react-dom": "^15.4.2", 17 | "react-redux": "^5.0.3", 18 | "redux": "^3.6.0", 19 | "redux-thunk": "^2.2.0", 20 | "toastr": "^2.1.2" 21 | }, 22 | "devDependencies": { 23 | "@types/react": "^15.0.16", 24 | "@types/react-dom": "^0.14.23", 25 | "@types/react-redux": "^4.4.37", 26 | "@types/toastr": "^2.1.32", 27 | "awesome-typescript-loader": "^3.1.2", 28 | "babel-core": "^6.24.0", 29 | "babel-preset-env": "^1.2.2", 30 | "css-loader": "^0.27.3", 31 | "extract-text-webpack-plugin": "^2.1.0", 32 | "file-loader": "^0.10.1", 33 | "html-webpack-plugin": "^2.28.0", 34 | "style-loader": "^0.14.1", 35 | "typescript": "^2.2.1", 36 | "url-loader": "^0.5.8", 37 | "webpack": "^2.2.1", 38 | "webpack-dev-server": "^2.4.2" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/src/components/common/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface Props { 4 | name: string; 5 | label: string; 6 | onChange: any; 7 | placeholder?: string; 8 | value: string; 9 | error: string; 10 | type?: string; 11 | } 12 | 13 | export class Input extends React.Component { 14 | static defaultProps = { 15 | type: 'text' 16 | }; 17 | 18 | render() { 19 | let wrapperClass = 'form-group'; 20 | if (this.props.error && this.props.error.length > 0) { 21 | wrapperClass = '${wrapperClass} has-error'; 22 | } 23 | 24 | return ( 25 |
    26 | 27 |
    28 | 37 |
    {this.props.error}
    38 |
    39 |
    40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/src/components/common/input.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export class Input extends React.Component { 4 | static propTypes = { 5 | name: React.PropTypes.string.isRequired, 6 | label: React.PropTypes.string.isRequired, 7 | type: React.PropTypes.string, 8 | value: React.PropTypes.string.isRequired, 9 | onChange: React.PropTypes.func.isRequired, 10 | error: React.PropTypes.string.isRequired, 11 | }; 12 | 13 | static defaultProps = { 14 | type: 'text', 15 | }; 16 | 17 | render() { 18 | let wrapperClass = 'form-group'; 19 | if (this.props.error && this.props.error.length > 0) { 20 | wrapperClass = '${wrapperClass} has-error'; 21 | } 22 | 23 | return ( 24 |
    25 | 26 |
    27 | 36 |
    {this.props.error}
    37 |
    38 |
    39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/components/common/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface Props { 4 | name: string; 5 | label: string; 6 | onChange: any; 7 | onBlur?: any; 8 | placeholder?: string; 9 | value: string; 10 | error: string; 11 | type?: string; 12 | } 13 | 14 | export class Input extends React.Component { 15 | static defaultProps = { 16 | type: 'text', 17 | }; 18 | 19 | constructor(props: Props) { 20 | super(props); 21 | } 22 | 23 | render() { 24 | let wrapperClass: string = 'form-group'; 25 | if (this.props.error && this.props.error.length > 0) { 26 | wrapperClass = '${wrapperClass} has-error'; 27 | } 28 | return ( 29 |
    30 | 31 |
    32 | 42 |
    {this.props.error}
    43 |
    44 |
    45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /samples/jquery/00 ShoppingForm/src/modules/app/validation/rules/nifValidator.js: -------------------------------------------------------------------------------- 1 | import { FieldValidationResult } from 'lc-form-validation'; 2 | 3 | export function nifValidator(value) { 4 | const isValid = isNifValid(value); 5 | const validationResult = new FieldValidationResult(); 6 | 7 | validationResult.succeeded = isValid; 8 | validationResult.errorMessage = isValid ? '' : 'The given value is not a valid NIF/NIE'; 9 | validationResult.type = 'NIF'; 10 | return validationResult; 11 | } 12 | 13 | function isNifValid(value) { 14 | return (typeof value === 'string' && value.length > 0) ? 15 | validateNif(value) : 16 | true; 17 | } 18 | 19 | function validateNif(value) { 20 | let isValid = false; 21 | const validChars = 'TRWAGMYFPDXBNJZSQVHLCKET'; 22 | const valueToUpper = value.toUpperCase(); 23 | 24 | if (isValidPattern(valueToUpper, validChars)) { 25 | const nie = valueToUpper 26 | .replace(/^[X]/, '0') 27 | .replace(/^[Y]/, '1') 28 | .replace(/^[Z]/, '2'); 29 | const letter = valueToUpper.substr(-1); 30 | const charIndex = parseInt(nie.substr(0, 8)) % 23; 31 | isValid = (validChars.charAt(charIndex) === letter); 32 | } 33 | 34 | return isValid; 35 | } 36 | 37 | function isValidPattern(value, validChars) { 38 | const nifRegExp = new RegExp(`^[0-9]{8}[${validChars}]{1}$`, 'i'); 39 | const nieRegExp = new RegExp(`^[XYZ]{1}[0-9]{7}[${validChars}]{1}$`, 'i'); 40 | 41 | return nifRegExp.test(value) || nieRegExp.test(value); 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/entities.ts: -------------------------------------------------------------------------------- 1 | export interface ValidationEventsFilter { 2 | [key: string]: boolean; 3 | } 4 | 5 | export class FieldValidation { 6 | validationFn: (value, vm, customParams) => Promise; 7 | eventsFilter: ValidationEventsFilter; 8 | customParams: any; 9 | } 10 | 11 | export class FieldValidationResult { 12 | key?: string; 13 | type: string; 14 | succeeded: boolean; 15 | errorMessage: string; 16 | 17 | constructor() { 18 | this.key = ''; 19 | this.type = ''; 20 | this.succeeded = false; 21 | this.errorMessage = ''; 22 | } 23 | } 24 | 25 | export class FormValidationResult { 26 | succeeded: boolean; 27 | fieldErrors: { [key: string]: FieldValidationResult }; 28 | formGlobalErrors: FieldValidationResult[]; 29 | 30 | constructor() { 31 | this.succeeded = false; 32 | this.fieldErrors = {}; 33 | } 34 | } 35 | 36 | export type ValidationResult = FieldValidationResult | Promise; 37 | 38 | export interface FormValidationFunction { 39 | (vm: any): ValidationResult; 40 | } 41 | 42 | export interface FieldValidationFunction { 43 | (value: any, vm: any, customParams: any): ValidationResult; 44 | } 45 | 46 | export interface FieldValidationConstraint { 47 | validator: FieldValidationFunction; 48 | eventsFilter?: ValidationEventsFilter; 49 | customParams?: any; 50 | } 51 | 52 | export interface ValidationConstraints { 53 | global?: FormValidationFunction[]; 54 | fields?: { [key: string]: FieldValidationConstraint[] }; 55 | } 56 | -------------------------------------------------------------------------------- /lib/src/rules/pattern.ts: -------------------------------------------------------------------------------- 1 | import { FieldValidationResult } from '../entities'; 2 | 3 | export interface PatternParams { 4 | pattern: string | RegExp; 5 | } 6 | 7 | const BAD_PARAMETER = 'FieldValidationError: pattern option for pattern validation is mandatory. Example: { pattern: /\d+/ }.'; 8 | 9 | export function pattern(value: string, vm, customParams: PatternParams): FieldValidationResult { 10 | const pattern = parsePattern(customParams); 11 | const isValid = isValidPattern(value, pattern); 12 | const validationResult = new FieldValidationResult(); 13 | 14 | validationResult.succeeded = isValid; 15 | validationResult.type = 'PATTERN'; 16 | validationResult.errorMessage = isValid ? '' : `Please provide a valid format.`; 17 | 18 | return validationResult; 19 | } 20 | 21 | function parsePattern({ pattern }: PatternParams): RegExp { 22 | // Avoid RegExp like /true/ /false/ and /null/ without an explicit "true", "false" or "null" 23 | if (typeof pattern === 'boolean' || pattern === null) { 24 | throw new Error(BAD_PARAMETER); 25 | } 26 | return getRegExp(pattern); 27 | } 28 | 29 | function getRegExp(pattern): RegExp { 30 | return pattern instanceof RegExp ? 31 | pattern : 32 | new RegExp(pattern); 33 | } 34 | 35 | function isEmptyValue(value) { 36 | return value === null || 37 | value === undefined || 38 | value === ''; 39 | } 40 | 41 | export function isValidPattern(value, pattern: RegExp): boolean { 42 | return isEmptyValue(value) ? 43 | true : 44 | pattern.test(value); 45 | } 46 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/src/components/common/input.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export class Input extends React.Component { 4 | static propTypes = { 5 | name: React.PropTypes.string.isRequired, 6 | label: React.PropTypes.string.isRequired, 7 | onChange: React.PropTypes.func.isRequired, 8 | onBlur: React.PropTypes.func, 9 | placeholder: React.PropTypes.string, 10 | value: React.PropTypes.string.isRequired, 11 | error: React.PropTypes.string.isRequired, 12 | type: React.PropTypes.string, 13 | }; 14 | 15 | static defaultProps = { 16 | type: 'text', 17 | }; 18 | 19 | constructor(props) { 20 | super(props); 21 | } 22 | 23 | render() { 24 | let wrapperClass = 'form-group'; 25 | if (this.props.error && this.props.error.length > 0) { 26 | wrapperClass = '${wrapperClass} has-error'; 27 | } 28 | return ( 29 |
    30 | 31 |
    32 | 42 |
    {this.props.error}
    43 |
    44 |
    45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/common/components/form/inputButton.tsx: -------------------------------------------------------------------------------- 1 | import Vue, {ComponentOptions} from 'vue'; 2 | 3 | interface Props extends Vue { 4 | className: string; 5 | placeholder: string; 6 | type: string; 7 | value: any; 8 | inputHandler: (event) => void; 9 | label: string; 10 | name: string; 11 | buttonText: string; 12 | buttonClickHandler: (event) => void; 13 | buttonClassName: string; 14 | } 15 | 16 | export const InputButtonComponent = Vue.extend({ 17 | props: [ 18 | 'className', 19 | 'placeholder', 20 | 'type', 21 | 'value', 22 | 'inputHandler', 23 | 'label', 24 | 'name', 25 | 'buttonText', 26 | 'buttonClickHandler', 27 | 'buttonClassName', 28 | ], 29 | render: function(h) { 30 | return ( 31 |
    32 | 35 |
    36 | 44 |
    45 | 51 |
    52 |
    53 |
    54 | ); 55 | }, 56 | } as ComponentOptions); -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/src/reducers/quiz.js: -------------------------------------------------------------------------------- 1 | import { QuizEntity, Question } from '../entity/quizEntity'; 2 | import { FieldValidationResult } from 'lc-form-validation'; 3 | import { actionsDef } from '../actions/actionsDef'; 4 | 5 | 6 | export class QuizState { 7 | constructor() { 8 | this.quiz = new QuizEntity(); 9 | this.quizResult = new FieldValidationResult(); 10 | this.quizResolveCompleted = false; 11 | } 12 | } 13 | 14 | export const quizReducer = (state = new QuizState(), action) => { 15 | switch (action.type) { 16 | case actionsDef.quiz.UI_INPUT_CHANGE: 17 | return quizProcessUIInputCompleted(state, action); 18 | 19 | case actionsDef.quiz.QUIZ_RESOLVE_COMPLETED: 20 | return quizResolveCompleted(state, action); 21 | 22 | case actionsDef.quiz.RESET_QUIZ_RESOLVE_COMPLETED: 23 | return { ...state, quizResolveCompleted: false }; 24 | 25 | default: 26 | return state; 27 | } 28 | } 29 | 30 | function quizProcessUIInputCompleted(state, action) { 31 | const newQuiz = Object.assign(new QuizEntity(), { 32 | [action.questionId]: new Question(action.value) 33 | }); 34 | 35 | return { 36 | ...state, 37 | quiz: newQuiz 38 | }; 39 | } 40 | 41 | function quizResolveCompleted(state, action) { 42 | let newFieldValidationResult; 43 | 44 | if (action.formValidationResult.formGlobalErrors.length > 0) { 45 | newFieldValidationResult = action.formValidationResult.formGlobalErrors[0]; 46 | } 47 | 48 | return { 49 | ...state, 50 | quizResult: newFieldValidationResult, 51 | quizResolveCompleted: true 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample-vue-js", 3 | "version": "1.0.0", 4 | "description": "Sample working with VueJS and lc-form-validation", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server", 8 | "build": "webpack" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Lemoncode/lcFormValidation.git" 13 | }, 14 | "keywords": [ 15 | "vue.js", 16 | "samples", 17 | "typescript", 18 | "webpack", 19 | "lc-form-validation" 20 | ], 21 | "author": "Lemoncode", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/Lemoncode/lcFormValidation/issues" 25 | }, 26 | "homepage": "https://github.com/Lemoncode/lcFormValidation#readme", 27 | "devDependencies": { 28 | "@types/webpack-env": "^1.13.0", 29 | "awesome-typescript-loader": "^3.1.2", 30 | "babel-core": "^6.24.1", 31 | "babel-helper-vue-jsx-merge-props": "^2.0.2", 32 | "babel-plugin-syntax-jsx": "^6.18.0", 33 | "babel-plugin-transform-vue-jsx": "^3.4.2", 34 | "babel-preset-env": "^1.3.3", 35 | "css-loader": "^0.28.0", 36 | "extract-text-webpack-plugin": "^2.1.0", 37 | "file-loader": "^0.11.1", 38 | "html-webpack-plugin": "^2.28.0", 39 | "style-loader": "^0.16.1", 40 | "typescript": "2.2.2", 41 | "url-loader": "^0.5.8", 42 | "webpack": "^2.3.3", 43 | "webpack-dev-server": "^2.4.2" 44 | }, 45 | "dependencies": { 46 | "bootstrap": "^3.3.7", 47 | "lc-form-validation": "file:../../../../lib/", 48 | "vue": "2.2.6", 49 | "vue-router": "2.4.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/src/reducers/signup.js: -------------------------------------------------------------------------------- 1 | import { } from 'core-js'; 2 | import { SignupEntity } from '../entity/signupEntity'; 3 | import { FormValidationResult, FieldValidationResult } from 'lc-form-validation'; 4 | import { actionsDef } from '../actions/actionsDef'; 5 | import { ISignupRequestCompletedAction } from '../actions/signupRequestCompleted'; 6 | import { ISignupUIOnInteractionCompletedAction } from '../actions/signupUIOnInteractionCompleted'; 7 | 8 | class SignupState { 9 | constructor() { 10 | this.signup = new SignupEntity(); 11 | this.signupErrors = {}; 12 | } 13 | } 14 | 15 | export const signupReducer = (state = new SignupState(), action) => { 16 | switch (action.type) { 17 | case actionsDef.signup.SIGNUP_PROCESS_UI_INTERACTION_COMPLETED: 18 | return signupProcessCompleted(state, action); 19 | 20 | case actionsDef.signup.SIGNUP_REQUEST_COMPLETED: 21 | return performSignupCompleted(state, action); 22 | 23 | default: 24 | return state; 25 | } 26 | } 27 | 28 | function signupProcessCompleted(state, action) { 29 | const newSignup = Object.assign(new SignupEntity(), state.signup, { 30 | [action.fieldName]: action.value 31 | }); 32 | 33 | const newSignupErrors = { 34 | ...state.signupErrors, 35 | [action.fieldName]: action.fieldValidationResult 36 | }; 37 | 38 | return { 39 | ...state, 40 | signup: newSignup, 41 | signupErrors: newSignupErrors 42 | }; 43 | } 44 | 45 | function performSignupCompleted(state, action) { 46 | return { 47 | ...state, 48 | signupErrors: { 49 | ...state.signupErrors, 50 | ...action.formValidationResult.fieldErrors, 51 | }, 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/src/reducers/customer.js: -------------------------------------------------------------------------------- 1 | import { } from 'core-js'; 2 | import { FormValidationResult, FieldValidationResult } from 'lc-form-validation'; 3 | import { actionsDef } from '../actions/actionsDef'; 4 | import { CustomerEntity } from '../entity/customerEntity'; 5 | import { ICustomerUIInputCompletedAction } from '../actions/customerUInputCompleted'; 6 | import { ICustomerSaveCompletedAction } from '../actions/customerSaveCompleted'; 7 | 8 | export class CustomerState { 9 | constructor() { 10 | this.customer = new CustomerEntity(); 11 | this.customerErrors = {}; 12 | } 13 | } 14 | 15 | export const customerReducer = (state = new CustomerState(), action) => { 16 | switch (action.type) { 17 | case actionsDef.customer.CUSTOMER_PROCESS_UI_INPUT_COMPLETED: 18 | return customerProcessUIInputCompleted(state, action); 19 | 20 | case actionsDef.customer.CUSTOMER_SAVE_COMPLETED: 21 | return customerSaveCompleted(state, action); 22 | 23 | default: 24 | return state; 25 | } 26 | } 27 | 28 | function customerProcessUIInputCompleted(state, action) { 29 | const newCustomer = Object.assign(new CustomerEntity(), state.customer, { 30 | [action.fieldName]: action.value 31 | }); 32 | 33 | const newCustomerErrors = { 34 | ...state.customerErrors, 35 | [action.fieldName]: action.fieldValidationResult 36 | }; 37 | 38 | return { 39 | ...state, 40 | customer: newCustomer, 41 | customerErrors: newCustomerErrors 42 | }; 43 | } 44 | 45 | function customerSaveCompleted(state, action) { 46 | return { 47 | ...state, 48 | customerErrors: { 49 | ...state.customerErrors, 50 | ...action.formValidationResult.fieldErrors, 51 | }, 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /lib/karma.conf.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | module.exports = function (config) { 3 | config.set({ 4 | basePath: '', 5 | frameworks: ['mocha', 'chai-as-promised', 'chai', 'sinon'], 6 | files: [ 7 | './test/test_index.js' 8 | ], 9 | exclude: [ 10 | ], 11 | preprocessors: { 12 | './test/test_index.js': ['webpack', 'sourcemap'] 13 | }, 14 | webpack: { 15 | devtool: 'inline-source-map', 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.(ts|tsx)$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: 'awesome-typescript-loader', 23 | options: { 24 | useBabel: true, 25 | } 26 | } 27 | }, 28 | ], 29 | //Configuration required to import sinon on spec.ts files 30 | noParse: [ 31 | /node_modules(\\|\/)sinon/, 32 | ] 33 | }, 34 | plugins: [ 35 | new webpack.ProvidePlugin({ 36 | Promise: 'es6-promise' 37 | }), 38 | ], 39 | resolve: { 40 | //Added .json extension required by cheerio (enzyme dependency) 41 | extensions: ['.js', '.ts'], 42 | //Configuration required to import sinon on spec.ts files 43 | // https://github.com/webpack/webpack/issues/304 44 | alias: { 45 | sinon: 'sinon/pkg/sinon' 46 | } 47 | }, 48 | }, 49 | webpackMiddleware: { 50 | // webpack-dev-middleware configuration 51 | noInfo: true 52 | }, 53 | 54 | reporters: ['progress'], 55 | port: 9876, 56 | colors: true, 57 | logLevel: config.LOG_INFO, 58 | autoWatch: true, 59 | browsers: ['Chrome'], 60 | singleRun: false, 61 | concurrency: Infinity 62 | }) 63 | } 64 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/reducers/signup.ts: -------------------------------------------------------------------------------- 1 | import { } from 'core-js'; 2 | import { SignupEntity } from '../entity/signupEntity'; 3 | import { SignupErrors } from '../entity/signupErrors'; 4 | import { FormValidationResult, FieldValidationResult } from 'lc-form-validation'; 5 | import { actionsDef } from '../actions/actionsDef'; 6 | import { ISignupRequestCompletedAction } from '../actions/signupRequestCompleted'; 7 | import { ISignupUIOnInteractionCompletedAction } from '../actions/signupUIOnInteractionCompleted'; 8 | 9 | class SignupState { 10 | signup: SignupEntity; 11 | signupErrors: SignupErrors; 12 | 13 | constructor() { 14 | this.signup = new SignupEntity(); 15 | this.signupErrors = new SignupErrors(); 16 | } 17 | } 18 | 19 | export const signupReducer = (state: SignupState = new SignupState(), action): SignupState => { 20 | switch (action.type) { 21 | case actionsDef.signup.SIGNUP_PROCESS_UI_INTERACTION_COMPLETED: 22 | return signupProcessCompleted(state, action); 23 | 24 | case actionsDef.signup.SIGNUP_REQUEST_COMPLETED: 25 | return performSignupCompleted(state, action); 26 | 27 | default: 28 | return state; 29 | } 30 | }; 31 | 32 | function signupProcessCompleted(state: SignupState, action: ISignupUIOnInteractionCompletedAction): SignupState { 33 | const newSignup: SignupEntity = { 34 | ...state.signup, 35 | [action.fieldName]: action.value 36 | }; 37 | 38 | const newSignupErrors: SignupErrors = { 39 | ...state.signupErrors, 40 | [action.fieldName]: action.fieldValidationResult 41 | }; 42 | 43 | return { 44 | ...state, 45 | signup: newSignup, 46 | signupErrors: newSignupErrors 47 | }; 48 | } 49 | 50 | function performSignupCompleted(state: SignupState, action: ISignupRequestCompletedAction): SignupState { 51 | return { 52 | ...state, 53 | signupErrors: { 54 | ...state.signupErrors, 55 | ...action.formValidationResult.fieldErrors, 56 | } 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/src/components/sampleForm/sampleForm.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Input } from '../common/input'; 3 | import { CustomerEntity } from '../../entity/customerEntity'; 4 | import { CustomerErrors } from '../../entity/customerErrors'; 5 | 6 | interface Props extends React.Props { 7 | customer: CustomerEntity; 8 | errors: CustomerErrors; 9 | fireValidationFieldValueChanged: (viewModel: any, fieldName: string, value: any) => void; 10 | saveCustomer: (customer: CustomerEntity) => void; 11 | } 12 | 13 | export class SampleForm extends React.Component { 14 | 15 | constructor(props) { 16 | super(props); 17 | 18 | this.onSave = this.onSave.bind(this); 19 | this.updateMemberFromUI = this.updateMemberFromUI.bind(this); 20 | } 21 | 22 | private updateMemberFromUI(event) { 23 | const field = event.target.name; 24 | const value = event.target.value; 25 | 26 | this.props.fireValidationFieldValueChanged(this.props.customer, field, value); 27 | } 28 | 29 | private onSave(event) { 30 | event.preventDefault(); 31 | this.props.saveCustomer(this.props.customer); 32 | } 33 | 34 | render() { 35 | return ( 36 |
    37 |

    Customer Form

    38 | 39 | 45 | 46 | 53 | 54 | 56 |
    57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/src/reducers/customer.ts: -------------------------------------------------------------------------------- 1 | import { } from 'core-js'; 2 | import { CustomerEntity } from '../entity/customerEntity'; 3 | import { CustomerErrors } from '../entity/customerErrors'; 4 | import { FormValidationResult, FieldValidationResult } from 'lc-form-validation'; 5 | import { actionsDef } from '../actions/actionsDef'; 6 | import { ICustomerUIInputCompletedAction } from '../actions/customerUInputCompleted'; 7 | import { ICustomerSaveCompletedAction } from '../actions/customerSaveCompleted'; 8 | 9 | export class CustomerState { 10 | customer: CustomerEntity; 11 | customerErrors: CustomerErrors; 12 | 13 | constructor() { 14 | this.customer = new CustomerEntity(); 15 | this.customerErrors = new CustomerErrors(); 16 | } 17 | } 18 | 19 | export const customerReducer = (state: CustomerState = new CustomerState(), action): CustomerState => { 20 | switch (action.type) { 21 | case actionsDef.customer.CUSTOMER_PROCESS_UI_INPUT_COMPLETED: 22 | return customerProcessUIInputCompleted(state, action); 23 | 24 | case actionsDef.customer.CUSTOMER_SAVE_COMPLETED: 25 | return customerSaveCompleted(state, action); 26 | 27 | default: 28 | return state; 29 | } 30 | }; 31 | 32 | function customerProcessUIInputCompleted(state: CustomerState, action: ICustomerUIInputCompletedAction): CustomerState { 33 | const newCustomer: CustomerEntity = { 34 | ...state.customer, 35 | [action.fieldName]: action.value 36 | }; 37 | 38 | const newCustomerErrors: CustomerErrors = { 39 | ...state.customerErrors, 40 | [action.fieldName]: action.fieldValidationResult 41 | }; 42 | 43 | return { 44 | ...state, 45 | customer: newCustomer, 46 | customerErrors: newCustomerErrors 47 | }; 48 | } 49 | 50 | function customerSaveCompleted(state: CustomerState, action: ICustomerSaveCompletedAction): CustomerState { 51 | return { 52 | ...state, 53 | customerErrors: { 54 | ...state.customerErrors, 55 | ...action.formValidationResult.fieldErrors, 56 | } 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/src/components/sampleForm/sampleForm.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Input } from '../common/input'; 3 | import { CustomerEntity } from '../../entity/customerEntity'; 4 | import { FieldValidationResult } from 'lc-form-validation'; 5 | 6 | export class SampleForm extends React.Component { 7 | static propTypes = { 8 | fireValidationFieldValueChanged: React.PropTypes.func.isRequired, 9 | saveCustomer: React.PropTypes.func.isRequired, 10 | customer: React.PropTypes.instanceOf(CustomerEntity).isRequired, 11 | errors: React.PropTypes.shape({ 12 | fullname: React.PropTypes.instanceOf(FieldValidationResult), 13 | password: React.PropTypes.instanceOf(FieldValidationResult), 14 | }), 15 | }; 16 | 17 | constructor(props) { 18 | super(props); 19 | 20 | this.onSave = this.onSave.bind(this); 21 | this.updateMemberFromUI = this.updateMemberFromUI.bind(this); 22 | } 23 | 24 | updateMemberFromUI(event) { 25 | const field = event.target.name; 26 | const value = event.target.value; 27 | 28 | this.props.fireValidationFieldValueChanged(this.props.customer, field, value); 29 | } 30 | 31 | onSave(event) { 32 | event.preventDefault(); 33 | this.props.saveCustomer(this.props.customer); 34 | } 35 | 36 | render() { 37 | return ( 38 |
    39 |

    Customer Form

    40 | 41 | 47 | 48 | 55 | 56 | 58 |
    59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /samples/jquery/00 ShoppingForm/src/services/productsService.js: -------------------------------------------------------------------------------- 1 | const brands = [ 2 | { 3 | id: 18, 4 | description: 'Corsair', 5 | products: [ 6 | { id: '63', description: 'K95 RGB' }, 7 | { id: '73', description: 'K70 RGB' }, 8 | { id: '15', description: 'Strafe RGB' }, 9 | { id: '31', description: 'Strafe RGB MX Silent' }, 10 | { id: '80', description: 'K65 RGB Rapidfire' }, 11 | ] 12 | }, 13 | { 14 | id: 46, 15 | description: 'Ozone', 16 | products: [ 17 | { id: '28', description: 'Strike X30' }, 18 | { id: '65', description: 'Strike Pro Spectra' }, 19 | { id: '54', description: 'Strike Pro' }, 20 | { id: '55', description: 'Strike Battle Spectra' }, 21 | { id: '84', description: 'Strike Battle' }, 22 | { id: '06', description: 'Blade' }, 23 | ] 24 | }, 25 | { 26 | id: 23, 27 | description: 'Razer', 28 | products: [ 29 | { id: '37', description: 'Blackwidow Chroma V2' }, 30 | { id: '52', description: 'Blackwidow X Chroma' }, 31 | { id: '02', description: 'Ornata Chroma' }, 32 | { id: '39', description: 'Blackwidow X Ultimate' }, 33 | { id: '04', description: 'Blackwidow 2016' }, 34 | { id: '46', description: 'Orbweaver Chroma' }, 35 | ] 36 | }, 37 | { 38 | id: 15, 39 | description: 'Logitech', 40 | products: [ 41 | { id: '40', description: 'Pro' }, 42 | { id: '63', description: 'G213' }, 43 | { id: '80', description: 'G910' }, 44 | { id: '01', description: 'G810' }, 45 | { id: '36', description: 'G610' }, 46 | { id: '56', description: 'G410' }, 47 | ] 48 | }, 49 | { 50 | id: 21, 51 | description: 'SteelSeries', 52 | products: [ 53 | { id: '86', description: 'Apex M800' }, 54 | { id: '65', description: 'Apex M500' }, 55 | { id: '75', description: 'Apex 350' }, 56 | { id: '72', description: 'Apex 300' }, 57 | { id: '47', description: 'Apex 100' }, 58 | ] 59 | }, 60 | ]; 61 | 62 | function fetchBrands() { 63 | return Promise.resolve(brands); 64 | } 65 | 66 | export const productsService = { 67 | fetchBrands, 68 | }; 69 | -------------------------------------------------------------------------------- /samples/jquery/00 ShoppingForm/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | LcFormValidation jQuery ES6 sample 8 | 9 | 10 |
    11 |
    12 |

    Buy a mechanical keyboard

    13 |
    14 |
    15 |
    16 |
    17 |
    18 | 19 | 22 | 23 |
    24 |
    25 | 26 | 29 | 30 |
    31 |
    32 | 33 | 34 | 35 |
    36 |
    37 | 38 | 39 | 40 |
    41 |
    42 | 43 |
    44 |
    45 |
    46 |
    47 |
    48 | 49 | 50 | -------------------------------------------------------------------------------- /lib/src/validationsResultBuilder.ts: -------------------------------------------------------------------------------- 1 | import { FieldValidationResult, FormValidationResult } from './entities'; 2 | import { consts } from './consts'; 3 | import set from 'lodash.set'; 4 | 5 | export class ValidationsResultBuilder { 6 | buildFormValidationsResult(fieldValidationResults: Array): FormValidationResult { 7 | let formValidationResult = new FormValidationResult(); 8 | 9 | if (fieldValidationResults && fieldValidationResults.length > 0) { 10 | let filteredFieldValidationResults = this.removeUndefinedValidationResults(fieldValidationResults); 11 | this.setGlobalKeyToEmptyKeys(filteredFieldValidationResults); 12 | 13 | formValidationResult.succeeded = filteredFieldValidationResults.every(fvr => fvr.succeeded); 14 | const fieldValidationResultList = filteredFieldValidationResults.filter(fvr => fvr.key !== consts.globalFormValidationId); 15 | formValidationResult.fieldErrors = this.mapFieldValidationResultListToFieldErrorsObject(fieldValidationResultList); 16 | formValidationResult.formGlobalErrors = filteredFieldValidationResults.filter(fvr => fvr.key === consts.globalFormValidationId); 17 | } 18 | 19 | return formValidationResult; 20 | } 21 | 22 | private removeUndefinedValidationResults(fieldValidationResults: Array): Array { 23 | return fieldValidationResults.filter( 24 | fvr => fvr !== undefined && 25 | fvr !== null 26 | ); 27 | } 28 | 29 | private setGlobalKeyToEmptyKeys(fieldValidationResults: Array) { 30 | fieldValidationResults.forEach(fieldValidationResult => { 31 | if (!fieldValidationResult.key) { 32 | fieldValidationResult.key = consts.globalFormValidationId; 33 | } 34 | }); 35 | } 36 | 37 | private mapFieldValidationResultListToFieldErrorsObject = (fieldValidationResultList: FieldValidationResult[]): { [key: string]: FieldValidationResult } => ( 38 | fieldValidationResultList.reduce((errors, result) => set(errors, result.key, result), {}) 39 | ) 40 | } 41 | 42 | let validationsResultBuilder = new ValidationsResultBuilder(); 43 | 44 | export { 45 | validationsResultBuilder 46 | } 47 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/src/components/sampleSignupForm/validations/signupFormValidation.js: -------------------------------------------------------------------------------- 1 | import { 2 | createFormValidation, 3 | FieldValidationResult, 4 | Validators, 5 | } from 'lc-form-validation'; 6 | import { gitHub } from '../../../api/gitHub'; 7 | 8 | function passwordAndConfirmPasswordValidationHandler(value, vm) { 9 | const passwordAndConfirmPasswordAreEqual = vm.password === value; 10 | const errorInfo = (passwordAndConfirmPasswordAreEqual) ? '' : 'Passwords do not match'; 11 | 12 | const fieldValidationResult = new FieldValidationResult(); 13 | fieldValidationResult.type = 'PASSWORD_MATCH'; 14 | fieldValidationResult.succeeded = passwordAndConfirmPasswordAreEqual; 15 | fieldValidationResult.errorMessage = errorInfo; 16 | 17 | return fieldValidationResult; 18 | } 19 | 20 | function loginExistOnGitHubValidationHandler(value) { 21 | return gitHub.doesLoginExists(value) 22 | .then((loginExists) => resolveLoginExists(loginExists)) 23 | .catch(error => console.log('ERROR', error)); 24 | } 25 | 26 | function resolveLoginExists(loginExists) { 27 | const fieldValidationResult = new FieldValidationResult(); 28 | fieldValidationResult.type = 'USER_GITHUB'; 29 | fieldValidationResult.succeeded = !loginExists; 30 | fieldValidationResult.errorMessage = (loginExists) ? 'This user exists on GitHub' : ''; 31 | return Promise.resolve(fieldValidationResult); 32 | } 33 | 34 | const signupValidationConstraints = { 35 | fields: { 36 | password: [ 37 | { validator: Validators.required }, 38 | { 39 | validator: Validators.minLength, 40 | customParams: { length: 4 }, 41 | }, 42 | ], 43 | confirmPassword: [ 44 | { validator: Validators.required }, 45 | { validator: passwordAndConfirmPasswordValidationHandler }, 46 | ], 47 | login: [ 48 | { 49 | validator: Validators.required, 50 | eventsFilter: { OnChange: true, OnBlur: true }, 51 | }, 52 | { 53 | validator: loginExistOnGitHubValidationHandler, 54 | eventsFilter: { OnBlur: true } 55 | }, 56 | ] 57 | } 58 | }; 59 | 60 | export const signupFormValidation = createFormValidation(signupValidationConstraints); 61 | -------------------------------------------------------------------------------- /lib/lc-form-validation.d.ts: -------------------------------------------------------------------------------- 1 | export class FieldValidationResult { 2 | key?: string; 3 | type: string; 4 | succeeded: boolean; 5 | errorMessage: string; 6 | } 7 | 8 | export class FormValidationResult { 9 | succeeded: boolean; 10 | fieldErrors: { [key: string]: FieldValidationResult }; 11 | formGlobalErrors: Array; 12 | } 13 | 14 | export interface ValidationEventsFilter { 15 | [key: string]: boolean; 16 | } 17 | 18 | interface FormValidation { 19 | validateField(vm: any, key: string, value: any, eventsFilter?: ValidationEventsFilter): Promise; 20 | validateForm(vm: any): Promise; 21 | isValidationInProgress(): boolean; 22 | isFormDirty(): boolean; 23 | isFormPristine(): boolean; 24 | } 25 | 26 | export type ValidationResult = FieldValidationResult | Promise; 27 | 28 | export interface FormValidationFunction { 29 | (vm: any): ValidationResult; 30 | } 31 | 32 | export interface FieldValidationFunction { 33 | (value: any, vm: any, customParams: any): ValidationResult; 34 | } 35 | 36 | export interface FieldValidationConstraint { 37 | validator: FieldValidationFunction; 38 | eventsFilter?: ValidationEventsFilter; 39 | customParams?: any; 40 | } 41 | 42 | export interface ValidationConstraints { 43 | global?: FormValidationFunction[]; 44 | fields?: { [key: string]: FieldValidationConstraint[] } 45 | } 46 | 47 | export function createFormValidation(validationCredentials: ValidationConstraints): FormValidation; 48 | 49 | export interface LengthParams { 50 | length: number; 51 | } 52 | 53 | export interface PatternParams { 54 | pattern: string | RegExp; 55 | } 56 | 57 | export interface RequiredParams { 58 | trim: boolean; 59 | } 60 | 61 | export namespace Validators { 62 | function required(value: any, vm: any, customParams: RequiredParams): FieldValidationResult; 63 | function minLength(value: any, vm: any, customParams: LengthParams): FieldValidationResult; 64 | function maxLength(value: any, vm: any, customParams: LengthParams): FieldValidationResult; 65 | function email(value: any, vm: any): FieldValidationResult; 66 | function pattern(value: any, vm: any, customParams: PatternParams): FieldValidationResult; 67 | } 68 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/src/reducers/quiz.ts: -------------------------------------------------------------------------------- 1 | import { } from 'core-js'; 2 | import { QuizEntity } from '../entity/quizEntity'; 3 | import { Question } from '../entity/quizEntity'; 4 | import { FormValidationResult, FieldValidationResult } from 'lc-form-validation'; 5 | import { actionsDef } from '../actions/actionsDef'; 6 | import { IQuizUIInputCompletedAction } from '../actions/quizUIInputCompleted'; 7 | import { IQuizResolveCompletedAction } from '../actions/quizResolveCompleted'; 8 | import { IQuizResetQuizResolveCompletedAction } from '../actions/resetQuizResolveCompleted'; 9 | 10 | 11 | export class QuizState { 12 | quiz: QuizEntity; 13 | quizResult: FieldValidationResult; 14 | quizResolveCompleted: boolean; 15 | 16 | constructor() { 17 | this.quiz = new QuizEntity(); 18 | this.quizResult = new FieldValidationResult(); 19 | this.quizResolveCompleted = false; 20 | } 21 | } 22 | 23 | export const quizReducer = (state: QuizState = new QuizState(), action: any): QuizState => { 24 | switch (action.type) { 25 | case actionsDef.quiz.UI_INPUT_CHANGE: 26 | return quizProcessUIInputCompleted(state, action); 27 | 28 | case actionsDef.quiz.QUIZ_RESOLVE_COMPLETED: 29 | return quizResolveCompleted(state, action); 30 | 31 | case actionsDef.quiz.RESET_QUIZ_RESOLVE_COMPLETED: 32 | return Object.assign({}, state, { quizResolveCompleted: false }); 33 | 34 | default: 35 | return state; 36 | } 37 | } 38 | 39 | function quizProcessUIInputCompleted(state: QuizState, action: IQuizUIInputCompletedAction): QuizState { 40 | let newQuiz: QuizEntity = Object.assign({}, state.quiz, { 41 | [action.questionId]: new Question(action.value) 42 | }); 43 | 44 | return Object.assign({}, state, { 45 | quiz: newQuiz 46 | }); 47 | } 48 | 49 | function quizResolveCompleted(state: QuizState, action: IQuizResolveCompletedAction): QuizState { 50 | let newFieldValidationResult: FieldValidationResult; 51 | 52 | if (action.formValidationResult.formGlobalErrors.length > 0) { 53 | newFieldValidationResult = action.formValidationResult.formGlobalErrors[0]; 54 | } 55 | 56 | return Object.assign({}, state, { 57 | quizResult: newFieldValidationResult, 58 | quizResolveCompleted: true 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/components/sampleSignupForm/validations/signupFormValidation.ts: -------------------------------------------------------------------------------- 1 | import { } from 'core-js'; 2 | import { 3 | createFormValidation, 4 | FieldValidationResult, 5 | ValidationConstraints, 6 | Validators, 7 | } from 'lc-form-validation'; 8 | import { gitHub } from '../../../api/gitHub'; 9 | 10 | function passwordAndConfirmPasswordValidationHandler(value: any, vm: any): FieldValidationResult { 11 | const passwordAndConfirmPasswordAreEqual = vm.password === value; 12 | const errorInfo = (passwordAndConfirmPasswordAreEqual) ? '' : 'Passwords do not match'; 13 | 14 | const fieldValidationResult: FieldValidationResult = new FieldValidationResult(); 15 | fieldValidationResult.type = 'PASSWORD_MATCH'; 16 | fieldValidationResult.succeeded = passwordAndConfirmPasswordAreEqual; 17 | fieldValidationResult.errorMessage = errorInfo; 18 | 19 | return fieldValidationResult; 20 | } 21 | 22 | function loginExistOnGitHubValidationHandler(value: any, vm: any): Promise { 23 | return gitHub.doesLoginExists(value) 24 | .then((loginExists) => resolveLoginExists(loginExists)); 25 | } 26 | 27 | function resolveLoginExists(loginExists: boolean): Promise { 28 | const fieldValidationResult: FieldValidationResult = new FieldValidationResult(); 29 | fieldValidationResult.type = 'USER_GITHUB'; 30 | fieldValidationResult.succeeded = !loginExists; 31 | fieldValidationResult.errorMessage = (loginExists) ? 'This user exists on GitHub' : ''; 32 | return Promise.resolve(fieldValidationResult); 33 | } 34 | 35 | const signupValidationConstraints: ValidationConstraints = { 36 | fields: { 37 | password: [ 38 | { validator: Validators.required }, 39 | { 40 | validator: Validators.minLength, 41 | customParams: { length: 4 }, 42 | }, 43 | ], 44 | confirmPassword: [ 45 | { validator: Validators.required }, 46 | { validator: passwordAndConfirmPasswordValidationHandler }, 47 | ], 48 | login: [ 49 | { 50 | validator: Validators.required, 51 | eventsFilter: { onChange: true, onBlur: true }, 52 | }, 53 | { 54 | validator: loginExistOnGitHubValidationHandler, 55 | eventsFilter: { onBlur: true } 56 | }, 57 | ] 58 | } 59 | }; 60 | 61 | export const signupFormValidation = createFormValidation(signupValidationConstraints); 62 | -------------------------------------------------------------------------------- /samples/jquery/00 ShoppingForm/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var webpack = require("webpack"); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | 6 | var basePath = __dirname; 7 | 8 | module.exports = { 9 | context: path.join(basePath, "src"), 10 | 11 | entry: { 12 | app: './index.js', 13 | vendor: [ 14 | "jquery", 15 | "core-js", 16 | "lc-form-validation", 17 | ], 18 | vendorStyles: [ 19 | '../node_modules/bootstrap/dist/css/bootstrap.css' 20 | ] 21 | }, 22 | 23 | output: { 24 | path: path.join(basePath, "dist"), 25 | filename: '[name].js' 26 | }, 27 | 28 | //https://webpack.github.io/docs/webpack-dev-server.html#webpack-dev-server-cli 29 | devServer: { 30 | contentBase: './dist', //Content base 31 | inline: true, //Enable watch and live reload 32 | host: 'localhost', 33 | noInfo: true, 34 | port: 8080 35 | }, 36 | 37 | // http://webpack.github.io/docs/configuration.html#devtool 38 | devtool: 'source-map', 39 | 40 | module: { 41 | rules: [ 42 | { 43 | test: /\.js$/, 44 | exclude: /node_modules/, 45 | use: 'babel-loader', 46 | }, 47 | //Note: Doesn't exclude node_modules to load bootstrap 48 | { 49 | test: /\.css$/, 50 | loader: ExtractTextPlugin.extract({ 51 | fallback: 'style-loader', 52 | use: 'css-loader' 53 | }) 54 | }, 55 | //Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 56 | //Using here url-loader and file-loader 57 | { test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=application/font-woff" }, 58 | { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=application/octet-stream" }, 59 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader" }, 60 | { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=image/svg+xml" } 61 | ] 62 | }, 63 | 64 | plugins: [ 65 | new webpack.optimize.CommonsChunkPlugin({ 66 | name: 'vendor', 67 | }), 68 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 69 | new HtmlWebpackPlugin({ 70 | filename: 'index.html', //Name of file in ./dist/ 71 | template: 'index.html' //Name of template in ./src 72 | }), 73 | //Generate bundle.css => https://github.com/webpack/extract-text-webpack-plugin 74 | new ExtractTextPlugin('[name].css'), 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/src/components/sampleSignupForm/sampleSignupForm.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Input } from '../common/input'; 3 | import { SignupEntity } from '../../entity/signupEntity'; 4 | import { SignupErrors } from '../../entity/signupErrors'; 5 | import { ValidationEventsFilter } from 'lc-form-validation'; 6 | 7 | interface Props extends React.Props { 8 | signup: SignupEntity; 9 | errors: SignupErrors; 10 | fireValidationField: (viewModel: any, fieldName: string, value: string, filter?: ValidationEventsFilter) => void; 11 | performSignup: (signup: SignupEntity) => void; 12 | } 13 | 14 | export class SampleSignupForm extends React.Component { 15 | constructor(props) { 16 | super(props); 17 | 18 | this.onSave = this.onSave.bind(this); 19 | } 20 | 21 | private applyFieldValidation(event, filter?: ValidationEventsFilter) { 22 | const { name, value } = event.target; 23 | this.props.fireValidationField(this.props.signup, name, value, filter); 24 | } 25 | 26 | private onSave(event) { 27 | event.preventDefault(); 28 | this.props.performSignup(this.props.signup); 29 | } 30 | 31 | render() { 32 | return ( 33 |
    34 |

    Signup Form

    35 | { 40 | this.applyFieldValidation(event); 41 | }} 42 | onBlur={(event) => { 43 | this.applyFieldValidation(event, { onBlur: true }); 44 | }} 45 | error={(this.props.errors.login) ? this.props.errors.login.errorMessage : ''} /> 46 | 47 | { 53 | this.applyFieldValidation(event); 54 | }} 55 | error={(this.props.errors.password) ? this.props.errors.password.errorMessage : ''} /> 56 | 57 | { 63 | this.applyFieldValidation(event); 64 | }} 65 | error={(this.props.errors.confirmPassword) ? this.props.errors.confirmPassword.errorMessage : ''} /> 66 | 68 |
    69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/src/components/quizForm/quizForm.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as toastr from 'toastr'; 3 | import { Question } from '../common/question'; 4 | import { QuizEntity } from '../../entity/quizEntity'; 5 | import { FieldValidationResult } from 'lc-form-validation'; 6 | 7 | interface Props extends React.Props { 8 | quiz: QuizEntity; 9 | quizResult: FieldValidationResult; 10 | onSelectedQuestionHandler: (questionId: string, value: boolean) => void; 11 | quizResolve: (quiz: QuizEntity) => void; 12 | quizResolveCompleted: boolean; 13 | resetQuizResolveCompleted: () => void; 14 | } 15 | 16 | export class QuizForm extends React.Component { 17 | constructor(props) { 18 | super(props); 19 | 20 | this.onSelectedQuestion = this.onSelectedQuestion.bind(this); 21 | this.onQuizResolve = this.onQuizResolve.bind(this); 22 | } 23 | 24 | private onSelectedQuestion(event) { 25 | this.props.onSelectedQuestionHandler(event.target.name, event.target.checked); 26 | } 27 | 28 | private onQuizResolve(event) { 29 | event.preventDefault(); 30 | this.props.quizResolve(this.props.quiz); 31 | } 32 | 33 | componentWillReceiveProps(nextProps: Props) { 34 | if (this.props.quizResolveCompleted !== nextProps.quizResolveCompleted && nextProps.quizResolveCompleted) { 35 | this.showToastr(nextProps); 36 | this.props.resetQuizResolveCompleted(); 37 | } 38 | } 39 | 40 | private showToastr(nextProps: Props) { 41 | toastr.clear(); 42 | if (nextProps.quizResult.succeeded) { 43 | toastr.success('Test passed'); 44 | } else { 45 | toastr.error(nextProps.quizResult.errorMessage); 46 | } 47 | } 48 | 49 | render() { 50 | return ( 51 |
    52 |
    53 |

    Mark any question as valid

    54 | 59 | 60 | 65 | 66 | 71 | 72 | 74 | 75 |
    76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/src/components/quizForm/quizForm.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import toastr from 'toastr'; 3 | import { Question } from '../common/question'; 4 | import { QuizEntity } from '../../entity/quizEntity'; 5 | import { FieldValidationResult } from 'lc-form-validation'; 6 | 7 | export class QuizForm extends React.Component { 8 | 9 | static propTypes = { 10 | quiz: React.PropTypes.instanceOf(QuizEntity).isRequired, 11 | quizResult: React.PropTypes.instanceOf(FieldValidationResult).isRequired, 12 | onSelectedQuestionHandler: React.PropTypes.func.isRequired, 13 | quizResolve: React.PropTypes.func.isRequired, 14 | quizResolveCompleted: React.PropTypes.bool.isRequired, 15 | resetQuizResolveCompleted: React.PropTypes.func.isRequired, 16 | }; 17 | 18 | constructor(props) { 19 | super(props); 20 | 21 | this.onSelectedQuestion = this.onSelectedQuestion.bind(this); 22 | this.onQuizResolve = this.onQuizResolve.bind(this); 23 | } 24 | 25 | onSelectedQuestion(event) { 26 | const { name, checked } = event.target; 27 | this.props.onSelectedQuestionHandler(name, checked); 28 | } 29 | 30 | onQuizResolve(event) { 31 | event.preventDefault(); 32 | this.props.quizResolve(this.props.quiz); 33 | } 34 | 35 | componentWillReceiveProps(nextProps) { 36 | if (this.props.quizResolveCompleted !== nextProps.quizResolveCompleted && nextProps.quizResolveCompleted) { 37 | this.showToastr(nextProps); 38 | this.props.resetQuizResolveCompleted(); 39 | } 40 | } 41 | 42 | showToastr(nextProps) { 43 | toastr.clear(); 44 | if (nextProps.quizResult.succeeded) { 45 | toastr.success('Test passed'); 46 | } else { 47 | toastr.error(nextProps.quizResult.errorMessage); 48 | } 49 | } 50 | 51 | render() { 52 | return ( 53 |
    54 |
    55 |

    Mark any question as valid

    56 | 61 | 62 | 67 | 68 | 73 | 74 | 76 | 77 |
    78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | 6 | var basePath = __dirname; 7 | 8 | module.exports = { 9 | context: path.join(basePath, 'src'), 10 | resolve: { 11 | extensions: ['.js', '.ts', '.tsx'], 12 | }, 13 | entry: { 14 | app: './main.tsx', 15 | vendor: [ 16 | 'vue', 17 | 'vue-router', 18 | 'lc-form-validation', 19 | ], 20 | vendorStyles: [ 21 | '../node_modules/bootstrap/dist/css/bootstrap.css', 22 | ], 23 | }, 24 | output: { 25 | path: path.join(basePath, 'dist'), 26 | filename: '[name].js', 27 | }, 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.tsx?$/, 32 | exclude: /node_modules/, 33 | use: { 34 | loader: 'awesome-typescript-loader', 35 | options: { 36 | useBabel: true, 37 | }, 38 | }, 39 | }, 40 | { 41 | test: /\.css$/, 42 | loader: ExtractTextPlugin.extract({ 43 | fallback: 'style-loader', 44 | use: { 45 | loader: 'css-loader', 46 | }, 47 | }), 48 | }, 49 | // Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 50 | // Using here url-loader and file-loader 51 | { 52 | test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, 53 | loader: 'url-loader?limit=10000&mimetype=application/font-woff' 54 | }, 55 | { 56 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 57 | loader: 'url-loader?limit=10000&mimetype=application/octet-stream' 58 | }, 59 | { 60 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 61 | loader: 'file-loader' 62 | }, 63 | { 64 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 65 | loader: 'url-loader?limit=10000&mimetype=image/svg+xml' 66 | }, 67 | ] 68 | }, 69 | devtool: 'inline-source-map', 70 | devServer: { 71 | open: true, 72 | }, 73 | plugins: [ 74 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 75 | new HtmlWebpackPlugin({ 76 | filename: 'index.html', //Name of file in ./dist/ 77 | template: 'index.html', //Name of template in ./src 78 | hash: true, 79 | }), 80 | new webpack.ProvidePlugin({ 81 | $: "jquery", 82 | jQuery: "jquery" 83 | }), 84 | new webpack.optimize.CommonsChunkPlugin({ 85 | names: ['vendor', 'manifest'], 86 | }), 87 | new ExtractTextPlugin({ 88 | filename: '[name].css', 89 | disable: false, 90 | allChunks: true, 91 | }), 92 | ], 93 | } -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/src/components/sampleSignupForm/sampleSignupForm.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input } from '../common/input'; 3 | import { SignupEntity } from '../../entity/signupEntity'; 4 | import { ValidationEventsFilter, FieldValidationResult } from 'lc-form-validation'; 5 | 6 | export class SampleSignupForm extends React.Component { 7 | static propTypes = { 8 | signup: React.PropTypes.instanceOf(SignupEntity).isRequired, 9 | errors: React.PropTypes.shape({ 10 | login: React.PropTypes.instanceOf(FieldValidationResult), 11 | password: React.PropTypes.instanceOf(FieldValidationResult), 12 | confirmPassword: React.PropTypes.instanceOf(FieldValidationResult), 13 | }).isRequired, 14 | fireValidationField: React.PropTypes.func.isRequired, 15 | performSignup: React.PropTypes.func.isRequired, 16 | }; 17 | 18 | constructor(props) { 19 | super(props); 20 | 21 | this.onSave = this.onSave.bind(this); 22 | } 23 | 24 | applyFieldValidation(event, filter) { 25 | const { name, value } = event.target; 26 | this.props.fireValidationField(this.props.signup, name, value, filter); 27 | } 28 | 29 | onSave(event) { 30 | event.preventDefault(); 31 | this.props.performSignup(this.props.signup); 32 | } 33 | 34 | render() { 35 | return ( 36 |
    37 |

    Signup Form

    38 | { 44 | this.applyFieldValidation(event); 45 | }} 46 | onBlur={(event) => { 47 | this.applyFieldValidation(event, { OnBlur: true }); 48 | }} 49 | error={(this.props.errors.login) ? this.props.errors.login.errorMessage : ''} /> 50 | 51 | { 57 | this.applyFieldValidation(event); 58 | }} 59 | error={(this.props.errors.password) ? this.props.errors.password.errorMessage : ''} /> 60 | 61 | { 67 | this.applyFieldValidation(event); 68 | }} 69 | error={(this.props.errors.confirmPassword) ? this.props.errors.confirmPassword.errorMessage : ''} /> 70 | 72 |
    73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /samples/react/es6/00 SimpleForm/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var webpack = require("webpack"); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | 6 | var basePath = __dirname; 7 | 8 | module.exports = { 9 | context: path.join(basePath, "src"), 10 | resolve: { 11 | extensions: ['.js', '.jsx'], 12 | }, 13 | entry: { 14 | app: './index.jsx', 15 | styles: [ 16 | './css/site.css', 17 | ], 18 | vendor: [ 19 | "core-js", 20 | "lc-form-validation", 21 | "react", 22 | "react-dom", 23 | "react-redux", 24 | "redux", 25 | "redux-thunk" 26 | ], 27 | vendorStyles: [ 28 | '../node_modules/bootstrap/dist/css/bootstrap.css' 29 | ] 30 | }, 31 | 32 | output: { 33 | path: path.join(basePath, "dist"), 34 | filename: '[name].js' 35 | }, 36 | 37 | //https://webpack.github.io/docs/webpack-dev-server.html#webpack-dev-server-cli 38 | devServer: { 39 | contentBase: './dist', //Content base 40 | inline: true, //Enable watch and live reload 41 | host: 'localhost', 42 | port: 8080, 43 | stats: 'errors-only', 44 | }, 45 | 46 | // http://webpack.github.io/docs/configuration.html#devtool 47 | devtool: 'source-map', 48 | 49 | module: { 50 | rules: [ 51 | { 52 | test: /\.jsx?$/, 53 | exclude: /node_modules/, 54 | loader: 'babel-loader', 55 | }, 56 | //Note: Doesn't exclude node_modules to load bootstrap 57 | { 58 | test: /\.css$/, 59 | loader: ExtractTextPlugin.extract({ 60 | fallback: 'style-loader', 61 | use: 'css-loader' 62 | }) 63 | }, 64 | //Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 65 | //Using here url-loader and file-loader 66 | { test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=application/font-woff" }, 67 | { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=application/octet-stream" }, 68 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader" }, 69 | { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=image/svg+xml" } 70 | ] 71 | }, 72 | 73 | plugins: [ 74 | new webpack.optimize.CommonsChunkPlugin({ 75 | name: 'vendor', 76 | }), 77 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 78 | new HtmlWebpackPlugin({ 79 | filename: 'index.html', //Name of file in ./dist/ 80 | template: 'index.html' //Name of template in ./src 81 | }), 82 | //Generate bundle.css => https://github.com/webpack/extract-text-webpack-plugin 83 | new ExtractTextPlugin('[name].css'), 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /samples/react/es6/01 SignupForm/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var webpack = require("webpack"); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | 6 | var basePath = __dirname; 7 | 8 | module.exports = { 9 | context: path.join(basePath, "src"), 10 | resolve: { 11 | extensions: ['.js', '.jsx'], 12 | }, 13 | entry: { 14 | app: './index.jsx', 15 | styles: [ 16 | './css/site.css', 17 | ], 18 | vendor: [ 19 | "core-js", 20 | "lc-form-validation", 21 | "react", 22 | "react-dom", 23 | "react-redux", 24 | "redux", 25 | "redux-thunk", 26 | "whatwg-fetch" 27 | ], 28 | vendorStyles: [ 29 | '../node_modules/bootstrap/dist/css/bootstrap.css' 30 | ] 31 | }, 32 | 33 | output: { 34 | path: path.join(basePath, "dist"), 35 | filename: '[name].js' 36 | }, 37 | 38 | //https://webpack.github.io/docs/webpack-dev-server.html#webpack-dev-server-cli 39 | devServer: { 40 | contentBase: './dist', //Content base 41 | inline: true, //Enable watch and live reload 42 | host: 'localhost', 43 | port: 8080, 44 | stats: 'errors-only', 45 | }, 46 | 47 | // http://webpack.github.io/docs/configuration.html#devtool 48 | devtool: 'source-map', 49 | 50 | module: { 51 | rules: [ 52 | { 53 | test: /\.jsx?$/, 54 | exclude: /node_modules/, 55 | loader: 'babel-loader', 56 | }, 57 | //Note: Doesn't exclude node_modules to load bootstrap 58 | { 59 | test: /\.css$/, 60 | loader: ExtractTextPlugin.extract({ 61 | fallback: 'style-loader', 62 | use: 'css-loader' 63 | }) 64 | }, 65 | //Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 66 | //Using here url-loader and file-loader 67 | { test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=application/font-woff" }, 68 | { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=application/octet-stream" }, 69 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader" }, 70 | { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=image/svg+xml" } 71 | ] 72 | }, 73 | 74 | plugins: [ 75 | new webpack.optimize.CommonsChunkPlugin({ 76 | name: 'vendor', 77 | }), 78 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 79 | new HtmlWebpackPlugin({ 80 | filename: 'index.html', //Name of file in ./dist/ 81 | template: 'index.html' //Name of template in ./src 82 | }), 83 | //Generate bundle.css => https://github.com/webpack/extract-text-webpack-plugin 84 | new ExtractTextPlugin('[name].css'), 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /samples/react/es6/02 QuizForm/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var webpack = require("webpack"); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | 6 | var basePath = __dirname; 7 | 8 | module.exports = { 9 | context: path.join(basePath, "src"), 10 | resolve: { 11 | extensions: ['.js', '.jsx'], 12 | }, 13 | entry: { 14 | app: './index.jsx', 15 | styles: [ 16 | './css/site.css', 17 | ], 18 | vendor: [ 19 | "core-js", 20 | "lc-form-validation", 21 | "react", 22 | "react-dom", 23 | "react-redux", 24 | "redux", 25 | "redux-thunk", 26 | "toastr", 27 | "whatwg-fetch" 28 | ], 29 | vendorStyles: [ 30 | '../node_modules/toastr/build/toastr.css', 31 | '../node_modules/bootstrap/dist/css/bootstrap.css' 32 | ] 33 | }, 34 | 35 | output: { 36 | path: path.join(basePath, "dist"), 37 | filename: '[name].js' 38 | }, 39 | 40 | //https://webpack.github.io/docs/webpack-dev-server.html#webpack-dev-server-cli 41 | devServer: { 42 | contentBase: './dist', //Content base 43 | inline: true, //Enable watch and live reload 44 | host: 'localhost', 45 | port: 8080, 46 | stats: 'errors-only', 47 | }, 48 | 49 | // http://webpack.github.io/docs/configuration.html#devtool 50 | devtool: 'source-map', 51 | 52 | module: { 53 | rules: [ 54 | { 55 | test: /\.jsx?$/, 56 | exclude: /node_modules/, 57 | loader: 'babel-loader', 58 | }, 59 | //Note: Doesn't exclude node_modules to load bootstrap 60 | { 61 | test: /\.css$/, 62 | loader: ExtractTextPlugin.extract({ 63 | fallback: 'style-loader', 64 | use: 'css-loader' 65 | }) 66 | }, 67 | //Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 68 | //Using here url-loader and file-loader 69 | { test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=application/font-woff" }, 70 | { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=application/octet-stream" }, 71 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader" }, 72 | { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=image/svg+xml" } 73 | ] 74 | }, 75 | 76 | plugins: [ 77 | new webpack.optimize.CommonsChunkPlugin({ 78 | name: 'vendor', 79 | }), 80 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 81 | new HtmlWebpackPlugin({ 82 | filename: 'index.html', //Name of file in ./dist/ 83 | template: 'index.html' //Name of template in ./src 84 | }), 85 | //Generate bundle.css => https://github.com/webpack/extract-text-webpack-plugin 86 | new ExtractTextPlugin('[name].css'), 87 | ] 88 | } 89 | -------------------------------------------------------------------------------- /lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lc-form-validation", 3 | "version": "2.0.0", 4 | "description": "lcFormValidation is an async form validation library heavily based on JavaScript (no HTML attributes or annotations). lcFormValidation is third party / framework agnostic so it can be easily integrated with frameworks like React.", 5 | "main": "dist/lc-form-validation.js", 6 | "scripts": { 7 | "test": "karma start --browsers PhantomJS --single-run", 8 | "test:watch": "karma start", 9 | "clean": "rimraf dist ReadmeResources", 10 | "build:prod": "webpack -p --output-filename dist/lc-form-validation.min.js", 11 | "build:dev": "webpack --output-filename dist/lc-form-validation.js", 12 | "build": "npm run clean && npm run build:dev && npm run build:prod", 13 | "prepublish": "npm run build" 14 | }, 15 | "files": [ 16 | "dist", 17 | "src", 18 | "lc-form-validation.d.ts", 19 | "package.json", 20 | "README.md", 21 | "ReadmeResources" 22 | ], 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/Lemoncode/lcFormValidation" 26 | }, 27 | "author": "Braulio Díez Botella", 28 | "license": "MIT", 29 | "homepage": "https://github.com/Lemoncode/lcFormValidation/blob/master/README.md", 30 | "devDependencies": { 31 | "@types/chai": "3.4.35", 32 | "@types/chai-as-promised": "0.0.30", 33 | "@types/karma-chai-sinon": "0.1.5", 34 | "@types/mocha": "2.2.40", 35 | "@types/sinon": "1.16.35", 36 | "awesome-typescript-loader": "3.1.2", 37 | "babel-core": "6.24.0", 38 | "babel-preset-env": "1.2.2", 39 | "chai": "3.5.0", 40 | "chai-as-promised": "5.3.0", 41 | "copy-webpack-plugin": "4.0.1", 42 | "json-loader": "0.5.4", 43 | "karma": "1.5.0", 44 | "karma-chai": "0.1.0", 45 | "karma-chai-as-promised": "0.1.2", 46 | "karma-chrome-launcher": "2.0.0", 47 | "karma-mocha": "1.3.0", 48 | "karma-phantomjs-launcher": "1.0.4", 49 | "karma-sinon": "1.0.5", 50 | "karma-sourcemap-loader": "0.3.7", 51 | "karma-webpack": "2.0.3", 52 | "mocha": "3.2.0", 53 | "phantomjs-prebuilt": "2.1.14", 54 | "rimraf": "2.6.1", 55 | "sinon": "1.17.6", 56 | "typescript": "2.2.1", 57 | "webpack": "2.2.1" 58 | }, 59 | "types": "./lc-form-validation.d.ts", 60 | "keywords": [ 61 | "validation", 62 | "form validation", 63 | "react validation", 64 | "validator", 65 | "lc-form-validation" 66 | ], 67 | "contributors": [ 68 | { 69 | "name": "Braulio Díez", 70 | "email": "braulio.diez@lemoncode.net" 71 | }, 72 | { 73 | "name": "Daniel Sánchez", 74 | "email": "daniel.sanchez@lemoncode.net" 75 | }, 76 | { 77 | "name": "Jaime Salas", 78 | "email": "jaime.salas@lemoncode.net" 79 | }, 80 | { 81 | "name": "Santiago Camargo Rodríguez", 82 | "email": "santi.camargo@lemoncode.net" 83 | } 84 | ], 85 | "dependencies": { 86 | "es6-promise": "4.1.0", 87 | "lodash.get": "^4.4.2", 88 | "lodash.set": "^4.3.2" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /samples/react/typescript/00 SimpleForm/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var webpack = require("webpack"); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | 6 | var basePath = __dirname; 7 | 8 | module.exports = { 9 | context: path.join(basePath, "src"), 10 | resolve: { 11 | // .js is required for react imports. 12 | // .tsx is for our app entry point. 13 | // .ts is optional, in case you will be importing any regular ts files. 14 | extensions: ['.js', '.ts', '.tsx'] 15 | }, 16 | 17 | entry: { 18 | app: './index.tsx', 19 | styles: [ 20 | './css/site.css', 21 | ], 22 | vendor: [ 23 | "core-js", 24 | "lc-form-validation", 25 | "react", 26 | "react-dom", 27 | "react-redux", 28 | "redux", 29 | "redux-thunk" 30 | ], 31 | vendorStyles: [ 32 | '../node_modules/bootstrap/dist/css/bootstrap.css' 33 | ] 34 | }, 35 | 36 | output: { 37 | path: path.join(basePath, "dist"), 38 | filename: '[name].js' 39 | }, 40 | 41 | //https://webpack.github.io/docs/webpack-dev-server.html#webpack-dev-server-cli 42 | devServer: { 43 | contentBase: './dist', //Content base 44 | inline: true, //Enable watch and live reload 45 | host: 'localhost', 46 | stats: 'errors-only', 47 | port: 8080 48 | }, 49 | 50 | // http://webpack.github.io/docs/configuration.html#devtool 51 | devtool: 'source-map', 52 | 53 | module: { 54 | rules: [ 55 | { 56 | test: /\.tsx?$/, 57 | exclude: /node_modules/, 58 | use: { 59 | loader: 'awesome-typescript-loader', 60 | options: { 61 | useBabel: true 62 | } 63 | } 64 | }, 65 | //Note: Doesn't exclude node_modules to load bootstrap 66 | { 67 | test: /\.css$/, 68 | loader: ExtractTextPlugin.extract({ 69 | fallback: 'style-loader', 70 | use: 'css-loader' 71 | }) 72 | }, 73 | //Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 74 | //Using here url-loader and file-loader 75 | { test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=application/font-woff" }, 76 | { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=application/octet-stream" }, 77 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader" }, 78 | { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=image/svg+xml" } 79 | ] 80 | }, 81 | 82 | plugins: [ 83 | new webpack.optimize.CommonsChunkPlugin({ 84 | name: 'vendor', 85 | }), 86 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 87 | new HtmlWebpackPlugin({ 88 | filename: 'index.html', //Name of file in ./dist/ 89 | template: 'index.html' //Name of template in ./src 90 | }), 91 | //Generate bundle.css => https://github.com/webpack/extract-text-webpack-plugin 92 | new ExtractTextPlugin('[name].css'), 93 | ] 94 | } 95 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/pages/recipe/edit/components/form.tsx: -------------------------------------------------------------------------------- 1 | import Vue, {ComponentOptions} from 'vue'; 2 | import {RecipeEntity, RecipeError} from '../../../../model'; 3 | import { 4 | ValidationComponent, InputComponent, InputButtonComponent 5 | } from '../../../../common/components/form'; 6 | import {IngredientListComponent} from './ingredientList'; 7 | 8 | interface FormComponentOptions extends Vue { 9 | recipe: RecipeEntity; 10 | recipeError: RecipeError; 11 | updateRecipe: (name) => void; 12 | addIngredient: (ingredient) => void; 13 | removeIngredient: (ingredient) => void; 14 | save: () => void; 15 | ingredient: string; 16 | addIngredientHandler: (event) => void; 17 | } 18 | 19 | export const FormComponent = Vue.extend({ 20 | props: [ 21 | 'recipe', 22 | 'recipeError', 23 | 'updateRecipe', 24 | 'addIngredient', 25 | 'removeIngredient', 26 | 'save', 27 | ], 28 | data: () => ({ 29 | ingredient: '' 30 | }), 31 | methods: { 32 | addIngredientHandler: function(e) { 33 | e.preventDefault(); 34 | if(this.ingredient) { 35 | this.addIngredient(this.ingredient); 36 | } 37 | }, 38 | }, 39 | render: function(h) { 40 | return ( 41 |
    42 |
    43 | 47 | { this.updateRecipe(e.target.value)}} 53 | /> 54 | 55 |
    56 |
    57 | { this.ingredient = e.target.value}} 63 | buttonText="Add" 64 | buttonClassName="btn btn-primary" 65 | buttonClickHandler={this.addIngredientHandler} 66 | /> 67 |
    68 |
    69 | 73 | 77 | 78 |
    79 |
    80 |
    81 | 88 |
    89 |
    90 |
    91 | ); 92 | }, 93 | } as ComponentOptions); 94 | -------------------------------------------------------------------------------- /samples/react/typescript/01 SignupForm/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var webpack = require("webpack"); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | 6 | var basePath = __dirname; 7 | 8 | module.exports = { 9 | context: path.join(basePath, "src"), 10 | resolve: { 11 | // .js is required for react imports. 12 | // .tsx is for our app entry point. 13 | // .ts is optional, in case you will be importing any regular ts files. 14 | extensions: ['.js', '.ts', '.tsx'] 15 | }, 16 | 17 | entry: { 18 | app: './index.tsx', 19 | styles: [ 20 | './css/site.css', 21 | ], 22 | vendor: [ 23 | "core-js", 24 | "whatwg-fetch", 25 | "lc-form-validation", 26 | "react", 27 | "react-dom", 28 | "react-redux", 29 | "redux", 30 | "redux-thunk" 31 | ], 32 | vendorStyles: [ 33 | '../node_modules/bootstrap/dist/css/bootstrap.css' 34 | ] 35 | }, 36 | 37 | output: { 38 | path: path.join(basePath, "dist"), 39 | filename: '[name].js' 40 | }, 41 | 42 | //https://webpack.github.io/docs/webpack-dev-server.html#webpack-dev-server-cli 43 | devServer: { 44 | contentBase: './dist', //Content base 45 | inline: true, //Enable watch and live reload 46 | host: 'localhost', 47 | stats: 'errors-only', 48 | port: 8080 49 | }, 50 | 51 | // http://webpack.github.io/docs/configuration.html#devtool 52 | devtool: 'source-map', 53 | 54 | module: { 55 | rules: [ 56 | { 57 | test: /\.tsx?$/, 58 | exclude: /node_modules/, 59 | use: { 60 | loader: 'awesome-typescript-loader', 61 | options: { 62 | useBabel: true 63 | } 64 | } 65 | }, 66 | //Note: Doesn't exclude node_modules to load bootstrap 67 | { 68 | test: /\.css$/, 69 | loader: ExtractTextPlugin.extract({ 70 | fallback: 'style-loader', 71 | use: 'css-loader' 72 | }) 73 | }, 74 | //Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 75 | //Using here url-loader and file-loader 76 | { test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=application/font-woff" }, 77 | { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=application/octet-stream" }, 78 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader" }, 79 | { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=image/svg+xml" } 80 | ] 81 | }, 82 | 83 | plugins: [ 84 | new webpack.optimize.CommonsChunkPlugin({ 85 | name: 'vendor', 86 | }), 87 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 88 | new HtmlWebpackPlugin({ 89 | filename: 'index.html', //Name of file in ./dist/ 90 | template: 'index.html' //Name of template in ./src 91 | }), 92 | //Generate bundle.css => https://github.com/webpack/extract-text-webpack-plugin 93 | new ExtractTextPlugin('[name].css'), 94 | ] 95 | } 96 | -------------------------------------------------------------------------------- /samples/react/typescript/02 QuizForm/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var webpack = require("webpack"); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | 6 | var basePath = __dirname; 7 | 8 | module.exports = { 9 | context: path.join(basePath, "src"), 10 | resolve: { 11 | // .js is required for react imports. 12 | // .tsx is for our app entry point. 13 | // .ts is optional, in case you will be importing any regular ts files. 14 | extensions: ['.js', '.ts', '.tsx'] 15 | }, 16 | 17 | entry: { 18 | app: './index.tsx', 19 | styles: [ 20 | './css/site.css', 21 | ], 22 | vendor: [ 23 | "core-js", 24 | "lc-form-validation", 25 | "react", 26 | "react-dom", 27 | "react-redux", 28 | "redux", 29 | "redux-thunk", 30 | "toastr", 31 | ], 32 | vendorStyles: [ 33 | '../node_modules/bootstrap/dist/css/bootstrap.css', 34 | '../node_modules/toastr/build/toastr.css', 35 | ] 36 | }, 37 | 38 | output: { 39 | path: path.join(basePath, "dist"), 40 | filename: '[name].js' 41 | }, 42 | 43 | //https://webpack.github.io/docs/webpack-dev-server.html#webpack-dev-server-cli 44 | devServer: { 45 | contentBase: './dist', //Content base 46 | inline: true, //Enable watch and live reload 47 | host: 'localhost', 48 | stats: 'errors-only', 49 | port: 8080 50 | }, 51 | 52 | // http://webpack.github.io/docs/configuration.html#devtool 53 | devtool: 'source-map', 54 | 55 | module: { 56 | rules: [ 57 | { 58 | test: /\.tsx?$/, 59 | exclude: /node_modules/, 60 | use: { 61 | loader: 'awesome-typescript-loader', 62 | options: { 63 | useBabel: true 64 | } 65 | } 66 | }, 67 | //Note: Doesn't exclude node_modules to load bootstrap 68 | { 69 | test: /\.css$/, 70 | loader: ExtractTextPlugin.extract({ 71 | fallback: 'style-loader', 72 | use: 'css-loader' 73 | }) 74 | }, 75 | //Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack 76 | //Using here url-loader and file-loader 77 | { test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=application/font-woff" }, 78 | { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=application/octet-stream" }, 79 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader" }, 80 | { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=image/svg+xml" } 81 | ] 82 | }, 83 | 84 | plugins: [ 85 | new webpack.optimize.CommonsChunkPlugin({ 86 | name: 'vendor', 87 | }), 88 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 89 | new HtmlWebpackPlugin({ 90 | filename: 'index.html', //Name of file in ./dist/ 91 | template: 'index.html' //Name of template in ./src 92 | }), 93 | //Generate bundle.css => https://github.com/webpack/extract-text-webpack-plugin 94 | new ExtractTextPlugin('[name].css'), 95 | ] 96 | } 97 | -------------------------------------------------------------------------------- /samples/vuejs/typescript/00 Custom Validator/src/pages/recipe/edit/pageContainer.tsx: -------------------------------------------------------------------------------- 1 | import Vue, { ComponentOptions } from 'vue'; 2 | import { RecipeEntity, RecipeError } from '../../../model'; 3 | import { recipeAPI } from '../../../api/recipe'; 4 | import { editFormValidation } from './validations/editFormValidation'; 5 | import { EditRecipePage } from './page'; 6 | 7 | interface EditRecipeContainerOptions extends Vue { 8 | recipe: RecipeEntity; 9 | recipeError: RecipeError; 10 | updateRecipe: (name) => void; 11 | addIngredient: (ingredient) => void; 12 | removeIngredient: (ingredient) => void; 13 | validateRecipeField: (field, value) => void; 14 | updateRecipeError: (field, result) => void; 15 | save: () => void; 16 | } 17 | 18 | export const EditRecipeContainer = Vue.extend({ 19 | render: function(h) { 20 | return ( 21 | 29 | ); 30 | }, 31 | props: [ 32 | 'id' 33 | ], 34 | data: function() { 35 | return { 36 | recipe: new RecipeEntity(), 37 | recipeError: new RecipeError(), 38 | }; 39 | }, 40 | beforeMount: function() { 41 | const id = Number(this["id"]) || 0; 42 | recipeAPI.fetchRecipeById(id) 43 | .then((recipe) => { 44 | this.recipe = recipe; 45 | }); 46 | }, 47 | methods: { 48 | updateRecipe: function(name) { 49 | this.recipe = { 50 | ...this.recipe, 51 | name, 52 | }; 53 | 54 | this.validateRecipeField('name', name); 55 | }, 56 | addIngredient: function(ingredient: string) { 57 | this.recipe = { 58 | ...this.recipe, 59 | ingredients: [...this.recipe.ingredients, ingredient], 60 | }; 61 | 62 | this.validateRecipeField('ingredients', this.recipe.ingredients); 63 | }, 64 | removeIngredient: function(ingredient: string) { 65 | this.recipe = { 66 | ...this.recipe, 67 | ingredients: this.recipe.ingredients.filter((i) => { 68 | return i !== ingredient; 69 | }), 70 | }; 71 | 72 | this.validateRecipeField('ingredients', this.recipe.ingredients); 73 | }, 74 | validateRecipeField: function(field, value) { 75 | editFormValidation.validateField(this.recipe, field, value) 76 | .then((result) => this.updateRecipeError(field, result)); 77 | }, 78 | updateRecipeError: function(field, result) { 79 | this.recipeError = { 80 | ...this.recipeError, 81 | [field]: result, 82 | }; 83 | }, 84 | save: function() { 85 | editFormValidation.validateForm(this.recipe) 86 | .then((result) => { 87 | this.recipeError = { 88 | ...this.recipeError, 89 | ...result.fieldErrors, 90 | }; 91 | 92 | if (result.succeeded) { 93 | console.log('Save recipe'); 94 | } 95 | }); 96 | }, 97 | } 98 | } as ComponentOptions); 99 | 100 | -------------------------------------------------------------------------------- /lib/src/baseFormValidation.ts: -------------------------------------------------------------------------------- 1 | import { ValidationEngine } from './validationEngine'; 2 | import { 3 | ValidationConstraints, 4 | FieldValidationFunction, 5 | FormValidationFunction, 6 | FieldValidationResult, 7 | FormValidationResult, 8 | FieldValidationConstraint, 9 | ValidationEventsFilter, 10 | } from './entities'; 11 | import { consts } from './consts'; 12 | 13 | interface FormValidation { 14 | validateField(vm: any, key: string, value: any, eventsFilter?: any): Promise; 15 | validateForm(vm: any): Promise; 16 | isValidationInProgress(): boolean; 17 | isFormDirty(): boolean; 18 | isFormPristine(): boolean; 19 | } 20 | 21 | export class BaseFormValidation implements FormValidation { 22 | private validationEngine: ValidationEngine; 23 | 24 | constructor(validationConstraints: ValidationConstraints) { 25 | this.validationEngine = new ValidationEngine(); 26 | this.parseValidationConstraints(validationConstraints); 27 | } 28 | 29 | private parseValidationConstraints(validationConstraints: ValidationConstraints) { 30 | if (validationConstraints && typeof validationConstraints === 'object') { 31 | const { global, fields } = validationConstraints; 32 | if (global && global instanceof Array) { 33 | this.parseFormValidations(global); 34 | } 35 | if (fields && typeof fields === 'object') { 36 | this.parseAllFieldsValidations(fields); 37 | } 38 | } 39 | } 40 | 41 | private parseFormValidations(validationFunctions: FormValidationFunction[]) { 42 | validationFunctions.forEach((validationFunction: FormValidationFunction) => { 43 | if (typeof validationFunction === 'function') { 44 | this.validationEngine.addFormValidation(validationFunction); 45 | } 46 | }); 47 | } 48 | 49 | private parseAllFieldsValidations(fields: { [key: string]: FieldValidationConstraint[] }) { 50 | for (let field in fields) { 51 | this.parseFieldValidations(field, fields[field]); 52 | } 53 | } 54 | 55 | private parseFieldValidations(field: string, fieldValidationConstraints: FieldValidationConstraint[]) { 56 | if (fieldValidationConstraints instanceof Array) { 57 | fieldValidationConstraints.forEach((fieldValidationConstraint) => { 58 | if (fieldValidationConstraint && typeof fieldValidationConstraint === 'object') { 59 | this.addFieldValidation(field, fieldValidationConstraint); 60 | } 61 | }); 62 | } 63 | } 64 | 65 | private addFieldValidation(field: string, validationConstraint: FieldValidationConstraint): FormValidation { 66 | this.validationEngine.addFieldValidation( 67 | field, 68 | validationConstraint.validator, 69 | validationConstraint.eventsFilter, 70 | validationConstraint.customParams 71 | ); 72 | return this; 73 | } 74 | 75 | validateField(vm: any, key: string, value: any, eventsFilter?: ValidationEventsFilter): Promise { 76 | return this.validationEngine.validateField(vm, key, value, eventsFilter); 77 | } 78 | 79 | validateForm(vm: any): Promise { 80 | return this.validationEngine.validateForm(vm); 81 | } 82 | 83 | isValidationInProgress(): boolean { 84 | return this.validationEngine.isValidationInProgress(); 85 | } 86 | 87 | isFormDirty(): boolean { 88 | return this.validationEngine.isFormDirty(); 89 | } 90 | 91 | isFormPristine(): boolean { 92 | return this.validationEngine.isFormPristine(); 93 | } 94 | } 95 | 96 | export function createFormValidation(validationConstraints: ValidationConstraints): FormValidation { 97 | return new BaseFormValidation(validationConstraints); 98 | } 99 | -------------------------------------------------------------------------------- /lib/src/validationsDispatcher.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FieldValidationResult, 3 | ValidationResult, 4 | FormValidationFunction, 5 | FieldValidation, 6 | } from './entities'; 7 | import get from 'lodash.get'; 8 | 9 | class ValidationParams { 10 | constructor( 11 | public vm: any, 12 | public value: any, 13 | public validationsPerField: FieldValidation[]) { 14 | } 15 | } 16 | 17 | export class ValidationDispatcher { 18 | fireSingleFieldValidations( 19 | vm: any, 20 | value: any, 21 | fieldValidations: FieldValidation[], 22 | ): Promise { 23 | let validationParams = new ValidationParams(vm, value, fieldValidations); 24 | 25 | let fieldValidationResultPromise = new Promise((resolve, reject) => { 26 | if (fieldValidations && fieldValidations.length > 0) { 27 | this.fireSingleValidation(resolve, reject, validationParams, 0) 28 | } else { 29 | resolve(); 30 | } 31 | }); 32 | 33 | return fieldValidationResultPromise; 34 | } 35 | 36 | private fireSingleValidation( 37 | resolve: any, 38 | reject: any, 39 | validationParams: ValidationParams, 40 | currentIndex: number 41 | ): void { 42 | const fieldValidation = validationParams.validationsPerField[currentIndex]; 43 | fieldValidation.validationFn(validationParams.value, validationParams.vm, fieldValidation.customParams) 44 | .then(fieldValidationResult => { 45 | if (this.fieldValidationFailedOrLastOne(fieldValidationResult, currentIndex, validationParams.validationsPerField.length)) { 46 | resolve(fieldValidationResult); 47 | } else { 48 | currentIndex++; 49 | this.fireSingleValidation(resolve, reject, validationParams, currentIndex); 50 | } 51 | }).catch(() => { 52 | reject(currentIndex); 53 | }); 54 | } 55 | 56 | private fieldValidationFailedOrLastOne(fieldValidationResult: FieldValidationResult, index: number, numberOfItems: number) { 57 | return !fieldValidationResult || 58 | !fieldValidationResult.succeeded || 59 | this.isLastElement(index, numberOfItems); 60 | } 61 | 62 | //TODO: Extract to bussines? 63 | private isLastElement(index: number, length: number) { 64 | return index === (length - 1); 65 | } 66 | 67 | fireAllFieldsValidations( 68 | vm: any, 69 | fieldsToValidate: string[], 70 | validationFn: (vm, key, value) => Promise 71 | ): Promise[] { 72 | 73 | const fieldValidationResultsPromises: Promise[] = []; 74 | 75 | if (this.areParametersDefined(vm, validationFn)) { 76 | fieldsToValidate.forEach((field) => { 77 | const vmFieldValue = get(vm, field, undefined); 78 | if (vmFieldValue !== undefined) { 79 | const fieldValidationResultsPromise = validationFn(vm, field, vmFieldValue); 80 | fieldValidationResultsPromises.push(fieldValidationResultsPromise); 81 | } 82 | }); 83 | } 84 | 85 | return fieldValidationResultsPromises; 86 | } 87 | 88 | fireGlobalFormValidations(vm: any, validations: FormValidationFunction[]) 89 | : ValidationResult[] { 90 | 91 | let validationResultsPromises: ValidationResult[] = []; 92 | 93 | //NOTE: Delegate into validationFn if vm is null, undefined, etc.. 94 | if (this.areParametersDefined(validations)) { 95 | validations.forEach((validationFn) => { 96 | validationResultsPromises.push(validationFn(vm)); 97 | }); 98 | } 99 | 100 | return validationResultsPromises; 101 | } 102 | 103 | //TODO: Extract to bussines? 104 | private areParametersDefined(...parameters) { 105 | return parameters.every(parameter => parameter); 106 | } 107 | } 108 | 109 | let validationsDispatcher = new ValidationDispatcher(); 110 | 111 | export { 112 | validationsDispatcher 113 | } 114 | -------------------------------------------------------------------------------- /lib/src/rules/spec/email.spec.ts: -------------------------------------------------------------------------------- 1 | import { email } from '../email'; 2 | import { FieldValidationResult } from '../../entities'; 3 | 4 | describe('[email] validation rule tests =>', () => { 5 | describe('When validating a non string value', () => { 6 | it('should return true if value is null', () => { 7 | // Arrange 8 | const value = null; 9 | const vm = undefined; 10 | const customParams = undefined; 11 | 12 | // Act 13 | const validationResult = email(value, vm, customParams) as FieldValidationResult; 14 | 15 | // Assert 16 | expect(validationResult.succeeded).to.be.true; 17 | expect(validationResult.type).to.be.equals('EMAIL'); 18 | expect(validationResult.errorMessage).to.be.empty; 19 | }); 20 | 21 | it('should return true if value is undefined', () => { 22 | // Arrange 23 | const value = undefined; 24 | const vm = undefined; 25 | const customParams = undefined; 26 | 27 | // Act 28 | const validationResult = email(value, vm, customParams) as FieldValidationResult; 29 | 30 | // Assert 31 | expect(validationResult.succeeded).to.be.true; 32 | expect(validationResult.type).to.be.equals('EMAIL'); 33 | expect(validationResult.errorMessage).to.be.empty; 34 | }); 35 | 36 | it('should return false if value is number', () => { 37 | // Arrange 38 | const value = Math.PI; 39 | const vm = undefined; 40 | const customParams = undefined; 41 | 42 | // Act 43 | const validationResult = email(value, vm, customParams) as FieldValidationResult; 44 | 45 | // Assert 46 | expect(validationResult.succeeded).to.be.false; 47 | expect(validationResult.type).to.be.equals('EMAIL'); 48 | expect(validationResult.errorMessage).to.be.equals('Please enter a valid email address.'); 49 | }); 50 | 51 | it('should return false if value is an object', () => { 52 | // Arrange 53 | const value = {}; 54 | const vm = undefined; 55 | const customParams = undefined; 56 | 57 | // Act 58 | const validationResult = email(value, vm, customParams) as FieldValidationResult; 59 | 60 | // Assert 61 | expect(validationResult.succeeded).to.be.false; 62 | expect(validationResult.type).to.be.equals('EMAIL'); 63 | expect(validationResult.errorMessage).to.be.equals('Please enter a valid email address.'); 64 | }); 65 | 66 | it('should return false if value is an array', () => { 67 | // Arrange 68 | const value = ['a', '@', 'b', '.', 'c', 'o', 'm']; 69 | const vm = undefined; 70 | const customParams = undefined; 71 | 72 | // Act 73 | const validationResult = email(value, vm, customParams) as FieldValidationResult; 74 | 75 | // Assert 76 | expect(validationResult.succeeded).to.be.false; 77 | expect(validationResult.type).to.be.equals('EMAIL'); 78 | expect(validationResult.errorMessage).to.be.equals('Please enter a valid email address.'); 79 | }); 80 | 81 | it('should return false if value is a function', () => { 82 | // Arrange 83 | const value = () => { }; 84 | const vm = undefined; 85 | const customParams = undefined; 86 | 87 | // Act 88 | const validationResult = email(value, vm, customParams) as FieldValidationResult; 89 | 90 | // Assert 91 | expect(validationResult.succeeded).to.be.false; 92 | expect(validationResult.type).to.be.equals('EMAIL'); 93 | expect(validationResult.errorMessage).to.be.equals('Please enter a valid email address.'); 94 | }); 95 | }); 96 | describe('When validating a string value', () => { 97 | it('should return false for invalid email address', () => { 98 | // Arrange 99 | const value = 'some text'; 100 | const vm = undefined; 101 | const customParams = undefined; 102 | 103 | // Act 104 | const validationResult = email(value, vm, customParams) as FieldValidationResult; 105 | 106 | // Assert 107 | expect(validationResult.succeeded).to.be.false; 108 | expect(validationResult.type).to.be.equals('EMAIL'); 109 | expect(validationResult.errorMessage).to.be.equals('Please enter a valid email address.'); 110 | }); 111 | it('should return true for a valid email address', () => { 112 | // Arrange 113 | const value = 'a@b.com'; 114 | const vm = undefined; 115 | const customParams = undefined; 116 | 117 | // Act 118 | const validationResult = email(value, vm, customParams) as FieldValidationResult; 119 | 120 | // Assert 121 | expect(validationResult.succeeded).to.be.true; 122 | expect(validationResult.type).to.be.equals('EMAIL'); 123 | expect(validationResult.errorMessage).to.be.empty; 124 | }); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /samples/jquery/00 ShoppingForm/src/modules/app/app.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import { productsService } from '../../services/productsService'; 3 | import { productsFormValidation } from './validation/formProductValidationService'; 4 | 5 | let $selBrands, $selProducts, $formProducts, $txtNif, $txtDiscount; 6 | 7 | class App { 8 | constructor() { 9 | this.brands = []; 10 | 11 | this.onBrandSelect = this.onBrandSelect.bind(this); 12 | this.onFieldChange = this.onFieldChange.bind(this); 13 | this.onSubmit = this.onSubmit.bind(this); 14 | 15 | $(this.init.bind(this)); 16 | } 17 | 18 | init() { 19 | this.loadBrands(); 20 | this.initializeSelectors(); 21 | this.setEventHandlers(); 22 | } 23 | 24 | initializeSelectors() { 25 | $selBrands = $('#selBrands'); 26 | $selProducts = $('#selProducts'); 27 | $txtNif = $('#txtNif'); 28 | $txtDiscount = $('#txtDiscount'); 29 | $formProducts = $('#formProducts'); 30 | } 31 | 32 | loadBrands() { 33 | productsService.fetchBrands() 34 | .then(fetchedProducts => { 35 | this.brands = fetchedProducts; 36 | this.loadSelect($selBrands, this.brands); 37 | }); 38 | } 39 | 40 | loadSelect($select, items = []) { 41 | $select.children().not(':first').remove(); 42 | const options = items 43 | .map(item => ``) 44 | .reduce((opts, option) => { 45 | return `${opts}${option}`; 46 | }, ''); 47 | $select.append(options); 48 | } 49 | 50 | setEventHandlers() { 51 | $selBrands.change(this.onBrandSelect); 52 | $txtNif.keyup(this.onFieldChange); 53 | $txtDiscount.keyup(this.onFieldChange); 54 | $txtDiscount.on('input', this.onDiscountType); 55 | $formProducts.submit(this.onSubmit); 56 | } 57 | 58 | onBrandSelect(event) { 59 | this.onFieldChange(event); 60 | const product = Number(event.currentTarget.value) || null; 61 | const isValidProduct = (product !== null); 62 | if (!isValidProduct) { 63 | $selProducts.val(''); 64 | } 65 | this.toggleAvailability($selProducts, isValidProduct); 66 | this.loadProductsByBrand(product); 67 | } 68 | 69 | toggleAvailability($element, isEnabled) { 70 | if (isEnabled) { 71 | $element.removeAttr('disabled'); 72 | } else { 73 | $element.attr('disabled', 'disabled'); 74 | } 75 | } 76 | 77 | loadProductsByBrand(brand) { 78 | if (brand !== null) { 79 | const products = this.getProductsByBrand(brand); 80 | this.loadSelect($selProducts, products); 81 | } 82 | } 83 | 84 | getProductsByBrand(id) { 85 | const brand = this.brands.filter(brand => brand.id === id); 86 | return brand[0] && brand[0].products; 87 | } 88 | 89 | onSubmit(event) { 90 | event.preventDefault(); 91 | const $form = $(event.currentTarget); 92 | const vm = this.getModel($form); 93 | productsFormValidation 94 | .validateForm(vm) 95 | .then(validationResult => { 96 | Object.keys(validationResult.fieldErrors).forEach((key) => { 97 | this.handleFieldValidationResult($form)(validationResult.fieldErrors[key]); 98 | }); 99 | if (validationResult.succeeded) { 100 | console.log('Form is sent'); 101 | } 102 | }) 103 | .catch(error => { 104 | console.log('Error validating form', error); 105 | }); 106 | } 107 | 108 | getModel($form) { 109 | return $form 110 | .serializeArray() 111 | 112 | // from { name: , value: } to [, ] } 113 | .map(field => ({ [field.name]: field.value })) 114 | 115 | // reduce all in an object 116 | .reduce((vm, field) => ({ ...vm, ...field }), {}); 117 | } 118 | 119 | handleFieldValidationResult($form) { 120 | return (fieldValidationResult) => { 121 | const field = $form.get(0)[fieldValidationResult.key]; 122 | this.toggleErrorMessage(fieldValidationResult, $(field)); 123 | }; 124 | } 125 | 126 | onFieldChange(event) { 127 | const $field = $(event.currentTarget); 128 | productsFormValidation 129 | .validateField(null, $field.attr('name'), $field.val()) 130 | .then(validationResult => { 131 | this.toggleErrorMessage(validationResult, $field); 132 | }) 133 | .catch(error => { 134 | console.log('Error validating field', error); 135 | }); 136 | } 137 | 138 | toggleErrorMessage(validationResult, $element) { 139 | const $parent = $element.closest('div.form-group'); 140 | if (validationResult.succeeded) { 141 | $parent.removeClass('has-error').find('span.help-block').html(''); 142 | } else { 143 | $parent.addClass('has-error').find('span.help-block').html(validationResult.errorMessage); 144 | } 145 | } 146 | 147 | onDiscountType(event) { 148 | const $element = $(event.currentTarget); 149 | $element.val($element.val().toUpperCase()); 150 | } 151 | } 152 | 153 | export { 154 | App 155 | }; 156 | -------------------------------------------------------------------------------- /lib/src/rules/spec/maxLength.spec.ts: -------------------------------------------------------------------------------- 1 | import { maxLength } from '../maxLength'; 2 | import { LengthParams } from '../length'; 3 | import { FieldValidationResult } from '../../entities'; 4 | 5 | describe('[maxLength] validation rule tests =>', () => { 6 | describe('When validating a non string value', () => { 7 | it('should return true if value is undefined', () => { 8 | // Arrange 9 | const value = undefined; 10 | const vm = undefined; 11 | const customParams: LengthParams = { length: 4 }; 12 | 13 | // Act 14 | const validationResult: FieldValidationResult = maxLength(value, vm, customParams); 15 | 16 | // Assert 17 | expect(validationResult.succeeded).to.be.true; 18 | expect(validationResult.type).to.be.equals('MAX_LENGTH'); 19 | expect(validationResult.errorMessage).to.be.empty; 20 | }); 21 | 22 | it('should return true if value is null', () => { 23 | // Arrange 24 | const value = null; 25 | const vm = undefined; 26 | const customParams: LengthParams = { length: 4 }; 27 | 28 | // Act 29 | const validationResult: FieldValidationResult = maxLength(value, vm, customParams); 30 | 31 | // Assert 32 | expect(validationResult.succeeded).to.be.true; 33 | expect(validationResult.type).to.be.equals('MAX_LENGTH'); 34 | expect(validationResult.errorMessage).to.be.empty; 35 | }); 36 | }); 37 | 38 | describe('When validating a string value', () => { 39 | it('should return true if value length is lesser than length option', () => { 40 | // Arrange 41 | const value = 'test'; 42 | const vm = undefined; 43 | const customParams: LengthParams = { length: 6 }; 44 | 45 | // Act 46 | const validationResult: FieldValidationResult = maxLength(value, vm, customParams); 47 | 48 | // Assert 49 | expect(validationResult.succeeded).to.be.true; 50 | expect(validationResult.type).to.be.equals('MAX_LENGTH'); 51 | expect(validationResult.errorMessage).to.be.empty; 52 | }); 53 | 54 | it('should return false if value length is greater than length option', () => { 55 | // Arrange 56 | const value = 'test'; 57 | const vm = undefined; 58 | const customParams: LengthParams = { length: 2 }; 59 | 60 | // Act 61 | const validationResult: FieldValidationResult = maxLength(value, vm, customParams); 62 | 63 | // Assert 64 | expect(validationResult.succeeded).to.be.false; 65 | expect(validationResult.type).to.be.equals('MAX_LENGTH'); 66 | expect(validationResult.errorMessage).to.be.equals('The value provided is too long. Length must not exceed 2 characters.'); 67 | }); 68 | 69 | it('should return true if value length is equal than length option', () => { 70 | // Arrange 71 | const value = 'test'; 72 | const vm = undefined; 73 | const customParams: LengthParams = { length: 4 }; 74 | 75 | // Act 76 | const validationResult: FieldValidationResult = maxLength(value, vm, customParams); 77 | 78 | // Assert 79 | expect(validationResult.succeeded).to.be.true; 80 | expect(validationResult.type).to.be.equals('MAX_LENGTH'); 81 | expect(validationResult.errorMessage).to.be.empty; 82 | }); 83 | 84 | it('should return true if value length is 0 and length option is 0', () => { 85 | // Arrange 86 | const value = ''; 87 | const vm = undefined; 88 | const customParams: LengthParams = { length: 0 }; 89 | 90 | // Act 91 | const validationResult: FieldValidationResult = maxLength(value, vm, customParams); 92 | 93 | // Assert 94 | expect(validationResult.succeeded).to.be.true; 95 | expect(validationResult.type).to.be.equals('MAX_LENGTH'); 96 | expect(validationResult.errorMessage).to.be.empty; 97 | }); 98 | }); 99 | 100 | describe('CustomParams boundaries =>', () => { 101 | it('should throw an error if no length option is provided', () => { 102 | // Arrange 103 | const value = 't'; 104 | const vm = undefined; 105 | const customParams: LengthParams = undefined; 106 | const thrownErrorMessage = 'FieldValidationError: Parameter "length" for maxLength in customParams is mandatory and should be a valid number. Example: { length: 4 }.'; 107 | 108 | // Act 109 | // Assert 110 | expect(maxLength.bind(null, value, vm, customParams)).to.throw(Error, thrownErrorMessage); 111 | }); 112 | it('should return false if length is not a valid number', () => { 113 | // Arrange 114 | const value = 'test'; 115 | const vm = undefined; 116 | const customParams: LengthParams = { length: null }; 117 | const thrownErrorMessage = 'FieldValidationError: Parameter "length" for maxLength in customParams is mandatory and should be a valid number. Example: { length: 4 }.'; 118 | 119 | // Act 120 | // Assert 121 | expect(maxLength.bind(null, value, vm, customParams)).to.throw(Error, thrownErrorMessage); 122 | }); 123 | }); 124 | }); 125 | -------------------------------------------------------------------------------- /lib/src/rules/spec/pattern.spec.ts: -------------------------------------------------------------------------------- 1 | import { pattern, PatternParams } from '../pattern'; 2 | import { FieldValidationResult } from '../../entities'; 3 | 4 | describe('[pattern] validation rule tests =>', () => { 5 | describe('Pattern option boundaries =>', () => { 6 | it('should throw an error if pattern is null', () => { 7 | // Arrange 8 | const value = 'test'; 9 | const vm = undefined; 10 | const customParams: PatternParams = { pattern: null }; 11 | const thrownErrrorMessage = 'FieldValidationError: pattern option for pattern validation is mandatory. Example: { pattern: /\d+/ }.'; 12 | 13 | // Act 14 | // Assert 15 | expect(pattern.bind(null, value, vm, customParams)).to.throw(Error, thrownErrrorMessage); 16 | }); 17 | 18 | it('should throw an error if pattern is boolean', () => { 19 | // Arrange 20 | const value = 'test'; 21 | const vm = undefined; 22 | const customParams: PatternParams = { pattern: (false as any) }; 23 | const thrownErrrorMessage = 'FieldValidationError: pattern option for pattern validation is mandatory. Example: { pattern: /\d+/ }.'; 24 | 25 | // Act 26 | // Assert 27 | expect(pattern.bind(null, value, vm, customParams)).to.throw(Error, thrownErrrorMessage); 28 | }); 29 | }); 30 | 31 | describe('Given a string as RegExp in pattern option', () => { 32 | it('should not use the entire pattern as a complete pattern', () => { 33 | // Arrange 34 | const value = 'Some tests needed'; 35 | const vm = undefined; 36 | const customParams: PatternParams = { pattern: 'tests' }; 37 | 38 | // Act 39 | const validationResult = pattern(value, vm, customParams) as FieldValidationResult; 40 | 41 | // Assert 42 | expect(validationResult.succeeded).to.be.true; 43 | expect(validationResult.type).to.be.equals('PATTERN'); 44 | expect(validationResult.errorMessage).to.be.empty; 45 | }); 46 | 47 | it('should return true for a value that matches pattern', () => { 48 | // Arrange 49 | const value = 'test'; 50 | const vm = undefined; 51 | const customParams: PatternParams = { pattern: 'test' }; 52 | 53 | // Act 54 | const validationResult = pattern(value, vm, customParams) as FieldValidationResult; 55 | 56 | // Assert 57 | expect(validationResult.succeeded).to.be.true; 58 | expect(validationResult.type).to.be.equals('PATTERN'); 59 | expect(validationResult.errorMessage).to.be.empty; 60 | }); 61 | }); 62 | 63 | describe('Given a RegExp object in pattern option', () => { 64 | it('should return true if field value matches the given pattern', () => { 65 | // Arrange 66 | const value = 'Some tests needed'; 67 | const vm = undefined; 68 | const customParams: PatternParams = { pattern: /tests/ }; 69 | 70 | // Act 71 | const validationResult = pattern(value, vm, customParams) as FieldValidationResult; 72 | 73 | // Assert 74 | expect(validationResult.succeeded).to.be.true; 75 | expect(validationResult.type).to.be.equals('PATTERN'); 76 | expect(validationResult.errorMessage).to.be.empty; 77 | }); 78 | 79 | it('should return false if field value does not match the given pattern', () => { 80 | // Arrange 81 | const value = 'Some tests needed'; 82 | const vm = undefined; 83 | const customParams: PatternParams = { pattern: /^tests/ }; 84 | 85 | // Act 86 | const validationResult = pattern(value, vm, customParams) as FieldValidationResult; 87 | 88 | // Assert 89 | expect(validationResult.succeeded).to.be.false; 90 | expect(validationResult.type).to.be.equals('PATTERN'); 91 | expect(validationResult.errorMessage).to.be.equals('Please provide a valid format.'); 92 | }); 93 | }); 94 | 95 | describe('Given an empty value', () => { 96 | it('should return true for null value', () => { 97 | // Arrange 98 | const value = null; 99 | const vm = undefined; 100 | const customParams: PatternParams = { pattern: 'test' }; 101 | 102 | // Act 103 | const validationResult = pattern(value, vm, customParams) as FieldValidationResult; 104 | 105 | // Assert 106 | expect(validationResult.succeeded).to.be.true; 107 | expect(validationResult.type).to.be.equals('PATTERN'); 108 | expect(validationResult.errorMessage).to.be.empty; 109 | }); 110 | 111 | it('should return true for undefined value', () => { 112 | // Arrange 113 | const value = undefined; 114 | const vm = undefined; 115 | const customParams: PatternParams = { pattern: 'test' }; 116 | 117 | // Act 118 | const validationResult = pattern(value, vm, customParams) as FieldValidationResult; 119 | 120 | // Assert 121 | expect(validationResult.succeeded).to.be.true; 122 | expect(validationResult.type).to.be.equals('PATTERN'); 123 | expect(validationResult.errorMessage).to.be.empty; 124 | }); 125 | 126 | it('should return true for an empty value', () => { 127 | // Arrange 128 | const value = ''; 129 | const vm = undefined; 130 | const customParams: PatternParams = { pattern: 'test' }; 131 | 132 | // Act 133 | const validationResult = pattern(value, vm, customParams) as FieldValidationResult; 134 | 135 | // Assert 136 | expect(validationResult.succeeded).to.be.true; 137 | expect(validationResult.type).to.be.equals('PATTERN'); 138 | expect(validationResult.errorMessage).to.be.empty; 139 | }); 140 | }); 141 | }); 142 | -------------------------------------------------------------------------------- /lib/src/rules/spec/minLength.spec.ts: -------------------------------------------------------------------------------- 1 | import { minLength } from '../minLength'; 2 | import { LengthParams } from '../length'; 3 | import { FieldValidationResult } from '../../entities'; 4 | 5 | describe('[minLength] validation rule tests =>', () => { 6 | describe('When validating a non string value', () => { 7 | it('should return true if value is undefined', () => { 8 | // Arrange 9 | const value = undefined; 10 | const vm = undefined; 11 | const customParams: LengthParams = { length: 4 }; 12 | 13 | // Act 14 | const validationResult = minLength(value, vm, customParams) as FieldValidationResult; 15 | 16 | // Assert 17 | expect(validationResult.succeeded).to.be.true; 18 | expect(validationResult.type).to.be.equals('MIN_LENGTH'); 19 | expect(validationResult.errorMessage).to.be.empty; 20 | }); 21 | 22 | it('should return true if value is null', () => { 23 | // Arrange 24 | const value = null; 25 | const vm = undefined; 26 | const customParams: LengthParams = { length: 4 }; 27 | 28 | // Act 29 | const validationResult = minLength(value, vm, customParams) as FieldValidationResult; 30 | 31 | // Assert 32 | expect(validationResult.succeeded).to.be.true; 33 | expect(validationResult.type).to.be.equals('MIN_LENGTH'); 34 | expect(validationResult.errorMessage).to.be.empty; 35 | }); 36 | }); 37 | 38 | describe('When validating a string value', () => { 39 | it('should return false for empty strings', () => { 40 | // Arrange 41 | const value = ''; 42 | const vm = undefined; 43 | const customParams: LengthParams = { length: 3 }; 44 | 45 | // Act 46 | const validationResult = minLength(value, vm, customParams) as FieldValidationResult; 47 | 48 | // Assert 49 | expect(validationResult.succeeded).to.be.false; 50 | expect(validationResult.errorMessage).to.be.equals('The value provided must have at least 3 characters.'); 51 | expect(validationResult.type).to.be.equals('MIN_LENGTH'); 52 | }); 53 | 54 | it('should return true if value length is greater than length option', () => { 55 | // Arrange 56 | const value = 'test'; 57 | const vm = undefined; 58 | const customParams: LengthParams = { length: 2 }; 59 | 60 | // Act 61 | const validationResult = minLength(value, vm, customParams) as FieldValidationResult; 62 | 63 | // Assert 64 | expect(validationResult.succeeded).to.be.true; 65 | expect(validationResult.errorMessage).to.be.empty; 66 | expect(validationResult.type).to.be.equals('MIN_LENGTH'); 67 | }); 68 | 69 | it('should return false if value length is lesser than length option', () => { 70 | // Arrange 71 | const value = 'test'; 72 | const vm = undefined; 73 | const customParams: LengthParams = { length: 6 }; 74 | 75 | // Act 76 | const validationResult = minLength(value, vm, customParams) as FieldValidationResult; 77 | 78 | // Assert 79 | expect(validationResult.succeeded).to.be.false; 80 | expect(validationResult.errorMessage).to.be.equals('The value provided must have at least 6 characters.'); 81 | expect(validationResult.type).to.be.equals('MIN_LENGTH'); 82 | }); 83 | 84 | it('should return true if value length is equal than length option', () => { 85 | // Arrange 86 | const value = 'test'; 87 | const vm = undefined; 88 | const customParams: LengthParams = { length: 4 }; 89 | 90 | // Act 91 | const validationResult = minLength(value, vm, customParams) as FieldValidationResult; 92 | 93 | // Assert 94 | expect(validationResult.succeeded).to.be.true; 95 | expect(validationResult.errorMessage).to.be.empty; 96 | expect(validationResult.type).to.be.equals('MIN_LENGTH'); 97 | }); 98 | 99 | it('should return true if value has length greater than 0 and length option is 0', () => { 100 | // Arrange 101 | const value = 'test'; 102 | const vm = undefined; 103 | const customParams: LengthParams = { length: 0 }; 104 | 105 | // Act 106 | const validationResult = minLength(value, vm, customParams) as FieldValidationResult; 107 | 108 | // Assert 109 | expect(validationResult.succeeded).to.be.true; 110 | expect(validationResult.errorMessage).to.be.empty; 111 | expect(validationResult.type).to.be.equals('MIN_LENGTH'); 112 | }); 113 | 114 | it('should return true if valuehas length of 0 and length option is 0', () => { 115 | // Arrange 116 | const value = ''; 117 | const vm = undefined; 118 | const customParams: LengthParams = { length: 0 }; 119 | 120 | // Act 121 | const validationResult = minLength(value, vm, customParams) as FieldValidationResult; 122 | 123 | // Assert 124 | expect(validationResult.succeeded).to.be.true; 125 | expect(validationResult.errorMessage).to.be.empty; 126 | expect(validationResult.type).to.be.equals('MIN_LENGTH'); 127 | }); 128 | }); 129 | 130 | describe('CustomParams boundaries =>', () => { 131 | it('should throw an error if no length option is provided', () => { 132 | // Arrange 133 | const value = 't'; 134 | const vm = undefined; 135 | const customParams: LengthParams = undefined; 136 | const thrownErrorMessage = 'FieldValidationError: Parameter "length" for minLength in customParams is mandatory and should be a valid number. Example: { length: 4 }.'; 137 | 138 | // Act 139 | 140 | // Assert 141 | expect(minLength.bind(null, value, vm, customParams)).to.throw(Error, thrownErrorMessage); 142 | }); 143 | 144 | it('should return false if length option is null', () => { 145 | // Arrange 146 | const value = 'test'; 147 | const vm = undefined; 148 | const customParams: LengthParams = { length: null }; 149 | const thrownErrorMessage = 'FieldValidationError: Parameter "length" for minLength in customParams is mandatory and should be a valid number. Example: { length: 4 }.'; 150 | 151 | // Act 152 | // Assert 153 | expect(minLength.bind(null, value, vm, customParams)).to.throw(Error, thrownErrorMessage); 154 | }); 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /lib/src/rules/spec/required.spec.ts: -------------------------------------------------------------------------------- 1 | import { required, RequiredParams } from '../required'; 2 | import { FieldValidationResult } from '../../entities'; 3 | 4 | describe('[required] validation rule tests =>', () => { 5 | describe('When validating a non string value', () => { 6 | it('should return false if value is null', () => { 7 | // Arrange 8 | const value = null; 9 | const vm = undefined; 10 | const customParams: RequiredParams = undefined; 11 | 12 | // Act 13 | const validationResult = required(value, vm, customParams) as FieldValidationResult; 14 | 15 | // Assert 16 | expect(validationResult.succeeded).to.be.false; 17 | expect(validationResult.type).to.be.equals('REQUIRED'); 18 | expect(validationResult.errorMessage).to.be.equals('Please fill in this mandatory field.'); 19 | }); 20 | 21 | it('should return false if value is undefined', () => { 22 | // Arrange 23 | const value = undefined; 24 | const vm = undefined; 25 | const customParams: RequiredParams = undefined; 26 | 27 | // Act 28 | const validationResult = required(value, vm, customParams) as FieldValidationResult; 29 | 30 | // Assert 31 | expect(validationResult.succeeded).to.be.false; 32 | expect(validationResult.type).to.be.equals('REQUIRED'); 33 | expect(validationResult.errorMessage).to.be.equals('Please fill in this mandatory field.'); 34 | }); 35 | 36 | it('should return false if value is false', () => { 37 | // Arrange 38 | const value = false; 39 | const vm = undefined; 40 | const customParams: RequiredParams = undefined; 41 | 42 | // Act 43 | const validationResult = required(value, vm, customParams) as FieldValidationResult; 44 | 45 | // Assert 46 | expect(validationResult.succeeded).to.be.false; 47 | expect(validationResult.type).to.be.equals('REQUIRED'); 48 | expect(validationResult.errorMessage).to.be.equals('Please fill in this mandatory field.'); 49 | }); 50 | 51 | it('should return true if value is true', () => { 52 | // Arrange 53 | const value = true; 54 | const vm = undefined; 55 | const customParams: RequiredParams = undefined; 56 | 57 | // Act 58 | const validationResult = required(value, vm, customParams) as FieldValidationResult; 59 | 60 | // Assert 61 | expect(validationResult.succeeded).to.be.true; 62 | expect(validationResult.type).to.be.equals('REQUIRED'); 63 | expect(validationResult.errorMessage).to.be.empty; 64 | }); 65 | 66 | }); 67 | 68 | describe('When validating a string value', () => { 69 | it('should return false if string is empty', () => { 70 | // Arrange 71 | const value = ''; 72 | const vm = undefined; 73 | const customParams: RequiredParams = undefined; 74 | 75 | // Act 76 | const validationResult = required(value, vm, customParams) as FieldValidationResult; 77 | 78 | // Assert 79 | expect(validationResult.succeeded).to.be.false; 80 | expect(validationResult.type).to.be.equals('REQUIRED'); 81 | expect(validationResult.errorMessage).to.be.equals('Please fill in this mandatory field.'); 82 | }); 83 | 84 | it('should return true if string has whitespace characters and trim option is false', () => { 85 | // Arrange 86 | const value = ' '; 87 | const vm = undefined; 88 | const customParams: RequiredParams = { trim: false }; 89 | 90 | // Act 91 | const validationResult = required(value, vm, customParams) as FieldValidationResult; 92 | 93 | // Assert 94 | expect(validationResult.succeeded).to.be.true; 95 | expect(validationResult.type).to.be.equals('REQUIRED'); 96 | expect(validationResult.errorMessage).to.be.empty; 97 | }); 98 | 99 | it('should return true if string has whitespace characters and trim option is null', () => { 100 | // Arrange 101 | const value = ' '; 102 | const vm = undefined; 103 | const customParams: RequiredParams = { trim: null }; 104 | 105 | // Act 106 | const validationResult = required(value, vm, customParams) as FieldValidationResult; 107 | 108 | // Assert 109 | expect(validationResult.succeeded).to.be.true; 110 | expect(validationResult.type).to.be.equals('REQUIRED'); 111 | expect(validationResult.errorMessage).to.be.empty; 112 | }); 113 | 114 | it('should return true if string has whitespace characters and trim option is undefined', () => { 115 | // Arrange 116 | const value = ' '; 117 | const vm = undefined; 118 | const customParams: RequiredParams = { trim: undefined }; 119 | 120 | // Act 121 | const validationResult = required(value, vm, customParams) as FieldValidationResult; 122 | 123 | // Assert 124 | expect(validationResult.succeeded).to.be.true; 125 | expect(validationResult.type).to.be.equals('REQUIRED'); 126 | expect(validationResult.errorMessage).to.be.empty; 127 | }); 128 | 129 | it('should return false if string has whitespace characters and trim option is true', () => { 130 | // Arrange 131 | const value = ' '; 132 | const vm = undefined; 133 | const customParams: RequiredParams = { trim: true }; 134 | 135 | // Act 136 | const validationResult = required(value, vm, customParams) as FieldValidationResult; 137 | 138 | // Assert 139 | expect(validationResult.succeeded).to.be.false; 140 | expect(validationResult.type).to.be.equals('REQUIRED'); 141 | expect(validationResult.errorMessage).to.be.equals('Please fill in this mandatory field.'); 142 | }); 143 | 144 | it('should trim by default', () => { 145 | // Arrange 146 | const value = ' '; 147 | const vm = undefined; 148 | const customParams = undefined; 149 | 150 | // Act 151 | const validationResult = required(value, vm, customParams) as FieldValidationResult; 152 | 153 | // Assert 154 | expect(validationResult.succeeded).to.be.false; 155 | expect(validationResult.type).to.be.equals('REQUIRED'); 156 | expect(validationResult.errorMessage).to.be.equals('Please fill in this mandatory field.'); 157 | }); 158 | }); 159 | }); 160 | --------------------------------------------------------------------------------