├── js ├── pre.js ├── transforms.js ├── utils.js ├── aggregates.js ├── rules.js ├── formats.js ├── shower.js └── form.js ├── css └── shower.css ├── .gitignore ├── .jshintrc ├── .versions ├── tests ├── transforms_tests.js ├── defaultValue_tests.js ├── aggregate_tests.js ├── required_tests.js ├── rules_tests.js └── format_tests.js ├── license.md ├── package.js ├── versions.json └── README.md /js/pre.js: -------------------------------------------------------------------------------- 1 | $ = Package.jquery.$; 2 | _ = Package.underscore._; 3 | -------------------------------------------------------------------------------- /css/shower.css: -------------------------------------------------------------------------------- 1 | .meso-error{ 2 | font-size:.8em; 3 | color:red; 4 | padding:0 5px; 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | mesosphere.sublime-project 2 | mesosphere.sublime-workspace 3 | .build* 4 | /.project 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals":{ 3 | "$": true, 4 | "_": true, 5 | "Meteor":false, 6 | "Template": false, 7 | "Package": false, 8 | "document": false, 9 | "Shower": true, 10 | "Form": true, 11 | "Utils": true, 12 | "Rules": true, 13 | "Transforms": true, 14 | "Aggregates": true, 15 | "Formats": true, 16 | "Tinytest": false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.versions: -------------------------------------------------------------------------------- 1 | base64@1.0.3 2 | binary-heap@1.0.3 3 | blaze@2.1.2 4 | blaze-tools@1.0.3 5 | boilerplate-generator@1.0.3 6 | callback-hook@1.0.3 7 | check@1.0.5 8 | copleykj:shower@0.1.16 9 | ddp@1.1.0 10 | deps@1.0.7 11 | ejson@1.0.6 12 | geojson-utils@1.0.3 13 | html-tools@1.0.4 14 | htmljs@1.0.4 15 | id-map@1.0.3 16 | jquery@1.11.3_2 17 | json@1.0.3 18 | local-test:copleykj:shower@0.1.16 19 | logging@1.0.7 20 | meteor@1.1.6 21 | minifiers@1.1.5 22 | minimongo@1.0.8 23 | mongo@1.1.0 24 | mrt:underscore-string-latest@2.3.3 25 | observe-sequence@1.0.6 26 | ordered-dict@1.0.3 27 | random@1.0.3 28 | reactive-var@1.0.5 29 | retry@1.0.3 30 | routepolicy@1.0.5 31 | spacebars@1.0.6 32 | spacebars-compiler@1.0.6 33 | templating@1.1.1 34 | test-helpers@1.0.4 35 | tinytest@1.0.5 36 | tracker@1.0.7 37 | ui@1.0.6 38 | underscore@1.0.3 39 | webapp@1.2.0 40 | webapp-hashing@1.0.3 41 | -------------------------------------------------------------------------------- /js/transforms.js: -------------------------------------------------------------------------------- 1 | //Data transformation functions 2 | Transforms = { 3 | trim: function(string) { 4 | return _(string).trim(); 5 | }, 6 | clean: function(string) { 7 | return _(string).clean(); 8 | }, 9 | capitalize: function(string) { 10 | return _(string).capitalize(); 11 | }, 12 | slugify:function(string) { 13 | return _(string).slugify(); 14 | }, 15 | humanize:function(string) { 16 | return _(string).humanize(); 17 | }, 18 | stripTags: function(string) { 19 | return _(string).stripTags(); 20 | }, 21 | escapeHTML: function(string) { 22 | return _(string).escapeHTML(); 23 | }, 24 | toUpperCase: function(string) { 25 | return string.toUpperCase(); 26 | }, 27 | toLowerCase: function(string) { 28 | return string.toLowerCase(); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /tests/transforms_tests.js: -------------------------------------------------------------------------------- 1 | Shower({ 2 | name: "transForm", 3 | fields: { 4 | alpha1: { 5 | format: "alphanumeric", 6 | message: "rule failed", 7 | transforms: ["clean", "capitalize"] 8 | }, 9 | alpha2: { 10 | format: "alphanumeric", 11 | message: "rule failed", 12 | transforms: ["humanize"] 13 | } 14 | }}); 15 | 16 | Tinytest.add("transforms tests", function (test) { 17 | 18 | var validationObject = Shower.transForm.validate([ 19 | {"name": "alpha1", "value": " olivier refalo "} 20 | ]); 21 | test.isTrue(validationObject.errors === false && validationObject.formData.alpha1==="Olivier refalo"); 22 | 23 | 24 | validationObject = Shower.transForm.validate([ 25 | {"name": "alpha2", "value": " capitalize dash-CamelCase_underscore trim "} 26 | ]); 27 | 28 | test.isTrue(validationObject.errors === false && validationObject.formData.alpha2==="Capitalize dash camel case underscore trim"); 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /tests/defaultValue_tests.js: -------------------------------------------------------------------------------- 1 | Shower({ 2 | name: "defaults", 3 | fields: { 4 | firstName: { 5 | message: "rule failed", 6 | defaultValue: function(formFieldsObject){ 7 | switch(formFieldsObject.gender){ 8 | case "Male": 9 | return "John"; 10 | case "Female": 11 | return "Jane"; 12 | } 13 | } 14 | }, 15 | lastName: { 16 | message: "rule failed", 17 | defaultValue: "Doe" 18 | } 19 | }}); 20 | 21 | Tinytest.add("default value tests", function (test) { 22 | 23 | var validationObject = Shower.defaults.validate([ 24 | {"name": "firstName", "value": ""}, 25 | {"name": "lastName", "value": ""}, 26 | {"name": "gender", "value": "Female"} 27 | ]); 28 | test.isTrue(validationObject.errors === false && validationObject.formData.firstName==="Jane"); 29 | 30 | test.isTrue(validationObject.errors === false && validationObject.formData.lastName==="Doe"); 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © <2013> 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /js/utils.js: -------------------------------------------------------------------------------- 1 | Utils = { 2 | getFormData: function(formElem){ 3 | var formData = $(formElem).serializeArray(), fileInputs = $(formElem).find("input[type=file]"); 4 | 5 | fileInputs.each(function () { 6 | var fileSize = 0, fileType = '', fieldName = this.name; 7 | 8 | if (this.files.length > 0) { 9 | fileSize = this.files[0].size; 10 | fileType = this.files[0].type; 11 | } 12 | formData.push({name: fieldName, fileSize: fileSize, fileType: fileType, files: this.files}); 13 | }); 14 | 15 | return formData; 16 | }, 17 | failureCallback: function(erroredFields, formHandle){ 18 | $(".meso-error").text(""); 19 | _(erroredFields).each(function(value, key) { 20 | formHandle.find("#"+key+"-error").addClass("meso-error").text(value.message); 21 | }); 22 | }, 23 | successCallback:function(formData, formHandle){ 24 | if(formHandle[0] && formHandle[0].reset) { 25 | formHandle[0].reset(); 26 | } 27 | $(".meso-error").text(""); 28 | $(".meso-error").removeClass("meso-error"); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /tests/aggregate_tests.js: -------------------------------------------------------------------------------- 1 | Shower({ 2 | name: "aggregatesForm", 3 | aggregates:{ 4 | birthDate:["join", ["year", "month", "day"], " "], 5 | sum:["sum", ["int1", "int2", "int3"]], 6 | avg:["avg", ["int1", "int2", "int3"]] 7 | }, 8 | fields: { 9 | 10 | } 11 | }); 12 | 13 | Shower.registerRule("minAge", function(fieldValue, ruleValue){ 14 | var birthDate = new Date(fieldValue).getTime(); 15 | var age = parseInt((new Date().getTime() - birthDate) / (1000 * 365 * 60 * 60 * 24), 10); 16 | if(age <= ruleValue){ 17 | return false; 18 | } 19 | return true; 20 | }); 21 | 22 | Tinytest.add("aggregates tests", function (test) { 23 | 24 | var validationObject = Shower.aggregatesForm.validate([ 25 | {"name": "year", "value": "1982"}, 26 | {"name": "month", "value": "November"}, 27 | {"name": "day", "value": "08"} 28 | ]); 29 | test.isTrue(validationObject.errors === false && validationObject.formData.birthDate==="1982 November 08"); 30 | 31 | validationObject = Shower.aggregatesForm.validate([ 32 | {"name": "int1", "value": "1"}, 33 | {"name": "int2", "value": "1.5"}, 34 | {"name": "int3", "value": "3.5"} 35 | ]); 36 | test.isTrue(validationObject.formData.avg==="2" && validationObject.formData.sum==="6"); 37 | }); 38 | -------------------------------------------------------------------------------- /js/aggregates.js: -------------------------------------------------------------------------------- 1 | Aggregates = { 2 | sum: function(fields, formFieldsObject){ 3 | var sum = 0; 4 | _(fields).each( function(fieldName) { 5 | var fieldValue = parseFloat(formFieldsObject[fieldName]); 6 | if(_.isNumber(fieldValue)){ 7 | sum += fieldValue; 8 | } 9 | }); 10 | return sum.toString(); 11 | }, 12 | avg: function(fields, formFieldsObject){ 13 | var sum = parseFloat(this.sum(fields, formFieldsObject)); 14 | sum = sum / fields.length; 15 | return sum.toString(); 16 | }, 17 | join: function(fields, formFieldsObject, argument){ 18 | var fieldValues = []; 19 | _(fields).each( function(fieldName) { 20 | fieldValues.push(formFieldsObject[fieldName]); 21 | }); 22 | return fieldValues.join(argument); 23 | }, 24 | arraySet: function(fields, formFieldsObject){ 25 | var newField = []; 26 | _(fields).each( function(fieldName) { 27 | newField.push(formFieldsObject[fieldName]); 28 | }); 29 | return newField; 30 | }, 31 | objectSet: function(fields, formFieldsObject){ 32 | var newField = {}; 33 | _(fields).each( function(fieldName) { 34 | newField[fieldName] = formFieldsObject[fieldName]; 35 | }); 36 | return newField; 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /js/rules.js: -------------------------------------------------------------------------------- 1 | //Rules are always passed 5 arguments, fieldValue, ruleValue, fieldName, formFieldsObject and fieldRequirements respectively. 2 | Rules = { 3 | maxLength: function(fieldValue, ruleValue) { 4 | return fieldValue.length <= ruleValue; 5 | }, 6 | minLength: function(fieldValue, ruleValue) { 7 | return fieldValue.length >= ruleValue; 8 | }, 9 | exactLength: function (fieldValue, ruleValue) { 10 | // keep comparator as == 11 | return fieldValue.length == ruleValue; 12 | }, 13 | failIfFound:function (fieldValue, ruleValue) { 14 | return fieldValue.indexOf(ruleValue) === -1; 15 | }, 16 | minValue: function(fieldValue, ruleValue) { 17 | return fieldValue >= ruleValue; 18 | }, 19 | maxValue: function(fieldValue, ruleValue) { 20 | return fieldValue <= ruleValue; 21 | }, 22 | equalsValue: function(fieldValue, ruleValue) { 23 | // keep comparator as == 24 | return fieldValue == ruleValue; 25 | }, 26 | equalsField: function(fieldValue, ruleValue, fieldName, formFieldsObject) { 27 | return formFieldsObject[ruleValue] == fieldValue 28 | }, 29 | notEqualsField: function(fieldValue, ruleValue, fieldName, formFieldsObject) { 30 | return formFieldsObject[ruleValue] != fieldValue 31 | }, 32 | maxFileSize: function(fieldValue, ruleValue) { 33 | return this.maxValue(fieldValue.fileSize, ruleValue); 34 | }, 35 | acceptedFileTypes: function(fieldValue, ruleValue) { 36 | var fileType = fieldValue.FileType; 37 | return ruleValue.indexOf(fileType) >= 0; 38 | } 39 | 40 | }; 41 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name:"copleykj:shower", 3 | summary: "A form-data validation and transformation package for Meteor", 4 | version: "0.1.16", 5 | git: "https://github.com/copleykj/meteor-shower.git" 6 | }); 7 | 8 | Package.onUse(function (api) { 9 | api.versionsFrom("METEOR@0.9.0"); 10 | 11 | api.use(['templating','jquery', 'underscore', "mrt:underscore-string-latest@2.3.3"], ['client', 'server']); 12 | api.addFiles( 13 | [ 14 | 'js/pre.js', 15 | 'js/aggregates.js', 16 | 'js/formats.js', 17 | 'js/rules.js', 18 | 'js/transforms.js', 19 | 'js/utils.js', 20 | 'js/form.js', 21 | 'js/shower.js' 22 | ], 23 | ['client', 'server'] 24 | ); 25 | 26 | api.addFiles('css/shower.css', 'client'); 27 | api.export('Shower'); 28 | }); 29 | 30 | Package.onTest(function (api) { 31 | api.use(['jquery', 'underscore', 'test-helpers', 'tinytest', 'mrt:underscore-string-latest@2.3.3'], ['client', 'server']); 32 | api.addFiles( 33 | [ 34 | 'js/pre.js', 35 | 'js/aggregates.js', 36 | 'js/formats.js', 37 | 'js/rules.js', 38 | 'js/transforms.js', 39 | 'js/utils.js', 40 | 'js/form.js', 41 | 'js/shower.js' 42 | ], 43 | ['client', 'server'] 44 | ); 45 | 46 | api.addFiles('tests/required_tests.js', ['client', 'server']); 47 | api.addFiles('tests/format_tests.js', ['client', 'server']); 48 | api.addFiles('tests/rules_tests.js', ['client', 'server']); 49 | api.addFiles('tests/transforms_tests.js', ['client', 'server']); 50 | api.addFiles('tests/aggregate_tests.js', ['client', 'server']); 51 | api.addFiles('tests/defaultValue_tests.js', ['client', 'server']); 52 | }); 53 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | [ 4 | "application-configuration", 5 | "1.0.3" 6 | ], 7 | [ 8 | "base64", 9 | "1.0.1" 10 | ], 11 | [ 12 | "binary-heap", 13 | "1.0.1" 14 | ], 15 | [ 16 | "blaze", 17 | "2.0.3" 18 | ], 19 | [ 20 | "blaze-tools", 21 | "1.0.1" 22 | ], 23 | [ 24 | "boilerplate-generator", 25 | "1.0.1" 26 | ], 27 | [ 28 | "callback-hook", 29 | "1.0.1" 30 | ], 31 | [ 32 | "check", 33 | "1.0.2" 34 | ], 35 | [ 36 | "ddp", 37 | "1.0.12" 38 | ], 39 | [ 40 | "deps", 41 | "1.0.5" 42 | ], 43 | [ 44 | "ejson", 45 | "1.0.4" 46 | ], 47 | [ 48 | "follower-livedata", 49 | "1.0.2" 50 | ], 51 | [ 52 | "geojson-utils", 53 | "1.0.1" 54 | ], 55 | [ 56 | "html-tools", 57 | "1.0.2" 58 | ], 59 | [ 60 | "htmljs", 61 | "1.0.2" 62 | ], 63 | [ 64 | "id-map", 65 | "1.0.1" 66 | ], 67 | [ 68 | "jquery", 69 | "1.0.1" 70 | ], 71 | [ 72 | "json", 73 | "1.0.1" 74 | ], 75 | [ 76 | "logging", 77 | "1.0.5" 78 | ], 79 | [ 80 | "meteor", 81 | "1.1.3" 82 | ], 83 | [ 84 | "minifiers", 85 | "1.1.2" 86 | ], 87 | [ 88 | "minimongo", 89 | "1.0.5" 90 | ], 91 | [ 92 | "mongo", 93 | "1.0.9" 94 | ], 95 | [ 96 | "mrt:underscore-string-latest", 97 | "2.3.3" 98 | ], 99 | [ 100 | "observe-sequence", 101 | "1.0.3" 102 | ], 103 | [ 104 | "ordered-dict", 105 | "1.0.1" 106 | ], 107 | [ 108 | "random", 109 | "1.0.1" 110 | ], 111 | [ 112 | "reactive-var", 113 | "1.0.3" 114 | ], 115 | [ 116 | "retry", 117 | "1.0.1" 118 | ], 119 | [ 120 | "routepolicy", 121 | "1.0.2" 122 | ], 123 | [ 124 | "spacebars", 125 | "1.0.3" 126 | ], 127 | [ 128 | "spacebars-compiler", 129 | "1.0.3" 130 | ], 131 | [ 132 | "templating", 133 | "1.0.9" 134 | ], 135 | [ 136 | "tracker", 137 | "1.0.3" 138 | ], 139 | [ 140 | "ui", 141 | "1.0.4" 142 | ], 143 | [ 144 | "underscore", 145 | "1.0.1" 146 | ], 147 | [ 148 | "webapp", 149 | "1.1.4" 150 | ], 151 | [ 152 | "webapp-hashing", 153 | "1.0.1" 154 | ] 155 | ], 156 | "pluginDependencies": [], 157 | "toolVersion": "meteor-tool@1.0.36", 158 | "format": "1.0" 159 | } -------------------------------------------------------------------------------- /js/formats.js: -------------------------------------------------------------------------------- 1 | Formats = { 2 | email: /^(([^<>()[\]\\.,;:\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,}))$/, 3 | money: /^[\$\€\£\¥]?[-]?[0-9]*[\.]?[0-9]+$/, 4 | integer: /^[-]?\d+$/, 5 | boolean: /^(yes|no|true|false|0|1)$/i, 6 | hex: /^[a-fA-F0-9]+$/, 7 | float: /^[-]?[0-9]*[\.]?[0-9]+$/, 8 | alphanumeric: /^[a-zA-Z0-9\ \']+$/, 9 | ipv4: /^((([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))[.]){3}(([0-1]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))$/, 10 | phone: /^([\+][0-9]{1,3}[\ \.\-])?([\(]{1}[0-9]{2,6}[\)])?([0-9\ \.\-\/]{3,20})((x|ext|extension)[\ ]?[0-9]{1,4})?$/, 11 | url: /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i, 12 | 13 | //TODO: Cleanup.. 14 | // Move var declarations to the top since they are hoisted anyway 15 | // Prefer while over do->while 16 | creditcard: function (val) { 17 | //spaces and dashes may be valid characters, but must be stripped to calculate the checksum. 18 | var valid = false, cardNumber = val.replace(/ +/g, '').replace(/-+/g, ''); 19 | 20 | var numDigits = cardNumber.length; 21 | 22 | if (numDigits >= 14 && numDigits <= 16 && parseInt(cardNumber, 10) > 0) { 23 | 24 | var sum = 0, i = numDigits - 1, pos = 1, digit, luhn = ""; 25 | do { 26 | digit = parseInt(cardNumber.charAt(i), 10); 27 | luhn += (pos++ % 2 === 0) ? digit * 2 : digit; 28 | } while (--i >= 0); 29 | 30 | for (i = 0; i < luhn.length; i++) { 31 | sum += parseInt(luhn.charAt(i), 10); 32 | } 33 | valid = sum % 10 === 0; 34 | } 35 | return valid; 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /tests/required_tests.js: -------------------------------------------------------------------------------- 1 | Shower({ 2 | name: "requiredForm", 3 | fields: { 4 | email: { 5 | // field is optional 6 | format: "email", 7 | message: "not an email" 8 | }, 9 | notifications: { 10 | // notification required when email filled 11 | required: { dependsOn: "email"}, 12 | format: "boolean", 13 | message: "Would you like to be notified?" 14 | }, 15 | country: { 16 | // country is required 17 | required: true, 18 | format: /^(USA|France)$/, 19 | message: "Please pick a country" 20 | }, 21 | zipcode: { 22 | // zipcode is only required when country="USA" 23 | required: { 24 | dependsOn: "country", 25 | value: "USA" 26 | }, 27 | format: /^[0-9]{5}$/ 28 | } 29 | } 30 | }); 31 | 32 | Tinytest.add("required test", function (test) { 33 | 34 | // debugger; 35 | 36 | var validationObject = Shower.requiredForm.validate([ 37 | {"name": "country", "value": "France"} 38 | ]); 39 | test.isTrue(validationObject.errors === false); 40 | 41 | validationObject = Shower.requiredForm.validate([ 42 | {"name": "country", "value": "USA"}, 43 | {"name": "zipcode", "value": "33178"} 44 | ]); 45 | test.isTrue(validationObject.errors === false); 46 | 47 | validationObject = Shower.requiredForm.validate([ 48 | {"name": "country", "value": "USA"}, 49 | {"name": "zipcode", "value": "33178"}, 50 | {"name": "email", "value": "nono@no.no"}, 51 | {"name": "notifications", "value": "true"} 52 | ]); 53 | test.isTrue(validationObject.errors === false); 54 | 55 | // BAD CASES 56 | 57 | validationObject = Shower.requiredForm.validate([ 58 | {"name": "country", "value": "USA"}, 59 | {"name": "zipcode", "value": "33178"}, 60 | {"name": "email", "value": "nono@no.no"}, 61 | {"name": "notifications", "value": ""} 62 | ]); 63 | test.isTrue(validationObject.errors !== false); 64 | 65 | // missing dependsOn required fields 66 | validationObject = Shower.requiredForm.validate([ 67 | {"name": "email", "value": "email@domain.ext"}, 68 | {"name": "country", "value": "USA"} 69 | ]); 70 | test.isTrue(validationObject.errors !== false); 71 | 72 | // missing required fields 73 | validationObject = Shower.requiredForm.validate([ 74 | {"name": "anotherField", "value": "+1 (305) 6131234 ext 123"} 75 | ]); 76 | test.isTrue(validationObject.errors !== false); 77 | 78 | // country invalid 79 | validationObject = Shower.requiredForm.validate([ 80 | {"name": "country", "value": ""} 81 | ]); 82 | test.isTrue(validationObject.errors !== false); 83 | 84 | // zip code expected 85 | validationObject = Shower.requiredForm.validate([ 86 | {"name": "country", "value": "USA"} 87 | ]); 88 | test.isTrue(validationObject.errors !== false); 89 | 90 | // missing required fields 91 | validationObject = Shower.requiredForm.validate([ 92 | ]); 93 | test.isTrue(validationObject.errors !== false); 94 | } 95 | ); 96 | -------------------------------------------------------------------------------- /js/shower.js: -------------------------------------------------------------------------------- 1 | Shower = function(optionsObject){ 2 | var selector = ""; 3 | var formIdentifier = optionsObject.name || optionsObject.id; 4 | 5 | optionsObject = _({onSuccess:Utils.successCallback, onFailure:Utils.failureCallback}).extend(optionsObject); 6 | 7 | //Make sure they've got all the info we need and they haven't provided the same form information twice 8 | if(!formIdentifier){ 9 | throw new Error("Please specify the name of the form to validate."); 10 | } 11 | if(!optionsObject.fields){ 12 | throw new Error("Please specify which fields to validate."); 13 | } 14 | if(Shower[formIdentifier]){ 15 | throw new Error("Form is already being validated"); 16 | } 17 | 18 | //Create a new form object scoped to Shower.formName 19 | Shower[formIdentifier] = new Form(optionsObject.fields, optionsObject.aggregates, optionsObject.removeFields, optionsObject.onSuccess, optionsObject.onFailure); 20 | 21 | //if this is the browser, set up a submit event handler. 22 | if(Meteor.isClient){ 23 | var events = {}; 24 | 25 | //decide which selector to use to grab the form handle 26 | if(optionsObject.name){ 27 | selector = 'form[name='+formIdentifier+']'; 28 | }else{ 29 | selector = '#'+formIdentifier; 30 | } 31 | 32 | 33 | if(!optionsObject.disableSubmit){ 34 | 35 | if(optionsObject.template && _(optionsObject.template).isString()){ 36 | events['submit '+ selector] = function (event) { 37 | var formFields = Shower.Utils.getFormData(event.target); 38 | Shower[formIdentifier].setSelector(event.target); 39 | event.preventDefault(); 40 | 41 | if(optionsObject.onSubmit){ 42 | optionsObject.onSubmit(event); 43 | } 44 | 45 | if(_(optionsObject.method).isFunction()){ 46 | optionsObject.method(formFields, this); 47 | }else{ 48 | Meteor.call(optionsObject.method, formFields, this); 49 | } 50 | }; 51 | Template[optionsObject.template].events(events); 52 | }else{ 53 | $(function(){ 54 | //attach a submit event to the form 55 | $(document.body).on('submit', selector, function (event) { 56 | event.preventDefault(); 57 | 58 | if(optionsObject.onSubmit){ 59 | optionsObject.onSubmit(event); 60 | } 61 | 62 | var formFields = Utils.getFormData(this); 63 | Shower[formIdentifier].setSelector(event.target); 64 | 65 | if(_(optionsObject.method).isFunction()){ 66 | optionsObject.method(formFields); 67 | }else{ 68 | Meteor.call(optionsObject.method, formFields); 69 | } 70 | }); 71 | }); 72 | } 73 | 74 | 75 | } 76 | 77 | 78 | } 79 | }; 80 | 81 | Shower.Rules = Rules; 82 | Shower.Transforms = Transforms; 83 | Shower.Formats = Formats; 84 | Shower.Aggregates = Aggregates; 85 | Shower.Utils = Utils; 86 | 87 | Shower.registerAggregate = function (name, fn) { 88 | if (Shower.Aggregates[name]) { 89 | throw new Error(name + " is already defined as a aggregate."); 90 | } 91 | Shower.Aggregates[name] = fn; 92 | }; 93 | 94 | Shower.registerFormat = function (name, fn) { 95 | if (Shower.Formats[name]) { 96 | throw new Error(name + " is already defined as a format."); 97 | } 98 | Shower.Formats[name] = fn; 99 | }; 100 | 101 | Shower.registerRule = function (name, fn) { 102 | if (Shower.Rules[name]) { 103 | throw new Error(name + " is already defined as a rule."); 104 | } 105 | Shower.Rules[name] = fn; 106 | }; 107 | 108 | Shower.registerTransform = function (name, fn) { 109 | if (Shower.Transforms[name]) { 110 | throw new Error(name + " is already defined as a transform."); 111 | } 112 | Shower.Transforms[name] = fn; 113 | }; 114 | -------------------------------------------------------------------------------- /tests/rules_tests.js: -------------------------------------------------------------------------------- 1 | Shower({ 2 | name: "rulesForm", 3 | fields: { 4 | 5 | int1: { 6 | format: "integer", 7 | message: "bad range", 8 | rules: { 9 | maxValue: 20, 10 | minValue: 4 11 | } 12 | }, 13 | int2: { 14 | format: "integer", 15 | message: "bad range", 16 | rules: { 17 | equalsValue: 10 18 | } 19 | }, 20 | int3: { 21 | format: "integer", 22 | message: "only positive", 23 | rules: { 24 | failIfFound: "-" 25 | } 26 | }, 27 | float1: { 28 | format: "float", 29 | message: "bad range", 30 | rules: { 31 | maxValue: 20.5, 32 | minValue: 4.1 33 | } 34 | }, 35 | float2: { 36 | format: "float", 37 | message: "bad range", 38 | rules: { 39 | equalsValue: 10.734 40 | } 41 | }, 42 | alpha1: { 43 | format: "alphanumeric", 44 | message: "rule failed", 45 | rules: { 46 | minLength: 10, 47 | maxLength: 20, 48 | failIfFound: "error" 49 | } 50 | }, 51 | alpha2: { 52 | format: "alphanumeric", 53 | message: "rule failed", 54 | rules: { 55 | exactLength: 10 56 | } 57 | } 58 | }}); 59 | 60 | 61 | Tinytest.add("minValue maxValue equalsValue rules", function (test) { 62 | 63 | 64 | var validationObject = Shower.rulesForm.validate([ 65 | {"name": "int1", "value": ""} 66 | ]); 67 | test.isTrue(validationObject.errors === false); 68 | 69 | validationObject = Shower.rulesForm.validate([ 70 | {"name": "int1", "value": 4} 71 | ]); 72 | test.isTrue(validationObject.errors === false); 73 | 74 | validationObject = Shower.rulesForm.validate([ 75 | {"name": "int1", "value": 20} 76 | ]); 77 | test.isTrue(validationObject.errors === false); 78 | 79 | validationObject = Shower.rulesForm.validate([ 80 | {"name": "int2", "value": 10} 81 | ]); 82 | test.isTrue(validationObject.errors === false); 83 | 84 | validationObject = Shower.rulesForm.validate([ 85 | {"name": "int2", "value": "10"} 86 | ]); 87 | test.isTrue(validationObject.errors === false); 88 | 89 | // floats 90 | 91 | validationObject = Shower.rulesForm.validate([ 92 | {"name": "float1", "value": 4.1} 93 | ]); 94 | test.isTrue(validationObject.errors === false); 95 | 96 | validationObject = Shower.rulesForm.validate([ 97 | {"name": "float1", "value": 5.7} 98 | ]); 99 | test.isTrue(validationObject.errors === false); 100 | 101 | validationObject = Shower.rulesForm.validate([ 102 | {"name": "float1", "value": 20.5} 103 | ]); 104 | test.isTrue(validationObject.errors === false); 105 | 106 | validationObject = Shower.rulesForm.validate([ 107 | {"name": "float2", "value": 10.734} 108 | ]); 109 | test.isTrue(validationObject.errors === false); 110 | 111 | 112 | // BAD CASES 113 | 114 | // int out of range 115 | validationObject = Shower.rulesForm.validate([ 116 | {"name": "int1", "value": 3} 117 | ]); 118 | test.isTrue(validationObject.errors !== false); 119 | 120 | validationObject = Shower.rulesForm.validate([ 121 | {"name": "int2", "value": 50} 122 | ]); 123 | test.isTrue(validationObject.errors !== false); 124 | 125 | validationObject = Shower.rulesForm.validate([ 126 | {"name": "float2", "value": 10} 127 | ]); 128 | test.isTrue(validationObject.errors !== false); 129 | 130 | 131 | } 132 | ); 133 | 134 | Tinytest.add("failIfFound rules", function (test) { 135 | 136 | 137 | var validationObject = Shower.rulesForm.validate([ 138 | {"name": "int3", "value": "100"} 139 | ]); 140 | test.isTrue(validationObject.errors === false); 141 | 142 | // BAD CASES 143 | 144 | validationObject = Shower.rulesForm.validate([ 145 | {"name": "int3", "value": "-100"} 146 | ]); 147 | test.isTrue(validationObject.errors !== false); 148 | }); 149 | 150 | Tinytest.add("minLength maxLength exactLength rules", function (test) { 151 | 152 | 153 | var validationObject = Shower.rulesForm.validate([ 154 | {"name": "alpha1", "value": "012345678901"} 155 | ]); 156 | test.isTrue(validationObject.errors === false); 157 | 158 | validationObject = Shower.rulesForm.validate([ 159 | {"name": "alpha2", "value": "01234567890123456789"} 160 | ]); 161 | test.isTrue(validationObject.errors !== false); 162 | 163 | // BAD CASES 164 | 165 | validationObject = Shower.rulesForm.validate([ 166 | {"name": "alpha1", "value": "012345"} 167 | ]); 168 | test.isTrue(validationObject.errors !== false); 169 | 170 | validationObject = Shower.rulesForm.validate([ 171 | {"name": "alpha1", "value": "012345678901234567890123456789"} 172 | ]); 173 | test.isTrue(validationObject.errors !== false); 174 | 175 | 176 | validationObject = Shower.rulesForm.validate([ 177 | {"name": "alpha2", "value": "012345"} 178 | ]); 179 | test.isTrue(validationObject.errors !== false); 180 | }); 181 | -------------------------------------------------------------------------------- /js/form.js: -------------------------------------------------------------------------------- 1 | Form = function(fields, aggregates, removeFields, onSuccess, onFailure, onSubmit){ 2 | this.fields = fields; 3 | this.onSuccess = onSuccess; 4 | this.onFailure = onFailure; 5 | this.aggregates = aggregates; 6 | this.removeFields = removeFields; 7 | this.onSubmit = onSubmit; 8 | this.erroredFields = {}; 9 | this.selector = ""; 10 | }; 11 | 12 | Form.prototype.setSelector = function(selector){ 13 | this.selector = selector; 14 | }; 15 | 16 | Form.prototype.validate = function (formFields, callback){ 17 | var self = this, result; 18 | var formFieldsObject = _.isArray(formFields) ? this.formToObject(formFields) : formFields; 19 | 20 | self.erroredFields = {}; 21 | 22 | _(self.fields).each( function(field, fieldName) { 23 | 24 | // get the current value of the field that we are validating 25 | var fieldValue = formFieldsObject[fieldName]; 26 | 27 | if(_.isEmpty(fieldValue) && field.defaultValue){ 28 | if(_.isFunction(field.defaultValue)){ 29 | formFieldsObject[fieldName] = field.defaultValue(formFieldsObject); 30 | }else{ 31 | formFieldsObject[fieldName] = field.defaultValue; 32 | } 33 | } 34 | 35 | // check if field is required (or conditional required) 36 | if (field.required && !(fieldValue && _(fieldValue).trim().length > 0)) { 37 | 38 | // simple case - required=true 39 | if (field.required === true) { 40 | self.addFieldError(fieldName, "required"); 41 | } else { 42 | // more complex case - required:{dependsOn: "otherfield"} 43 | if (field.required.dependsOn) { 44 | var dependsOnValue = formFieldsObject[field.required.dependsOn]; 45 | if (dependsOnValue && _(dependsOnValue).trim().length > 0) { 46 | if (field.required.value) { 47 | // even more complex case - required:{dependsOn: "otherfield", value:"USA"} 48 | if (field.required.value === dependsOnValue) { 49 | self.addFieldError(fieldName, "required"); 50 | } 51 | } else { 52 | self.addFieldError(fieldName, "required"); 53 | } 54 | } 55 | } 56 | 57 | if(field.required.whenFieldAbsent && _(formFieldsObject[field.required.whenFieldAbsent]).isUndefined()){ 58 | self.addFieldError(fieldName, "required"); 59 | } 60 | } 61 | 62 | } 63 | 64 | // if there is a value we are going to validate it 65 | if(fieldValue){ 66 | 67 | // transform the data if need be. 68 | if(field.transforms){ 69 | fieldValue=transform(fieldValue, field.transforms); 70 | formFieldsObject[fieldName]=fieldValue; 71 | } 72 | 73 | // check the data format 74 | if(field.format) { 75 | if(_.isArray(fieldValue)){ 76 | _(fieldValue).each(function(subValue) { 77 | self.checkFormat(subValue, fieldName, field.format); 78 | }); 79 | }else{ 80 | self.checkFormat(fieldValue, fieldName, field.format); 81 | } 82 | } 83 | 84 | // check rule sets 85 | _(field.rules).each( function( ruleValue, ruleName ) { 86 | if(_.isArray(fieldValue)){ 87 | _(fieldValue).each( function( subValue, key ) { 88 | result = Rules[ruleName](subValue, ruleValue, fieldName, formFieldsObject, self.fields); 89 | if(!result){ 90 | self.addFieldError(fieldName, ruleName, key); 91 | } 92 | }); 93 | }else{ 94 | result = Rules[ruleName](fieldValue, ruleValue, fieldName, formFieldsObject, self.fields); 95 | if(!result){ 96 | self.addFieldError(fieldName, ruleName); 97 | } 98 | } 99 | }); 100 | } 101 | 102 | 103 | }); 104 | 105 | //aggregate here before we remove fields that could be part of aggregation.. We shouldn't need to validate these fields 106 | _(self.aggregates).each( function(aggregateInfo, newFieldName) { 107 | var aggregateName = aggregateInfo[0]; 108 | var aggregateFields = aggregateInfo[1]; 109 | var aggregateArgs = aggregateInfo[2]; 110 | var newField = Aggregates[aggregateName](aggregateFields, formFieldsObject, aggregateArgs); 111 | 112 | formFieldsObject[newFieldName] = newField; 113 | }); 114 | 115 | //remove any unwanted fields 116 | _(self.removeFields).each( function( value ) { 117 | delete formFieldsObject[value]; 118 | }); 119 | 120 | if(_.isEmpty(self.erroredFields)){ 121 | self.erroredFields = false; 122 | if(Meteor.isClient){ 123 | self.onSuccess(formFieldsObject, $(self.selector)); 124 | } 125 | }else{ 126 | self.addMessages(); 127 | if(Meteor.isClient){ 128 | self.onFailure(self.erroredFields, $(self.selector)); 129 | } 130 | } 131 | 132 | if(callback && _(callback).isFunction()){ 133 | callback(self.erroredFields, formFieldsObject); 134 | }else{ 135 | return {errors:self.erroredFields, formData:formFieldsObject}; 136 | } 137 | 138 | }; 139 | 140 | Form.prototype.addMessages = function(){ 141 | var self = this; 142 | _(self.erroredFields).each( function( value, key ) { 143 | self.erroredFields[key].message = self.erroredFields[key].required ? self.fields[key].requiredMessage || "*Required Field*" : self.fields[key].message || "*Invalid Input*"; 144 | }); 145 | }; 146 | 147 | Form.prototype.addFieldError = function(fieldName, ruleName, key){ 148 | 149 | if(!this.erroredFields[fieldName]){ 150 | this.erroredFields[fieldName] = {}; 151 | } 152 | if(key){ 153 | if(!this.erroredFields[fieldName][ruleName]){ 154 | this.erroredFields[fieldName][ruleName] = []; 155 | } 156 | this.erroredFields[fieldName][ruleName][key] = true; 157 | }else{ 158 | this.erroredFields[fieldName][ruleName] = true; 159 | } 160 | }; 161 | 162 | Form.prototype.checkFormat = function(fieldValue, fieldName, fieldFormat) { 163 | var self = this; 164 | var format; 165 | 166 | if(_.isString(fieldFormat)){ 167 | format=Formats[fieldFormat]; 168 | }else{ 169 | format = fieldFormat; 170 | } 171 | 172 | if(!format){ 173 | throw new Error("Unknown format:"+fieldFormat); 174 | } 175 | else { 176 | if( _.isRegExp(format) ) { 177 | // it's a regular expression 178 | if(!format.test(fieldValue)){ 179 | self.addFieldError(fieldName, "Invalid format"); 180 | } 181 | } else { 182 | // it's a function 183 | if(!format(fieldValue)){ 184 | self.addFieldError(fieldName, "Invalid format"); 185 | } 186 | } 187 | } 188 | }; 189 | 190 | Form.prototype.formToObject = function(formFields){ 191 | var formFieldsObject = {}; 192 | 193 | _(formFields).each( function( field ) { 194 | var name = field.name; 195 | var value = field.fileSize ? _(field).pick(['fileType', 'fileSize', 'files']) : field.value; 196 | 197 | if(_.isUndefined(formFieldsObject[name])){ 198 | formFieldsObject[name] = value; 199 | }else if(_.isArray(formFieldsObject[name])){ 200 | formFieldsObject[name].push(value); 201 | }else{ 202 | formFieldsObject[name] = [formFieldsObject[name], value]; 203 | } 204 | }); 205 | 206 | return formFieldsObject; 207 | }; 208 | 209 | var transform = function (fieldValue, transformList) { 210 | _(transformList).each(function (transformName) { 211 | var transform=Transforms[transformName]; 212 | if (transform){ 213 | fieldValue = transform(fieldValue); 214 | } 215 | else{ 216 | throw new Error("Invalid transform:" + transformName); 217 | } 218 | }); 219 | return fieldValue; 220 | }; 221 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Meteor Shower :-) 2 | __*Deprecacation Warning:*__ I currently recommend using simple schema for all validation as it integrates with your collections and allows you to fully secure and use client side operations in totality. 3 | 4 | [![Gitter chat](https://badges.gitter.im/copleykj/Shower.png)](https://gitter.im/copleykj/Shower) 5 | 6 | A dual client/server side form data validation, transformation, and aggregation package for **Meteor** 7 | 8 | ## Features 9 | 10 | * Same validations logic on client **and** server sides 11 | * Clean validation workflow with 12 | - Transformations: trim, capitalize, clean... 13 | - Formats: email, float, boolean, url, creditcard... 14 | - Rules: min, max, equals... 15 | - Field Aggregation: sum, avg, join 16 | * Conditional required fields 17 | * Frontend hooks 18 | * Extensible via api 19 | 20 | ## Installation 21 | 22 | `meteor add copleykj:shower` 23 | 24 | ## Usage 25 | 26 | Shower invocation happens in two steps: 27 | 28 | 1. First a configuration step 29 | 2. Then runtime validation step 30 | 31 | ### Configuration 32 | ```javascript 33 | Shower(formDescriptionObject); 34 | ``` 35 | When invoked, Shower creates a new form object and stores it as a property of itself. You can then gain access to the form by referring to *Shower.***formName**. 36 | 37 | ### Validation 38 | 39 | 40 | To validate a form, call the **validate()** method on the form object and it will return an object containing errors and validated/transformed form-data. Validation can happen client and server side. 41 | 42 | `var validationObject = Shower.loginForm.validate(rawFormData);` 43 | 44 | For instance, the above call could return: 45 | 46 | ```javascript 47 | validationObject:{ 48 | errors:{ 49 | username:{ 50 | required:true, 51 | message:"*Required Field*" 52 | } 53 | }, 54 | formData:{ 55 | username:"", 56 | password:"C00lPa$$w0rd" 57 | } 58 | } 59 | ``` 60 | If there are no errors, **validationObject.errors** will be empty; 61 | 62 | You can also supply a callback as the second parameter of the **validate()** method. In this case the validation object will not be returned as it was in the above example, but split and passed as parameters to the callback. The call back will be passed the errors object as the first parameter and the formData object as the second. 63 | 64 | ```javascript 65 | Shower.loginForm.validate(rawFormData, function(errors, formData){ 66 | 67 | }); 68 | ``` 69 | 70 | The validation workflow is processed as follows: first the fields are aggregated, checked for requirement, and transformed, They are then compared aginst their _format_ and evaluated against each _rule_. Finally any fields specified by _removeFields_ are removed. 71 | 72 | ## Form description 73 | 74 | Shower requires an object that describe the form and fields elements. This section describes the different elements which can compose that description. 75 | 76 | ### Basics elements 77 | 78 | **name/id** - The form must contain either a name or an id key which corresponds to the name or id of the form element in your html. This will create a new form object scoped to **Shower.formName** or **Shower.formId**. These accessors are the main access point to any validation checks. 79 | 80 | **template** - (Optional) The name of the template that contains the form. If specified, Shower will attach the submit event to the template otherwise it will attache the submit using jQuery. Attaching via the template will cause the data context from the template to be passed to the validation call back. *This is completely useless when combined with the* **disableSubmit** *option* 81 | 82 | **disableSubmit** - If true, Shower will not validate the form when it is submitted and it will be up to you to handle this. *When using this option the Shower.Utils.getFormData() method can be useful.* 83 | 84 | **method** - The name of an optional remote method to call. If you pass in a string for the method, Shower will call the Meteor server method by this name, if you pass in a function then it will call the function. Either way it passes one argument to the method or function which is the raw data taken from the form. 85 | 86 | **aggregates** - An object who's keys are the name of a new field to be created from a set of other form elements. The value for each key is an array with it's first element as the name of the aggregate function, an the second another array containing the names of the fields you would like to aggregate. A third element to the array can be added which will be passed as an argument to the aggregate function. 87 | 88 | ```javascript 89 | aggregates:{ 90 | birthDate:["join", ["month", "day", "year"], " "], 91 | fullName:["join", ["firstName", "LastName"], " "] 92 | } 93 | ``` 94 | 95 | Aggregation will be performed after validation and transformation. Any validation should happen on fields that the aggregation depends on to ensure valid data will be aggregated. If there is a need to transform aggregated data you can accomplish this by creating a custom aggregation function and return the transformed data. 96 | 97 | Current aggregation functions are: 98 | 99 | * sum - calculates the sum of the specified fields. 100 | * avg - calculates the average of specified fields. 101 | * join - concatenates the values of the specified fields. Takes optional separator argument. 102 | * arraySet - creates new array of values of the specified fields. 103 | * objectSet - creates new object of key:value pairs of specified fields. 104 | 105 | **removeFields** - list of fields that should be removed from returned form values. Especially useful if you wish to remove fields that have been aggregated together into new fields. 106 | 107 | ```javascript 108 | removeFields["month", "day", "year", "firstName", "lastName"] 109 | ``` 110 | 111 | **fields** - This is a list of fields with each key being the name of a form field and the value being an object telling Shower about that field. See next chapter. 112 | 113 | ```javascript 114 | fields:{ 115 | firstname:{ 116 | //field description 117 | }, 118 | userID:{ 119 | //field description 120 | } 121 | } 122 | ``` 123 | 124 | ### Field description 125 | 126 | **required** - The required key tells Shower if this is a required field, if the field is required dependently of another field, or if the field is required only when the field is absent from the submitted fields. If required dependently the field can be conditionaly required based on another field value. 127 | 128 | Let's see this in practice: 129 | 130 | ```javascript 131 | 132 | // field is required 133 | required: true 134 | 135 | or 136 | 137 | // field is required when country is set 138 | required:{ 139 | dependsOn:"country" 140 | } 141 | 142 | or 143 | 144 | // field is required when country is set to "USA" 145 | required:{ 146 | dependsOn:"country", 147 | value:"USA" 148 | } 149 | 150 | or 151 | 152 | // field is only required if other field is missing in form submission 153 | required:{ 154 | whenFieldAbsent:"country" 155 | } 156 | ``` 157 | 158 | **message** - The message key is the message added to the fields in the erroredFields object which is returned from the validate function and passed to the *onFailure* callback when validation fails. if message is not defined, the error message is set to *\*Invalid Input\** 159 | 160 | **requiredMessage** - The requiredMessage key is added to the fields in the erroredFields object when a required field is not present and is returned from the validate function and passed to the *onFailure* callback. If requiredMessage is not defined the error message is set to *\*Required Field\** 161 | 162 | #### Transformations 163 | 164 | ```javascript 165 | Shower({ 166 | name: "testForm", 167 | fields: { 168 | firstname: { 169 | required: true, 170 | transforms: ["clean", "capitalize"], 171 | format: "alphanumeric", 172 | message: "That's a weird first name!" 173 | } 174 | } 175 | }); 176 | 177 | ``` 178 | 179 | Array telling Shower the transforms to perform on the field prior to any validation. Note that multiple transforms can be applied. 180 | 181 | The following transforms are currently implemented: 182 | 183 | * trim - Trims left & right spaces 184 | * clean - Trims and removes extra spaces 185 | * capitalize - Upper Case first letter 186 | * stripTags - Removes any xml tags 187 | * escapeHTML - Escapes HTML tags 188 | * toUpperCase - all to UPPERCASE 189 | * toLowerCase - all to lowercase 190 | * slugify 191 | * humanize 192 | 193 | Please check the [underscore.js documentation](http://underscorejs.org/) and [underscore.string documentation](https://github.com/epeli/underscore.string) for more details about these operations. 194 | 195 | #### Formats 196 | 197 | Element that defines the field data format. the format can be expressed as a predefined string, a regex or a function. 198 | 199 | * boolean - _0, 1, true, false, yes, no_ 200 | * integer - _65342, -1_ 201 | * hex - _FE7B, 1b83de_ 202 | * float - _127.1, -57.3_ 203 | * alphanumeric - _that's it_ 204 | * email _dest@domain.ext_ 205 | * money _$100.00, $-200_ 206 | * ipv4 _127.0.0.1_ 207 | * phone _+1 (305) 1234567, +33 47 57 11 22_ 208 | * url _https://www.crionics.com, ftp://127.0.1.3_ 209 | * creditcard _378282246310005, 4012-8888-8888-1881, 4012 8888 8888 1881_ 210 | 211 | When using a regex, you could for instance use something as follows. Note how the format references a RegEx. 212 | 213 | ```javascript 214 | Shower({ 215 | name: "testForm", 216 | fields: { 217 | zipcode: { 218 | required: true, 219 | format: /^[0-9]{5}$/, 220 | message: "bad zip code" 221 | } 222 | }); 223 | 224 | ``` 225 | 226 | Using a function is equally straighforward. For instance in the exmaple below, we define a function that only accepts strings with a "Thank you" note. Format functions are typically used when a regular expression falls short. 227 | 228 | ```javascript 229 | Shower({ 230 | name: "testForm", 231 | fields: { 232 | msg: { 233 | required: true, 234 | format: function(val) { return val.indexOf("Thank you")}, 235 | message: "You didn't say thank you!" 236 | } 237 | }); 238 | 239 | ``` 240 | 241 | #### Rules 242 | 243 | List of rules for validating the field value. Note that this is different from a **the format** which acts more as a _regular expression_. Rules implement precise logic such as boundaries or edge case detection. 244 | 245 | ```javascript 246 | Shower({ 247 | name: "testForm", 248 | fields: { 249 | firstname: { 250 | required: true, 251 | format: "alphanumeric", 252 | message: "bad zip code", 253 | rules:{ 254 | maxLength:20, 255 | minLength:4 256 | } 257 | } 258 | } 259 | }); 260 | 261 | ``` 262 | 263 | The list of predefined rules is the following: 264 | 265 | * maxLength 266 | * minLength 267 | * exactLength 268 | * failIfFound 269 | * minValue 270 | * maxValue 271 | * equalsValue 272 | * maxFileSize 273 | * acceptedFileTypes 274 | 275 | #### Default Values 276 | 277 | In cases where you need to insert a default value when no value is provided (blank text boxes, unchecked checkboxes, etc.) there is the `defaultValue` option on the field description. If this is specified as a function it will call the function and assign the return value as the default, otherwise the value will be assigned directly as is. 278 | 279 | ```javascript 280 | Shower({ 281 | name: "testForm", 282 | fields: { 283 | firstname: { 284 | required: true, 285 | format: "alpha", 286 | message: "Only letters in a first name.", 287 | rules:{ 288 | maxLength:20, 289 | minLength:4 290 | }, 291 | defaultValue: function(formFieldsObject){ 292 | switch(formFieldsObject["gender"]){ 293 | case "Male": 294 | return "John"; 295 | case "Female": 296 | return "jane"; 297 | } 298 | } 299 | } 300 | } 301 | }); 302 | 303 | ``` 304 | 305 | ### Error management 306 | 307 | **onFailure** - The client side callback when validation has failed. This gets passed an erroredFields object as the first parameter, which has each field that failed validation, which rules it failed and the message you provided. The second parmeter passed is a jQuery handle to the form. 308 | 309 | By default this tries to insert the error message provided in an element with an id of fieldname-error 310 | 311 | For example for a field with a name of username, this function will try to insert the message text in an element like this. 312 | 313 | ```html 314 | 315 | ``` 316 | When overriding the default call back you can maintain default functionality, while building upon it, by calling the *failureCallback* function and passing in the erroredFields object that was passed to the onFailure callback. 317 | 318 | ```javascript 319 | onFailure: function(erroredFields, formHandle){ 320 | //custom code here 321 | Shower.Utils.failureCallback(erroredFields, formHandle); 322 | } 323 | ``` 324 | 325 | **onSuccess** - The client side callback when validation has succeeded. This is passed an object of validated and transformed form-data as *key:value* pairs as the first parameter, and a jQuery handle of the form as the second. By default this just clears all the displayed errors. 326 | 327 | This, like the onFailure callback can maintain and build upon the default functionality by calling the *successCallback*. This however does not require you to pass in the form-data since it doesn't use it. 328 | 329 | ```javascript 330 | onSuccess: function(formData, formHandle){ 331 | //custom code here 332 | Shower.Utils.successCallback(formData, formHandle); 333 | } 334 | ``` 335 | 336 | 337 | ### Dual client/server validation 338 | 339 | To achieve dual client/server validation, pass in the name of the Meteor server method as a string to the method attribute of the formDescriptionObject. This method will be passed the unvalidated form data, and if a template was specified it will pass the template context as the second param. If data is available from the template context, this give access to it. e.g `templateContext._id`. 340 | 341 | On the client, Shower will then call the method when the form is submitted, passing in the form data. Then use the forms validate function in the server method and pass in the form data and specify the validation callback. This callback is passed an errors object as its first parameter which is empty if no fields errored. The second parameter passed is an object containing the validated and tranformed data for further manipulation or storage. 342 | 343 | ```javascript 344 | Meteor.methods({ 345 | login:function(rawFormData, templateData){ 346 | Shower.loginForm.validate(rawFormData, function(errors, formFieldsObject){ 347 | if(!errors){ 348 | //Do what we need to do here; 349 | } 350 | }); 351 | } 352 | }); 353 | 354 | ``` 355 | 356 | Next call Shower to wrap your form for validation. Shower takes one argument. This argument is an object describing the form, how to validate and transform the data, the Meteor method to use, an optional template name to attach the submit event to, and the client side callbacks. 357 | 358 | Simple example: 359 | 360 | ```javascript 361 | Shower({ 362 | name:"loginForm", 363 | method:"login", 364 | template:"loginTemplate", 365 | fields:{ 366 | username:{ 367 | required:true, 368 | message:"letters or numbers, between 3 and 20 characters.", 369 | format:/^[A-Z0-9]{3,20}$/i, 370 | transform:["trim"] 371 | }, 372 | password:{ 373 | required:true, 374 | message:"Must be at least 6 characters.", 375 | rules:{ 376 | minLength:6 377 | } 378 | } 379 | } 380 | }); 381 | ``` 382 | 383 | ## Shower.Utils 384 | 385 | ###getFormData 386 | 387 | The getFormData Method is exposed as `Shower.Utils.getFormData(formReference)`. This is useful when handling form events yourself. You will need to pass in a reference to the form as the first parameter. 388 | 389 | ### successCallback 390 | 391 | The successCallback method is exposed as `Shower.Utils.successCallback(formData, formHandle). This is the default client side success callback used to reset the form and clear the form errors. This is useful if you replace the onSuccess callback in a form description and wish to call this to retain default functionality while adding your own. 392 | 393 | ### failureCallback 394 | 395 | The failureCallback method is exposed as `Shower.Utils.failureCallback(erroredFields, formHandle). This is the default client side failure callback used to reset the form and clear the form errors. This is useful if you replace the onFailure callback in a form description and wish to call this to retain default functionality while adding your own. 396 | 397 | ## Extending 398 | 399 | You may add you own set of rules & transforms by using the following methods: 400 | 401 | ```javascript 402 | // fn takes the fieldValue and the defined ruleValue 403 | Shower.registerFormat(name, fn); 404 | // fn takes the fieldValue and the defined ruleValue 405 | Shower.registerRule(name, fn); 406 | // fn recieves the string to transform 407 | Shower.registerTransform(name, fn); 408 | //fn recieves an array of field names, an object containing the current submitted form values and optionally an argument passed to the function. 409 | Shower.registerAggregate(name, fn); 410 | ``` 411 | 412 | For instance, 413 | 414 | ```javascript 415 | Shower.registerRule("force-zipcode", function(fieldValue, ruleValue){ 416 | return fieldValue === ruleValue; 417 | }); 418 | 419 | Shower({ 420 | name:"myForm", 421 | method:'checkZip', 422 | fields:{ 423 | zipcode:{ 424 | required:true, 425 | message:"Zip code 33178 is required", 426 | rules:{ 427 | force-zipcode:"33178" 428 | }, 429 | transform:["trim"] 430 | } 431 | } 432 | }); 433 | 434 | ``` 435 | ## Sublime Text Snippets 436 | 437 | A set of Shower snippets for Sublime Text are available here: https://github.com/copleykj/mesosphere-snippets 438 | 439 | This is not a Sublime Text package so you will have to install it manually. If anyone would like to tackle the task of turning it onto a Sublime Text Package please feel free to fork and do so. 440 | 441 | ## Complete Usage Example 442 | 443 | For now the example has been removed. Examples are coming shortly as a Meteor site available as a repository that can be cloned and ran or viewed on the meteor.com site. 444 | 445 | ## Contribution 446 | 447 | Contributions are always welcome, please follow these steps to submit your changes via a github pull request. 448 | 449 | Your code should include unit tests & documentation changes when applicable. 450 | 451 | Ensure all unit tests pass sucessfully. 452 | 453 | meteor test-packages ./ 454 | 455 | -------------------------------------------------------------------------------- /tests/format_tests.js: -------------------------------------------------------------------------------- 1 | Shower({ 2 | name: "formatsForm", 3 | fields: { 4 | 5 | email: { 6 | format: "email", 7 | message: "not an email" 8 | }, 9 | 10 | hex: { 11 | format: "hex", 12 | message: "not a hexadecimal value" 13 | }, 14 | int: { 15 | format: "integer", 16 | message: "not a hexadecimal value" 17 | }, 18 | float: { 19 | format: "float", 20 | message: "not a float value" 21 | }, 22 | money: { 23 | format: "money", 24 | message: "not a money value" 25 | }, 26 | alpha: { 27 | format: "alphanumeric", 28 | message: "not a money value" 29 | }, 30 | url: { 31 | format: "url", 32 | message: "not a url value" 33 | }, 34 | ipv4: { 35 | format: "ipv4", 36 | message: "not an ip value" 37 | }, 38 | phone: { 39 | format: "phone", 40 | message: "not a phone value" 41 | }, 42 | regex: { 43 | format: /^[0-9]{5}$/, 44 | message: "not a valid zip code" 45 | }, 46 | cc: { 47 | format: "creditcard", 48 | message: "not a valid cc" 49 | }, 50 | bool: { 51 | format: "boolean", 52 | message: "not a boolean value" 53 | } 54 | }}); 55 | 56 | Tinytest.add("boolean format", function (test) { 57 | 58 | var validationObject = Shower.formatsForm.validate([ 59 | {"name": "bool", "value": "true"} 60 | ]); 61 | test.isTrue(validationObject.errors === false); 62 | 63 | validationObject = Shower.formatsForm.validate([ 64 | {"name": "bool", "value": "false"} 65 | ]); 66 | test.isTrue(validationObject.errors === false); 67 | 68 | validationObject = Shower.formatsForm.validate([ 69 | {"name": "bool", "value": "0"} 70 | ]); 71 | test.isTrue(validationObject.errors === false); 72 | 73 | validationObject = Shower.formatsForm.validate([ 74 | {"name": "bool", "value": "1"} 75 | ]); 76 | test.isTrue(validationObject.errors === false); 77 | 78 | validationObject = Shower.formatsForm.validate([ 79 | {"name": "bool", "value": "yes"} 80 | ]); 81 | test.isTrue(validationObject.errors === false); 82 | 83 | validationObject = Shower.formatsForm.validate([ 84 | {"name": "bool", "value": "no"} 85 | ]); 86 | test.isTrue(validationObject.errors === false); 87 | 88 | validationObject = Shower.formatsForm.validate([ 89 | {"name": "bool", "value": "True"} 90 | ]); 91 | test.isTrue(validationObject.errors === false); 92 | 93 | validationObject = Shower.formatsForm.validate([ 94 | {"name": "bool", "value": "No"} 95 | ]); 96 | test.isTrue(validationObject.errors === false); 97 | 98 | // BAD CASES 99 | 100 | validationObject = Shower.formatsForm.validate([ 101 | {"name": "bool", "value": "email@domain"} 102 | ]); 103 | test.isTrue(validationObject.errors !== false); 104 | 105 | validationObject = Shower.formatsForm.validate([ 106 | {"name": "bool", "value": "True0"} 107 | ]); 108 | test.isTrue(validationObject.errors !== false); 109 | 110 | validationObject = Shower.formatsForm.validate([ 111 | {"name": "bool", "value": "00"} 112 | ]); 113 | test.isTrue(validationObject.errors !== false); 114 | 115 | } 116 | ); 117 | 118 | Tinytest.add("cc format", function (test) { 119 | 120 | var validationObject = Shower.formatsForm.validate([ 121 | {"name": "cc", "value": "378282246310005"} 122 | ]); 123 | test.isTrue(validationObject.errors === false); 124 | 125 | validationObject = Shower.formatsForm.validate([ 126 | {"name": "cc", "value": "4012 8888 8888 1881"} 127 | ]); 128 | test.isTrue(validationObject.errors === false); 129 | 130 | validationObject = Shower.formatsForm.validate([ 131 | {"name": "cc", "value": "4012-8888-8888-1881"} 132 | ]); 133 | test.isTrue(validationObject.errors === false); 134 | // BAD CASES 135 | 136 | validationObject = Shower.formatsForm.validate([ 137 | {"name": "cc", "value": "3566002020360506"} 138 | ]); 139 | test.isTrue(validationObject.errors !== false); 140 | 141 | 142 | validationObject = Shower.formatsForm.validate([ 143 | {"name": "cc", "value": "email@domain"} 144 | ]); 145 | test.isTrue(validationObject.errors !== false); 146 | 147 | validationObject = Shower.formatsForm.validate([ 148 | {"name": "cc", "value": "string!!"} 149 | ]); 150 | test.isTrue(validationObject.errors !== false); 151 | 152 | } 153 | ); 154 | 155 | Tinytest.add("regex format", function (test) { 156 | 157 | 158 | var validationObject = Shower.formatsForm.validate([ 159 | {"name": "regex", "value": "33143"} 160 | ]); 161 | test.isTrue(validationObject.errors === false); 162 | 163 | validationObject = Shower.formatsForm.validate([ 164 | {"name": "regex", "value": "78465"} 165 | ]); 166 | test.isTrue(validationObject.errors === false); 167 | 168 | // BAD CASES 169 | 170 | validationObject = Shower.formatsForm.validate([ 171 | {"name": "regex", "value": "email@domain"} 172 | ]); 173 | test.isTrue(validationObject.errors !== false); 174 | 175 | validationObject = Shower.formatsForm.validate([ 176 | {"name": "regex", "value": "string!!"} 177 | ]); 178 | test.isTrue(validationObject.errors !== false); 179 | 180 | } 181 | ); 182 | 183 | 184 | Tinytest.add("phone format", function (test) { 185 | 186 | 187 | var validationObject = Shower.formatsForm.validate([ 188 | {"name": "phone", "value": ""} 189 | ]); 190 | test.isTrue(validationObject.errors === false); 191 | 192 | validationObject = Shower.formatsForm.validate([ 193 | {"name": "phone", "value": "305 613 1234"} 194 | ]); 195 | test.isTrue(validationObject.errors === false); 196 | 197 | validationObject = Shower.formatsForm.validate([ 198 | {"name": "phone", "value": "+1 305 613 1234"} 199 | ]); 200 | test.isTrue(validationObject.errors === false); 201 | 202 | validationObject = Shower.formatsForm.validate([ 203 | {"name": "phone", "value": "+1 (305) 613 1234"} 204 | ]); 205 | test.isTrue(validationObject.errors === false); 206 | 207 | validationObject = Shower.formatsForm.validate([ 208 | {"name": "phone", "value": "+1 (305) 6131234"} 209 | ]); 210 | test.isTrue(validationObject.errors === false); 211 | 212 | validationObject = Shower.formatsForm.validate([ 213 | {"name": "phone", "value": "+1 (305) 6131234 ext 123"} 214 | ]); 215 | test.isTrue(validationObject.errors === false); 216 | 217 | validationObject = Shower.formatsForm.validate([ 218 | {"name": "phone", "value": "+33 1 47 37 44 55 ext 123"} 219 | ]); 220 | test.isTrue(validationObject.errors === false); 221 | 222 | // BAD CASES 223 | 224 | validationObject = Shower.formatsForm.validate([ 225 | {"name": "phone", "value": "email@domain"} 226 | ]); 227 | test.isTrue(validationObject.errors !== false); 228 | 229 | validationObject = Shower.formatsForm.validate([ 230 | {"name": "phone", "value": "string!!"} 231 | ]); 232 | test.isTrue(validationObject.errors !== false); 233 | 234 | } 235 | ); 236 | 237 | Tinytest.add("ipv4 format", function (test) { 238 | 239 | var validationObject = Shower.formatsForm.validate([ 240 | {"name": "ipv4", "value": ""} 241 | ]); 242 | test.isTrue(validationObject.errors === false); 243 | 244 | validationObject = Shower.formatsForm.validate([ 245 | {"name": "ipv4", "value": "127.0.0.1"} 246 | ]); 247 | test.isTrue(validationObject.errors === false); 248 | 249 | validationObject = Shower.formatsForm.validate([ 250 | {"name": "ipv4", "value": "192.168.1.1"} 251 | ]); 252 | test.isTrue(validationObject.errors === false); 253 | 254 | validationObject = Shower.formatsForm.validate([ 255 | {"name": "ipv4", "value": "255.255.255.255"} 256 | ]); 257 | test.isTrue(validationObject.errors === false); 258 | 259 | // BAD CASES 260 | 261 | validationObject = Shower.formatsForm.validate([ 262 | {"name": "ipv4", "value": "192.168.2."} 263 | ]); 264 | test.isTrue(validationObject.errors !== false); 265 | 266 | validationObject = Shower.formatsForm.validate([ 267 | {"name": "ipv4", "value": "192.168.2.1.2"} 268 | ]); 269 | test.isTrue(validationObject.errors !== false); 270 | 271 | validationObject = Shower.formatsForm.validate([ 272 | {"name": "ipv4", "value": "192"} 273 | ]); 274 | test.isTrue(validationObject.errors !== false); 275 | 276 | validationObject = Shower.formatsForm.validate([ 277 | {"name": "ipv4", "value": "email@domain"} 278 | ]); 279 | test.isTrue(validationObject.errors !== false); 280 | 281 | validationObject = Shower.formatsForm.validate([ 282 | {"name": "ipv4", "value": "string!!"} 283 | ]); 284 | test.isTrue(validationObject.errors !== false); 285 | 286 | } 287 | ); 288 | 289 | Tinytest.add("url format", function (test) { 290 | 291 | 292 | var validationObject = Shower.formatsForm.validate([ 293 | {"name": "url", "value": ""} 294 | ]); 295 | test.isTrue(validationObject.errors === false); 296 | 297 | validationObject = Shower.formatsForm.validate([ 298 | {"name": "url", "value": "http://www.crionics.com"} 299 | ]); 300 | test.isTrue(validationObject.errors === false); 301 | 302 | validationObject = Shower.formatsForm.validate([ 303 | {"name": "url", "value": "https://www.crionics.com"} 304 | ]); 305 | test.isTrue(validationObject.errors === false); 306 | 307 | validationObject = Shower.formatsForm.validate([ 308 | {"name": "url", "value": "ftp://127.0.1.3"} 309 | ]); 310 | test.isTrue(validationObject.errors === false); 311 | 312 | // BAD CASES 313 | 314 | validationObject = Shower.formatsForm.validate([ 315 | {"name": "url", "value": "email@domain"} 316 | ]); 317 | test.isTrue(validationObject.errors !== false); 318 | 319 | validationObject = Shower.formatsForm.validate([ 320 | {"name": "url", "value": "string!!"} 321 | ]); 322 | test.isTrue(validationObject.errors !== false); 323 | 324 | } 325 | ); 326 | 327 | Tinytest.add("alphanumeric format", function (test) { 328 | 329 | 330 | var validationObject = Shower.formatsForm.validate([ 331 | {"name": "alpha", "value": ""} 332 | ]); 333 | test.isTrue(validationObject.errors === false); 334 | 335 | validationObject = Shower.formatsForm.validate([ 336 | {"name": "alpha", "value": "first and last name"} 337 | ]); 338 | test.isTrue(validationObject.errors === false); 339 | 340 | // BAD CASES 341 | 342 | validationObject = Shower.formatsForm.validate([ 343 | {"name": "alpha", "value": "email@domain"} 344 | ]); 345 | test.isTrue(validationObject.errors !== false); 346 | 347 | validationObject = Shower.formatsForm.validate([ 348 | {"name": "alpha", "value": "string!!"} 349 | ]); 350 | test.isTrue(validationObject.errors !== false); 351 | 352 | } 353 | ); 354 | 355 | 356 | Tinytest.add("email format", function (test) { 357 | 358 | 359 | var validationObject = Shower.formatsForm.validate([ 360 | {"name": "email", "value": ""} 361 | ]); 362 | test.isTrue(validationObject.errors === false); 363 | 364 | validationObject = Shower.formatsForm.validate([ 365 | {"name": "email", "value": "email@domain.ext"} 366 | ]); 367 | test.isTrue(validationObject.errors === false); 368 | 369 | validationObject = Shower.formatsForm.validate([ 370 | {"name": "email", "value": "EMAIL@domAin.EXT"} 371 | ]); 372 | test.isTrue(validationObject.errors === false); 373 | 374 | validationObject = Shower.formatsForm.validate([ 375 | {"name": "anotherfield", "value": "email@domain.ext"} 376 | ]); 377 | test.isTrue(validationObject.errors === false); 378 | 379 | // BAD CASES 380 | 381 | validationObject = Shower.formatsForm.validate([ 382 | {"name": "email", "value": "email@domain"} 383 | ]); 384 | test.isTrue(validationObject.errors !== false); 385 | 386 | validationObject = Shower.formatsForm.validate([ 387 | {"name": "email", "value": "email@"} 388 | ]); 389 | test.isTrue(validationObject.errors !== false); 390 | 391 | validationObject = Shower.formatsForm.validate([ 392 | {"name": "email", "value": "email"} 393 | ]); 394 | test.isTrue(validationObject.errors !== false); 395 | 396 | validationObject = Shower.formatsForm.validate([ 397 | {"name": "email", "value": "email@domain@domain.com"} 398 | ]); 399 | test.isTrue(validationObject.errors !== false); 400 | } 401 | ); 402 | 403 | Tinytest.add("hex format", function (test) { 404 | 405 | 406 | var validationObject = Shower.formatsForm.validate([ 407 | {"name": "hex", "value": ""} 408 | ]); 409 | test.isTrue(validationObject.errors === false); 410 | 411 | validationObject = Shower.formatsForm.validate([ 412 | {"name": "hex", "value": "1234567890abcdef"} 413 | ]); 414 | test.isTrue(validationObject.errors === false); 415 | 416 | 417 | validationObject = Shower.formatsForm.validate([ 418 | {"name": "hex", "value": "1234567890ABCDEF"} 419 | ]); 420 | test.isTrue(validationObject.errors === false); 421 | 422 | validationObject = Shower.formatsForm.validate([ 423 | {"name": "anotherfield", "value": "email@domain.ext"} 424 | ]); 425 | test.isTrue(validationObject.errors === false); 426 | 427 | // BAD CASES 428 | 429 | validationObject = Shower.formatsForm.validate([ 430 | {"name": "hex", "value": "035GH"} 431 | ]); 432 | test.isTrue(validationObject.errors !== false); 433 | 434 | } 435 | ); 436 | 437 | Tinytest.add("integer format", function (test) { 438 | 439 | 440 | var validationObject = Shower.formatsForm.validate([ 441 | {"name": "int", "value": ""} 442 | ]); 443 | test.isTrue(validationObject.errors === false); 444 | 445 | validationObject = Shower.formatsForm.validate([ 446 | {"name": "int", "value": "1234567890"} 447 | ]); 448 | test.isTrue(validationObject.errors === false); 449 | 450 | 451 | validationObject = Shower.formatsForm.validate([ 452 | {"name": "int", "value": "-1234567890"} 453 | ]); 454 | test.isTrue(validationObject.errors === false); 455 | 456 | validationObject = Shower.formatsForm.validate([ 457 | {"name": "anotherfield", "value": "email@domain.ext"} 458 | ]); 459 | test.isTrue(validationObject.errors === false); 460 | 461 | // BAD CASES 462 | 463 | validationObject = Shower.formatsForm.validate([ 464 | {"name": "int", "value": "035GH"} 465 | ]); 466 | test.isTrue(validationObject.errors !== false); 467 | 468 | validationObject = Shower.formatsForm.validate([ 469 | {"name": "int", "value": "127.01"} 470 | ]); 471 | test.isTrue(validationObject.errors !== false); 472 | 473 | validationObject = Shower.formatsForm.validate([ 474 | {"name": "int", "value": "127,01"} 475 | ]); 476 | test.isTrue(validationObject.errors !== false); 477 | 478 | 479 | } 480 | ); 481 | 482 | 483 | Tinytest.add("float format", function (test) { 484 | 485 | 486 | var validationObject = Shower.formatsForm.validate([ 487 | {"name": "float", "value": ""} 488 | ]); 489 | test.isTrue(validationObject.errors === false); 490 | 491 | validationObject = Shower.formatsForm.validate([ 492 | {"name": "float", "value": "1234567890"} 493 | ]); 494 | test.isTrue(validationObject.errors === false); 495 | 496 | validationObject = Shower.formatsForm.validate([ 497 | {"name": "float", "value": "123.567"} 498 | ]); 499 | test.isTrue(validationObject.errors === false); 500 | 501 | 502 | validationObject = Shower.formatsForm.validate([ 503 | {"name": "float", "value": "-1234567890"} 504 | ]); 505 | test.isTrue(validationObject.errors === false); 506 | 507 | validationObject = Shower.formatsForm.validate([ 508 | {"name": "float", "value": "-123.567"} 509 | ]); 510 | test.isTrue(validationObject.errors === false); 511 | 512 | 513 | validationObject = Shower.formatsForm.validate([ 514 | {"name": "anotherfield", "value": "email@domain.ext"} 515 | ]); 516 | test.isTrue(validationObject.errors === false); 517 | 518 | // BAD CASES 519 | 520 | validationObject = Shower.formatsForm.validate([ 521 | {"name": "float", "value": "035GH"} 522 | ]); 523 | test.isTrue(validationObject.errors !== false); 524 | 525 | validationObject = Shower.formatsForm.validate([ 526 | {"name": "float", "value": "127.9.3"} 527 | ]); 528 | test.isTrue(validationObject.errors !== false); 529 | 530 | validationObject = Shower.formatsForm.validate([ 531 | {"name": "float", "value": "--127"} 532 | ]); 533 | test.isTrue(validationObject.errors !== false); 534 | 535 | validationObject = Shower.formatsForm.validate([ 536 | {"name": "float", "value": "127,9"} 537 | ]); 538 | test.isTrue(validationObject.errors !== false); 539 | } 540 | ); 541 | 542 | 543 | Tinytest.add("money format", function (test) { 544 | 545 | 546 | var validationObject = Shower.formatsForm.validate([ 547 | {"name": "money", "value": ""} 548 | ]); 549 | test.isTrue(validationObject.errors === false); 550 | 551 | validationObject = Shower.formatsForm.validate([ 552 | {"name": "money", "value": "1234567890"} 553 | ]); 554 | test.isTrue(validationObject.errors === false); 555 | 556 | validationObject = Shower.formatsForm.validate([ 557 | {"name": "money", "value": "123.567"} 558 | ]); 559 | test.isTrue(validationObject.errors === false); 560 | 561 | 562 | validationObject = Shower.formatsForm.validate([ 563 | {"name": "money", "value": "-1234567890"} 564 | ]); 565 | test.isTrue(validationObject.errors === false); 566 | 567 | validationObject = Shower.formatsForm.validate([ 568 | {"name": "money", "value": "-123.567"} 569 | ]); 570 | test.isTrue(validationObject.errors === false); 571 | 572 | validationObject = Shower.formatsForm.validate([ 573 | {"name": "money", "value": "$-123.567"} 574 | ]); 575 | test.isTrue(validationObject.errors === false); 576 | 577 | validationObject = Shower.formatsForm.validate([ 578 | {"name": "money", "value": "€-123.567"} 579 | ]); 580 | test.isTrue(validationObject.errors === false); 581 | 582 | validationObject = Shower.formatsForm.validate([ 583 | {"name": "money", "value": "£-123.567"} 584 | ]); 585 | test.isTrue(validationObject.errors === false); 586 | 587 | validationObject = Shower.formatsForm.validate([ 588 | {"name": "money", "value": "¥-123.567"} 589 | ]); 590 | test.isTrue(validationObject.errors === false); 591 | 592 | validationObject = Shower.formatsForm.validate([ 593 | {"name": "anotherfield", "value": "email@domain.ext"} 594 | ]); 595 | test.isTrue(validationObject.errors === false); 596 | 597 | // BAD CASES 598 | 599 | validationObject = Shower.formatsForm.validate([ 600 | {"name": "money", "value": "035GH"} 601 | ]); 602 | test.isTrue(validationObject.errors !== false); 603 | 604 | validationObject = Shower.formatsForm.validate([ 605 | {"name": "money", "value": "127.9.3"} 606 | ]); 607 | test.isTrue(validationObject.errors !== false); 608 | 609 | validationObject = Shower.formatsForm.validate([ 610 | {"name": "money", "value": "--127"} 611 | ]); 612 | test.isTrue(validationObject.errors !== false); 613 | 614 | validationObject = Shower.formatsForm.validate([ 615 | {"name": "money", "value": "127,9"} 616 | ]); 617 | test.isTrue(validationObject.errors !== false); 618 | 619 | validationObject = Shower.formatsForm.validate([ 620 | {"name": "money", "value": "-$127.9"} 621 | ]); 622 | test.isTrue(validationObject.errors !== false); 623 | 624 | } 625 | ); 626 | --------------------------------------------------------------------------------