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.
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.
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.