Sunday, 2 November 2014

Use input[type="number"]

The HTML5 number is a great little input, especially when it's being used on a mobile device. When I first had a smartphone I used to play with the different spinner interfaces, so form filling started taking longer than it really needed to. This is my third post on the input directives.

HTML to Write

<input type="number"
name="number"
ng-model="numberValue"
min="0"
max="100"
ng-required="checkboxValue"
ng-minlength="1"
ng-maxlength="3"
ng-pattern=""
ng-change="numberChanged()" />


AngularJs uses the type attribute to trigger the specific directive. The name attribute is optional, but if you want to be able to programatically query anything about the input you need to have it. There are two ways to query if the input is in a valid state using JavaScript, and it's easier to use both of them if there's a name attribute to reference it by.

ngModel declares the $scope variable that the value of the input should be assigned to. I like assigning a default value for this variable in the controller only if I'm providing a validation range by populating min and max. When I do pre-populate this I do it with one extreme or the other, or a value in the middle, depending on which is most likely to be used as I think it can help on mobile devices to have a starting point.

ngRequired creates the standard HTML5 required attribute if the value of the attribute is true. This allows you to make one field's requirement dependent upon a given boolean, such as could be generated by another field on the same form. For instance, you may have an e-commerce site and after the user has selected an item to purchase you want to know the quantity they need before they can add it to their basket. In my example above I've used the $scope variable of checkboxValue (as seen in my post about input[type="checkbox"]). By doing this my number input is required only when the user has checked that particular checkbox. If you don't want to set this programatically, then just use required.

I find it quite strange to have the ngMinLength and ngMaxLength validators on this kind of input. I also find it frustrating that even though the ngMinLength is set to one, an empty value is valid. I wouldn't advice using ngMinLength because of this. However, ngMaxLength does appear to work as expected and turns the input invalid when a number string longer than it's value is entered.

ngPattern is where you can insert your own regex string. Again, I'm not sure what use this is in a number input. If it's empty (as mine is) then nothing is rendered in the browser, as you can see below.

Finally, ngChange is an expression to execute when the user changes the value of the input. In the example above I've put a function that is declared on the $scope. This will only trigger if the value of the input is currently valid, or the change that the user has made makes the value of the input valid. I find it very confusing when debugging that this isn't hit every time the user makes some change to the value, but it does mean that you can guarantee that this expression or function will only be run if the value is valid and therefore you don't have to check that it is.

Generated HTML

<input type="number"
name="number"
ng-model="numberValue"
min="0"
max="100"
ng-required="
checkboxValue"
ng-minlength="1"
ng-maxlength="3"
ng-change="numberChanged()"
class="ng-pristine ng-untouched ng-valid ng-valid-min ng-valid-max ng-valid-pattern ng-valid-minlength ng-valid-maxlength ng-valid-number ng-valid-required">


This is the HTML that is generated in the browser once the page loads. As you can see most of it is as written. The classes are where I think this starts to get interesting.

CSS Classes

If you've read other posts of mine on input types, you're going to find some of this repetitive...

AngularJs assigns different classes to the input depending on the state of the input and how the user has interacted with it. It pretty much has all the bases covered, which is great for us interface developers. Thanks to these automatically assigned classes we can concentrate on providing an intuitive user experience without having to worry about building all the code to check and handle the user's interaction with it. I know I could have produced many better forms if I'd had AngularJs to work with at the time thanks to these shortcuts, but when there's a deadline and budget constraints it's the nice-to-haves like this that get cut. These classes come from ngModel.NgModelController.

ngPristine means that the user hasn't yet interacted with the input.

ngUntouched means the input hasn't lost focus yet. This could be a bit of a gotcha as it could mean that the input hasn't had focus to lose, or that the input has focus currently and for the first time and therefore hasn't lost it yet.

ngValid means that the state of the input is valid. This includes all the validators that you have added on as attributes. As mentioned above, watch out as an empty string is a valid input value.

ngValid* shouldn't be too hard to work out. These are flags for each of the validators that are valid.

When the user has tabbed over the input in the form without interacting with the input in any other way AngularJs will remove ngUntouched and add ngTouched (ng-touched). ngTouched means that the input has had focus at some point and has now lost it.

Once the user starts entering a value into the input the ngValid and ngValid* classes will be added and removed depending upon whether or not any given validator (or all the validators in the case of ngValid) are returning that the input value is valid. When these conditions are not true AngularJs will show ngInvalid and ngInvalid*:

ng-touched ng-dirty ng-invalid ng-invalid-number ng-invalid-min ng-invalid-max ng-invalid-minlength ng-invalid-maxlength ng-invalid-pattern

The above is for illustrative purposes only. I've as yet been unable to trigger ngInvalidRequired on any input type so I've not included it at all, and of course you're not going to have an invalid min and max length together, nor an invalid min and max together, unless you've made an error populating the validators.

At this point AngularJs will also remove ngPristine and replace it with ngDirty. ngDirty signifies that the user has made some interaction with the value of this input by changing it in some way.

Querying Validity

These classes are fantastic as styling hooks so that you can inform the user that their input is or isn't valid, but what if you want to know this programatically so that you can do some other logic based on that? Thankfully HTML5 has thought about this, and AngularJs has added some sugar on top for use in the DOM. This is where the name attributes come in. Assume that the above input is wrapped in the following form element:

<form ng-submit="submit()" name="angularForm"></form>

From the Controller

In order to query the validity of the input value from the controller you need to use vanilla HTML5:

angularForm.number.validity.valid

This will return true if valid and false if invalid, which makes it perfect for use in an if statement. Don't use this to set a $scope variable to be used on the DOM though.

From the DOM

To query the validity of the input value from the DOM use AngularJs's syntactic sugar:

angularForm.number.$valid

This also returns true when valid and false when invalid, which makes it perfect for showing or hiding error messages related to this input.

Note: Normally I would prefix all the ng-* attributes with data- in order to make the HTML valid. I omitted this time for the sake of clarity.

No comments:

Post a Comment