[譯] 離線友好的表單

磊仔發表於2019-01-24

網路不佳時網頁表單的表現通常並不理想。如果你試圖在離線狀態下提交表單,那就很可能丟失剛剛填好的資料。下面就看看我們是如何修復這個問題的。

太長,勿點:這裡是本文的 CodePen Demo

隨著 Service Workers 的推行,現在開發者們甚至可以實現離線版的網頁了。靜態資源的快取相對容易,而像表單這樣需要伺服器互動的情況就很難優化了。即使這樣,提供一些有用的離線回退方案還是有可能的。

首先,我們為離線友好的表單建立一個新的類。接著我們儲存一些 <form> 元素的屬性然後繫結一個觸發 submit 事件的函式:

class OfflineForm {
  // 配置例項。
  constructor(form) {
    this.id = form.id;
    this.action = form.action;
    this.data = {};

    form.addEventListener('submit', e => this.handleSubmit(e));
  }
}複製程式碼

在 submit 處理函式中,我們使用 navigator.onLine 屬性內建一個簡單的網路檢查器。瀏覽器對它的支援很好,而且實現它也不難。

⚠️ 但它還是有一定誤報的可能,因為這個屬性只能檢查客戶端是否連線到網路,而不能檢測實際的網路連通性。另一方面,一個 false 值意味著“離線”是相對確定的。因此,比起其他方式這個判斷方法是最好的。

如果一個使用者當前處於離線狀態,我們就暫停表單的提交,把資料儲存在本地。

handleSubmit(e) {
  e.preventDefault();
  // 解析表單輸入,儲存到物件中
  this.getFormData();

  if (!navigator.onLine) {
    // 使用者離線,在裝置中儲存資料
    this.storeData();
  } else {
    // 使用者線上,通過 ajax 傳送資料 
    this.sendData();
  }
}複製程式碼

儲存表單資料

儲存資料到使用者裝置有幾種不同的方式。根據資料的不同,如果你不希望本地副本持久儲存在記憶體中,可以使用 sessionStorage。在我們的例子中,我們可以一起使用 localStorage

我們可以給表單資料附上時間戳,把它賦值給一個新的物件,並且使用 localStorage.setItem 儲存。這個方法接受兩個引數:key(表單 id)和 value(資料的 JSON 串)。

storeData() {
  // 檢測 localStorage 是否可用
  if (typeof Storage !== 'undefined') {
    const entry = {
      time: new Date().getTime(),
      data: this.data,
    };
    // 把資料儲存為 JSON 串
    localStorage.setItem(this.id, JSON.stringify(entry));
    return true;
  }
  return false;
}複製程式碼

提示:你可以在 Chrome 的開發者工具 “Application” 中檢視儲存資料。如果不出差錯,你可以看到內容如下:

通知使用者發生了什麼也是個好主意,這樣他們會知道他們的資料不會丟失。我們可以擴充套件 handleSubmit 函式來顯示某些反饋資訊。

多麼周到的表單!

檢查儲存的資料

一旦使用者聯網,我們想檢查一下是否有被儲存的提交。我們可以監聽 online 事件來捕獲網路連結的改變,還有頁面重新整理時的 load 事件:

constructor(form){
  ...
  window.addEventListener('online', () => this.checkStorage());
  window.addEventListener('load', () => this.checkStorage());
}複製程式碼
checkStorage() {
  if (typeof Storage !== 'undefined') {
    // 檢測我們是否在 localStorage 之中儲存了資料
    const item = localStorage.getItem(this.id);
    const entry = item && JSON.parse(item);

    if (entry) {
      // 捨棄超過一天的提交。 (可選)
      const now = new Date().getTime();
      const day = 24 * 60 * 60 * 1000;
      if (now - day > entry.time) {
        localStorage.removeItem(this.id);
        return;
      }

      // 我們已經驗證了表單資料,嘗試提交它
      this.data = entry.data;
      this.sendData();
    }
  }
}複製程式碼

一旦我們成功提交了表單,那最後一步就是移除 localStorage 中的資料,來避免重複提交。假設是一個 ajax 表單,我們可以在伺服器響應成功的回撥裡做這件事。很簡單,這裡我們可以使用 storage 物件的 removeItem() 方法。

sendData() {
  // 向伺服器傳送 ajax 請求
  axios.post(this.action, this.data)
    .then((response) => {
      if (response.status === 200) {
        // 成功時移除儲存的資料
        localStorage.removeItem(this.id);
      }
    })
    .catch((error) => {
      console.warn(error);
    });
}複製程式碼

如果你不想使用 ajax 提交,另一個方案是將儲存的資料回填到表單,然後呼叫 form.submit() 或讓使用者自己點選提交按鈕。

☝️ 注意:簡單起見,我在這個案例中省略了一些其他部分,比如表單驗證和安全 token 驗證等,這些東西在真正的生產環境是必不可少的。這裡的另一個問題是處理敏感資料,就是說你不能在本地儲存一些密碼或者信用卡資料等私密資訊。

如果你感興趣,請查閱 CodePen 上的全部示例


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章