iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 22
1
Modern Web

30天走訪Progressive Web Apps(PWAs)系列 第 22

Day22-就是偏要在無網路情況下送表單(Background Sync)

截至目前為止,已經可以順利在Offline的情境下,
正常瀏覽網站內容,也能暫存網站的內容到客戶端的DB,
但如果網站頁面上,很常會有表單的需求,例如,註冊會員、新增產品等等,
即使離線情況下,使用者點選「送出」按鈕,一樣會失敗,
因為沒有網路的情況下是無法向伺服器(Server)發出請求的,
Background Sync正好可以解決此問題。

Background Sync 如何運作?

https://ithelp.ithome.com.tw/upload/images/20180107/20103808p3MarJVADN.png

既然沒網路情況下,無法將資料傳出去,那麼我們可以將資料先暫存IndexedDB,並註冊一個Sync Task,等網路重新連線時,就會出發Service Worker中的sync監聽事件,
這時候,再將資料庫中的資料取出來,POST到伺服器,就可以成功實現離線延遲送出的效果。

實作

在post.js寫送出文章的submit事件

判斷瀏覽器是否支援

開啟post.js

  if('serviceWorker' in navigator && 'SyncManager' in window){
  ...
  } else{
  ...
  }

Service Worker一樣,使用前要先檢查SyncManager是否支援,如果支援就可以使用此功能,如果不存在,就保持原本執行的流程。

navigator.serviceWorker.ready
      .then(function(sw){
        var form = {
          id: new Date().toISOString(),
          title: inputTitle.value,
          content: inputContent.value,
          location: inputLocation.value,
          image: '"https://firebasestorage.googleapis.com/v0/b/days-pwas-practice.appspot.com/o/taipei_street.PNG?alt=media&token=8736b68e-216c-4c63-a7d4-a129875ba71e"'
        };
        writeData('sync-posts', form)
          .then(function(){
            sw.sync.register('sync-new-post');
          })
          .then(function(){           
            console.log('文章以使用Background Sync方式儲存');
          })
          .catch(function(err){
            console.log('Error',err);
          });
      });

假設環境支援,即可透過navigator.serviceWorker.readyDOM中,操作Service Worker,成功則回傳Promise,接著將使用者輸入的內容丟入form變數中。

writeData('sync-posts', form),是我們之前寫的寫入資料的功能,接著將表單寫入sync-posts之中。

寫入成功後,接著向Service Worker註冊任務sync-new-post

修改IndexedDB.js

var dbPromise = idb.open('articles', 1, function(db){
    if(!db.objectStoreNames.contains('article'))
        db.createObjectStore('article', {keyPath: 'id'});
    if(!db.objectStoreNames.contains('sync-posts'))
        db.createObjectStore('sync-posts', {keyPath: 'id'});
});

post.js中,寫道的是要將資料丟入sync-posts中,因此,在IndexedDB.js中,如果sync-posts不存在,就建立資料表。

假如資料表沒建立,資料會因為找不到對應的表而無法存入資料

處理完瀏覽器有支援的情況,接著如果沒有支援,則必須執行原本送出表單的方式。

else內容新增

var articleUrl = 'https://days-pwas-practice.firebaseio.com/article.json';
function sendForm(){
  fetch(articleUrl,{
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    },
    body: JSON.stringify({
      id: new Date().toISOString(),
      title: inputTitle.value,
      location: inputLocation.value,
      content: inputContent.value,
      image: '"https://firebasestorage.googleapis.com/v0/b/days-pwas-practice.appspot.com/o/taipei_street.PNG?alt=media&token=8736b68e-216c-4c63-a7d4-a129875ba71e"'
    })
  })
  .then(function (res) {
    console.log('送出表單',res);
    getArticleFromDB();
  });
}

  if('serviceWorker' in navigator && 'SyncManager' in window){} 
  else{
      sendForm();
  }

這裡透過fetch方式,post資料到firebase將資料存入。

撰寫sync監聽事件

self.addEventListener('sync', function(event){
    console.log('[SW] Background syncing', event);
    if(event.tag === 'sync-new-post') {
        console.log('抓到TAG-POST 表單');
        event.waitUntil(
            readAllData('sync-posts')
                .then(function(data){
                    for(var post of data)
                    {
                        fetch('https://days-pwas-practice.firebaseio.com/article.json',{
                            method: 'POST',
                            headers: {
                            'Content-Type': 'application/json',
                            'Accept': 'application/json'
                            },
                            body: JSON.stringify({
                                id: post.id,
                                title: post.title,
                                location: post.location,
                                content: post.content,
                                image: post.image
                            })
                        })
                        .then(function (res) {
                            console.log('送出表單',res);
                            if(res.ok){
                                deleteArticleData('sync-posts',post.id);
                            }
                        })
                        .catch(function(err){
                            console.log('POST表單失敗!',err);
                        });
                    }
                      
                })
        );
    }
});

接著透過event.tag來比對我們設定的task,如果一致,則從我們前幾天,寫的readAllData()功能中,將要post的內容,用for迴圈送出到伺服器。

如果表單送出成功,接著要刪除sync-posts資料表中已送出的內容,避免發生重複送出表單的情形發生。
ok可以簡單的得知response回應的是不是200(表示成功)。
deleteArticleData('sync-posts',post.id)透過此功能刪除資料庫中的內容。

在執行前,還必須做一件事情,必須先將firebase上的資料庫改成可以寫入的規則。
https://ithelp.ithome.com.tw/upload/images/20180107/20103808WWIGMpBgje.png
現在我們可以來測試了!

測試

Test Case1: 有網路情境下,POST表單

  1. 填完內容後,點選「發佈」
    https://ithelp.ithome.com.tw/upload/images/20180107/201038084b6jTPwho4.png

https://ithelp.ithome.com.tw/upload/images/20180107/201038089VVyZKptL9.png
從「Console」中,瀏覽器有支援因此會執行將資料存入IndexedDB
又因網路是順暢的,所以會直接執行sync的內容,將表單送出去。

  1. 查看「Application/IndexedDB」,右鍵「Refresh」資料庫,sync-posts會是空的,表示正確清除了已送出的表單。
    https://ithelp.ithome.com.tw/upload/images/20180107/201038089wzsnrc1IB.png

  2. 重整畫面,資料即透過fetch顯示出來了。
    https://ithelp.ithome.com.tw/upload/images/20180107/20103808XudRKJh02Z.png

  3. Firebase顯示資料
    https://ithelp.ithome.com.tw/upload/images/20180107/20103808gygzHERUII.png

Test Case2: 測試無網路下,POST表單

  1. 拔掉網路線或關掉Wi-fi

  2. 填表單
    https://ithelp.ithome.com.tw/upload/images/20180107/20103808LRK6sUTXIP.png

  3. 點「發佈」並檢查sync-posts
    https://ithelp.ithome.com.tw/upload/images/20180107/20103808ILrD2lpiMt.png

  4. 插回網路線
    https://ithelp.ithome.com.tw/upload/images/20180107/20103808rYLGSZ7bMu.png
    sync偵測到網路觸發執行送發表單。

  5. 檢查Firebase
    https://ithelp.ithome.com.tw/upload/images/20180107/20103808Al3MLWIqO1.png

  6. 重整網頁
    https://ithelp.ithome.com.tw/upload/images/20180107/20103808e7mQRYfpSz.png
    完工!


上一篇
Day21-Responsive Design
下一篇
Day23-Firebase Function
系列文
30天走訪Progressive Web Apps(PWAs)30

尚未有邦友留言

立即登入留言