How to make Ionic Button Bar replace dropdowns in forms

Many examples around the web have Button Bars to replace Dropdowns inside <form>s. These dropdowns are also known as <select> tag. Some refer to them as Radio Groups. In iOS, Ionic Button Bars resemble UISegmentedControl in look and functionality.

In iOS, the Button Bar wasn’t created to be a form input element. It was originally created for navigation. Below is an example of a Button Bar to change views.

iOS Button Bar UISegmentControl as navigation to change views.
The Button Bar or UISegmentControl in iOS Music app to change views between Library and Playlist.

In Ionic 1.x’s documentation of Button Bars, the Button Bars are shown as this:


<div class="button-bar">
  <a class="button">First<a/>
  <a class="button">Second<a/>
  <a class="button">Third<a/>
</div>

By looking at the anchor tags, I personally assume that the intention was to use Button Bars as a navigation. As a side note, basic HTML forms don’t have Button Bars as input elements.

Turn Button Bar into a directive

But we can turn the Button Bar into a form element and replace <select>. We can do this by putting the Ionic Button Bar example as a template in a directive. This template will be expanded later in this article.


.directive('colorPicker', function() {
return {
   restrict: 'E',
   replace: true,
   require: 'ngModel',
   scope: {
     segment: '=ngModel'
   },
   link: function(scope, element, attr, ngModelCtrl) {
   },
   template: '<div class="button-bar">
                 <a class="button button-positive">Red</a>
                 <a class="button button-positive">Blue</a>
                 <a class="button button-positive">Green</a>
              </div>'
};
});

The code above is the basic directive implementation to turn Ionic’s Button Bar into an Radio Group input element. It’s more like a bare bones directive with all the necessary features to create a Radio Group that can be used in any <form>.

We want this directive to be strictly an element. Just like we use <select> in a <form>, we can use <color-picker>.

This directive will be part of the <form>, replacing <select> tag, so we require='ngModel' which is accessed in the link function’s fourth parameter. In the above code, ngModelCtrl is the fourth parameter variable. The ngModel requirement enables the synchronization of the input data between this directive and the <form>.

This directive’s scope is private to prevent accidental data manipulation of the other form elements. This scope.segment binds with this directive’s ng-model attribute. What I mean is this (do not confuse with the require='ngModel'):


<color-picker ng-model="myForm.colorChoice"></color-picker>

The scope.segment binding with the ng-model attribute acts as a bridge for the data between this directive and the <form> it’s in. So when we use the ngModel requirement to set the data inside the directive, the change is reflected to the outside, which is the <form>.

The template has choices for Red, Blue and Green. Right now the buttons look like they’re all selected.

Ionic Button Bar is now an input element inside a form.
Ionic Button Bar is now an input element inside a form. But all of the segments look selected.

Only one selected and the rest are deselected look

There should be only one selected segment, and that selected segment should look filled-in. The rest that are deselected will look outlined.

To do this, we need ng-click() and ng-class for each segment.

Let’s add ng-click() on each segment.


<a class="button button-positive" ng-click="selectColor(\'Red\')">Red</a>
<a class="button button-positive" ng-click="selectColor(\'Blue\')">Blue</a>
<a class="button button-positive" ng-click="selectColor(\'Green\')">Green</a>

Define the selectColor() method inside the link function.


scope.selectColor = function(color) { 
   ngModelCtrl.$setViewValue(color); 
};

Each segment has its own default value. This selectColor() method causes the two-way data-binding change in the view’s ng-model attribute. In turn, this directive’s local scope.segment‘s value also changes to that value. So if the Red segment is clicked, then the ng-model value will be Red. If the Blue segment is clicked, then the ng-model value is Blue, and so on. There can only be one choice.

Add ng-class to each segment

We can select one button bar at a time now but we have to give the impression to the user that the selection has been applied. As stated earlier, There should be only one selected segment, and that selected segment should look filled-in. The rest that are deselected will look outlined. We can do this by adding ng-class to each segment to add Ionic’s button-outline CSS class to the rest of the segments that aren’t selected. Each of the segments should look like this:


<a class="button button-positive" ng-click="selectColor(\'Red\')"
   ng-class="{\'button-outline\': (segment != \'Red\')}" >Red</a>
<a class="button button-positive" ng-click="selectColor(\'Blue\')"
   ng-class="{\'button-outline\': (segment != \'Blue\')}">Blue</a>
<a class="button button-positive" ng-click="selectColor(\'Green\')"
   ng-class="{\'button-outline\': (segment != \'Green\')}">Green</a>

The CSS button-outline is only applied to a segment if the local scope.segment doesn’t equal to the default value of that segment.

The finished directive

So the directive as a whole looks like this:


.directive('colorPicker', function() {
return {
   restrict: 'E',
   replace: true,
   require: 'ngModel',
   scope: {
     segment: '=ngModel'
   },
   link: function(scope, element, attr, ngModelCtrl) {
      scope.selectColor(color) {
         ngModelCtrl.$setViewValue(color);
      };
   },
   template: '<div class="button-bar">
                 <a class="button button-positive" ng-click="selectColor(\'Red\')"
                    ng-class="{\'button-outline\': (segment != \'Red\')}" >Red</a>
                 <a class="button button-positive" ng-click="selectColor(\'Blue\')"
                    ng-class="{\'button-outline\': (segment != \'Blue\')}">Blue</a>
                 <a class="button button-positive" ng-click="selectColor(\'Green\')"
                    ng-class="{\'button-outline\': (segment != \'Green\')}">Green</a>
              </div>'
};
});

Add this directive to a <form>

Below is a simple <form>.


<form name="simpleForm" ng-submit="submitForm(myForm)">
   <input type="text" ng-model="myForm.text" />
   <color-picker ng-model="myForm.colorChoice"></color-picker>
   <button type="submit" class="button button-assertive">Submit</button>
</form>

Test if this directive works with the <form>


.controller("myFormController", function($scope) {
   $scope.submitForm = function(myForm) {
        console.log(myForm.colorChoice);
   };
});

Run the app. Press one of the button bars and click submit. You’ll see the value of the selected button bar in the console log.