Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Stay Standalone: Prevent links in standalone web apps opening Mobile Safari

#Stay Standalone

A short script to prevent internal links to a "webapp" added to iPhone home screen to open in Safari instead of navigating internally.

(function(a,b,c){if(c in b&&b[c]){var d,e=a.location,f=/^(a|html)$/i;a.addEventListener("click",function(a){d=a.target;while(!f.test(d.nodeName))d=d.parentNode;"href"in d&&(chref=d.href).replace(e.href,"").indexOf("#")&&(!/^[a-z\+\.\-]+:/i.test(chref)||chref.indexOf(e.protocol+"//"+e.host)===0)&&(a.preventDefault(),e.href=d.href)},!1)}})(document,window.navigator,"standalone");
<!DOCTYPE html>
<html>
<head>
<title>stay standalone</title>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width,initial-scale=1.5,user-scalable=no">
<script type="text/javascript">
(function(document,navigator,standalone) {
// prevents links from apps from oppening in mobile safari
// this javascript must be the first script in your <head>
if ((standalone in navigator) && navigator[standalone]) {
var curnode, location=document.location, stop=/^(a|html)$/i;
document.addEventListener('click', function(e) {
curnode=e.target;
while (!(stop).test(curnode.nodeName)) {
curnode=curnode.parentNode;
}
// Condidions to do this only on links to your own app
// if you want all links, use if('href' in curnode) instead.
if(
'href' in curnode && // is a link
(chref=curnode.href).replace(location.href,'').indexOf('#') && // is not an anchor
( !(/^[a-z\+\.\-]+:/i).test(chref) || // either does not have a proper scheme (relative links)
chref.indexOf(location.protocol+'//'+location.host)===0 ) // or is in the same protocol and domain
) {
e.preventDefault();
location.href = curnode.href;
}
},false);
}
})(document,window.navigator,'standalone');
</script>
</head>
<body>
<p><a href="http://google.com/">google</a></p>
<script type="text/javascript" charset="utf-8">
// NEVER user document.write, unless for test porposes.
document.write('<p><a href="http://'+document.location.host+'/test/">Same domain</a></p>')
</script>
<p><a href="/test/"><span>absolute path</span></a></p>
<p><a href="javascript:alert('alerts should work')">alert</a></p>
<p><a href="test_page.html">relative path</a></p>
<p><a href="/test?http://othersite.com">http not on beginning</a></p>
<p><a href="#test">anchor</a></p>
<p><a href="tel:+1234512345">This link fires a call</a></p>
<p><a href="mailto:me@example.com">Mailto</a></p>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>stay standalone</title>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width,initial-scale=1.5,user-scalable=no">
<script src="compressed.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<p><a href="javascript:location.href=document.referrer">javascript:location.href=document.referrer</a></p>
<p><a href="javascript:history.go(-1)">THIS DON'T WORK: javascript:history.go(-1)</a></p>
</body>
</html>
MIT License
===========
Copyright (c) 2009–2011
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
BSD License
===========
Copyright (c) 2011
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Organization nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Thanks for the advice, switched to bubble up method on https://gist.github.com/1042026. Using indexOf instead of regex for performance and stoping at html not body as body tag is not compulsory.

You could make it even shorter if you wanted to by using this.navigator instead of window.navigator :) Nice work!

All I can say is this script is AWESOME. I have been literally looking for hours and hours for something like this. It worked perfectly!!! Thanks so much!!!!!

Thanks for a great little snippet!

Just out of curiosity, what's the advantage of using if('href' in curnode) instead of for instance if(curnode.href) on line 20?
For me changing that solved a problem with some ajax functions for links that have empty href attributes, but am I missing something?

Great script but I'm having a problem and hope you can help me with it.
When I use this script and someone clicks a link everything works fine.
It's when someone clicks a link with javascript:history.go(-1) and after that clicks an normal link the problem starts. When the link is clicked after javascript:history.go(-1) it will open in safari. :/
Do you have any solution to this?

Owner

irae commented Jan 18, 2012

@lilleskutt — I don't ever use javascript: urls, so this didn't show up as a problem in the past.
You can tweak the if statement on line 20 to ignore javascript links.

@irae It's not the javascript: link that isn't working. It's clicking any normal link after clicking the javascript:history.go(-1) link that will open in safari.

@lilleskutt same here... did you find a solution to prevent links after navigation to/from a page via history.go()?

thanks in advance, cole

@thecoleorton Nope sadly not :( Gave up after some more google searches. So now I just use a normal link. Not optimal but it will do until someone finds a solution.
I read that when you go back you return to the exact same state as when you left. So I think the javascript function that's supposed to block outgoing links has already been run and will not run again. If you find a solution please write it here so I can use it too :)

@thecoleorton Hmm it doesn't seem to work for me on the phone. It just returns the URL on a blank page. Exactly how did you write the <a href="" ? Maybe I'm doing it wrong.

Edit: Eeh did you just remove your post?

Owner

irae commented Jan 31, 2012

I looked further into this issue. Mobile Safari doesn't seems to execute the javascript after javascript:history.go(-1). This prevents the page to add the event handler and consequently the links starts opening in Safari. I found a workaround. Instead of history.go you can make a back link like this:

<a href="javascript:location.href=document.referrer">go back</a>

Also, I updated the script from a slightly different version that I was using on a project of mine. It now supports mailto:, tel: and other links. The compressed version was also updated.

Owner

irae commented Jan 31, 2012

@thecoleorton, @lilleskutt, any feedback on this issue? forgot to ping you back with my response yesterday ;)

@irae Sure that works but it's not the same as history.go(-1). It reloads the site again which was what I was trying to avoid to lower data usage.
Started using the new code though because of the added support for tel: and such. Thx for your efforts on finding a solution!

Owner

irae commented Jan 31, 2012

@lilleskutt, you might want to take a look in pushState, replaceState and popState events.

I have a little project that aims to a drop-in script to manage history with some layer of caching. It's not focused on standalone mode, but it might just work for you, since it prevents the page to be reloaded. It's in early development and any feedback is welcome: https://github.com/irae/pisca

Nice will take a look at that when I get back from holiday next week.

@irae correct me if i'm wrong, but wouldn't using document.referrer for a back button put you into an endless loop between two pages (as mentioned in my most recent post) instead of being a true replacement for history.go(-1)?

instead my team and i ended up using your gist, as well as this block:

$("a").click(function (event) {
  event.preventDefault();
  window.location = $(this).attr("href");
});

which worked for our usage.

Owner

irae commented Feb 1, 2012

@thecoleorton nice catch about the endless loop! It is usable tough, it should work fine for simple cases (one level deep). In my past projects when the content needed back and forward navigation I just don't use the standalone mode.

About your code block, It almost renders my gist useless. the main difference is that the gist works with delegation and can be put on the . But I don't see how your snipet could help with history.go(-1).

aufmkolk commented Feb 9, 2012

Thanks for this great solution.

If your hostname includes uppercase letters, the script should be edited as follows on line 24: chref.toLowerCase().indexOf(location.protocol+'//'+location.host.toLowerCase())===0 )

bruno commented Feb 12, 2012

@irae thanks a lot for this amazing small piece of functionality, we had been struggling to implement something like this for a while and all our solutions fell short of this. Nice work! I ❤️ it :) We are using Rails and jquery-ujs (for unobtrusive javascript awesomeness) and we were having a few issues where in some cases when using the helpers to do remote calls, we were having double requests being sent to the server so I add a small conditional:

(!(curnode.attributes.getNamedItem('data-remote'))) && // does not contain the data-remote attribute used by jquery-ujs

on the conditional just after L15 that way it won't mess with links that have the data-remote attribute set by our helpers. I thought I'd comment here with this little change for others that might be facing the same issue in the future. Again, thanks for this :)

I've just put it on https://gist.github.com/1811212 for convenience.

Owner

irae commented Feb 14, 2012

@bruno I am glad the script worked for you and that you were able to pull off this nice solution for compatibility with jquery-ujs. This code is meant to be small and this is precisely why it is a gist and not a repo. This way it encourages people to copy, paste and make modifications, just like you did.

Thanks for coming back to leave this comment for future use! =)

mildfuzz commented Mar 2, 2012

For some reason, when I use this with Jquery Mobile, the server is called twice for each link, which is less than ideal!

http://bit.ly/w6fSf8

Owner

irae commented Mar 3, 2012

I never used jQuery Mobile before, but I took a look at its code and was very impressed by the amount of effort that was put in optimizing mobile performance there.

jQuery Mobile's default configuration really conflicts with this snipet. This gist really shines wen you are not using a full blown framework like jQuery or jQuery Mobile. It's kept short to be either the only javascript needed on page or to be used in conjunction with some other micro frameworks.

I answered your stack overflow question in great detail on the original page and proposed 2 possible solutions. I hope it helps.
Cheers!

@ghost

ghost commented May 21, 2012

I am not a javascript coder so can't fix or even have clue as to where to begin.

I use Facebook Connect on my forum, (phpbb) and i have an iphone theme installed, I Have added this to the template and its working fine, i can now homepage the forum and links work without throwing me out to safari, this was what i wanted.

The only issue i have is that this somehow breaks the facebook connect login making it simply reload the same page but does nothing as to logging in the user. Is there somewhere that i need to edit to make this work?

I didnt write the connect coding so i couldnt tell you how it works i just know i had to modify the forum to apply the connect feature.

The Facebook mod is fb2011 and can be downloaded from phpbb mods area if you need this to figure it.

Any other info just ask and il try to pass it over.

Mod page: http://www.phpbb.com/community/viewtopic.php?t=2123524
Download link: http://sourceforge.net/projects/fb2011/files/fb2011.zip/download?use_mirror=aarnet

cjanis commented Oct 17, 2012

Hi smart people, I'm running into a problem with my site. This script works wonderfully on the first page of the site — all the links seem to be opening just fine without opening Safari — but anywhere except the home page it's having trouble. I think the problem is that on the other pages some FB social plugin JS is being dynamically inserted above this code. Any idea how to make sure that this script is always the first JS in the header? If you want to take a look at the site: http://craigjanis.net

cjanis commented Oct 17, 2012

I ended up with this: https://gist.github.com/3908053

It works just fine for me.

iamtyce commented Jan 29, 2013

Hey guys,

Amazing script, does exactly what I want!

Only problem is, I'm firing a bunch of jQuery events after this is triggered, and their seems to be issues once I use this script.

Any ideas why?

Hey guys, great solution. It works great for me except for the "Pin" button for Pinterest, which is javascript. When you click it, the page just reloads the web app page. Is there a way to allow users to pin in app or send them to safari and have the script ignore javascript links? The first option would be preferable. I'm not versed in javascript, so any help is appreciated. Thanks very much.

toxaq commented Jun 6, 2013

Used @emilsall's change to fix the same problem of anchors with no href attributes. I'd also be interested in the 'href' in curnode reasoning.

Hello
(excuse for my bad englis, i'm french)
Your gist works well on my little webapp, but i'm using a jquery plugin (http://srobbin.github.io/jquery-pageslide/) to display a side and slide menu opened from the left of the screen.

The problem is that all the links inside this left menu are opened outside, i mean in ios Safari...
Could you have a look at my test site : http://sqliwpdev.com/quizz/
thanks

jdfm commented May 28, 2014

@irae: The script works great, but is it supposed to leak the chref variable into the global scope?

This script is working great for me.... thanks!

found a nasty bug if you use facebook together with this piece of javascript:
change
{code}
(function(document,navigator,standalone) {
// prevents links from apps from oppening in mobile safari
// this javascript must be the first script in your
{code}
to
{code}
(function(document,navigator,standalone) {
// prevents links from apps from oppening in mobile safari
// this javascript must be the first script in your head
{code}

Facebook code search for and insert himself after the tag, this was breaking the javascript

when I use the script it kills the Nivo-Lightbox ( https://github.com/gilbitron/Nivo-Lightbox) launch up and it just opens the image on its own :( Anyone have any ideas? help me please

adding this works to get the naive-lightbox working:-)

                    (!(curnode.attributes.getNamedItem('data-lightbox-gallery'))) &&
                    (!(curnode.attributes.getNamedItem('data-remote'))) && // does not contain the data-remote attribute used by jquery-ujs 

rejas commented Feb 24, 2016

I had to add this line (chref !== "") && after line 22 due to my navigation using a-tags without hrefs.

This is amazing - any chance on the snippet being published as a Bower repo? 😄

This script is not working in iOS 9.3.5 :(

It does not work on Android for me.
Please help.
Thank you so much!

amitshob commented Apr 24, 2017 edited

Thanks The script worked for internal links which were opening new pages. But ...

  • The virtual keyboard is being a goofball. It keeps popping up and going down while opening the app from the homescreen or when opening a new page for no reason.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment