18
\$\begingroup\$

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:

  1. 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.
  2. The KnockoutJS binding on the form fields triggers the field validation after each keystroke.
  3. 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.
  4. Any validation errors will cause the form to be unable to submit

For this project, I opted to use 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

\$\endgroup\$
11
\$\begingroup\$

This review is mostly about improving the implementation. I don't know JavaScript enough to answer your big questions. Try to catch @konijn or @flambino in the 2nd monitor chat room.

Naming

The first most noticeable thing to me is the naming of variables. I think the convention is to use PascalCase for classes, and camelCase for variables.

JSHint

If you paste your code to http://jshint.com/, it points out a couple of interesting issues, most notably:

  • You have CreateInstance twice in the same object: the second will overwrite the first
  • Parentheses () missing when invoking ValidProto in var protoInstance = new ValidProto;, and by the way protoInstance seems to be unused
  • A couple of other minor issues, do see it for yourself and correct all if possible

Simplification

Although I'm against clever tricks that try to make the code super-compact, I would shorten the code in a couple of places in a not yet too clever way.

Instead of this:

if (self.IsValid()) {
    return "";
} else {
    return self.ValidationMessageFormat().replace("{0}", self.DisplayName());
} // end if/else

I would use a ternary operator:

return self.IsValid() ? "" : self.ValidationMessageFormat().replace("{0}", self.DisplayName());

Instead of this:

var request = new types[type](args);
return request;

Why not simply:

return new types[type](args);

Instead of this:

  if(!isNullOrWhiteSpace(data)) {
    self.IsValid(true);
  } else {
    self.IsValid(false);
  } // end if/else

This is much better:

self.IsValid(!isNullOrWhiteSpace(data));

Instead of this:

var oldClass = "";
if(newClass == "invalid") {
  oldClass = "valid";
} else {
  oldClass = "invalid";
} // end if/else

This is better:

var oldClass = newClass == "invalid" ? "valid" : newClass;

Coding style

The // end if/else and such are indeed pretty annoying, as @JaDogg said, just noise in the code.

I would also remove the unnecessary parentheses from around expressions like these:

return (val == null || val == undefined);
return (isNullOrUndefined(val) || val == "" || val.trim() == "");
\$\endgroup\$
  • \$\begingroup\$ Thanks for all the pointers! Interesting point about the CreateInstance method. I assumed (it seems wrongly) that JS supported method overloading. I also like the use of the ternary operator. The protoInstance is unused on purpose to give a "type" to validate incoming classes against. I tried to achieve inheritance initially using a base Validator type, but the prototypal structure I learned essentially creates a "shared" method across all instances which had some undesirable side-effects. \$\endgroup\$ – xDaevax Oct 22 '14 at 18:49
  • \$\begingroup\$ // end if is useful if your condition block spans several pages. That being said, I do realise that condition blocks shouldn't span several pages. \$\endgroup\$ – John Dvorak Nov 14 '14 at 19:17
7
+25
\$\begingroup\$

(The following is about the HTML only.)

Identation

The identation of your markup is all over the place. I suggest identing by atleast two spaces. I prefer four spaces, though. I’ll append a fixed version below.

Space

Use it.

Separate distinct blocks of code with one or two newlines. Don’t clump elements together like <label>content</label><input>. Doing this will make it hard to find the right things to edit in your code.

If you’re worrying about performance due to large amounts of whitespace, you can safely stop at this point. This can be done later (and should be automated anyways).

Empty HTML Elements

Avoid using empty HTML elements only to apply CSS to them. <div class="spacer"></div> does not belong into your HTML. Separate the concerns and control spacing with CSS. Depending on what the spacer class does, apply it to the parent div.

Nitpicking on your <head>

When using HTML5, you can omit the type attributes on most of the things in the head. Some declarations like for RSS feeds still need them, but JavaScript and CSS don’t.

Fixed Markup

The following is your code idented by four spaces while several chunks of code were put on new lines.

Additional note: I have removed the /’s from void elements like meta, link and input. This is valid HTML5 and doesn’t change anything. If anything it’s a couple of bytes shaved off of your document.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>JavaScript Validator</title>
    <link rel="stylesheet" href="styles/style.css">
    <script src="scripts/jquery.js"></script>
    <script src="scripts/jquery-ui.js"></script>
    <script src="scripts/knockout.js"></script>
    <script src="scripts/library.js"></script>
    <script src="scripts/validators.js"></script>
    <script src="scripts/main.js"></script>
    <script>
        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>
    </div>
</body>
</html>
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.