Hello guys. This one is going to be a mobile app development related post again. I came across an use case where I had to create a done button in the Android InAppBrowser, just like how we have it in iOS. It required some work to get it done, so thought I should share it with you guys too. Let’s see how to do it.
First, let me get the problem straight. This is how the InAppBrowser appears on Android and iOS.
As you can see, iOS has a neat done button at the bottom which the user can press to close the InAppBrowser after he is done using it, whereas in the case of Android, the user has to either press the cross next to the location bar or use the back button to close the InAppBrowser. We’ll try to recreate this done button on Android too.
You might be asking why can’t we just add the button to the source of the page that we are opening, by injecting it using javascript or even more easy, have it declared in the page that we are opening itself??? The reason being, in most of the use cases, you will be opening a third party site like Facebook or Youtube, rather than the site that you create, in which case the technique that we are going to learn will be very useful.
Use case
The use case we’ll be working on is this. The user opens Youtube in the InAppBrowser. After he has done using it, he’ll be using the done button to close the InAppBrowser. This ensures that the user has to go through the same procedure to close the InAppBrowser in both iOS and Android.
I’ll be using Ionic as the hybrid mobile app framework. For simplicity I’ll ignore using Angular for now.
Create a Blank Mobile App
First of all let’s create a blank ionic app.
ionic start InAppBrowser blank
Now we have created a blank Ionic app. Also make sure to add your required platforms. I’ll be adding Android and iOS.
ionic platform add android ionic platform add ios
Install InAppBrowser
Next, we have to install the InAppBrowser plugin. To do so, type the following command:
cordova plugin add cordova-plugin-inappbrowser
You can find the repository for the plugin here. Now we have added the InAppBrowser plugin.
Understanding InAppBrowser
Before we start with the implementation, let us understand the basics of InAppBrowser. An InAppBrowser allows a mobile application to open web pages within the app without the user having to leave the app. If there was no InAppBrowser the page would open in the mobile’s default browser instead.
Opening a web page in the InAppBrowser is very simple. We just have to call window.open
(Of course, after installing the InAppBrowser plugin).
var win = window.open( "http://google.com", "_blank", "location=yes" ); win.addEventListener( "loadstop", function() { //code goes here win.executeScript({ code: "localStorage.setItem('name', '')" }); });
The first argument is the web page to be opened, the second in this case is _blank
, which ensures that the page is always opened in a separate tab. The third is a list of configuration options. We can use these options to do a lot of things, like deciding whether to show the address bar or not and so on. You can have a look at the documentation here.
It has an addEventListener method, but it only supports the following four events :
- loadstart – event fires when the InAppBrowser starts to load a URL.
- loadstop – event fires when the InAppBrowser finishes loading a URL.
- loaderror – event fires when the InAppBrowser encounters an error when loading a URL.
- exit – event fires after the InAppBrowser window is closed.
In the above code, we have added a listener to the loadstop
event. In the loadstop
callback function, we make use of a function called executeScript
to execute some code.
executeScript
executeScript
is a method that executes javascript in the context of the InAppBrowser. The executeScript
method has two parameters. The first parameter is an object which can contain one of the two possible key-value pairs:
code – a string representing the script to be executed
file – a string representing the name of the file containing the script to be executed
The second parameter is a callback function, the first argument of which is an array containing the return value of the scripts executed as the first parameter. Consider the following example:
win.executeScript({ code: "(function() { return 'sidthesloth' })()" }, function(values) { //the first element of the array contains the value returned by the script var name = values[0]; });
When the above code is executed, the value returned by the function will be available as the first element of the array argument to the callback function, in this case value[0]
will be ‘sidthesloth’.
insertCSS
insertCSS
is another method that can be used to inject CSS in the InAppBrowser’s window. The insertCSS
method has two parameters. The first parameter is an object which can contain one of the two possible key-value pairs:
code – a string representing the CSS to be injected
file – a string representing the name of the file containing the CSS to be injected
Consider the following example:
win.insertCSS({ "code": ".body { background: rgba(0, 0, 0, 0.8); }" });
We will be making use of these two functions to create our button.
Base template
The base template will just have a button that will launch the InAppBrowser.
<!DOCTYPE html>
<html>
<head>
<title>Sharing Data between App and InAppBrowser</title>
</head>
<body ng-app="starter">
<ion-pane>
<ion-header-bar class="bar-stable">
<h1 class="title">Ionic Blank Starter</h1>
</ion-header-bar>
<ion-content>
<button type="button" onclick="openInAppBrowser()">Open In App Browser</button>
</ion-content>
</ion-pane>
< script type="text/javascript">
function openInAppBrowser() {
var win = window.open( "test.html", "_blank", "location=yes" );
}
</ script >
</body>
</html>
Let’s us inspect the openInAppBrowser
function to see what is being done.
function openInAppBrowser() { var win = window.open('http://youtube.com', '_blank', 'location=no'); win.addEventListener('loadstop', function() { //code to inject CSS and JS }) }
We open the InAppBrowser, using the window.open
function, passing in the URL to be opened as youtube.com
and _blank
to ensure that the site opens in a new tab. We’ll pass in a configuration parameter location=no
to hide the location bar in Android. This also ensures that in Android, users will not be able to use the cross next to the location bar to close the InAppBrowser.
Note: The location bar does not appear in iOS irrespective of the value of this parameter, as it is Android only.
We’ll then attach a loadstop
event, to inject the CSS and execute the required scripts.
We saw earlier that both the injectCSS
and executeScript
method take two options, one is code
which allows the code/style to be passed as a string, whereas the other one is file
which allows either a .js or .css file to be passed.
But we cannot use the file
option when we are loading a third party site. The reason is because, when the file
option is used, all that the insertCSS/executeScript
function does is that, it injects link
or script
tags into the body of the opened html page. This means a request will be made to load the .css/.js
files from the site that we opened in the InAppBrowser (i.e) in this case a request will be made from Youtube to load a file which we have in our computer, which is forbidden, as it is a cross-origin request.
So we have to sadly stick with the code option for now..:(. When using the code
option, the style
tag will be used to inject inline CSS and script
tags will be used to inject inline JS. Yeah I know it is bad but that’s the only choice we have. Having said that, if it’s a site that you are developing, which is opened in the InAppBrowser, then you can use the file option.
Injecting the CSS
We’ll make use of the insertCSS
method to inject the required CSS.
.youtube_done_button { position: fixed; bottom: 0; width: 100%; background: rgba(0, 0, 0, 0.8); color: #2196F3; padding: 10px; font-size: 20px; }
The above code applies style to the done button to make it appear similar to the button that appears on iOS.
Inside the loadstop
event callback, inject the CSS using the insertCSS
function.
win.insertCSS({ "code": ".youtube_done_button { position: fixed; bottom: 0; width: 100%; background: rgba(0, 0, 0, 0.8); color: #2196F3; padding: 10px; font-size: 20px;}" });
Creating the done button in JS
Next, we will inject the JS code to create a button
(function () { var body = document.querySelector('body'); var button = document.createElement('div'); button.innerHTML = 'Done'; button.classList.add('youtube_done_button'); button.onclick = function () { localStorage.setItem('close', 'true'); }; body.appendChild(button); })();
The above code creates a button and attaches it to the body of the page opened in the InAppBrowser. It also attaches an onclick listener to it.
Inside the loadstop
event callback, add the following code:
win.executeScript({ code: "(function() { var body = document.querySelector('body'); var button = document.createElement('div'); button.innerHTML = 'Done'; button.classList.add('youtube_done_button'); button.onclick = function() { localStorage.setItem('close', 'true'); }; body.appendChild(button); })();" });
You might be wondering why are we setting a value with key close
in localStorage
instead of just closing the InAppBrowser. But we can’t do that…:(
The reason being that in Javascript, only scripts that opened a window are allowed to close it. This means that, in this case, only the parent window
which has a reference to the opened child window
in the variable win
can close it.
However, the child window does not have a reference to the win
variable and hence it cannot close itself.
In javascript, whenever a function is defined, a scope is created at the level where it is defined and is made available to it. We make use of this fact. The callback function to the setInterval
function is defined in the scope of the openInAppBrowser
function.
As the win
variable is defined inside the openInAppBrowser
function, the callback function has access to the win
variable. Now we can use win.close
to close the InAppBrowser. But we don’t know when the user clicks on the close button. This is what we are going to solve.
function openInAppBrowser() { var close; var closeLoop; var win = window.open('http://youtube.com', '_blank', 'location=no'); win.addEventListener('loadstop', function() { //injecting the CSS win.insertCSS({ "code": ".youtube_done_button { position: fixed; bottom: 0; width: 100%; background: rgba(0, 0, 0, 0.8); color: #2196F3; padding: 10px; font-size: 20px;}" }); //setting close to false when the InAppBrowser is opened win.executeScript({ code: "localStorage.setItem('close', 'false');" }); //creating and attaching a button with click listener to the opened page win.executeScript({ code: "(function() { var body = document.querySelector('body'); var button = document.createElement('div'); button.innerHTML = 'Done'; button.classList.add('youtube_done_button'); button.onclick = function() { localStorage.setItem('close', 'true'); }; body.appendChild(button); })();" }); closeLoop = setInterval(function() { win.executeScript({ code: "localStorage.getItem('close');" }, function(values) { close = values[0]; if (close == "true") { clearInterval(closeLoop); win.close(); } }); }, 1000); }) }
Here, we declare two variables close
and closeLoop
. First, we set the close value in local storage to false every time the InAppBrowser is opened.
Then we inject a button with a click listener and it’s associated CSS. When this button is clicked, it sets the close
local storage variable to true.
We also declare a function that executes at an interval of one second. This function gets the value with key close
from local storage. We then check if it is true, if so, we proceed to clear the interval and close the window. If it is false, then we do nothing.
When the user opens the InAppBrowser and is done using it, he clicks the close button which we have injected. The button sets the close
local storage variable to true. On the next execution of the interval function, the value returned by the close
local storage variable will be true, and we proceed to close the InAppBrowser.
Note: We do not have access to win
inside the onclick
of the button as it is injected dynamically into the opened page as inline JS using script
tag (i.e) in other words, it has access only to the child window context.
The interval of one second is used to ensure that if the user closes the InAppBrowser and opens it again, there is enough time for the code:
win.executeScript({ code: "localStorage.setItem('close', 'false');" });
to execute and reset the code
variable in local storage to false. This ensures that the interval function executes only after the variable code
in local storage has been reset to false, thus avoiding accidental closing of the InAppBrowser.
That’s about it to creating your own close button in Android’s InAppBrowser. It might seem that we are going a little overboard to achieve this, but if you ask me it’s worth it, because it ensures that the user has to remember only one way of closing the InAppBrowser. See you in the next one guys…Peace..:)
It didnt work for me, i m not getting any error though, but “Done” button is not displayed. I am using genymotion as a simulator.
LikeLike
It works. Can use localstorage storage event to instead of polling. angular.element($window).on(‘storage’, function (event) {
if (event.key === ‘close’) {
ref.executeScript({
code: “localStorage.getItem(‘close’);”
},
function (values) {
if (values[0] === “true”) {
angular.element($window).off(‘storage’);
ref.close();
}
});
}
});
LikeLiked by 1 person
Thanks for the really nice explanation! It works for me. Had to make a small change though. The line “button.classList.add(‘youtube_done_button” needs a closing inverted comma, bracket and semicolon.
LikeLike
Hm, something strange is happening. If while in the browser I open a link, go back, hit the close then open the page again, it goes directly to win.close. Ideas?
LikeLike
Just remove the item from the local store, when you are closing
if (close == “true”) {
//setting close to false when the InAppBrowser is opened
that. win.executeScript({
code: “localStorage.removeItem(‘close’);”
});
clearInterval(closeLoop);
that. win.close();
}
LikeLike
My PR to add a native footer “Done” button to the Cordova Inappbrowser for the Android platform has been merged so will be present in the next plugin release: https://github.com/apache/cordova-plugin-inappbrowser/pull/258
LikeLike
Thank you very much, it works and solved me a lot of problems.
Please fix error in code as also described by Vanitha.
Replace this string “button.classList.add(‘youtube_done_buttonbutton.onclick” with this “button.classList.add(‘youtube_done_button’); button.onclick”
LikeLike