Soft keyboards in Android / iPhone PhoneGap applications when we call focus()

android phone screenshot with a picture of hand touching it Android and iPhone – despite being rival products – both have one very annoying security / performance thing in common. None of them will show you a soft keyboard when you programmatically focus on a text field or a text area. This still remains an unsolvable puzzle for web developers, however for native applications (like PhoneGap ones), here comes the rescue :)


Why no keyboard on focus?

All sources I came across will basically tell you same thing – it’s a performance or security issue. Due to many developers not handling focus() very intelligently, browsers on these mobile devices disallowed showing and hiding the keyboard as you focus on an element. This is because the animation would create an enormous computation overhead if a developer uses focus() and blur()frequently on a page. So much for our joyful lives :(


Showing soft keyboard on Android

Fortunately, it IS possible to show a soft keyboard programmatically. There is a simple PhoneGap plugin called SoftKeyBoard that will allow you to do so. It only needs a bit of polishing on JavaScript part, where you need to replace all “PhoneGap” words to “cordova” and remove the PluginManager.addService partcompletely. Easy enough!

For those folks who aren’t able to update the plugin on their own, this is the updated source. Java first:

package com.cordova.plugin.softkeyboard;

import org.json.JSONArray;
import android.content.Context;
import android.view.KeyEvent;
import android.view.inputmethod.InputMethodManager;

import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;

public class SoftKeyBoard extends Plugin {

    public SoftKeyBoard() {
    }

    public void showKeyBoard() {
    	try {
	        //use this for PhoneGape version before 2.0: InputMethodManager mgr = (InputMethodManager) this.ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
    		InputMethodManager mgr = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
	        mgr.showSoftInput(webView, InputMethodManager.SHOW_IMPLICIT);
	
	        //use this for PhoneGape version before 2.0: ((InputMethodManager) this.ctx.getSystemService(Context.INPUT_METHOD_SERVICE)).showSoftInput(webView, 0);
	        ((InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE)).showSoftInput(webView, 0);

                // this code will send a "DELETE" keypress to your page, thus making your input active and moving caret inside it
                // replace cordova.getActivity() by this.ctx  if running PhoneGap before version 2.0
	        cordova.getActivity().runOnUiThread(new Runnable() {
	        	public void run() {
	    	        webView.dispatchKeyEvent(new KeyEvent( KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL ));
	    	        webView.dispatchKeyEvent(new KeyEvent( KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL ));
	        	}
	        });
	        
    	}
    }
    
    public void hideKeyBoard() {
        //use this for PhoneGape version before 2.0: InputMethodManager mgr = (InputMethodManager) this.ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
    	InputMethodManager mgr = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
        mgr.hideSoftInputFromWindow(webView.getWindowToken(), 0);
    }
    
    public boolean isKeyBoardShowing() {
        
    	int heightDiff = webView.getRootView().getHeight() - webView.getHeight();
    	return (100 < heightDiff); // if more than 100 pixels, its probably a keyboard...
    }

	public PluginResult execute(String action, JSONArray args, String callbackId) {
		if (action.equals("show")) {
            this.showKeyBoard();
			return new PluginResult(PluginResult.Status.OK, "done");
		} 
        else if (action.equals("hide")) {
            this.hideKeyBoard();
            return new PluginResult(PluginResult.Status.OK);
        }
        else if (action.equals("isShowing")) {
			
            return new PluginResult(PluginResult.Status.OK, this.isKeyBoardShowing());
        }
		else {
			return new PluginResult(PluginResult.Status.INVALID_ACTION);
		}
	}    
}

And the JavaScript:

        // soft keyboard plugin
	var SoftKeyBoard = {
		show: function(win, fail){
			return cordova.exec(function(args){
				if (win !== undefined) {
					win(args);
				}
			}, function(args){
				if (fail !== undefined) {
					fail(args);
				}
			}, "SoftKeyBoard", "show", []);
		},
		
		hide: function(win, fail){
			return cordova.exec(function(args){
				if (win !== undefined) {
					win(args);
				}
			}, function(args){
				if (fail !== undefined) {
					fail(args);
				}
			}, "SoftKeyBoard", "hide", []);
		},
		
		isShowing: function(win, fail){
			return cordova.exec(function(args){
				if (win !== undefined) {
					win(args);
				}
			}, function(args){
				if (fail !== undefined) {
					fail(args);
				}
			}, "SoftKeyBoard", "isShowing", []);
		}
	};

        // usage:
        // SoftKeyBoard.show(function () {
	    // success
	//},function () {
	   // fail
	//   console.log('keyboard show error');
	//});


Showing soft keyboard on iPhone

UPDATE (31 Jan 2013) – as of Cordova 2.2, the iOS property mentioned above can simply be added to the Cordova.plist (or from 2.3 config.xml) file: KeyboardDisplayRequiresUserAction, boolean, NO

UPDATE: thanks to a wonderful collaboration with Andrei Filip and the correction my own mistake, we have a working example of code that will show soft keyboard on iPhone. It’s a bit hack-ish and needs a plugin for it to work, but it’s all perfect and cool… and even valid, as it uses public API (a requirement for Apple Store applications)!

First of all, the reason nothing worked for me before was because I was calling a focus() function on the input element via JavaScript. This works fine if I don’t make a parallel call to our plugin.

This plugin attempts to make another (invisible, generated) text field and make it active – this showing a virtual keyboard. So I had to focus() the HTML element first and make a setTimeout() call to our plugin after 100ms to create the new – invisible – text field and make it active.

For those who did not get the link to our solution, here it is: http://stackoverflow.com/questions/12140484/programmatically-show-soft-keyboard-on-iphone-in-a-phonegap-application

And here it is transcribed:

file keyboardHelper.h

//
//  keyboardHelper.h
//  soft keyboard displaying plugin for PhoneGap
//
//  Copyright 2012 Martin Ambrus.
//

#import <Foundation/Foundation.h>
#ifdef CORDOVA_FRAMEWORK
#import <Cordova/CDVPlugin.h>
#else
#import "CDVPlugin.h"
#endif

@interface keyboardHelper : CDVPlugin {
NSString *callbackID;
}

@property (nonatomic, copy) NSString *callbackID;

- (void)showKeyboard:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;

@end

file keyboardhelper.m

//
//  keyboardHelper.m
//  soft keyboard displaying plugin for PhoneGap
//
//  Copyright 2012 Martin Ambrus.
//

#import "keyboardHelper.h"
#import "AppDelegate.h"

@implementation keyboardHelper
@synthesize callbackID;

-(void)showKeyboard:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options {
    self.callbackID = [arguments pop];

    //Get text field coordinate from webview. - You should do this after the webview gets loaded
    //myCustomDiv is a div in the html that contains the textField.
    int textFieldContainerHeightOutput = [[((AppDelegate *)[[UIApplication sharedApplication] delegate]).viewController.webView stringByEvaluatingJavaScriptFromString:@"document.getElementById(\"myCustomDiv\").offsetHeight;"] intValue];

    int textFieldContainerWidthOutput = [[((AppDelegate *)[[UIApplication sharedApplication] delegate]).viewController.webView  stringByEvaluatingJavaScriptFromString:@"document.getElementById(\"myCustomDiv\").offsetWidth;"] intValue];

    int textFieldContainerYOffset = [[((AppDelegate *)[[UIApplication sharedApplication] delegate]).viewController.webView  stringByEvaluatingJavaScriptFromString:@"document.getElementById(\"myCustomDiv\").offsetTop;"] intValue];

    int textFieldContainerXOffset = [[((AppDelegate *)[[UIApplication sharedApplication] delegate]).viewController.webView  stringByEvaluatingJavaScriptFromString:@"document.getElementById(\"myCustomDiv\").offsetLeft;"] intValue];

    UITextField *myTextField = [[UITextField alloc] initWithFrame: CGRectMake(textFieldContainerXOffset, textFieldContainerYOffset, textFieldContainerWidthOutput, textFieldContainerHeightOutput)];

    [((AppDelegate *)[[UIApplication sharedApplication] delegate]).viewController.webView addSubview:myTextField];
    myTextField.delegate = self;

    CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString: @"ok"];

    [self writeJavascript:[pluginResult toSuccessCallbackString:self.callbackID]];
}

-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
//here you create your request to the server
return NO;
}

-(BOOL)textFieldDidEndEditing:(UITextField *)textField
{
//here you create your request to the server
return NO;
}

@end

javascript:

var keyboardHelper = {
showKeyboard: function(types, success, fail) {
return Cordova.exec(success, fail, "keyboardHelper", "showKeyboard", types);
}
};


Numeric keyboard only?

Now comes the tricky part. Showing an ordinary soft keyboard is not much of a big deal. When you have your element focus()-ed and start typing, you will automatically start typing into the right element. So no catch there. But what if you have an element which should bring up a numeric keyboard only? Let’s say you need the user to input their phone number. In HTML5, there is a nice specification, allowing you to do just that:

<input id="phone" type="tel" name="phone" value="" size="10" />

By using the tel input type, Android and iPhone will present you with a very special numeric keyboard, similar to the one you might be using to place calls on the device. But there is no way (that I know of) to actually bring this type of keyboard up programmatically. So we need to hack a bit here…


Android

The webView variable, available to all PhoneGap plugins, allows us to pass various events to the HTML page. In other words – we can simulate a mouse click or a key press on the page itself. For this kind of keyboard, it should be sufficient to dispatch a keypress event – as if the user would type the letter “a” into such field. This alone will basically switch our keyboard layout into a telephone keypad automatically:

webView.dispatchKeyEvent(new KeyEvent( KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A ));
webView.dispatchKeyEvent(new KeyEvent( KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A ));

If we wanted to, we could play with mouse clicks – but that would require us to pass exact coordinates of the input to our application, so we perform a click into the right element. In any case, this would be the code required to accomplish a mouse click:

webView.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, posx, posy, 0));
webView.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, posx, posy, 0));

Parameters posx and posy can be anywhere between 0 and SCREEN_WITH / SCREEN_HEIGHT.

IMPORTANT NOTE: as of Cordova version 2.0, you apparently need to call these methods from within UI thread, like this:

cordova.getActivity().runOnUiThread(new Runnable() {
    public void run() {
        webView.dispatchKeyEvent(new KeyEvent( KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL ));
        webView.dispatchKeyEvent(new KeyEvent( KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL ));
    }
});


iPhone

This is much easier that you would probably think. Because of the native input box we created, the only thing we need to add before (or after) the line “myTextField.delegate = self;” is this:

myTextField.keyboardType = UIKeyboardTypeNumberPad;
About these ads
Categories: PhoneGap | Tags: , , | 15 Comments

Post navigation

15 thoughts on “Soft keyboards in Android / iPhone PhoneGap applications when we call focus()

  1. Chris

    Hi,

    I don’t suppose you could give any more details on how to get SoftKeyBoard plugin working for Android. I just can’t get it to work and can’t seem to find any thorough documentation or examples. I’m new to PhoneGap/Cordova plugins.

    Thanks.

    • sorry for the late reply – I just discovered that Cordova 2.0 breaks the functionality outlined in this article with regards to numeric keyboard showing… updating article now to reflect changes and including the updated plugin source ;)

  2. There’s an error at line “package com.cordova.plugin.softkeyboard;”
    I’m suppose to change it to my package is it?
    I can’t get it work too. Thank you.

  3. Apologies for what may seem like a stupid question, but how is the Android Java integrated into the app? I assume it has to be in a particular directory and (perhaps) referred to in a configuration file, but as someone who only did a tiny bit of Java programming over a decade ago, I’m stumped.

    Thanks.

    • I would suggest to use Eclipse and the Android SDK plugin for your project development (http://developer.android.com/sdk/index.html). I can only help you with Eclipse, since that’s what I ever used for Java development.

      In Eclipse, you would need to create a new Android project (see Cordova documentation for a howto).
      In the project, create a folder structure of “src/com/cordova/plugin/softkeyboard” and put the SoftKeyBoard.java file into that folder. From that point, it should work fine.

  4. Andrés Palacio

    How can I open the numeric keyboard on Android?

  5. Pto

    It’s not working for me…

    .show(function(){
    console.log(‘SUCCESS’);
    },function(){
    console.log(‘FAILED’);
    });

    I’ve SUCCESS when I focus an input but nothing append

    • Unfortunately, this can be caused by a multitude of device-specific details.

      I do not have resources to help every developer with their specific device experience myself, however you can try StackOverflow (www.stackoverflow.com), where people would be most probably able to help you.

    • Andrés Palacio

      You can’t do focus on success function, you must do it before calling the plugin

  6. Andrés Palacio

    Thanks, it’s perfect…

  7. jamaljohnson2013

    I’m trying to get the numeric keyboard to work for Android but have not had any luck. I’ve tried the KEYCODE_A trick and it still always brings up the regular keyboard. I’m using KitKat (4.4), would that make a difference? I’m also using a “number” field, not a “tel” field. Any help is greatly appreciated! Thank you.

    • unfortunately, I’ve not touched PhonGap in about a year now, so I can’t really answer this… approving comment, so perhaps someone else will be able to help (or use StackOverflow to ask the question?)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com. The Adventure Journal Theme.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: