Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
Posted December 27, 2011 at 10:06 AM by Ben Nadel
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.
Reader 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?
Ben,
returning false in the handler will have the same effect:
- $('a').click( function() { location.href = $( this ).attr( "href" ); return false; });
@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].
@Ben
Thanks for enlightening!
Good trick.
Do you know an easy way to check (via js) if our site runs as stand alone app or is running in Safari?
@Djam,
My pleasure!
@Radoslav,
Yeah, you can actually check for the existence of some JavaScript objects to determine if the browser is in "app mode":
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.
how would about handling a form post? would that open in mobile safari?
@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.
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 :-)
@Priit,
I did some Googling and found the old blog posts from PPK:
http://www.quirksmode.org/blog/archives/2010/10/click_event_del_1.html
I think someone, somewhere suggested that it might have to do with the fact that a touch-screen needs more points of data to help figure out which item was intended to receive the touch event (since the finger is large).
:-)
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.
@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.
How do you get the cool video of your iphone while yer developing
Love it!
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
Thx man, this was a BIG issue for me!! You code worked flawlessly
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.
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
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");
- }
- }
- );



