Wednesday 5 November 2014

Use input[type="datetime-local"]

Continuing on in my repetitive mini-series on input types. Today I'm looking at the datatime-local input type. This is an interesting input. I like the different methods of user interactions the browsers have created for this input type. A few of the implementations still need some work, but then whose user interfaces don't?

HTML to Write

<input type="datetime-local"
name="dateTimelocal"
ng-model="dateTimeLocalValue"
ng-required="checkboxValue"
min="2001-01-01T00:00:00"
max="2020-12-31T00:00:00"
 
ng-change="changeDateTimeLocal()"/>

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've tried assigning a default value to this variable in the controller before, but think that this gives quite an annoying user experience as they have to delete it before they can enter their own. I would recommend that you don't assign a default value.

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 be offering users different services, and once they've selected one they need to choose a date and time that they would like an appointment for a given service. 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 email 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.

min and max are also HTML5 attributes, but AngularJs does do validation on these for you. As you can see these are in an interesting format. yyyy-mm-dd is fabulous, as it avoids confusion between American and rest-of-the-world date format. This is followed by a T to indicate that the next bit is time. You don't need to include this T, but it is good practice to do so. The time should be in hh:mm:ss format.

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="datetime-local"
name="dateTimelocal"
ng-model="dateTimeLocalValue"
ng-change="changeDateTimeLocal()"
ng-required="checkboxValue"
min="2001-01-01T00:00:00"
max="2020-12-31T00:00:00"
class="ng-pristine ng-untouched ng-valid ng-valid-min ng-valid-max ng-valid-datetimelocal ng-valid-required">


This is what it looks like in Chrome:


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-invalid-datetimelocal ng-invalid-pattern ng-invalid-min ng-invalid-max ng-invalid-required

The above is for illustrative purposes only. I've as yet been unable to trigger ngInvalidRequired on an email input type, and of course you're not going to have an invalid min and max length 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.dateTimelocal.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.dateTimelocal.$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