Open link in native browser from InAppBrowser - ios

I'm building an app that uses the InAppBrowser quite a lot. At some point, the user is able to click an external link from within this window. I tried diffrent methods, but none seems to get a good working result.
The best solution so far is listening to the loadstart event (As described here):
app.browser.addEventListener('loadstart', function (inAppBrowser) {
if(inAppBrowser.url.match(/domain\.com/) === null) {
var url = inAppBrowser.url;
window.open(url, "_system");
}
}
This opens the link in a new window, but also in the original InAppBrowser. Is it possible to cancel this event? Or is there a other approach i can try?
I already tried the following approaches:
Cross window communication.
Inserting a history.back(-1) via the executeScript method.
Call the window.open(url, '_system'); from within the InAppBrowser.
This is for iOS specific.
EDIT:
I ended up by adding this code in platforms/ios/APPNAME/Plugins/org.apache.cordova.inappbrowser/CDVInAppBrowser.m:
NSString *domainStr = [NSString stringWithFormat:#"domain.com"];
NSString *urlStr = [NSString stringWithFormat:#"%#", request.URL];
NSRange result = [urlStr rangeOfString:domainStr];
if(result.location == NSNotFound) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlStr]];
return NO;
}
above this code:
return [self.navigationDelegate webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType];

You have some options:
hide the external links injecting css to inappbrowser, if they don't appear, then they can't be clicked
add a loadstop listener and then hide the links
app.browser.addEventListener('loadstop', hideLinks);
function hideLinks(){
app.browser.insertCSS({
code: "a { display: none; }"
}, function() {
console.log("Styles Altered");
});
}
modify/subclass inappbrowser, changing the shouldStartLoadWithRequest method
change the return [self.navigationDelegate webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType]; to return NO if the URL isn't yours

Related

NativeScript WebView open url in default browser

I am trying to build app with WebView and click/tap events on URL-s inside WebView. Solution below opens external browser and URL but it loads same url content in webview as well. Is there a way to prevent loading new url inside webview?
Here is my code sample.
function onWebViewLoaded(webargs) {
const page = webargs.object.page;
const vm = page.bindingContext;
const webview = webargs.object;
webview.on(webViewModule.WebView.loadFinishedEvent, (args) => {
let message = "Loading in progress....";
if (!args.error) {
message = `WebView loading finished with url: ${args.url}`;
} else {
message = `Error received ${args.url} : ${args.error}`;
}
if (args.url.indexOf('http://') === 0) {
// Stop the loading url first... but how..
// Open URL in external default browser
utilityModule.openUrl(args.url);
}
});
}
I have tried with setting a flag isUserInteractionEnabled="false" added to my xml view but then all interactions are disabled. Does someone knows how to do this?
Try with loadStartedEvent
webview.on(webViewModule.WebView.loadStartedEvent, (args) => {
if(!args.url.includes("whatever.net")){
webview.stopLoading();
utilsModule.openUrl(args.url);
}
});
This way we catch the loadStartedEvent instead of loadFinishedEvent (where the url is already loaded), we check the url if we want to filter depending on it, and here we can webview.stopLoading() for stop the page load.

How can I reliably detect a link click in UIWebView?

I have a UIWebView and I need to do something when user taps a link. There’s a delegate callback that can be used to detect the taps:
- (BOOL) webView: (UIWebView*) webView
shouldStartLoadWithRequest: (NSURLRequest*) request
navigationType: (UIWebViewNavigationType) navigationType
{
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
…
}
}
The problem is that this code doesn’t handle all link clicks. As an example, a plain Google Search results page does something weird with the links:
<a href="http://example.com/" class="l" onmousedown="return rwt(…)">
<em>Link Text</em>
</a>
The rwt function results in the links not triggering the UIWebViewNavigationTypeLinkClicked event when tapped. Is there a way to reliably detect all events that fall into the “navigate to some other page” bucket?
So far I have arrived at the following solution. First, I inject some JS code into the page when loaded:
function reportBackToObjectiveC(string)
{
var iframe = document.createElement("iframe");
iframe.setAttribute("src", "callback://" + string);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
}
var links = document.getElementsByTagName("a");
for (var i=0; i<links.length; i++) {
links[i].addEventListener("click", function() {
reportBackToObjectiveC("link-clicked");
}, true);
}
When user taps a link, I know it in advance thanks to the webView:shouldStartLoadWithRequest: navigationType: delegate call:
if ([[[request URL] scheme] isEqualToString:#"callback"]) {
[self setNavigationLeavingCurrentPage:YES];
return NO;
}
Then if another request comes and _navigationLeavingCurrentPage is true, I know the user has clicked a link even though the navigation type flag is UIWebViewNavigationTypeOther. I still have to test the solution extensively, for I’m afraid that it will lead to some false positives.
I believe this can be done, by embedding in the HTML code of the website a custom JavaScript which will monitor the events, and based on which event you want to monitor, it could trigger a page redirect with a custom URL scheme, which you can intercept in the shouldStartLoadWithRequest.
Something like this:
<script>
// Function to capture events
function captureEvent(el) {
window.location.href="callback://"+el.href;
}
var elms = document.getElementsByTagName("a");
for (var i=0; i<elms.length; i++) {
elms[i].addEventListener("onmousedown", function(){captureEvent(el)}, true);
}
</script>
Then in the shouldStartLoadWithRequest, you can search for NSURLRequest's that have a callback:// url scheme, and do whatever you want.
This has not been tested, but something like this might get you in the right direction.
Also, since this was mentioned, yes you can add your custom script to any webpage, by using this:
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[super webViewDidFinishLoad:webView];
[webView stringByEvaluatingJavaScriptFromString:#"document.body.insertAdjacentHTML('BeforeEnd','<script>....</script>');"];
}
You need to add this line
webView.dataDetectorTypes = UIDataDetectorTypeAll;
then
(BOOL) webView:(UIWebView *)inWeb shouldStartLoadWithRequest:(NSURLRequest *)inRequest navigationType:(UIWebViewNavigationType)inType
method will get call.
I had a bunch of problems getting this to work. Using a UITapGestureRecognizer works, except that it will always receive the taps, even for links. Unfortunately, links have about a 300 ms delay before they get recognized, which means that the gesture recognizer gets the taps before the link is recognized, which creates a timing problem (even worse because you shouldn't hard code a time to wait in case Apple changes it). Plus, waiting 300+ ms for the tap gives a poor user experience for my app.
So what I ended up doing was overlaying a transparent div on top of everything that gets the non-link taps. Then, put the links at a higher z-level. Finally, make sure everything uses ontouchend="javascript:window.location.href='...';". ontouchend will only act on a tap release, which is what users expect. Setting window.location.href loads a new page, ensuring that all taps call -webView:shouldStartLoadWithRequest:navigationType: and I can check the URL to see which I need to do.
The HTML looks like:
...
...
...
...
(I'm not totally sure if "position: relative" always places things in the same position as otherwise, but it worked nicely for my simple page; your mileage may vary. However, you will needs position:something in order to get z-index to work.)

Phonegap: BarcodeScanner & Childbrowser plugins

I'm facing a problem using this 2 PhoneGap plugins: "BarcodeScanner" & "ChildBrowser" (inside an iOS app, with XCode 4 & PhoneGap 2.0).
I've a button "Scan" on my app UI. When the user clic on this button, the barcode scanner is launched.
So, in the Success function of the barcode scanner callback, I need to open the recovered URL from the scan in a new Childbrowser window (inner the app).
But the new Childbrowser window is never been opened, while the console displays "Opening Url : http://fr.wikipedia.org/" (for example).
Here is my JS part of code:
$("#btnStartScan").click(function() {
var scanBarcode = window.plugins.barcodeScanner.scan(
function(result) {
if (!result.cancelled){
openUrl(result.text);
}
},
function(error) {
navigator.notification.alert("scanning failed: " + error);
});
});
function openUrl(url)
{
try {
var root = this;
var cb = window.plugins.childBrowser;
if(cb != null) {
cb.showWebPage(url);
}
else{
alert("childbrowser is null");
}
}
catch (err) {
alert(err);
}
}
And all works fine if I call my openURL() function inside a Confirm alert callback for example, like this:
if (!result.cancelled){
navigator.notification.confirm("Confirm?",
function (b) {
if (b === 1) {
openUrl(result.text);
}
},
'Test',
'Yes, No');
}
But I need to launch the ChildBrowser window directly after a scan, without any confirm alert etc.
Does anybody know how to solve this please?
I also have this same problem.
Solve it by set timeout.
var scanBarcode = window.plugins.barcodeScanner.scan(
function(result) {
if (!result.cancelled){
setTimeout(function(){ openUrl(result.text); },500);
}
},
function(error) {
navigator.notification.alert("scanning failed: " + error);
});
I'm running into the exact same problem.
My application also has another mechanism to show a webpage besides the barcode reader and when I do that action I can see that the barcode-related page HAD loaded, but it never was shown.
In ChildBrowserViewController.m, I'm looking at the last line of loadURL() which is webView.hidden = NO; and I'm thinking that the child browser is set visible after we barcode but something about the barcode reader window caused the child browser to get set to the wrong z-order, but I'm not familiar enough with the sdk to know how to test that or try to bring it to the front.
Hope this helps target a potential area.

Phonegap fire custom events on iOS

I'm Trying to fire a custom event in phonegap on iOS. Ok what I've done so far :
I've created a custom plugin , I'm able to call my plugin from Javascript and all work properly. Basically the plugin show a ModalViewController presenting some native functionality
such as recording and editing a video, once the user has finished I will upload the video to youtube. I would like to fire an event when the download is completed but at the moment I wasn't able to do this.
This is part of the code I use :
In my index.html I have this function triggered by a click on a button, (I'm not a Javascript developer) nativeFunction basically call my custom plugin.
function testCustomPlugin()
{
MyClass.nativeFunction(
function(result) {
alert("Success : \r\n"+result);
},
function(error) {
alert("Error : \r\n"+error);
}
);
document.addEventListener("post_sent",onPostSent,false);
}
function onPostSent()
{
alert("post sent");
}
This is my MyClass.js :
var MyClass = {
nativeFunction: function(types, success, fail) {
return Cordova.exec(success, fail, "MyClass", "showInterface", types);
}
}
Inside MyClass.m I have two methods : showInterface and sendNotification,
showInterface.m
- (void) showInterface:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
{
self.callbackID = [arguments objectAtIndex:0];
// missing code
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(sendSuccessNotification:)
name:#"PostSentNotification" object:nil];
}
-(void)sendSuccessNotification:(NSNotification *)notification
{
if (self.callbackID) {
NSLog(#"%#",callbackID);
NSLog(#"sendSuccessNotification");
NSDictionary *dictionary = [NSDictionary dictionaryWithObject:#"testCallBack"
forKey:#"returnValue"];
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
messageAsDictionary:dictionary
[result setKeepCallbackAsBool:YES];
[super writeJavascript:[result toSuccessCallbackString:self.callbackID]];
}
}
I can see in my log that sendSuccessNotification is called but the event is not fired, I am sure that I do something wrong in the javascript but the problem is that I don't know what.
Thanks in advance for any help
According to your code, I guess you are using PhoneGap / Cordova 1.5.0, right?
I'm asking because a lot of code has been changed in version 1.6.0.
In my opinion the following line is incorrect:
self.callbackID = [arguments objectAtIndex:0];
You can check this yourself by debugging the string value.
According to my information the following statement should be used to retrieve the correct callback id for your result object:
self.callbackID = [arguments pop];
Also, please make sure that you have registered your plugin in your Cordova.plist.
Hope this solves your issue.
Best,
Martin
your js does look a little weird - I recommend following this tutorial (or just download the project and take it from there;-)
http://www.adobe.com/devnet/html5/articles/extending-phonegap-with-native-plugins-for-ios.html

iOS Web App: Showing content only if the application is standalone

If a user visits my websites example, from Safari Mobile how could I place there a blank page that says "Add To Homescreen."? Once added it would show different content.
You'll want to check two things. First, is it running on an iOS device? Second, is window.navigator.standalone == true?
window.navigator.standalone is primarily used by Webkit browsers to indicate the app is in fullscreen (or standalone) mode. Plenty of devices (like phones running Android), support this property, but don't have the option to 'Add to Homescreen' like iOS devices do, so you need to check both.
Demo:
Javascript:
function isIOS() {
var userAgent = window.navigator.userAgent.toLowerCase();
return /iphone|ipad|ipod/.test( userAgent );
};
function isStandalone() {
return ( isIOS() && window.navigator.standalone );
};
window.onload = function () {
if( isStandalone() || !isIOS() ) { //either ios+standalone or not ios
//start app
} else {
//display add to homescreen page
};
};
Check window.navigator.standalone.
Slight slight different code, based on #ThinkingStiff solution, and this other question on this Post, to support IOS7 detection to provide CSS interface to add more padding-top in case of transparent app title.
isIOS7 = function() {
return navigator.userAgent.match(/(iPad|iPhone|iPod touch);.*CPU.*OS 7_\d/i);
};
isStandaloneAndIOS7 = function() {
return isIOS7() && window.navigator.standalone;
};
if (isStandaloneAndIOS7()) {
body = document.getElementsByTagName("body")[0];
body.className = body.className + " standalone";
}

Resources