Category: Custom Control

AngularJS 1.5 Input Components

Introduction

The promise of JavaScript component-based architecture frameworks such as AngularJS 1.5, Angular 2, or Aurelia is enabling developers to write less code, both HTML markup, and JavaScript.  The ability to encapsulate HTML and JavaScript into reusable components should simplify the application consuming these components greatly.

Please, review this post and code, and then start your own component library for your applications. There are enough components here to get you started.  I have chosen to make these components granular with a single responsibility, rather that make a single component with many properties to change the behavior. This approach provides clean and readable HTML markup, clearly describing the components role and behavior.

This blog post and GitHub repo are written using ES6. I wrote why I use ES6 JavaScript here. I also leverage ES7 experimental class property features in my applications.

AngularJS 1.5 components are uncomplicated element directives. These components can encapsulate business logic and are testable.

If you have time, compare the amount of JavaScript code is required by Angular 2 components to the AngularJS 1.5 components. You’ll find that AngularJS 1.5 is simpler and currently requires considerably less framework code to register. You’ll also see that I separate the component registration from the component, this removes Angular from the component enabling very fast executing unit tests.  See the Review Presentation repo example.

The below images show the clean and minimal HTML required to produce a typical data form with comprehensive validation and associated messages, including password strength metrics. The “match” directive (see app.module.js) on the second input-password component handles comparing the two passwords. The corresponding error message is displayed below these components. The input control placeholder is included for instructional purposes only and is probably not needed in a simple form like this one.

html

form

References

I did my research before embarking on creating these input components. I’m very grateful to the many community authors for their blog posts and answers on StackOverflow. The below links helped me to understand this problem space and I recommend that you study them:

Creating a custom form field with validation using AngularJS 1.5 and TypeScript

Password Strength Meter

InputBaseComponent

The InputBaseComponent is the base class for the text input components. It exposes properties common to all HTML text input controls.

The InputBaseComponent creates the component label from the name attribute if the label attribute is not supplied. It’s onInit, onChange, and validate methods are invoked by deriving classes.

Normally an AngularJS 1.5 component does not update it’s parent model directly. As explained in the above blog post this component design calls for updating the model instead of using two-way bindings or a one-way binding with a corresponding event.

I agree with this architecture in this specific use case because it mimics the interaction that an input control would have if it was not encapsulated in a separate component. It is assumed that the form these components are hosted on would be adhering to the typical AngularJS component design pattern described in the AngularJS documentation.

By requiring the model in the component registration (see input-components.module.js), the model will be injected and is available to the component controller for reading and writing. When the view needs to be updated, $render is invoked and the below function updates the component value. When the component value is changed, the model value is set in the onChanage method.

COMPONENT REGISTRATION: 
require: {
    model: "ngModel"
}

ONINIT:
this.model.$render = () => {
  this.value = this.model.$viewValue;
}

ONCHANGE:
this.model.$setViewValue(this.value);

This base class handles setting sane values for the maximumLength and for setting  isRequired based on the minimumLength. The isRequired property is used in the HTML to set a required attribute.

The onChange method provides data input length validation by limiting the value length to the maximumLength.  This code is wrapped in try-catch block to provide a seam for logging an obvious attempt to hack your site. Remember, always defend in layers and never trust any data from the internet either on the client or server.

The validate method checks the input for minimum or maximum length validation rule violations. What is nice about this implementation is that the validation messages can easily be customized to provide the user with good information.  In this application, I’ve chosen to display the validation message next to the label.  If you don’t do this, then be sure to include the label in your validation message.

export default class InputBaseComponent {

  label;
  model;
  value;
  name;
  validationError;
  minimumLength;
  maximumLength;
  isRequired = false;

  constructor(StringUtility) {
    this.StringUtility = StringUtility;
    if(this.name && !this.label) {
      this.label = this.StringUtility.parseWords(this.name);
    }
  }

  onInit() {
    this.model.$render = () => {
      this.value = this.model.$viewValue;
    }
    if(!this.maximumLength) {
      this.maximumLength = 200;
    }
    if(this.minimumLength) {
      this.isRequired = true;
    }
  }

  onChange() {
    try {
      if(this.value && this.maximumLength && 
          this.value.length > this.maximumLength) {
        this.value = this.value.substring(0, this.maximumLength);
      }
    } catch(e) {
      this.value = '';
      // log this to your system as a security message
    }
    this.model.$setViewValue(this.value);
  }

  validate() {
    if(this.isRequired && this.minimumLength) {
      if(!this.value || this.value.length < this.minimumLength) {
        this.validationError = `has a minium length of ${this.minimumLength}`;
        return false;
      }
    }
    if(this.value && this.maximumLength && 
        this.value.length > this.maximumLength) {
      this.validationError = `has a maximum length of ${this.maximumLength}`;
      // log this to your system as a security message
      return false;
    }

    this.validationError = '';
    return true;
  }

}

InputBaseComponent.$inject = ['StringUtility'];

InputTextComponent

Now that we have all the hard work done, let’s extend InputBase and call the required methods.  For an input of type text, no additional validation is required.

import InputBase from '../input-base'

export default class InputTextComponent extends InputBase {

  constructor(StringUtility) {
    super(StringUtility);
  }

  $onInit() {
    super.onInit();
    super.validate();
  }

  onChange() {
    super.onChange();
    super.validate();
  }
}

InputTextComponent.$inject = ['StringUtility'];

InputEmailComponent

The InputEmailComponent extends the InputBase and adds email input validation. Notice that if the call to super validate returns false, this validation does not run.  You can change this logic to display all validation errors or display them one at a time as I do.

import InputBase from '../input-base'

export default class InputEmailComponent extends InputBase {

  constructor(StringUtility) {
    super(StringUtility);
  }

  $onInit() {
    super.onInit();
    this.validate();
  }

  onChange() {
    super.onChange();
    this.validate();
  }

  validate(){
    if(super.validate()) {
      if(this.value && 
          !this.StringUtility.isEmailValid(this.value)) {
        this.validationError = ' has invalid format';
        return;
      }
    }
  }
}

InputEmailComponent.$inject = ['StringUtility'];

InputTextComponent Template

This template has four groups:

  • input-group – apply CSS styles for spacing and alignment
  • form – subform container for the component
  • label-area – displays the label and associated validation messages
  • input – the input control

The validation messages are shown when:

  • the form control is invalid
  • and validation error text has a value
  • and
    • the control has been touched
    • or parent form has been submitted

You can choose when users see validation messages. I’ve used the above logic in my applications and users are happy with the experience.

<input-group>
  <ng-form name="{{vm.name}}Form">
    <label-area>
      <label for="{{vm.name}}">{{vm.label}}</label>
      <error-message ng-show="{{vm.name}}Form.{{vm.name}}.$invalid && 
        vm.validationError && 
        ({{vm.name}}Form.{{vm.name}}.$touched || 
        {{vm.name}}Form.$$parentForm.$submitted)">
          {{vm.validationError}}</error-message>
    </label-area>
    <input
      type="text"
      placeholder="{{vm.label | lowercase}}"
      autocomplete="off"
      ng-model-options="{allowInvalid: true, debounce: 250}"
      name="{{vm.name}}"
      id="{{vm.name}}"
      ng-model="vm.value"
      ng-change="vm.onChange()"
      ng-required="vm.isRequired"
      ng-minlength="vm.minimumLength"
      ng-maxlength="vm.maximumLength"
      maxlength="{{vm.maximumLength}}"
      >
    </input>
  </ng-form>
</input-group>

HTML Input Control

  • placeholder – optional.  I like, that I can bind the label and then use an Angular filter to change the label to lower case.
  • autocomplete – choose the option that meets your requirements
  • ng-model-options
    • allowInvalid – when true, allow ng-change to be raised on keystrokes when the control is invalid.
    • debounce – the time delay for model updating since the last keyboard input.
  • ng-model – binds to the component value
  • ng-change invokes the onChange method
  • ng-required – when true, sets the required attribute on the control
  • ng-minlength – sets the minimum length rule for the control
  • ng-maxlength – sets the maximum length rule for the control
  • maxlength – I STRONGLY urge you to ALWAYS set this attribute on any text input control.  Setting this attribute limits the number of characters that can be entered, pasted or dragged into the control at runtime. Remember that security is always in layers.

Form Submit Button Options

There are two schools of thought around form submit buttons.  One is to disable the submit button when the form is invalid, the other to allow the user to click the submit button and then show all input validation messages.

Your choice will depend on the application requirements and stakeholder choices.

The submit button strategy needs to take into account the applications timing of when to render validation messages.

I prefer to only show validation messages after the user visits the control or when the user submits the form and the control is invalid.

Thoughts

Whether you’re using AngularJS 1.5, Angular 2, Aurelia, et al., components provide the HTML and JavaScript developer a powerful programming paradigm that embraces separation of concerns, test ability, maintainability, readability, and are down right fun to work with.

Download

https://github.com/Oceanware/InputComponents

Close

Have a great day,

Just a grain of sand on the world’s beaches

Xamarin Forms Bindable Picker v2

Introduction

I’ve updated the BindablePicker from a previous blog post, added new features and created a github reopro for the code.

Xamarin Forms is a new and cool API for quickly building native apps for IOS, Android, and Windows UWP in C#.

The Xamarin Forms API comes with a primitive Picker control that lacks typical bindable properties that developers expect a Picker (similar functionally that a desktop ComboBox has) to have.

Xamarin Forms makes it very easy for developers to extend the API, write your own custom controls, or write custom renderers for controls.

This BindablePIcker is the result of studying blog and forum posts and receiving feedback and bug report on the original version.

API Comparison

Xamarin Forms Picker API

  • SelectedIndex (bindable)
  • Items (not bindable)

Bindable Picker API

  • ItemsSource (bindable)
  • SelectedItem (bindable)
  • SelectedValue (bindable)
  • DisplayMemberPath
  • SelectedValuePath

New Features Added

  • Support for collections that implement INotityCollectionChanged like the ObservableCollection

Bug Fixed

The original BindablePicker did not correctly set the SelectedItem after the ItemsSource was refreshed at runtime.

Bindable Picker Source

This repro contains a project that demonstrates scenarios for using this control and it has the source for the BindablePicker.

https://github.com/Oceanware/XamarinFormsBindablePicker

Training Video – XAML Power Toys BindablePicker Scenarios

This short video explains three common use cases for the BindablePicker.

Close

Have a great day.

Just a grain of sand on the worlds beaches.