AngularJS, Bootstrap Input Clear Directive

Sometimes the rate of progress moves so fast, there are certain features you see so often you start to believe they are default behavior.  One such example is a clear feature on simple text inputs.  This is useful for things like search queries where you might change your mind and want to completely clear out the field and start over.  This isn’t much of a chore in a experience with a physical keyboard; you can either do a control-A to select everything and delete or just hold down the backspace until everything is gone.  When you have a virtual keyboard, like in a touchscreen/mobile experience, these options are either not available or cumbersome.

Doing some Googling for “bootstrap input clear”, I only found a handful of examples, some of which didn’t actually work.  My suspicion is updates to Bootstrap broke some of the assumptions those developers made about the CSS and JavaScript hooks that were in play.  I also wanted something that would plug into my current preferred framework: AngularJS.

Let’s start with the UI and then work backward to the functionality.  Some examples I saw suggested lots of additional CSS to what already comes with Bootstrap.  That just felt wrong since it already has so many classes and mechanisms.  One suggestion involved using “has-feedback” mechanism which is normally reserved for validation state.  If you ignore the additional classes like “has-error” that enforce color, you actually have something pretty close.

<div class="form-group has-feedback">
  <label class="control-label">Example</label>
  <input type="text" class="form-control" />
  <span class="glyphicon glyphicon-remove form-control-feedback" aria-hidden="true" />
</div>

angular bootstrap input clear 001

It just needs a couple tweaks.  First, we want to adjust the z-index so the span actually sits on top of the input.  We also want the mouse cursor to indicate it’s clickable if we’re in a situation where the user can rollover the span.  Lastly, I think it makes sense to tone it down by changing the opacity to 50%.

.has-clear .form-control-feedback {
  z-index: 10;
  pointer-events: auto;
  cursor: pointer;
  opacity: 0.5;
}

So now it looks how we want.  Now it’s time to make it behave like we want.  If possible, we’d like to write the code once and be able to reuse it for lots of controls.  In the AngularJS world, the most likely choice is a directive.  I obviously didn’t run immediately to this final code; I tinkered a bit with a controller that had the necessary ng-click and ng-show events that worked against a single input and then generalized them.  Here’s where I ended up:

app.directive('inputClear', function () {
  return {
    template: '<span class="glyphicon glyphicon-remove form-control-feedback" aria-hidden="true" ng-click="model = null" ng-show="show()"></span>',
    replace: true,
    scope: {
      model: '='
    },
    controller: ['$scope', function ($scope) {
      $scope.show = function () {
        return (typeof $scope.model !== 'undefined') && $scope.model !== null && $scope.model !== '';
      }
    }]
  }
})

The HTML is short enough that I was comfortable just having it in a string.  I’m defining a scope variable called “model” which will match the ng-model of whatever input this clear will accompany.  It is worth noting I needed to employ the “replace” attribute.  I found that if I didn’t include this, the display didn’t work out exactly as I had hoped.  The code from the template gets injected inside the markup which creates an extra layer and CSS doesn’t recognize the same rules.  There’s some discussion about the replace attribute being deprecated in future releases of Angular but for now this does the job.

One piece that got slightly more complicate was the mechanism to hide/show the clear based on the model’s value.  At first I could have just done ng=show=”model” and test for “truthiness”.  However, certain outliers like inputs with type=number and the value of 0 would end up being false and the clear wouldn’t show.

Now all that’s left is the markup that shows both the inputs for the user to populate as well as multiple instances of our directive:

<div class="form-group has-feedback has-clear">
  <label class="control-label">First Name</label>
  <input type="text" class="form-control" ng-model="data.firstName" />
  <input-clear model="data.firstName" />
</div>

<div class="form-group has-feedback has-clear">
  <label class="control-label">Last Name</label>
  <input type="text" class="form-control" ng-model="data.lastName" />
  <input-clear model="data.lastName" />
</div>

<div class="form-group has-feedback has-clear">
  <label class="control-label">Age</label>
  <input type="number" class="form-control" ng-model="data.age" />
  <input-clear model="data.age" />
</div>

<div class="form-group has-feedback has-clear">
  <label class="control-label">Email</label>
  <input type="email" class="form-control" ng-model="data.email">
  <input-clear model="data.email" />
</div>

Take a look at it in action:

Conclusion

The AngularJS code for this solution is rather straight-forward so you should be able to drop it into an existing solution or a reusable library.  It could also be ported to other frameworks or jQuery if necessary.  I think the heavy lifting is Bootstrap’s CSS placing the icon.

Side-Note

I did notice something I hadn’t expected when testing this code.  Turns out, if you have an input with type=email, Angular doesn’t register a value until you have text that is considered a legit email address.  That means the clear element doesn’t appear until that’s the case either, since Angular tells my code that the value is null.  I leave it to you whether this is a show-stopper.  The alternative is making the input a normal type=text and doing all the validation by hand.

Leave a Reply