Implementing Decimal Keypad on iOS in a Hybrid Application (Ionic v1)

Being an early employee at any startup gives you the advantage of working on various things at a time. At Fyle, I was able to work on various front-end applications including web app, extension and mobile app. We use the power of ionic to build android and iOS apps.

For those who are not familiar with ionic

It is an open source UI toolkit for building performant, high-quality mobile and desktop apps using web technologies (HTML, CSS, JavaScript).

Since we are into expense management space and our customers heavily use our mobile app to file expenses on the fly, we want them to have a seamless experience when it comes to entering decimal values for amount. The decimal keyboard works out of the box on android but for long, we were struggling to get the same working on iOS.

Since ionic is built on web technologies, the HTML5 input type="number" markup shows the number keypad. Lets make use of this markup on our ionic app to show the numeric keypad.

{% code %}
<input type="number" ng-model="vm.decimalNumber" />
{% code-end %}

Android detects the type=”number” format for us and slides up the decimal keypad by default. But the same markup does not work on iOS. Lets take a look at how the keypads looks on each platform when using the above markup.

As you can see from the above screenshot, on iOS this will display a standard keyboard but slightly modified with a row of numbers at the top and some alphabetic keys at the bottom which isn't ideal. So how do we solve this? To overcome this, we can put in a small hack to make iOS display only numbers. Here is how we can do that.

{% code %}
<input type="number" min="0" inputmode="numeric" pattern="[0-9]*" ng-model="vm.onlyNumbers" />
{% code-end %}

Perfect! This will show up the numeric keyboard on iOS which is what we are expecting. But there is a problem with this approach. The above code works perfectly fine if we want to use a numeric keyboard, but it doesn't show up a decimal keypad. You are restricted to just numbers here and cannot use any decimal numbers.

So what can be done? To overcome this problem we can make use of a cordova plugin that shows a decimal keypad on iOS. We are going to use the plugin called cordova-plugin-decimal-keyboard by mrchandoo.

First let's begin by installing this plugin.

{% code %}
ionic cordova plugin add cordova-plugin-decimal-keyboard
{% code-end %}

And this is how we use the plugin.

{% code %}
<input type="text" pattern="[0-9]*" decimal="true">
{% code-end %}

This plugin only works on iOS. If you notice from the above markup we are using type=”text” and for android we use type=”number”. So how do we make this work on both android and iOS? Ionic provides a utility function to check if the platform is android or iOS and we can use this utility to display the required markup selectively.

In the angular controller:

{% code %}
$scope.isAndroid = ionic.Platform.isAndroid();
{% code-end %}

In the HTML page:

{% code %}
<input type="number" ng-model="decimalNumber" ng-if="isAndroid" /><!-- Android -->
<input type="text" pattern="[0-9]*" decimal="true" ng-model="decimalNumber" ng-if="!isAndroid" /><!-- iOS -->
{% code-end %}

So the ionic.Platform.isAndroid(); will return if our app is running on android and we display the input field based on the returned value.

Even though the above code work perfectly fine, there is one catch. The value of decimalNumber on android will be decimal (floating point number), but on iOS it will be a string. This is because we are using type=”text” on iOS and changing this to number will not display the decimal keypad on android. To fix this we have to convert the string to decimal value. We can do this in the save function just before we send out data to the backend.

{% code %}
if (!$scope.isAndroid) {
   // Convert the value from string to float
$scope.decimalNumber = parseFloat($scope.decimalNumber, 10);
}
{% code-end %}

And here is how the keypad on our iOS app look like.

Awesome! right? Now that we have got the decimal keypad working on both android and iOS, there is one more problem. Using this code in lots of the places will soon become redundant. And in future if we were to change the plugin we would have to change the code in all the places.

What's the solution?

Let us create a directive to handle all of these irrespective of what platform we are using. Our directive should do the following things for us:

  1. Use the appropriate markup to show up the decimal keypad
  2. Return the value as a floating point number to the ng-model

Lets decide on how our directive code should look and work. We can create attribute directive and use it along with the input field. Our markup looks something like this.

{% code %}
<input type="number" decimal-keypad class="amount" ng-model="decimalAmount" name="amount" ng-min="0.01" placeholder="Amount" required>
{% code-end %}

Now lets create our directive. Our directive will have 2 files.

  1. {% code %}
    decimal_keypad.template.html
    {% code-end %}
  2. {% code %}
    decimal_keypad.directive.js
    {% code-end%}

Our directive code looks like this.

{% code %}
<!--- decimal_keypad.template.html -->

<input type="number" />
{% code-end %}

{% code %}
<!-- decimal_keypad.controller.js -->

;(function () {
 'use strict';

 angular
   .module('starter')
   .directive('decimalKeypad', decimalKeypad);

 function decimalKeypad () {
   var directive = {
     restrict: 'A',
     require: 'ngModel',
     replace: true,
     scope: {
     },
     link: decimalKeypadLinkFn,
     bindToController: {
       modelValue: '=ngModel',
       numberValue: '='
     }
   };

   return directive;

   function decimalKeypadLinkFn (scope, element, attrs, ngModel) {
     scope.isAndroid = ionic.Platform.isAndroid();

     if (scope.isAndroid) {
       return;
     }

     element.attr('type', 'text');
     element.attr('pattern', '[0-9]*');
     element.attr('decimal', 'true');

     scope.onAmountFocus = function () {
       if (ngModel.$viewValue) {
         ngModel.$viewValue = ngModel.$viewValue.toString();
       }

       element.attr('type', 'text');
       element.attr('pattern', '[0-9]*');
     };

     scope.onAmountBlur = function () {
       if (ngModel.$viewValue) {
         ngModel.$viewValue = parseFloat(ngModel.$viewValue, 10);
       }

       element.attr('type', 'number');
       element.attr('pattern', '');
     };

     element[0].addEventListener('focus', function () {
       scope.onAmountFocus();
     });

     element[0].addEventListener('blur', function () {
       scope.onAmountBlur();
     });
   };
 }

})();
{% code-end %}

Even though the above code is written for Angular JS, the same logic can be applied to any JavaScript code.

So let me explain what the above code does.

  1. Our template will have the default input as type="number" and we are adding events when user focus and blur the input field via the controller.

    {% code %}
    <input type="number" />
    {% code-end %}
  2. Check if the platform is android. If yes, then return without executing the code further.

    {% code %}
    scope.isAndroid = ionic.Platform.isAndroid();

    if (scope.isAndroid) {
       return;
    }
    {% code-end %}
  3. If the platform is iOS, then we are changing the input type to text, setting the pattern to display numbers and tell the plugin to show the decimal point on the keypad. We are also adding event listeners to the input field to listen for focus and blur events when user clicks on and off the input field.

    {% code %}
    element.attr('type', 'text');
    element.attr('pattern', '[0-9]*');
    element.attr('decimal', 'true');

    element[0].addEventListener('focus', function () {
       scope.onAmountFocus();
    });

    element[0].addEventListener('blur', function () {
       scope.onAmountBlur();
    });
    {% code-end %}
  4. When the user focus on the input field we are changing the model value to string and changing the element's type attribute to text and setting the pattern to display numbers.

    {% code %}
    scope.onAmountFocus = function () {
       if (ngModel.$viewValue) {
           ngModel.$viewValue = ngModel.$viewValue.toString();
       }

       element.attr('type', 'text');
       element.attr('pattern', '[0-9]*');
    };
    {% code-end %}
  5. When the user clicks away from the input field we are changing the model value to float and changing the input type to number and removing the pattern attribute. By doing this the value returned to ng-model is always a number and setting the type to number displays it without any error.

    {% code %}
    scope.onAmountBlur = function () {
       if (ngModel.$viewValue) {
           ngModel.$viewValue = parseFloat(ngModel.$viewValue, 10);
       }

       element.attr('type', 'number');
       element.attr('pattern', '');
    };
    {% code-end %}

By following the above logic we can get the decimal keypad to work on ionic v1 app or any hybrid apps.

If you are looking to implement this on your app, please do try out and let us know of any issues.

Sudheer Ranga

Sudheer is an ex-member of Fyle, but his work carries on with a blog post he once penned. :)

More of our stories from

Engineering
How we created a Medium-like blurry background effect

Here's how we improved user experience, decreased load time and made Fyle accessible for users without a fast internet.

Read more...
Bye bye WordPress, welcome Webflow.

This blogpost documents our journey as we bid goodbye to WordPress and migrated to Webflow.

Read more...
How we reduced our website build time by 59%

I came up with five 3 second changes to reduce the build time by over 59%. Here's more about my experience.

Read more...
Hello, Web Technologies!

I’m a first-time entrepreneur and I’ll be recording my learnings and experiments over time. I am always eager to interac

Read more...
The Non-Boring Guide to OAuth 2.0

If you’re developing an application that needs access to a user’s Google / Facebook / LinkedIn information, you’ll need

Read more...
Dealing with Nested Objects in your Web Application

A couple of weeks ago, I ran into a peculiar problem that I think might be useful to talk about. It took me a bunch of

Read more...
Eliminate Boilerplate Java code with Lombok

I’ve been writing a lot of boilerplate Java code, lately — getters, setters, hashCode, equals and toString. Actually, I’

Read more...
Hello, Web Technologies! — Part II

This is a follow-up to my first post about technology choices I made while building out our product. I wanted to pen my

Read more...
Sharing Files using S3 Pre-signed URLs

Amazon’s S3 is a reliable, cheap way to store data. We use it to store user-uploaded images and documents as s3 objects

Read more...
JSON Web Token Concepts

There are many technical articles about JSON web tokens (JWT) on the interwebs, but I haven’t found one that explains...

Read more...

All Topics