如何製作離線友好的表單?
本文翻譯自
Offline-Friendly Forms。
在網路不佳的情況下表單的表現並不理想。如果提交表單的時候恰好斷網了,辛苦填好的資料有可能就找不回來了。下面就分享一下我們是如何修復這個問題的。
先睹為快,CodePen 上的 Demo。
自從有了 Service Worker,開發者可以為使用者提供離線版的 Web 應用。靜態資源的快取相對比較容易,但對於需要和服務端互動的表單來說就有點困難了。不過還好,我們還是有辦法為離線提供一種 fallback 的方案。
首先,建立一個與離線友好表單相對應的 Class。將表單的 id、action 儲存下來,並繫結表單的 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
));
}
}
在 submit 的處理函式中,可以使用 navigator。onLine 來檢查網路連結情況,這個 API 瀏覽器都支援得差不多了,就算要實現也比較簡單。
但是這個 API 存在誤報的可能。因為這個屬性只能表示存在網路連結,並不保證網路是通的。反過來,如果結果是 false 就可以明確表示是斷網的。因此據此判斷是否離線是一種相對靠譜的方式。
如果使用者處於離線狀態,我們將表單提 hold 住,把表單資料儲存在本地。
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
();
}
}
儲存表單
我們有數種在使用者裝置商儲存資料的方式。根據資料的特點,你可以使用 sessionStorage,如果不想把資料儲存在記憶體種,也可儲存在localStorage 中。
給表單資料付上一個時間戳,掛在一個新物件上。然後使用 localStorage。setItem 儲存。這個方法接受兩個引數,
key
(表單 id) 和
value
(通常是一個 JSON 字串)。
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
;
}
注意:可以在 Chrome 的開發者工具的 Application 標籤中檢視 storage 資料。如果不出差錯,裡面的內容如下:
還有最好將離線的情況告知使用者,告訴他們填寫的資料並不會丟失。補充 handleSubmit 方法,將這些資訊反饋給使用者。
考慮得真是周到呀!
檢查儲存的資料
一旦使用者聯網,就需要檢查一下本地是否存在未提交的表單。我們需要監聽 online 事件跟蹤網路連結的變化,或者頁面重新整理觸發的 load 事件。
constructor
(
form
){
。。。
window
。
addEventListener
(
‘online’
,
()
=>
this
。
checkStorage
());
window
。
addEventListener
(
‘load’
,
()
=>
this
。
checkStorage
());
}
當這些事件觸發時,我們只需檢查在 storage 中是否存在與表單 id 相匹配的資料。根據表單資料型別的不同,可能需要新增一個過期時間的判斷,只允許在特定的時間內的表單。這一點對於偶爾網路連結異常的情況很有效果,避免錯誤地把兩個月以前儲存在本地的表單提交。
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
();
}
}
}
如果表單成功提交,則還需要最後一步,將表單資料從 localStorage 清除。比如說一個 Ajax 表單,我們可以在服務端成功返回後馬上實施清除動作。這裡簡單使用 storage removeItem() 方法即可。
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
);
});
}
如果實在不想使用 Ajax 提交,另外一種處理方案就是將資料回填表單,直接呼叫 form。submit()提交,或者讓使用者自己點選提交按鈕提交。
注意:簡單起見,我略去了一些環節,比如表單驗證以及安全 token 驗證等等。這些步驟在真正的產品開發中是必不可少的。還有一個問題是關於敏感資料的處理,避免在本地明文儲存使用者密碼信用卡資訊等等。
如果有興趣,可以在 CodePen 裡檢視完整的例子。