
Offline-Friendly Forms - mxbck
https://mxb.at/blog/offline-forms/
======
dspillett
_> If a user is currently offline, we’ll hold off submitting the form for now_

Be careful not to completely rely on navigator.onLine for this as your next
request could be the first to fail, so you need to deal with submission errors
and timeouts too (or safer: always put the data in local storage and remove
once you have confirmation of a successful save).

The article seems good overall though, I can forgive leaving out some of the
nitty-gritty like this to keep the main points clear.

~~~
hire_charts
On top of that, I'd consider adding a bit of idempotency to the form
submission -- to make sure that if the request did in fact succeed even though
you did not get the confirmation of a success, subsequent retry/resubmit
attempts won't result in duplicate entries.

A rough way to do this would be to add a hidden UUID value in the form data
that is generated once up front and used by both the client and the server to
ensure that the result of the first successful request can be cached and re-
returned.

~~~
agentultra
The approach of using versioned objects with Etag/If-None-Match and
appropriate responses works rather well:
[http://www.clairereynaud.net/blog/adding-offline-mode-to-
you...](http://www.clairereynaud.net/blog/adding-offline-mode-to-your-mobile-
app/)

------
vinnymac
Improving UX is important here, but keep in mind `navigator.onLine` is prone
to false positives when it returns true in Chrome and Safari.

[https://developer.mozilla.org/en-
US/docs/Web/API/NavigatorOn...](https://developer.mozilla.org/en-
US/docs/Web/API/NavigatorOnLine/onLine)

If you need to support more browsers it isn't difficult. Just make a request
to a small static asset or a health route and keep track of the online status
yourself. You could even combine this with `navigator.onLine` for the best of
both worlds.

~~~
Brakenshire
It's a shame the API's aren't better for this situation, pinging a static
asset to determine network status is not good for battery life on smartphones
if it's going to occur for any length of time.

~~~
devdad
Do you have any information to support this claim about battery drain?
Genuinely interested, not trying to negate what you're saying.

~~~
Brakenshire
I don't have any citations, it's just my understanding that switching on the
mobile radio is a major battery drain, and should be avoided as much as
possible.

------
aamederen
As a user, instead of the "we will send the data once you are online" message,
I would prefer "you seem offline, please try again once you are online" kind
of approach. I would not like to rely on a background worker on a mobile
device and I would not know whether my form has been sent, a timeout occured,
what happens if I refresh the tab, what happens in a poor connection (seems
online but can't connect to internet). Therefore, I would like to be just
notified about the connection issue and try again once I got out of the metro.

~~~
askvictor
If this tied into the platforms' notification systems, it could get around
that; a notification appears for an unsubmitted form, and hangs around until
it has successfully gone through, possibly with another notification of
success, or failure after a certain timeout.

------
kenrick95
I'm actually expecting a Service Worker solution using Background Sync
feature, like the description of the problem in this article:
[https://developers.google.com/web/updates/2015/12/background...](https://developers.google.com/web/updates/2015/12/background-
sync)

~~~
Brakenshire
Seems to have already shipped in Chrome. And making progress on Firefox:

[https://bugzilla.mozilla.org/show_bug.cgi?id=1217544](https://bugzilla.mozilla.org/show_bug.cgi?id=1217544)

------
FLUX-YOU
Be careful with sensitive information (not just passwords):

[https://stackoverflow.com/questions/7859972/storing-
credenti...](https://stackoverflow.com/questions/7859972/storing-credentials-
in-local-storage)

Not storing that kind of stuff will need prompts to re-enter it too.

~~~
swellep
If you delete it as soon as the user comes online, wouldn't their information
be safe? No one else would've been able to get the information because they
were offline, right? And so it's not still saved if the user exits the webpage
before they return online, you can use SessionStorage.

~~~
FLUX-YOU
It's more that you put the password on the disk in unencrypted format without
telling the user. For instance, chrome stores them in sqlite databases which
you can just open and select from:

[http://i.imgur.com/zauv4sK.png](http://i.imgur.com/zauv4sK.png)

Your disk could be encrypted, but not everyone's will be. It's better to just
localStorage as it was intended.

Plus if you introduce a bug later down the line, you might not prune older
localStorage entries, meaning they will stay there for much longer than you
want. AND the user may not revisit your site ever again after going offline,
which doesn't give you an opportunity to prune it.

~~~
swellep
So ask the user if they're cool with it being unencrypted, and you're all
good? How would you encrypt it though, since you're offline?

~~~
FLUX-YOU
No one's going to have a lot of confidence if you ask them to store your
sensitive info in plaintext. Just don't do it. The convenience or UX isn't
worth it.

------
cagmz
> _If you dont want to use ajax to send your form submission, another solution
> would be to just repopulate the form fields with the stored data, then
> calling form.submit()_

What are the pros and cons between these 2 approaches?

~~~
jbob2000
It mostly depends on the server receiving the form submission. If your server
is setup to receive form posts, constructing a form post in javascript would
be really cumbersome, so just repopulating the form and resubmitting would be
the easiest. But if your server handles JSON, you can just take your stored
data and post it right to the server.

~~~
wongarsu
Using jquery, the following looks exactly like the submitted form to the
server side:

    
    
        $.ajax({
               type: "POST",
               url: url,
               data: $("#form").serialize(), // serializes the form's elements.
               success: function(data)
               {
                   //do stuff
               }
        });
    

Instead of serializing the form, jquery can also just serialize a flat js
object. I wouldn't call that complicated enough to make a distinction. (it's
probably really easy in modern plain js too)

~~~
jbob2000
In your example, you'll also need to change the headers because $.ajax will
set content type to application/json, which wouldn't work if your server is
setup to receive x-www-form-urlencoded or multipart/form-data.

Starting to get complicated now, might be easier to just do
$("#form").submit().

~~~
wongarsu
right, forgot to change the header.

Sure, it's more complicated than $("#form").submit(). But it's close enough
that other considerations become more important (e.g. do I want a page reload,
do I have to populate the form first etc)

------
agentultra
You could also use caching headers to know whether the client is about to
update an object older than the current on on the server in the case where the
form is being used to "edit" an object. Hopefully the server would respond to
the _If-None-Match_ header with the object version and give you a _304: Not
Modified_ response... yay, you're free to upload the data! Otherwise you might
want to notify the user that their changes are out-dated when the server
returns the newer version of the object.

------
nfriedly
How did I not know about navigator.onLine and the online/offline events!?
Before reading this, I figured that would be the hardest part.

------
imtringued
How do you signal to the user when the form has been commited to a server?
Would you have to create a queue view that shows all the pending modifications
and send push notifications when an item in the queue has been committed?

------
tmaly
This is a super useful concept, thanks for sharing this.

------
Numberwang
This is amazingly useful.

------
megamindbrian
Ok, let me just open up my developer console using F12 and check the Offline
box on the network panel. So glad I took the extra 20 seconds to do that
before reading part of the article and navigating away from this
"distraction".

------
noiv
> window.addEventListener('online', () => this.checkStorage());

Every time I see this double function safety pattern a small part of my JS
heart dies.

~~~
wongarsu
What's the better alternative?

~~~
reificator
If I understand their concerns correctly, they would prefer this:

> window.addEventListener('online', this.checkStorage);

~~~
allover
That wouldn't work, you'd need to at least use bind() so that the `this`
keyword within `this.checkStorage` references the right thing, e.g.

    
    
        window.addEventListener('online', this.checkStorage.bind(this));

~~~
mikewhy
The language can do that for you:

    
    
        class Foo {
          checkStorage = () => {
            // Do check storage stuff here 
          }
        }
    

And the language will take care of binding the function. (Really, it's just
doing `this.checkStorage = this.checkStorage.bind(this)` in the constructor).

~~~
allover
That's not in 'the language' yet :)

