Tuesday, 12 August 2014

My Unrequited Love for Isolate Scope

I wrote a little while ago about creating a directive to present server errors on the screen in an Angular application. In my own (not so humble opinion), it was really quite nice. I was particularly proud of my usage of isolate scope. However, pride comes before a fall.

It turns out that using isolate scope in a directive is not always wise. Or rather – not always possible. And this is why:

Error: [$compile:multidir] Multiple directives [datepickerPopup, serverError] asking for new/isolated scope on: <input name="sage.dateOfBirth" class="col-xs-12 col-sm-9" type="text" value="" ng-click="vm.dateOfBirthDatePickerOpen()" server-error="vm.errors" ng-model="vm.sage.dateOfBirth" is-open="vm.dateOfBirthDatePickerIsOpen" datepicker-popup="dd MMM yyyy">

Ug. What happened here? Well, I had a date field that I was using my serverError directive on. Nothing too controversial there. The problem came when I tried to plug in UI Bootstrap’s datepicker as well. That’s right the directives are fighting. Sad face.

To be more precise, it turns out that only one directive on an element is allowed to create an isolated scope. So if I want to use UI Bootstrap’s datepicker (and I do) – well my serverError directive is toast.

A New Hope

So ladies and gentlemen, let me present serverError 2.0 – this time without isolated scope:

serverError.ts

(function () {
  "use strict";

  var app = angular.module("app");

  // Plant a validation message to the right of the element when it is declared invalid by the server
  app.directive("serverError", [function () {

    // Usage:
    // <input class="col-xs-12 col-sm-9" 
    //        name="sage.name" ng-model="vm.sage.name" server-error="vm.errors" />

    var directive = {
      link: link,
      restrict: "A",
      require: "ngModel" // supply the ngModel controller as the 4th parameter in the link function
    };
    return directive;

    function link(scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ngModelController: ng.INgModelController) {
      // Extract values from attributes (deliberately not using isolated scope)
      var errorKey: string = attrs["name"]; // eg "sage.name"
      var errorDictionaryExpression: string = attrs["serverError"]; // eg "vm.errors"

      // Bootstrap alert template for error
      var template = '<div class="alert alert-danger col-xs-9 col-xs-offset-2" role="alert"><i class="glyphicon glyphicon-warning-sign larger"></i> %error%</div>';

      // Create an element to hold the validation message
      var decorator = angular.element('<div></div>');
      element.after(decorator);

      // Watch ngModelController.$error.server & show/hide validation accordingly
      scope.$watch(safeWatch(() => ngModelController.$error.server), showHideValidation);

      function showHideValidation(serverError: boolean) {

        // Display an error if serverError is true otherwise clear the element
        var errorHtml = "";
        if (serverError) {
          var errorDictionary: { [field: string]: string } = scope.$eval(errorDictionaryExpression);
          errorHtml = template.replace(/%error%/, errorDictionary[errorKey] || "Unknown error occurred...");
        }
        decorator.html(errorHtml);
      }

      // wipe the server error message upon keyup or change events so can revalidate with server 
      element.on("keyup change", (event) => {
        scope.$apply(() => { ngModelController.$setValidity("server", true); });
      });
    }
  }]);

  // Thanks @Basarat! http://stackoverflow.com/a/24863256/761388
  function safeWatch<T extends Function>(expression: T) {
    return () => {
      try {
        return expression();
      }
      catch (e) {
        return null;
      }
    };
  }
})();

serverError.js

(function () {
  "use strict";

  var app = angular.module("app");

  // Plant a validation message to the right of the element when it is declared invalid by the server
  app.directive("serverError", [function () {
    // Usage:
    // <input class="col-xs-12 col-sm-9" 
    //        name="sage.name" ng-model="vm.sage.name" server-error="vm.errors" />
    var directive = {
      link: link,
      restrict: "A",
      require: "ngModel"
    };
    return directive;

    function link(scope, element, attrs, ngModelController) {
      // Extract values from attributes (deliberately not using isolated scope)
      var errorKey = attrs["name"];
      var errorDictionaryExpression = attrs["serverError"];

      // Bootstrap alert template for error
      var template = '<div class="alert alert-danger col-xs-9 col-xs-offset-2" role="alert"><i class="glyphicon glyphicon-warning-sign larger"></i> %error%</div>';

      // Create an element to hold the validation message
      var decorator = angular.element('<div></div>');
      element.after(decorator);

      // Watch ngModelController.$error.server & show/hide validation accordingly
      scope.$watch(safeWatch(function () {
        return ngModelController.$error.server;
      }), showHideValidation);

      function showHideValidation(serverError) {
        // Display an error if serverError is true otherwise clear the element
        var errorHtml = "";
        if (serverError) {
        var errorDictionary = scope.$eval(errorDictionaryExpression);
        errorHtml = template.replace(/%error%/, errorDictionary[errorKey] || "Unknown error occurred...");
        }
        decorator.html(errorHtml);
      }

      // wipe the server error message upon keyup or change events so can revalidate with server
      element.on("keyup change", function (event) {
        scope.$apply(function () {
        ngModelController.$setValidity("server", true);
        });
      });
    }
  }]);

  // Thanks @Basarat! http://stackoverflow.com/a/24863256/761388
  function safeWatch(expression) {
    return function () {
    try  {
      return expression();
    } catch (e) {
      return null;
    }
    };
  }
})();

This version of the serverError directive is from a users perspective identical to the previous version. But it doesn’t use isolated scope – this means it can be used in concert with other directives which do.

It works by pulling the name and serverError values off the attrs parameter. name is just a string - the value of which never changes so it can be used as is. serverError is an expression that represents the error dictionary that is used to store the server error messages. This is accessed through use of scope.$eval as an when it needs to.

My Plea

What I’ve outlined here works. I’ll admit that usage of $eval makes me feel a little bit dirty (I’ve got “eval is evil” running through my head). Whilst it works, I’m not sure what I’ve done is necessarily best practice. After all the Angular docs themselves say:

Best Practice: Use the scope option to create isolate scopes when making components that you want to reuse throughout your app.

But as we’ve seen this isn’t always an option. I’ve written this post to document my own particular struggle and ask the question “is there a better way?” If you know then please tell me!

Friday, 8 August 2014

Getting more RESTful with Web API and IHttpActionResult

Up until, well yesterday really, I tended to have my Web API action methods all returning 200's no matter what. Successful request? 200 for you sir! Some validation error in the model? 200 for you too ma'am - but I'll wrap up the validation errors and send them back too. Database error? 200 and and an error message.

It kind of looked like this (this example taken from a previous post):


public class SageController : ApiController
{
  // ...

  public IHttpActionResult Post(User sage)
  {
    if (!ModelState.IsValid) {

      return Ok(new {
        Success = false,
        Errors = ModelState.ToErrorDictionary()
      });
    }

    sage = _userService.Save(sage);

    return Ok(new {
      Success = true,
      Entity = sage
    });
  }

  // ...
}

Well I'm no RESTafarian but this felt a little... wrong. Like I wasn't fully embracing the web. I didn't want to have to include my own Success flag to indicate whether the request was good or not. I decided that I'd rather have it at least a little more webby. To that end, I decided I'd like to have 2xx success status codes for genuine success only and 4xx client error status codes for failures.

Lose the wrapper - embrace the web. This post is about doing just that.

Web API 2 - Bad Job on on the BadRequest Helper

Web API 2 ships with a whole host of API helper methods. Things like Ok (which you can see me using above) and BadRequest. BadRequest was what I had in mind to use in place of Ok where I had some kind of error I wanted to report to the client like so:


public class SageController : ApiController
{
  // ...

  public IHttpActionResult Post(User sage)
  {
    if (!ModelState.IsValid) {

      return BadRequest(new  { 
        Errors = ModelState.ToErrorDictionary()
      });
    }

    sage = _userService.Save(sage);

    return Ok(new {
      Entity = sage
    });
  }

  // ...
}

Looks good right? No more need for my Success flag. Terser. Less code is better code. Unfortunately the built in BadRequest helper method doesn't have the flexibility of the Ok helper method - it doesn't allow you to send anything back you want. Fortunately this is easily remedied with a short extension method for ApiController:


using System.Net;
using System.Web.Http;
using System.Web.Http.Results;

namespace System.Web.Http
{
    public static class ControllerExtensions
    {
        public static IHttpActionResult BadRequest<T>(this ApiController controller, T obj)
        {
            return new NegotiatedContentResult<T>(HttpStatusCode.BadRequest, obj, controller);
        }
    }
}

With this in place I can then tweak my implementation to hook into the extension method:


public class SageController : ApiController
{
  // ...

  public IHttpActionResult Post(User sage)
  {
    if (!ModelState.IsValid) {
      // See how we have "this." before BadRequest so the Extension method is invoked
      return this.BadRequest(new  { 
        Errors = ModelState.ToErrorDictionary()
      });
    }

    sage = _userService.Save(sage);

    return Ok(new {
      Entity = sage
    });
  }

  // ...
}

And now we have have an endpoint that serves up 2xx status codes or 4xx status codes just as I'd hoped. Obviously this change in the way my action methods are returning will have implications for the consuming client (in my case an app built using AngularJS and $q). Essentially I can now use my then to handle the successes and my catch to handle the errors.

Friday, 1 August 2014

AngularJS meet ASP.Net Server Validation

So. You're using AngularJS to build your front end with ASP.Net running on the server side. You're a trustworthy dev - you know that validation on the client will only get you so far. You need to validate on the server.

My particular scenario is where you have a form which you are saving. Angular serves you well when it comes to hooking in your own client side validation. But it doesn't really ship with anything that supports nicely presenting server side validation on the client. Invariably when you look around you find people duplicating their server side validation on the client and presenting all their server side validation in a <div> at the top of the screen.

This works but it's not as helpful to the user as it might be. It groups together all the validation from the server into one place. What I want is field level validation from the server that's presented on a field level basis on the screen. Like this:

I know. A thing of beauty is a joy forever. Let us travel together to this promised land...

What do we need client side?

Well, let's start with a directive which I'll call serverError. This plants a validation message just after the element being validated which is displayed when that element is declared invalid by the server. (That is to say when the ngModel has a $error.server set.) When the element is changed then the $error.server is unset in order that validation can be hidden and the form can be revalidated against the server.

I'm using TypeScript with Angular so for my JavaScript examples I'll give you both the TypeScript which I originally wrote and the generated JavaScript as well.

TypeScript

interface serverErrorScope extends ng.IScope {
    name: string;
    serverError: { [field: string]: string };
}

app.directive("serverError", [function () {

  // Usage:
  // <input class="col-xs-12 col-sm-9" 
  //        name="sage.name" ng-model="vm.sage.name" server-error="vm.errors" />
  var directive = {
    link: link,
    restrict: "A",
    require: "ngModel", // supply the ngModel controller as the 4th parameter in the link function
    scope: { // Pass in name and serverError to the scope
      name: "@",
      serverError: "="
    }
  };
  return directive;

  function link(scope: serverErrorScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ngModelController: ng.INgModelController) {

    // Bootstrap alert template for error
    var template = '<div class="alert alert-danger" role="alert">' +
                               '<i class="glyphicon glyphicon-warning-sign"></i> ' +
                               '%error%</div>';

    // Create an element to hold the validation message
    var decorator = angular.element('<div></div>');
    element.after(decorator);

    // Watch ngModelController.$error.server & show/hide validation accordingly
    scope.$watch(safeWatch(() => ngModelController.$error.server), showHideValidation);

    function showHideValidation(serverError: boolean) {

      // Display an error if serverError is true otherwise clear the element
      var errorHtml = "";
      if (serverError) {
        // Aliasing serverError and name to make it more obvious what their purpose is
        var errorDictionary = scope.serverError;
        var errorKey = scope.name;
        errorHtml = template.replace(/%error%/, errorDictionary[errorKey] || "Unknown error occurred...");
      }
      decorator.html(errorHtml);
    }

    // wipe the server error message upon keyup or change events so can revalidate with server 
    element.on("keyup change", (event) => {
      scope.$apply(() => { ngModelController.$setValidity("server", true); });
    });
  }
}]);

// Thanks @Basarat! http://stackoverflow.com/a/24863256/761388
function safeWatch(expression: T) {
  return () => {
    try {
      return expression();
    }
    catch (e) {
      return null;
    }
  };
}

JavaScript

app.directive("serverError", [function () {
  // Usage:
  // <input class="col-xs-12 col-sm-9" 
  //        name="sage.name" ng-model="vm.sage.name" server-error="vm.errors" />
  var directive = {
    link: link,
    restrict: "A",
    require: "ngModel",
    scope: {
      name: "@",
      serverError: "="
    }
  };
  return directive;

  function link(scope, element, attrs, ngModelController) {
    // Bootstrap alert template for error
    var template = '<div class="alert alert-danger" role="alert">' +
                               '<i class="glyphicon glyphicon-warning-sign"></i> ' +
                               '%error%</div>';

    // Create an element to hold the validation message
    var decorator = angular.element('<div></div>');
    element.after(decorator);

    // Watch ngModelController.$error.server & show/hide validation accordingly
    scope.$watch(safeWatch(function () {
      return ngModelController.$error.server;
    }), showHideValidation);

    function showHideValidation(serverError) {
      // Display an error if serverError is true otherwise clear the element
      var errorHtml = "";
      if (serverError) {
        // Aliasing serverError and name to make it more obvious what their purpose is
        var errorDictionary = scope.serverError;
        var errorKey = scope.name;
        errorHtml = template.replace(/%error%/, errorDictionary[errorKey] || "Unknown error occurred...");
      }
      decorator.html(errorHtml);
    }

    // wipe the server error message upon keyup or change events so can revalidate with server
    element.on("keyup change", function (event) {
      scope.$apply(function () {
        ngModelController.$setValidity("server", true);
      });
    });
  }
}]);

// Thanks @Basarat! http://stackoverflow.com/a/24863256/761388
function safeWatch(expression) {
  return function () {
    try  {
      return expression();
    } catch (e) {
      return null;
    }
  };
}

If you look closely at this directive you'll see it is restricted to be used as an attribute and it depends on 2 things:

  1. The value that the server-error attribute is set to should be an object which will contain key / values where the keys represent fields that are being validated.
  2. The element being validated must have a name property (which will be used to look up the validation message in the server-error error "dictionary".

Totally not clear, right? Let's have an example. Here is my "sageEdit" screen which you saw the screenshot of earlier:


<section class="mainbar" ng-controller="sageEdit as vm">
    <section class="matter">
        <div class="container-fluid">
            <form name="form" novalidate role="form">
                <div>
                    <button class="btn btn-info"
                            ng-click="vm.save()"
                            ng-disabled="!vm.canSave">
                        <i class="glyphicon glyphicon-save"></i>Save
                    </button>
                    <span ng-show="vm.hasChanges"
                          class="dissolve-animation ng-hide">
                        <i class="glyphicon glyphicon-asterisk orange"></i>
                    </span>
                </div>
                <div class="widget wblue">
                    <div data-cc-widget-header title="{{vm.title}}"></div>
                    <div class="widget-content form-horizontal">
                        <div class="form-group">
                            <label class="col-xs-12 col-sm-2">Name</label>
                            <input class="col-xs-12 col-sm-9" 
                                   name="sage.name" ng-model="vm.sage.name" 
                                   server-error="vm.errors" />
                        </div>
                        <div class="form-group">
                            <label class="col-xs-12 col-sm-2">Username</label>
                            <input class="col-xs-12 col-sm-9" 
                                   name="sage.userName" ng-model="vm.sage.userName" 
                                   server-error="vm.errors" />
                        </div>
                        <div class="form-group">
                            <label class="col-xs-12 col-sm-2">Email</label>
                            <input class="col-xs-12 col-sm-9"
                                   type="email"
                                   name="sage.email" ng-model="vm.sage.email" 
                                   server-error="vm.errors" />
                        </div>
                    </div>
                </div>
            </form>
        </div>
    </section>
</section>

If you look closely at where server-error is used we have a name attribute set (eg "sage.email") and we're passing in something called vm.errors as the server-error attribute value. That's because we're using the "controller as" syntax and our controller is called vm.

On that controller we're going to have a dictionary style object called errors. If you wanted to you could put that object on the scope instead and omit the "vm." prefix. You could call it wrongThingsWhatISpottedWithYourModel or barry - whatever floats your boat really. You get my point; it's flexible.

Let's take a look at our sageEdit Angular controller:

TypeScript

module controllers {

  "use strict";

  interface sageEditRouteParams extends ng.route.IRouteParamsService {
    id: number;
  }

  interface sageEditScope extends ng.IScope {
    form: ng.IFormController;
  }

  class SageEdit {

    errors: { [field: string]: string };
    log: loggerFunction;
    logError: loggerFunction;
    logSuccess: loggerFunction;
    sage: sage;
    title: string;

    private _isSaving: boolean;

    static $inject = ["$location", "$routeParams", "$scope", "common", "datacontext"];
    constructor(
      private $location: ng.ILocationService,
      private $routeParams: sageEditRouteParams,
      private $scope: sageEditScope,
      private common: common,
      private datacontext: datacontext
      ) {

      this.errors = {};
      this.log = common.logger.getLogFn(controllerId);
      this.logError = common.logger.getLogFn(controllerId, "error");
      this.logSuccess = common.logger.getLogFn(controllerId, "success");
      this.sage = undefined;
      this.title = "Sage Edit";

      this._isSaving = false;

      this.activate();
    }

    // Prototype methods

    activate() {
      var id = this.$routeParams.id;
      var dataPromises: ng.IPromise<any>[] = [this.getSage(id)];

      this.common.activateController(dataPromises, controllerId, this.title)
        .then(() => {
          this.log("Activated Sage Edit View");
          this.title = "Sage Edit: " + this.sage.name;
        });
    }

    getSage(id: number) {
      return this.datacontext.sage.getById(id).then(sage => {
        this.sage = sage;
      });
    }

    save() {

      this.errors = {}; //Wipe server errors
      this._isSaving = true;
      this.datacontext.sage.save(this.sage).then(response => {

        if (response.success) {
          this.sage = response.entity;
          this.logSuccess("Saved " + this.sage.name + " [" + this.sage.id + "]");
          this.$location.path("/sages/detail/" + this.sage.id);
        }
        else {
          this.logError("Failed to save", response.errors);

          angular.forEach(response.errors, (errors, field) => {
            (<ng.INgModelController>this.$scope.form[field]).$setValidity("server", false);
            this.errors[field] = errors.join(",");
          });
        }

        this._isSaving = false;
      });
    }

    // Properties

    get hasChanges(): boolean {
      return this.$scope.form.$dirty;
    }

    get canSave(): boolean {
      return this.hasChanges && !this._isSaving && this.$scope.form.$valid;
    }
  }

  var controllerId = "sageEdit";
  angular.module("app").controller(controllerId, SageEdit);
}

JavaScript

var controllers;
(function (controllers) {
  "use strict";

  var SageEdit = (function () {
    function SageEdit($location, $routeParams, $scope, common, datacontext) {
      this.$location = $location;
      this.$routeParams = $routeParams;
      this.$scope = $scope;
      this.common = common;
      this.datacontext = datacontext;
      this.errors = {};
      this.log = common.logger.getLogFn(controllerId);
      this.logError = common.logger.getLogFn(controllerId, "error");
      this.logSuccess = common.logger.getLogFn(controllerId, "success");
      this.sage = undefined;
      this.title = "Sage Edit";

      this._isSaving = false;

      this.activate();
    }
    // Prototype methods
    SageEdit.prototype.activate = function () {
      var _this = this;
      var id = this.$routeParams.id;
      var dataPromises = [this.getSage(id)];

      this.common.activateController(dataPromises, controllerId, this.title).then(function () {
        _this.log("Activated Sage Edit View");
        _this.title = "Sage Edit: " + _this.sage.name;
      });
    };

    SageEdit.prototype.getSage = function (id) {
      var _this = this;
      return this.datacontext.sage.getById(id).then(function (sage) {
        _this.sage = sage;
      });
    };

    SageEdit.prototype.save = function () {
      var _this = this;
      this.errors = {}; //Wipe server errors
      this._isSaving = true;
      this.datacontext.sage.save(this.sage).then(function (response) {
        if (response.success) {
          _this.sage = response.entity;
          _this.logSuccess("Saved " + _this.sage.name + " [" + _this.sage.id + "]");

          _this.$location.path("/sages/detail/" + _this.sage.id);
        } else {
          _this.logError("Failed to save", response.errors);

          angular.forEach(response.errors, function (errors, field) {
            _this.$scope.form[field].$setValidity("server", false);
            _this.errors[field] = errors.join(",");
          });
        }

        _this._isSaving = false;
      });
    };

    Object.defineProperty(SageEdit.prototype, "hasChanges", {
      // Properties
      get: function () {
        return this.$scope.form.$dirty;
      },
      enumerable: true,
      configurable: true
    });

    Object.defineProperty(SageEdit.prototype, "canSave", {
      get: function () {
        return this.hasChanges && !this._isSaving && this.$scope.form.$valid;
      },
      enumerable: true,
      configurable: true
    });
    SageEdit.$inject = ["$location", "$routeParams", "$scope", "common", "datacontext"];
    return SageEdit;
  })();

  var controllerId = "sageEdit";
  angular.module("app").controller(controllerId, SageEdit);
})(controllers || (controllers = {}));

Okay - this is a shedload of code and most of it isn't relevant to you. I share it as I like to see things in context. Let's focus in on the important bits that you should take away. Firstly, our controller has a property called errors.

Secondly, when we attempt to save our server sends back a JSON payload which, given a validation failure, looks something like this:


{
  "success":false,
  "errors":{
    "sage.name":["The Name field is required."],
    "sage.userName":[
      "The UserName field is required.",
      "The field UserName must be a string with a minimum length of 3 and a maximum length of 30."
    ],
    "sage.email":["The Email field is not a valid e-mail address."]
  }
}

So let's pare back our save function to the bare necessities (those simple bare necessities, forget about your worries and your strife...):

TypeScript

    save() {

      this.errors = {}; //Wipe server errors

      this.datacontext.sage.save(this.sage).then(response => {

        if (response.success) {
          this.sage = response.entity;
        }
        else {
          angular.forEach(response.errors, (errors, field) => {
            (<ng.INgModelController>this.$scope.form[field]).$setValidity("server", false);
            this.errors[field] = errors.join(",");
          });
        }
      });
    }

JavaScript

    SageEdit.prototype.save = function () {
      var _this = this;
      this.errors = {}; //Wipe server errors
      this.datacontext.sage.save(this.sage).then(function (response) {
        if (response.success) {
          _this.sage = response.entity;
        } else {
          angular.forEach(response.errors, function (errors, field) {
            _this.$scope.form[field].$setValidity("server", false);
            _this.errors[field] = errors.join(",");
          });
        }
      });
    };

At the point of save we wipe any server error messages that might be stored on the client. Then, if we receive back a payload with errors we store those errors and set the validity of the relevant form element to false. This will trigger the display of the message by our directive.

That's us done for the client side. You're no doubt now asking yourself this question:

How can I get ASP.Net to send me this information?

So glad you asked. We've a simple model that looks like this which has a number of data annotations:


public class Sage
{
  public int Id { get; set; }

  [Required]
  public string Name { get; set; }

  [Required]
  [StringLength(30, MinimumLength = 3)]
  public string UserName { get; set; }

  [EmailAddress]
  public string Email { get; set; }
}

When we save we post back to a Web API controller that looks like this:


public class SageController : ApiController
{
  // ...

  public IHttpActionResult Post(User sage)
  {
    if (!ModelState.IsValid) {

      return Ok(new
      {
        Success = false,
        Errors = ModelState.ToErrorDictionary()
      });
    }

    sage = _userService.Save(sage);

    return Ok(new
    {
      Success = true,
      Entity = sage
    });
  }

  // ...
}

As you can see, when ModelState is not valid we send back a dictionary of the ModelState error messages keyed by property name. We generate this with an extension method I wrote called ToErrorDictionary:


public static class ModelStateExtensions
{
  public static Dictionary<string, IEnumerable<string>> ToErrorDictionary(
    this System.Web.Http.ModelBinding.ModelStateDictionary modelState, bool camelCaseKeyName = true) 
  {
    var errors = modelState
      .Where(x => x.Value.Errors.Any())
      .ToDictionary(
        kvp => CamelCasePropNames(kvp.Key),
        kvp => kvp.Value.Errors.Select(e => e.ErrorMessage)
      );

    return errors;
  }

  private static string CamelCasePropNames(string propName)
  {
    var array = propName.Split('.');
    var camelCaseList = new string[array.Length];
    for (var i = 0; i < array.Length; i++)
    {
      var prop = array[i];
      camelCaseList[i] = prop.Substring(0, 1).ToLower() + prop.Substring(1, prop.Length - 1);
    }
    return string.Join(".", camelCaseList);
  }
}

That's it - your solution front to back. It would be quite easy to hook other types of validation in server-side (database level checks etc). I hope you find this useful.