Hybrid app development with Cordova can be challenging at times. While it makes so much sense to build Web applications that can run as an app in a WebView container that can run on most device platforms, it's good to remember that these apps are not 'just' Web apps. There are all these little gotchas that you run into with seemingly simple things that work in a normal Web application but have wonky behavior or work very differently when running inside of a WebView container. Here's one of these fun ones: External links that should not open inside of the current application but in a full web browser instance. It sure is a lot harder than it should be.
What's the problem? External Links stay in the WebView
Cordova applications run in a WebView container in iOS and one of the gotchas that you are likely to run into if you have any external links is that external links will try to display in the current WebView of your application. So lets say in my AlbumViewer application I'm viewing an album loaded from the application which displays content from an internal HTML link (or a re-rendered view in this case from Angular).
Then there are a few links on the page that link to external content – in this case external links to buy an album on Amazon or play it on Spotify. Here's what this looks like in my sample app:
On a Web page you might do something very simple to link these external URLs to Amazon or Spotify - in this case by simply having HREF links like this:
<a ng-href="{{view.album.AmazonUrl}}" class="btn btn-sm btn-default" target="_blank"><i class="fa fa-dollar" ></i> Buy</a>
and that works fine in the browser. Because of the target="blank" the window opens in a new tab and you can easily get back to the original tab. Even in the same window without the target="blank" you can always use the back button to get back.
However, in a Hybrid app running in a WebView you don't have tabs or a back button. There's no browser chrome and you can't use a backspace key or swipe right to go back since those gestures are not supported:
You're stuck on this page.
This behavior is actually what you want most of the time. Since hybrid apps are supposed to be 'apps' and not just wrapped Web pages, apps should provide for their own navigation features. You shouldn't be able to arbitrarily jump around the application by moving back and forth unless you explicitly expose that functionality as part of your UI.
That's all well and good, but in the code above this obviously not the behavior we want – we want to navigate to external content and then somehow actually get back. Target links don't do it and neither does the following script code calling window.open():
vm.openAmazonUrl = function(album) {
window.open(album.AmazonUrl,"_system");
}
Even the explicit window.open operation forces the window to open in the WebView rather than in a new browser window. Note that the behavior varies. Android for example, does the right thing with window.open() and opens a new window. iOS… not so much.
Low Level Fixes
iOS requires a low level workaround to this problem and the workaround for this problem is – you guessed it – a plug-in. It seems really sad that something so simple requires an actual plug-in to work. The solution on iOS lies with a very low level fix – which is o create a custom Objective-C handler for the Web View control that detects external link requests and then opens them externally? Are you serious? Here's an old Stackoverflow Question that goes over a few solutions that no longer work with the exception of the Objective-C hack. Crazy huh?
The InAppBrowser Plug-in
Luckily there's a cordova.inAppBrowser plug-in that encapsulates this hack in an easy to add plug-in. This is a much simpler solution that doesn't require hacking the generated cordova WebView wrapper code that can be overwritten by updates. The plug-in basically provides the ability for window.open() to open a new window in the external browser.
You can add this plug in with:
cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-inappbrowser.git
in your Cordova project or – if you're using Visual Studio's Cordova Tools by adding it from the Visual Studio add-in Configuration page.
The plugin basically replaces the window.open() function inside of the WebView control and so causes a new instance of the device browser to open – on iOS that'd be Safari. So rather than using a direct link in my Angular app I had to change the code a bit to either using an onclick handler or an Angular call to a controller method:
<a ng-click="vm.openAmazonUrl(view.album)" class="btn btn-sm btn-default"><i class="fa fa-dollar"></i> Buy</a>
and then adding this function to the controller:
vm.openAmazonUrl = function(album) {
window.open(album.AmazonUrl);
}
And here's what you get:
It's a full instance of Safari opened in a separate window. More importantly you see both this browser view and the original application in the task list so you can switch back and forth easily:
Handling Target Links
With the plug-in installed you can also simplify the process a bit more with a little bit of script code to capture target links and then automatically opening them in the browser. You can use the following as part of the startup code in your Cordova app:
window.addEventListener('load', function () {
$(document).on('click', 'a[target="_system"],a[target="_blank"]', function (e) {
e.preventDefault();
var url = this.href;
window.open(url,"_system");
});
//}
}, false);
This code basically looks for anything that has _blank or _system in the target tag and if it does, routes that to window.open() instead. This makes it a little easier to use the functionality so that you don't have to hook up code just to open a new window. So instead calling my Angular handler or using an onclick that calls window.open() I can use a simple link instead and essentially get the behavior I'd normally expect in the browser:
<a ng-href="{{view.album.AmazonUrl}}" target="_blank"
class="btn btn-sm btn-default"><i class="fa fa-dollar"></i> Buy</a>
Much nicer and more importantly allows me to leave my existing links – assuming they go to the right target – intact without having to change code specifically for Cordova.
Summary
It's amazing how complicated some simple things like this can be, isn't it? It seems like this would be trivial to handle natively inside of the WebView control. A window.open() *should* go out to a new browser window just like it would in a browser. Some devices – notably recent versions of Android – do get this right and work without requiring a plug-in to make this happen. On those platforms that natively support browsing to a new browser the implementation is just passed through. Unfortunately other platforms like iOS do require this lousy workaround and so we're stuck with using a plug-in. It's easy enough once you know what needs to happen and what the problem is. It's just another one of those weird things you have to remember. Hopefully this blog post helps finding this info.
Resources
Other Posts you might also like