I am in the process of diving a bit deeper into JavaScript development and am looking at some common language patterns (module, and factory in particular). In this code, my aim is to create a re-usable framework for creating custom field validators in JS that can be easily extended. In addition, I want the code that does the validation to NOT have a big conditional logic block that needs to be maintained (for example: if(required) {} elseif(regex) {} elseif(numeric){}, etc....
).
The Factory allows new types of validators to be added to its registration at any point, provided they have a common prototype. The particular type of validator can then be instantiated by the factory by passing the name that was registered with that type.
I have segmented the code into separate files as I intend to be able to drop this into projects I'm working on, add any custom validation needed for the application, and move on.
The general flow of the UI is this:
- When the page first loads, the fields are empty. Because of this, (and the required validation), I have an initial state that prevents the page from showing errors on load.
- The KnockoutJS binding on the form fields triggers the field validation after each keystroke.
- If at any point the form is invalid, the field will be highlighted and a "validation summary" will be shown at the top, indicating the state of the form.
- Any validation errors will cause the form to be unable to submit
For this project, I opted to use knockout.js as a MVVM framework for binding to the UI and making the form responsive.
The Code
I have the following HTML form (in its entirety):
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>JavaScript Validator</title> <link rel="stylesheet" type="text/css" href="styles/style.css" /> <script type="text/javascript" src="scripts/jquery.js"></script> <script type="text/javascript" src="scripts/jquery-ui.js"></script> <script type="text/javascript" src="scripts/knockout.js"></script> <script type="text/javascript" src="scripts/library.js"></script> <script type="text/javascript" src="scripts/validators.js"></script> <script type="text/javascript" src="scripts/main.js"></script> <script type="text/javascript"> var model = new FormViewModel(); $(function() { ko.applyBindings(model); }); </script> </head> <body> <div class="liner"> <div class="spacer"></div> <header data-bind="animator: IsValid() ? 'Valid' : 'Invalid'"> <div class="validation-summary"> <h4>Form Status:</h4><mark data-bind="text: IsValid() ? 'Valid' : 'Invalid'"></mark> </div> </header> <section class="main"> <form name="validation-form" id="validation-form" method="post" action=""> <div class="form-liner"> <h5>Validation Testing</h5> <span class="form-item"> <label for="first-name">First Name:<mark data-bind="text: FirstName.State.Indicator, visible: FirstName.State.ShowIndicator"></mark></label> <input type="text" name="first-name" id="first-name" data-bind="textInput: FirstName, click: function() {$(document).trigger('data-click', {EventData: $data.FirstName(), Source: $(this)})}" maxlength="100" tabindex="1" /> <span class="validation-error" data-bind="text: FirstName.State.ValidationMessage"></span> </span> <span class="form-item"> <label for="last-name">Last Name:<mark></mark></label> <input type="text" name="last-name" id="last-name" data-bind="textInput: LastName" maxlength="100" tabindex="2" /> </span> </div> </form> </section> <footer> </footer> </div> </body> </html>
As a base JS class upon which other things are built, I have the following library.js
file:
//Requires Knockout JS var States = { Initial: 0, Modified: 1 }; //Base field validator class var ValidatableField = function() { var self = this; console.log("constructor called"); self.SetCustomOptions = function(options) { //Override }; self.IsValid = ko.observable(false); self.FieldName = ko.observable(""); self.DisplayName = ko.observable(""); self.ValidationMessageFormat = ko.observable(""); self.ValidationMessage = ko.pureComputed(function() { if(self.IsValid()) { return ""; } else { return self.ValidationMessageFormat().replace("{0}", self.DisplayName()); } // end if/else }, self); self.ShowIndicator = ko.observable(false); self.Indicator = ko.observable("*"); //Default indicator }; var ValidProto = function() { var self = this; }; var protoInstance = new ValidProto; //Factories namespace used for object creation. var Factories = (function () { var ObjectCreationException = function(data) { var self = this; self.FormattedMessage = ""; self.Value = ""; //the name property is left lowercase so that the name of the type will be able to be read by developer consoles in the debugging and output windows. self.name = "ObjectCreationException"; if(data != null) { self.FormattedMessage = data.MessageFormat; self.Value = data.Value; } // end if //DOC_IMPORTANT: Self invoking function is required as the message prototype is a string value, this allows us to perform logic but show the correct value to console's and debuggers. self.message = function() { return self.FormattedMessage.replace("{0}", self.Value); }(); }; ObjectCreationException.prototype = new Error(); var internalValidationRuleFactory = (function() { var types = null; internalCreate = function(aType) { if(aType != null && aType != undefined && aType != "") { if(types != null) { if(types[aType] != undefined) { return true; } else { throw new ObjectCreationException({MessageFormat: "No type binding for the name: '{0}', was found.", Value: aType}); } // end if/else } else { throw new ObjectCreationException({MessageFormat: "No type bindings configured."}); } // end if/else } else { throw new ObjectCreationException({MessageFormat: "No type specified"}); } } // end function internalCreate return { CreateInstance: function (aType) { return (function(type) { if(internalCreate(type)) { var request = new types[type](); return request; } // end if }(aType)); }, CreateInstance: function(aType, aArgs) { return (function(type, args) { if(internalCreate(type)) { var request = new types[type](args); return request; } // end if }(aType, aArgs)); }, RegisterType: function(typeName, instance) { var proto = instance.prototype; //Lazy initialization if(!types) { types = {}; } // end if if(proto instanceof ValidProto) { types[typeName] = instance; } else { throw new ObjectCreationException({MessageFormat: "Invalid type registration. The type: '{0}' is not of type 'ValidatableField'.", Value: typeName}); } // end if/else return internalValidationRuleFactory; } // end anonymouse class }; // end class internalValidationRuleFactory })(); //Controlled exposure of the internal type return { RuleValidationFactory: internalValidationRuleFactory }; })();
This library is probably the area I have the most questions about.
- Is this a proper implementation of the JS factory pattern?
- Am I using prototypes properly?
- Is the factory pattern the right tool for the job if I want to have X number of validators in the future?
Given the above form and Library, I also have the validators.js
file that contains the different types of field validators.
//Requires Knockout JS //Requires library.js //Validators namespace used for object creation. var Validators = (function () { //checks a value to see if it is null or undefined function isNullOrUndefined(val) { return (val == null || val == undefined); }; // end function isNullOrUndefined //checks a value to see if it is truly empty function isNullOrWhiteSpace(val) { return (isNullOrUndefined(val) || val == "" || val.trim() == ""); }; // end function isNullOrWhiteSpace //checks to see if a value is numeric function isNumber(val) { return (!isNaN(parseFloat(n)) && isFinite(n)); }; // end function isNumber var requiredField = function() { var self = this; ValidatableField.call(self); // copy the methods from ValidatableField //Override the prototype validate method self.Validate = function(data) { console.log("Required Validation..."); if(!isNullOrWhiteSpace(data)) { self.IsValid(true); } else { self.IsValid(false); } // end if/else }; // end method Validate self.ValidationMessageFormat("{0} is required."); }; requiredField.prototype = protoInstance; var numericField = function() { var self = this; //Override the prototype validate method self.Validate = function(data) { console.log("Numeric Validation..."); if(isNumeric(data)) { self.IsValid(true); } else { self.IsValid(false); } }; // end method Validate }; numericField.prototype = protoInstance; var rangedField = function() { var self = this; self.Validate = function(data) { console.log("Ranged Validation..."); }; }; rangedField.prototype = protoInstance; return { RequiredField: requiredField, NumericField: numericField, RangedField: rangedField }; })();
The above code was my first attempt at using a namespaced module pattern in JS. My second question: Have I implemented this properly? What can be done to improve it?
Finally, the main.js
file is the file that handles the functionality specific to this form and is the least re-usable piece of the code, where the other JS files are meant to be able to be "packaged" and re-used in other projects.
ko.bindingHandlers.animator = { "update": function(element, valueAccessor) { var newClass = valueAccessor().toLowerCase(); var oldClass = ""; if(newClass == "invalid") { oldClass = "valid"; } else { oldClass = "invalid"; } // end if/else $(element).switchClass(oldClass, newClass, 500, 'easeInOutQuad'); } }; Factories.RuleValidationFactory.RegisterType("required", Validators.RequiredField); ko.extenders.validatable = function(target, options) { target.State = Factories.RuleValidationFactory.CreateInstance(options.validator); target.State.FieldName(options.name); target.State.DisplayName(options.display); target.State.ShowIndicator(options.indicator); function validate(newValue) { $(document).trigger("form-state-changed"); target.State.Validate(newValue); } // end function validate target.subscribe(validate); return target; }; var ValidatableViewModel = function() { var self = this; var initialState = States.Initial; self.AvailableFields = ko.observableArray([]); self.State = ko.observable(initialState); self.Bind = function(target) { console.log("Binding validatable properties."); //Call bind after all observable properties have been declared on the inherited view model. //Build a list of valid members to check the validation status of var members = []; for (var item in target) { if(!ko.isComputed(target[item]) && ko.isObservable(target[item])) { if(target[item].State != undefined && target[item].State != null) { members.push({name: item, type: typeof target[item], instance: target[item]}); } // end if } // end if } // end for loop self.AvailableFields(members); }; self.IsValid = ko.pureComputed(function() { if(self.State() == States.Initial) { return true; } // end if var totalCount = self.AvailableFields().length; var validCount = 0; ko.utils.arrayForEach(self.AvailableFields(), function(item) { var isValid = item.instance.State.IsValid(); if(isValid) { validCount++; } // end if }); return validCount == totalCount; }, self); self.setInitialState = function() { self.State(States.Modified); }; self.ValidationState = ko.pureComputed(function() { return self.IsValid() ? 'valid' : 'invalid'; }, self); }; var FormViewModel = function() { var self = this; self.FirstName = ko.observable("").extend({ validatable: { name: "FirstName", display: "First Name", validator: "required", indicator: true } }); self.LastName = ko.observable("").extend({validatable: {name: "LastName", display: "Last Name", validator: "required"}}); self.Bind(self); }; FormViewModel.prototype = new ValidatableViewModel; $(function() { $(document).bind("form-state-changed", function(e, data) { if(model.State() == States.Initial) { model.State(States.Modified); } // end if }); });
Other bits of information:
- Using jQuery 2.1.0
- Using jQuery-UI 1.11.1
- Using KnockoutJS 3.2.0
I have omitted the CSS file for brevity, but I can include it on request.
Summary of Concerns:
- Is this a good application for the factory pattern?
- Am I implementing the factory / module patterns properly?
- Are there any things I should consider in terms of how well this code will scale and flex?
- How efficient is this likely to be on a large form?
- Am I understanding the prototypal inheritance model correctly?
I am open to any and all criticisms.
I am working on a GitHub repository for this also. The complete source listing that can be more easily run can be found here: https://github.com/xDaevax/Industrial-Validation