Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Does not work with Ipad/Iphone #34

Closed
lfjeff opened this issue Mar 13, 2014 · 13 comments
Closed

Does not work with Ipad/Iphone #34

lfjeff opened this issue Mar 13, 2014 · 13 comments
Milestone

Comments

@lfjeff
Copy link

lfjeff commented Mar 13, 2014

Has anyone figured out how to make this work with Ipad/Iphone browsers?

They do not handle the 'beforeunload' event properly and I've been beating my head trying to figure out how to issue a message if anything in the form changes. I tried your module and used 'pagehide' instead of 'beforeunload', but it did not work.

@codedance
Copy link
Owner

Unfortunately Mobile Safari does not support "beforeunload". The only way I can think of to get partial support would be to "intercept" all possible page exist events on the page (e.g. URL clicks, form submits, etc.) and manually fire off a beforeunload event. There is some discussion on the issue here:

http://stackoverflow.com/questions/4127621/is-there-any-way-to-use-window-onbeforeunload-on-mobile-safari-for-ios-devices

Adding code like this to support this I think would run contrary to the project's light-weight goal. I hope Apple offer support for this one day!

I'll leave this issue open and see what other ideas/enthusiasm exists.

Cheers,

Chris

@lfjeff
Copy link
Author

lfjeff commented Mar 13, 2014

Hi Chris,
 
After spending two days of research
and test coding, I have come to the conclusion
it is impossible to use 'beforeunload' on
any of the Apple mobile devices (don't know
about Android devices).
 
I had already created a simple script
to intercept outgoing links, but it did
not have some of the other features
I liked in your module.
 
Since I liked your stuff, I just did
a pull request and merged my code into yours.
 
It was fairly simple and I tried to keep my
code as clean as possible.  I also added
a few enhancements (like optional support
for jquery.validate).
 
Take a look and let me know what
you think.
 
Thanks,
 
Jeff
 

 Unfortunately
Mobile Safari does not support "beforeunload".
 The only way >>   I
can think of to get partial support would
be to "intercept" all >>   possible
page exist events on the page (e.g. URL clicks,
form submits, >>   etc.)
and manually fire off a beforeunload event.
 There is some >>   discussion
on the issue here:  >>  http://stackoverflow.com/questions/4127621/is-there-any-way-to-use-window- >>
 onbeforeunload-on-mobile-safari-for-ios-devices
 >>  Adding code like
this to support this I think would run contrary
to the >>   project's
light-weight goal.  I hope Apple offer
support for this one >>   day!
 >>  I'll leave this
issue open and see what other ideas/enthusiasm
exists.  >>  Cheers,
 >>  Chris  >>
 Reply to this email directly or view
it on GitHub.

----------------------- Original
Message -----------------------
  
From: Chris Dance notifications@github.com
To: "codedance/jquery.AreYouSure"
jquery.AreYouSure@noreply.github.com
Date: Wed, 12 Mar 2014 19:44:46
-0700
Subject: Re: [jquery.AreYouSure]
Does not work with Ipad/Iphone (#34)
  
Unfortunately Mobile Safari does not support
"beforeunload". The only way I can think
of to get partial support would be to "intercept"
all possible page exist events on the page
(e.g. URL clicks, form submits, etc.) and
manually fire off a beforeunload event. There
is some discussion on the issue here:
http://stackoverflow.com/questions/4127621/is-there-any-way-to-use-window-onbeforeunload-on-mobile-safari-for-ios-devices
Adding code like this to support this
I think would run contrary to the project's
light-weight goal. I hope Apple offer support
for this one day!
I'll leave this issue open and see what
other ideas/enthusiasm exists.
Cheers,
Chris
—Reply
to this email directly or view
it on GitHub.

@codedance
Copy link
Owner

Thanks for the pull-request. I've taken a quick look and can see what you're doing. I'm keen to keep the code very lightweight and adding a workaround/hack for this rubs a little the wrong way. My thinking at the moment is to use a "shim" approach and make the changes outside the main jquery.Are-You-Sure code.

What I'm think is that we can write a shim that "emulates" the beforeunload event using the method you've used. You'd enabled this by simply adding an extra script to your page. e.g.

<script src="/scripts/jquery.are-you-sure.js"></script>
<script src="/scripts/jquery.are-you-sure.beforeunload-shim.js"></script>

The beforeunload-shim would:

  1. Only run code if it's on a platform with known beforeunload "issues".
  2. Run code to intercept links (using your methods) and also form submits.
  3. Raise a custom event $(window).trigger('are-beforeunload') when a navigation event is detected.
  4. The shim manages the dialog and navigation exit.

Here is some code I've mocked up:

New file jquery.are-you-sure.beforeunload-shim.js

$(function() {
  if (!navigator.userAgent.toLowerCase().match(/iphone|ipod|ipad/)) {
    return;
  }

  $('a').bind('click', function(evt) {
     var href = $(evt.target).attr('href');
     if (href !== undefined && !(href.match(/^#/) || href.trim() == '')) {
       var ret = {};
       $.when($(window).trigger('ays-beforeunload', ret))
           .done(function() {
             if (ret.message) {
               var msg = ret.message + "\n\nPress OK to leave the page or Cancel to return."
               if (confirm(msg)) {
                 window.location.href = href;
               }
             } else {
               window.location.href = href;
             }
           });
       return false;
     }
  });
})

This change that need to be made to the Are-You-Sure main code are very minimal:

  • Bind on a custom event (ays-beforeunload)
  • Pass back the message on a "return" object if defined

e.g.

    $(window).bind('beforeunload ays-beforeunload', function(evt, ret) {
        $dirtyForms = $("form").filter('.' + settings.dirtyClass);
        if ($dirtyForms.length > 0) {
          if (ret) {
            ret.message = settings.message;
          }
          return settings.message;
        }
      });

I'd welcome your thoughts.

@lfjeff
Copy link
Author

lfjeff commented Mar 14, 2014

In terms of keeping the code lightweight (at least in execution), I doubt that a shim is any faster than inline code. It is unlikely that load or execute times would be much different whether the shim is included or not, especially if the code is minified.

I don't have any philosophical problem with an external "shim" and it might be better in terms of separating the code to handle weird browser bugs. However, it's hard to know in advance if you will be running on a platform that needs the shim. An external module would certainly make more sense with large modules, but even with my additions, the total code size is still small and could hardly be called bloated.

The reason I needed a solution like yours was due to strange problems we were having with customer-filled forms that were incomplete.

For several weeks, we could never duplicate the problem. Finally, I decided to go through the server logs and discovered that we only had problems with customers who were using an Ipad to fill out the form. They swore they filled it out correctly, but apparently they navigated away or never clicked the "Submit" button at the bottom of the page. The current jquery.validate and other jquery code had no way of detecting that they had left, which resulted in incomplete forms.

I guess Ipad users expect to use magic widgets which automatically save stuff without ever needing to hit the "Submit" button. We're planning to redo our forms to be mobile-friendly, but until then we need a quick solution to our problem.

After testing with users, I added the "doubleConfirm" option. Too many users were confused with the confirm() prompts and would click "OK" when they actually wanted to stay. I also put in the hook for jquery.validate (it works only if jquery.validate is loaded), to prevent them from exiting if there are errors. Your original code was bypassing jquery.validate.

In this case, I think an "all-in-one" solution is better. Unless you are running in a closed environment, you cannot know what kind of device or browser that your customer will be using.

Whatever you do, please note that you need the closest('a') to get the correct href:
var href = $(evt.target).closest('a').attr('href');
Otherwise you will have problems with links that enclose other tags, like this: <a href="mylink.html"><span>Click me</span></a>

My solution is certainly not bulletproof and there are probably other browser quirks that may cause it to fail. But I believe it is a good start, especially when compared to many of the overly-complicated (and many non-working) solutions I found. I tested my code on several Apple mobile devices (Ipad and Iphone) and it seems to work on all of them.

If you have an Ipad, I suggest you test things yourself before deciding which way to go.

@lfjeff
Copy link
Author

lfjeff commented Mar 14, 2014

Also, I'm not sure if your window.location.href = href; will work correctly. I have not tested your code, but I think I tried that when I was experimenting on my Ipad.

I'll have to test your code to make sure.

@codedance
Copy link
Owner

My reference to "lightweight" is more around code maintainability/simplicity rather than execution speed. My gut-feel is that eventually Apple will fix this in the future and it would be nice to not have this crufty code sitting around!

I agree with your point that you can't anticipate the browser people are hitting your form with. You're right. Maybe the shim should live in the plugin's (but still abstracted so it's not cluttering the code).

Let me sleep on this for a bit and I'll think about the best way to do it.

Also thanks for the tip on the .closest('a'). I never thought about that :-)

@lfjeff
Copy link
Author

lfjeff commented Mar 14, 2014

I don't like crufty code either, but sometimes practicality outweighs the desire for elegance.

Don't know how long it will take Apple to fix the problem, it appears to have been around for quite a while. All the Apple browsers (Chrome, Dolphin, Safari) I tried must use the same code base, because they all have the same bug.

Abstracting the code is fine. I only took a couple hours to merge my fixes, so I didn't have time to fully understand your architecture.

I learned about the "closest('a')" after wasting a lot of time trying to make it work with all kinds of links. I was trying to write Javascript to parse the link and extract the href when I discovered the "closest()" method.

@codedance codedance added this to the 1.8 milestone May 18, 2014
@codedance
Copy link
Owner

Supported added in 1.7 (currently on 1.7-dev branch)

@santino
Copy link

santino commented Jul 29, 2014

I was wondering why on this workaround you are forcing the event to change the window location redirecting to the desired link if you click 'ok' and confirm you want to navigate away from the page.

In my application I have a number of links where I prevent the default event, but since this workaround is listening to all A clicks it get fired all times.
For example I have something like this:

$('a.myclass').click(function(e) {
    e.stopPropagation();
    e.preventDefault();
    // other stuff here
}

Is clear that I'm going to have a custom action on this element, and so I don't want the browser to be redirected to another page, but with your workaround this is ignored and I get redirected when I confirm I want to leave the page, or even worse if the plugin is not even running (f.i. we are in a page without forms).

A quick fix is just to remove the last 2 lines from the shim:

window.location.href = href;
return false;

This way we are not forcing the redirect, and not stopping the link from doing it's default action.
If you click 'cancel' in the confirm alert it will just stop the link from continuing the execution, otherwise if you want to go ahead it won't act at all and so everything will run just as if the workaround wouldn't be there.

@Draco18s
Copy link

Landed here trying to find a solution for this problem, except the use-case I have is forcing a form submission when the user leaves a page (internal company skill assessments) but specifically related to tab closure and native-app webview viewports.

Our client would like this application to be accessible from company issued iPads running an off-the-shelf shell app that can load and navigate custom content using webview. But we noticed that the shell app's "back" and "home" buttons will allow navigation away from the assessment pages without either confirmation OR forced submission of a (probably failing) test. The same thing occurs in mobile safari by closing a tab.

The only other solution I've run across is window.onpopstate, which also is ignored/skilled by Mobile Safari in this situation.

@codedance
Copy link
Owner

It's a hard problem to solve. I haven't really come up with anything better for iOS. Back and Tab-closing actions can't be intercepted and preempted on iOS. It's an Apple design decision.

If you're in native code, there may be some ways around this, however this is not an area I have much expertise in.

@Draco18s
Copy link

Thanks for the reply!
We ended up using a cookie (which oddly, had to be Javascript, not serverside ASP) and if the cookie is present when they hit the login screen we just shunt them back to the assessment, subtract off the time they were gone, and let them start from scratch (we could save the questions/answers too, but as the client hasn't paid for a resume feature we did the bare minimum to address their "but why do I have to log in again?" question and minimize cheating).

@highdownts
Copy link

highdownts commented Aug 17, 2016

My usage requirement is slightly different. I would like to save data to isolated storage on before unload. I am going to have a look at the code, but wanted to see if you thought this was possible with your code base.

*** Update ***
I had a chance to look at the code and test it. It does not capture the required event to use in our test case. Navigating to another tab on iOS or closing the window is not detected. No solution seems to exist.

The code does offer some interesting functionality for the defined scenario.

Thanks for sharing with the community...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants