本文翻译自 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 里查看完整的例子。