Skip to main content
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Ryan Anklam and Jim Walker
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Ryan Anklam Jim Walker

Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari

By
Published in Comments (47)

When developing web applications for the iPhone, one of the cool things that you can do is save your web application to the "home screen" of your phone. This creates a shortcut to your web application which will launch the site in "standalone mode" or "app mode". When your web application is being viewed in standalone mode, anchor tags (ie. links) open up in the mobile Safari browser which jumps you out of your standalone application. I thought this was just an irritating fact of life. But then yesterday, I came across a Gist by Kyle Barrow that demonstrated how to stay in the iPhone's standalone app mode while still changing the URL of the current document.

The trick, as demonstrated by the Gist, was to prevent the default behavior of links. Instead of letting the browser handle the link actions, we could use event handlers to intercept the click event and manually change the location of the window. In doing so, we can jump from page to page without having to exit the standalone mode and jump into mobile Safari.

Since this was completely changing the way that I understood standalone mode / app mode on the iPhone, I naturally wanted to do some testing. To see this in action, I created two pages that linked to each other:

Index.htm

<!DOCTYPE html>
<html>
<head>
	<title>Index</title>

	<!-- Stand-alone settings for iOS. -->
	<meta name="apple-mobile-web-app-capable" content="yes" />
	<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
	<meta name="apple-mobile-web-app-status-bar-style" content="black" />

	<!-- Load scripts. -->
	<script type="text/javascript" src="./jquery-1.7.1.min.js"></script>
	<script type="text/javascript" src="./links.js"></script>
</head>
<body>

	<h1>
		Hello, Index Here
	</h1>

	<p>
		Go to <a href="./index2.htm">Index 2</a>.
	</p>

</body>
</html>

Index2.htm

<!DOCTYPE html>
<html>
<head>
	<title>Index 2</title>

	<!-- Stand-alone settings for iOS. -->
	<meta name="apple-mobile-web-app-capable" content="yes" />
	<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
	<meta name="apple-mobile-web-app-status-bar-style" content="black" />

	<!-- Load scripts. -->
	<script type="text/javascript" src="./jquery-1.7.1.min.js"></script>
	<script type="text/javascript" src="./links.js"></script>
</head>
<body>

	<h1>
		Hello, Index 2 Here
	</h1>

	<p>
		Go to <a href="./index.htm">Index</a>.
	</p>

</body>
</html>

As you can see, both of these pages use a Meta tag with the "apple-mobile-web-app-capable" setting enabled. This allows the pages to be used in standalone mode / app mode once they are saved to the home screen of the iPhone.

The JavaScript file, links.js, is where we have the script that overrides the default behavior of links in order to keep the standalone web application in standalone mode.

links.js

// Listen for ALL links at the top level of the document. For
// testing purposes, we're not going to worry about LOCAL vs.
// EXTERNAL links - we'll just demonstrate the feature.
$( document ).on(
	"click",
	"a",
	function( event ){

		// Stop the default behavior of the browser, which
		// is to change the URL of the page.
		event.preventDefault();

		// Manually change the location of the page to stay in
		// "Standalone" mode and change the URL at the same time.
		location.href = $( event.target ).attr( "href" );

	}
);

Since this is just a test, I'm not worrying about local links vs. external links vs. document links; I'm just overriding all links in the document. And, in order to do this in the most efficient way possible, I'm using jQuery's event delegation to assign a single, top-level event handler to all links on the page. When a click event is detected, we cancel the default behavior (which is to open in mobile Safari) and then manually change the window's location. This approach allows the page to be changed without breaking the iPhone standalone application experience.

This is pretty awesome! Up until now, I've thought that iPhone-targeted web applications had to keep all of the required code on a single page. Now that I see that the URL of a standalone application can be changed without jumping into mobile Safari, things are going to get a good deal easier.

Want to use code from this post? Check out the license.

Reader Comments

1 Comments

I don't have an iphone to test this, but could you not simply add target="_self" (via markup or javascript) to those links which need to open in the current window?

2 Comments

Ben,

returning false in the handler will have the same effect:

$('a').click( function() { location.href = $( this ).attr( "href" ); return false; });
15,902 Comments

@Jon,

I haven't tested that yet; but, I think it would still open up in mobile Safari. I'll have to get back to you on that, though.

@Djam,

Good point! The only thing to be careful of is that returning false prevents the default behavior *and* stops propagation. Since the delegation is at the root of the document, I don't think it would be a problem (since I don't believe it stops immediate propagation); but, you just want to be careful not to stop the triggering other event handlers that may or may not be bound to a[click].

6 Comments

Well, if you are developing web applications, as you say in first sentence, then you must let go of thinking in web pages. Web page needs to be accessible, usable and crawlable by links, but web application absolutely needs not. My way is to ditch links (<a> tags) completely when developing web apps. If something in web app needs to be resposive to the click, add click handler to it and thats it :-) Generally the same with anything which requires Javascript - ditch links from html.

15,902 Comments

@Priit,

You bring up a good point - once we get into the realm of creating "applications", we do need to shift our point of view. The same rules don't necessarily apply.

As an aside, on that topic, however, I think I remember PPK once mentioning that on mobile browsers, items that aren't a true [A] tag need to have a "cursor: pointer" in order for the click events to register. I could be mis-remembering though, and I don't recall which mobile OS he was talking about (I think iPhone).

@Doug,

Hmm, that's an excellent question! I assume you're correct - I can't think of anyway to submit a form manually without invoking the native behavior. Perhaps this only works with links.

6 Comments

No, I'm not aware of that and that would be illogical too (presentational messes with DOM?). So far everything registers clikcs for me, I have global handler in my apps, exactly like you in this example - only one handler, attached to document and all goes through that.

On the other hand - you need to set cursor:pointer anyway, assuming that your app is universal :-)

6 Comments

:-)
Did too and you are correct. OTOH I do not use any actual click events on touch interfaces at all because of that 300ms delay. I have written my own handler who handles either mouseupdowns or touchstartends and creates virtual clicks. So this is the reason I didn't know.

290 Comments

@Doug,

jQuery.post() can do an HTTP post request, so you can code an onsubmit that does that, then returns false.

I'm such a creature of habit. I originally ended the previous paragraph with a semicolon.

1 Comments

Hey Ben - do you think it's still possible to keep things loading in the stand alone web app even when using an iframe? I've been able to use your technique above successfully, but when I try to utilize iframes, such as in a lightbox, any links in the iframe bust out and load in Safari instead of continuing within the iframe. Any insight you might have is appreciated..

Thanks,
-Joe

1 Comments

Thanks!! This bug was a thorn in my side. Login form was working, then when a user clicked something it opened in Safari and prompted a login again! What a pain. By swapping the link functionality I'm able to keep the entire experience in standalone mode. Thanks.

1 Comments

Hey Ben, i´ve got a question:
Everything works well with the standalone mode, except for one thing: Some things i´ve changed in the CSS dont work in "standalone-mode"; just simple thinks like: body{background-color:#fff}.
I´m testing it on a Iphone and deleted the cookies and caches many times!

You have any idea how to solve my problem??

Thanks in advance,

greetings, Andi

2 Comments

I was having difficulty with making this work with images that were wrapped in a link, as the img tag obviously has no href.

Anywho, this slightly modified version solved that for me:

// Listen for ALL links at the top level of the document. For
// testing purposes, we're not going to worry about LOCAL vs.
// EXTERNAL links - we'll just demonstrate the feature.
$(document).on(
	"click",
	"a",
	function(event){
		// Stop the default behavior of the browser, which
		// is to change the URL of the page.
		event.preventDefault();
		// Manually change the location of the page to stay in
		// "Standalone" mode and change the URL at the same time.
		// Get href of parent if clicked on an image.
		if(!$(event.target).attr("href")){
			location.href = $(event.target).parent().attr("href");
		}
		else{
			location.href = $(event.target).attr("href");
		}
	}
);
1 Comments

Dear Ben,

Thank you for this complete guide that worked for me as well.

In my opinion you should add a check to your guide that looks for the HTTP_USER_AGENT to avoid that the code is not executed in case there is no mobile Safari user calling the web page.

Rumors about iOS 6 are saying that it will come finally with an optional full-screen mode anyway.

Kind regards
Tobias Topyla

1 Comments

hey there, i have used your link.js codes and the other one on my iphone web ap, but its blocking my links to external sites like facebook and twitter, as much as the social medium annoys me it's an imperative part of my business. Do you have some secret else if or else javascript code i can add to the link.js file that you created and i am now using to allow http request to go ahead? thanks in advance, Tiff. love your work BTW!

2 Comments

@Tiffany,

It sounds like we had a similar issue. I wanted links to some sites to open in mobile Safari, so I modified this. If a link with class "external" is clicked, the default behavior occurs. Otherwise, the usual links.js business happens.

SO:

  1. create link with class="external"
  2. new links.js code is:
$(document).on(
	"click",
	"a",
	function(event){
		if(!$(this).hasClass("external")){
			event.preventDefault();
			if(!$(event.target).attr("href")){
				location.href = $(event.target).parent().attr("href");
			}
			else{
				location.href = $(event.target).attr("href");
			}
		}
		else {
		}
	}
);
3 Comments

Ben I have used white label app design compamys 1 kept most links in Web APP except Google Maps I think.

Another I use now starts from icon in What I will call standalone mode then on 1st link shows url bar.

Well I like it staying in so I tried ur 2 js files and 2 index files.

And it worked!!! Nice!!!

Now I had written a bunch of redirects on another domain that is a subdomain of my main MobileAdvertisingAPPs.com

The Redirects were to allow smartphone people to type a lot less thus making less mistakes.

Well ur technique worked for a few times. Then I would start in index.htm
and click on the link to go to index2.htm and it would go to My Main Domain MobileAdvertisingAPPs.com and its minisite for detected mobile phones with code error 404.

However in My full size browser it would also go to My Full size site not my small redirect site.

What I noticed was in my big browser If I click on link it gets 404 error.

But if i right click and tell it open in new tab it goes to the correct file the index2.htm.

To me and I am very new to all this.
It seems almost like the js files is a dynamic file. Like someone is changing it over the time I am using it. I assume this cant be so since the files reside on my host.

So it worked ok - then it got Error 404 then later it worked ok.

I never edited the js files. But I was reading on the jQuery.com it said something about using the mobile version which they refer to as stable.

So I downloaded it wrote it into the index & index2.htm files

so if u would try it on my system.
at getvips.com/myapp/index.htm

When I click on the link in Index.htm link it goes to MobileAdvertisingAPPs.com whether i use my iphone 4 or my PC.

The iphone gets redirected to the mobile optimized site the PC of course does not.

I love how it works - when it works right.

The white label apps i used b4 worked well every time. Has anyone else had these kind of problems?

And is there a problem using the mobile version jquery.mobile-1.1.1.min.js

I only tried it when i had problems but have left it in my index.htm code for now since jQuery said it recommended using Mobile. I know i am rambling just want to get this worked out. I will give u my phone if u would be willing to call me.

What about the comments above are any of them a better way to implement this?

Thanks in Advance
JohnO

3 Comments

Ben dont know if this will help u but a developer group named uncorked works with mobile and sprites. I was looking for a way to show some things and animated gif the white label I use didnt support them they said check ouot spritely.net Hope this helps.

1 Comments

So, I'm using the latest version of the ipad. Also using the "apple-mobile-web-app-capable" & "viewport" tags.

Not using the jquery code and everything seems to be working fine, EXCEPT: The CSS to the second page I'm linking to isn't working. It does open up full screen as it should, but again, the styles are missing.

Note: the css works fine when NOT in app mode, but for some reason, none of my styles are showing when IN app mode.

Has anyone else run into this problem?

Thanks in advance,
Stephen

1 Comments

Can you guys please confirm this is working on your current sites in iPhone?

Can't get to work - so just checking if iOS 5 has put an end to this.

Thanks

2 Comments

Hi, Thanks for the tutorial.

Just a question: I have been able to open all links within the web-app which is great. My web-app opens a PDF file within the web-app but the problem I'm having is that there is way of returning back to the main page from the PDF.

Any help will be much appreciated.

Thanks

2 Comments

Hi,

Great stuff. Except when lauching from the Home screen and touching index2 it's slides away and opens new instance of mobile Safari. I can then seamlessly click between index2 and index without a new instance opening but the Mobile Safari address bar remains at the top of the screen.

I'm assuming it's ignoring the non Mobile Safari code?

I'm using iOS 6.1 on iPhone 4

Any help would wonderful!

Cheers,

Stephen

2 Comments

Further to this, I'm not sure any JS is being loaded?
As if I delete the JS from the server and run
It show the same behaviour. This is after clearing the cache

1 Comments

@Muhmmadibn

Did you figure out a solution to launching PDFs? I am running into the same issues myself. There is no way to close the PDF or go back once you launch it.

Thanks in advance!

1 Comments

This seems great, very useful. I am having an issue calling the link however after the default is blocked. I copied the code in as is and on iOS, the web app is not launching safari upon click. However it is no longer able to load the linked pages. The page returns as undefined. I have the same result in my desktop browser. Any idea why this wouldn't be working?

1 Comments

For those of you using the modified version to check for the presence of a nested tag in the link:

if(!$(event.target).attr("href")){
	location.href = $(event.target).parent().attr("href");
} else {
	location.href = $(event.target).attr("href");
}

and better way to handle nesting of any depth is to use the jQuery closest method:

location.href = $(event.target).closest("a").attr("href");

this will traverse the DOM starting with the target element and find the first ancestor that is an <a> tag.

1 Comments

@Brandon, @Muhmmadibn,

I too am trying to figure out a way to deal with this. I have a webapp which downloads generated PDFs, and there is no way to leave them or do anything else.

If I could get them to print instead of download that would work, but since data is sent to the server and a PDF is returned, I have not found a way to keep it from taking over the full-screen webapp in iOS.

1 Comments

Hi,

I use CLASSIC ASP in my applications, but, when i click in the link, the next page open in safari and not in your script.

Sorry, my english is very bad!

Tks a lot for your dedication !

Best regares !

Bruno form Brazil !

1 Comments

works great. though i would consider changing

event.preventDefault();

to

if ($( event.target ).prop( "tagName" ) !== "IMG")
event.preventDefault();

so you don't have issue with IMG tags

1 Comments

I am having trouble getting this to work with 3 different image maps I have. Everything else is working great. Do you have any advice on how to adjust this code to address image maps?

1 Comments

How can the link /jump association be preserved when saving as a standalone through the normal user interface controls without additional programming?
I'm a stage lighting and audio professional and the programming takes me a gazillion times longer than if my IT skills included more programming. I really enjoy the challenge and your great brainstorming. You all seriously rock!

1 Comments

Hi all

Anyone managed to integrate in web-app-payments (without leaving the standalone mode)?

I currently use PayPal, but that opens up a page in safari…

Thanks!
Fred

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel