In the next two posts I’d like to show you three things:
1. What is a “masked input”.
2. How to create a masked input custom angular directive.
3. And the cherry on top – implement it applying TDD!
So what it is “masked input”?
It allows users to easily enter fixed length input where you would like them to enter the data in a certain format (dates, phone numbers, ids, credit card number and more).
Most of forms out there don’t supply the users any hints about the data’s format and make the users guess it (most of the time the input fields will be blank textboxes). If there’s a format mismatch then the user will receive an error and will try again.
Using masks allow us to show the users the exact format and force them to enter it correctly.
Let’s implement it!
Remember that we’ll do it using TDD.
So first let’s define the desired API:
We want to create a custom attribute directive that will have three properties:
1. Mask – the mask that the user will see in the textbox. These values will be replaced by the user’s input as they type.
2. Skip – an array of chars that we want to be left along with the user’s input like ‘/’ in a date textbox – 01/06/1991
3. Ng-model – the associated model from some controller.
Great! Now after we know our goal we can start writing code.
1. Create ASP.NET Web API project
Add the following nuget packages:
- jasmineTest – this is the jasmine.js testing framework for ASP.NET MVC
The last two packages include definition files used generally for TypeScript for the 3rd parties intellisense. However I found that even if you don’t write TypeScript there intellisense is pretty helpful.
Now let’s add a Client directory, index.html file, app.module.js, app.controller.js and a InputMask directory (all the angular code is style accordingly the John’s Papa style guide).
2. Enter TDD
Before we implement the controller we’ll add a spec file to test the controller, we will add a basic test, let it fail and only then implement the controller and the module.
Add a form.controller.spec.js file (this is a style guide naming convention, formControllerSpec.js is also fine). Now we’ll add a basic jasmine test for testing that our controller is defined.
Nothing special here: we inject the $controller service in order to get our controller, inject it with a new scope and assert that the controller is defined.
Now let’s add all the files into the SpecRunner.cshtml file:
I’ve added angular, angular.mocks, the module and the controller.
The test fails since there’s no module or a defined controller– let’s add them.
First the module:
I’m using IIFE to prevent my functions getting into the global scope and prevent the tests get to my private methods.
And the controller:
I’m using the $inject pattern in order to make sure that my controller parameters won’t get ruined after minifying the code.
Let’s run the test:
Now let’s add another test to validate that the scope works:
Notice that even in js tests I still use the AAA pattern. It makes the tests readable, especially when they get complex.
Run and pass:
1. Adding the directive’s controller
The logic regarding the mask’s chars replacement will be inside the controller (the logic regarding the display will be inside the link function. We’ll get there later).
Add an InputMask directory and place the controller and its spec file in it:
And the first test:
I’m placing the specs files near the source files so that the specs will be immediately noticed when someone looks at the solution and so that it will be extremely easy to see that a file is lacking its tests.
Add the controller and the test will pass.
Now let’s start adding some functional tests.
Every time a user enters a letter a controller function must be called in order to edit the mask, it will check the current input length – it shouldn’t be longer than the defined mask and it should replace the mask characters while skipping the values provided in the “skip” array.
And the implementation:
Replacing the mask as the user enters input:
Notice that the controller sets the ngModel with the mask value on its initialization. We’ll provide the mask value for the ngModel before calling for the editMask method from out test.
Now let’s add a test and its implementation for the “skip” characters functionality.
Pretty straight forward. Notice how the controller doesn’t do any UI logic– just the way it supposed to be.
In a real life application you should add as many test you can for your controllers in order to achieve maximum code and use cases coverage, in this post (which is already quiet long) we’ll stop here and move towards the next topic – testing the directive.