惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tailwind CSS Blog
MyScale Blog
MyScale Blog
博客园 - Franky
酷 壳 – CoolShell
酷 壳 – CoolShell
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
雷峰网
雷峰网
罗磊的独立博客
小众软件
小众软件
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
V2EX
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The Cloudflare Blog
Hugging Face - Blog
Hugging Face - Blog
博客园 - 【当耐特】
博客园 - 司徒正美
Last Week in AI
Last Week in AI
月光博客
月光博客
阮一峰的网络日志
阮一峰的网络日志
美团技术团队
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
量子位
宝玉的分享
宝玉的分享

Max Böck

Faster Horses A year in review: 2024 Going Buildless Live CMS Previews with Sanity and Eleventy Upgrading to Eleventy v3 Old Dogs, new CSS Tricks A year in review: 2023 7 Reasons why I don't write The IndieWeb for Everyone Make Free Stuff Media Queries in Times of @container Container Queries in Web Components Asset Pipelines in Eleventy Medium-Style Share Highlights in Eleventy Space Jam Webmention Analytics Making Persistent Build Folders in Netlify A year in review: 2020 Whimsical Website Club The Return of the 90s Web Human Code Color Theme Switcher Eleventy Résumé Builder The Emergency Website Kit Making a Gigposter A year in review: 2019 Webclerks Conference Roads IndieWeb Link Sharing Good Enough
Offline-Friendly Forms
Max Böck · 2017-08-23 · via Max Böck

Forms on the web don't usually play nice with bad connections. If you try to submit a form while offline, you'll most likely just lose your input. Here's how we might fix that.

TL;DR: Here’s the CodePen Demo of this post.

With the introduction of Service Workers, developers are now able to supply experiences on the web that will work even without an internet connection. While it’s relatively easy to cache static resources, things like forms that require server interaction are harder to optimize. It is possible to provide a somewhat useful offline fallback though.

First, we have to set up a new class for our offline-friendly forms. We’ll save a few properties of the <form> element and then attach a function to fire on submit:

class OfflineForm {
  // setup the instance.
  constructor(form) {
    this.id = form.id;
    this.action = form.action;
    this.data = {};
    
    form.addEventListener('submit', e => this.handleSubmit(e));
  }
}

In the submit handler, we can include a simple connectivity check using the navigator.onLine property. Browser support for it is great across the board, and it’s trivial to implement.

⚠️ There is however a possibility of false positives with it, as the property can only detect if the client is connected to a network, not if there’s actual internet access. A false value on the other hand can be trusted to mean “offline” with relative certainty. So it’s best to check for that, instead of the other way around.

If a user is currently offline, we’ll hold off submitting the form for now and instead store the data locally.

handleSubmit(e) {
  e.preventDefault();
  // parse form inputs into data object.
  this.getFormData();
  
  if (!navigator.onLine) {
    // user is offline, store data on device.
    this.storeData();
  } else {
    // user is online, send data via ajax.
    this.sendData();
  }
}

Storing the Form Input

Permalink to “Storing the Form Input”

There are a few different options on how to store arbitrary data on the user’s device. Depending on your data, you could use sessionStorage if you don’t want the local copy to persist in memory. For our example, let’s go with localStorage.

We can timestamp the form data, put it into a new object and then save it using localStorage.setItem. This method takes two arguments: a key (the form id) and a value (the JSON string of our data).

storeData() {
  // check if localStorage is available.
  if (typeof Storage !== 'undefined') {
    const entry = {
      time: new Date().getTime(),
      data: this.data,
    };
    // save data as JSON string.
    localStorage.setItem(this.id, JSON.stringify(entry));
    return true;
  }
  return false;
}

Hint: You can check the storage in Chrome’s devtools under the “Application” tab. If everything went as planned, you should see something like this:

chrome devtools showing the localstorage contents

It’s also a good idea to inform the user of what happened, so they know that their data wasn’t just lost.
We could extend the handleSubmit function to display some kind of feedback message.

feedback message explaining the offline state
How thoughtful of you, form!

Checking for Saved Data

Permalink to “Checking for Saved Data”

Once the user comes back online, we want to check if there’s any stored submissions. We can listen to the online event to catch connection changes, and to the load event in case the page is refreshed:

constructor(form){
  ...
  window.addEventListener('online', () => this.checkStorage());
  window.addEventListener('load', () => this.checkStorage());
}

When these events fire, we’ll simply look for an entry in the storage matching our form’s id. Depending on what type of data the form represents, we can also add an “expiry date” check that will only allow submissions below a certain age. This might be useful if we only want to optimize for temporary connectivity problems, and prevent users from accidentally submitting data they entered two months ago.

checkStorage() {
  if (typeof Storage !== 'undefined') {
    // check if we have saved data in localStorage.
    const item = localStorage.getItem(this.id);
    const entry = item && JSON.parse(item);

    if (entry) {
      // discard submissions older than one day. (optional)
      const now = new Date().getTime();
      const day = 24 * 60 * 60 * 1000;
      if (now - day > entry.time) {
        localStorage.removeItem(this.id);
        return;
      }

      // we have valid form data, try to submit it.
      this.data = entry.data;
      this.sendData();
    }
  }
}

The last step would be to remove the data from localStorage once we have successfully sent it, to avoid multiple submissions. Assuming an ajax form, we can do this as soon as we get a successful response back from the server. We can simply use the storage object’s removeItem() method here.

sendData() {
  // send ajax request to server
  axios.post(this.action, this.data)
    .then((response) => {
      if (response.status === 200) {
        // remove stored data on success
        localStorage.removeItem(this.id);
      }
    })
    .catch((error) => {
      console.warn(error);
    });
}

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() or have the user press the button themselves.

☝️ Note: I’ve omitted some other parts like form validation and security tokens in this demo to keep it short, obviously these would have to be implemented in a real production-ready thing. Dealing with sensitive data is another issue here, as you should not store stuff like passwords or credit card data unencrypted locally.

If you’re interested, check out the full example on CodePen: