WooCommerce Cat 外掛開發 (五) – 修改購物車

目錄

前情提要:上一篇我們實作了 Alpine.js 的迴圈,也認識了 x-bind 綁定屬性、x-text/x-html 輸出文字,以及對於 JavaScript 非同步事件的理解,知道該如何輸出資料後,接下來我們進行輸入的部分,也就是修改購物車的商品數量以及刪除功能。

商品數量與刪除功能的介面如下:

https://oberonlai.blog/wp-content/uploads/woocommerce-cat-5/woocommerce-cat-01.jpg

我們希望完成的功能如下:

  • 當使用者點擊數量的加號按鈕時,數量會加一
  • 當使用者點擊數量的加號按鈕時,如果數量超過該商品的庫存數量,則顯示剩餘庫存數量,並提示庫存上限
  • 當使用者點擊數量的減號按鈕時,數量會減一
  • 當使用者點擊數量的減號按鈕時,如果數量小於一時,則顯示 1
  • 當使用者修改數量時,只能輸入正整數
  • 當使用者點擊刪除按鈕時,購物車會將該商品移除

需要處理的事件有點多,我們一個一個步驟來吧~

購物車物件

四、x-on 事件綁定

先回顧目前的 HTML:

<ul>
    <template x-for="item in cart.items" :key="item.id">
        <li>
             <!--商品圖片-->
            <img :src="item.images[0].src" />

            <!--商品名稱-->
            <h3 x-text="item.name"></h3>

            <!--單價-->
            <p x-text="item.prices.currency_symbol+new Intl.NumberFormat().format(item.prices.price)"></p>

            <!--數量-->
            <div>...</div>

            <!--小計-->
            <div class="float:right f:24 f:red-50 float:none@sm mt:10 mt:0@sm" x-text="item.prices.currency_symbol+new Intl.NumberFormat().format((item.prices.price*item.quantity))"></div>

            <!--刪除-->
            <div>...</div>

        </li>
    </template>
</ul>

新增減少數量的按鈕,加入以下程式碼:

    // 略

    <!--數量-->
    <div>
        <!--減少數量按鈕-->
        <div type="button" class="bg:fade-90:hover" :class="item.quantity === 1 ? 'pointer-events:none opacity:.5':''" @click.prevent="updateQty('decre',item.key,item.quantity,'',$el)">-</div>
    </div>

首先可以看到在這個按鈕裡面,有兩個 CSS 的 class 屬性,差別在於一個有冒號一個沒有,這是前篇文章提到的 x-bind 的用法,沒有冒號的是原生的寫法,有冒號的則是 Alpine.js 的動態綁定,也就是我可以在裡面使用 JS 根據條件動態產生 class 名稱。

<div :class="item.quantity === 1 ? 'pointer-events:none opacity:.5':''"></div>

這邊使用三元運算子的寫法,也就是當 item.quantity 商品數量等於 1 的時候,降低按鈕透明度以及讓滑鼠點不到的 CSS 屬性,藉此讓減少數量按鈕在商品數量為 1 時不可點擊,以避免變成負數,然後在商品數量不等於 1 的情況下不輸出任何 CSS 屬性,原生 class 裡面的 CSS 屬性則不會受到商品數量的影響。

接下來看到我們的主角 x-on 事件綁定:

<div type="button" @click.prevent="updateQty('decre',item.key,item.quantity,item.quantity_limits.maximum,$el)">-</div>

x-on 的簡寫使用 @ 這個符號,所以滑擊就是 @click、按下鍵盤就是 @keyup,JS 內建的事件都可以使用,至於 @click\.prevent 後面的 prevent 則是避免觸發預設的事件,也就是 preventDefault() 的縮寫,點擊按鈕後我們觸發 updateQty() 方法,它帶有五個參數:

  1. type – 修改類型,decre 是減少、incre 是增加、change 是用文字框輸入修改數量。
  2. itemKey – 商品的 key,這是 Store API 用來修改購物車內容的必要參數
  3. quantity – 目前的商品數量
  4. quantity_limits – 商品庫存數量
  5. el – 觸發事件的對象,在這邊的話就是減少按鈕本身

本來是打算把減少、增加、修改數量用三個方法來處理,但因為處理的任務內容差不多,因此我把它設計在一起,updateQty() 實作如下:

updateQty(type, itemKey, quantity, quantity_limits = null, el = null) {
    let qty;    

    quantity = parseInt(quantity)
    quantity_limits = parseInt(quantity_limits)

    switch (type) {
        case 'decre':
            qty = (quantity === 1) ? 1 : quantity - 1
            break;
        default:
            break;
    }

    // 實際請求 Store API 修改購物車內容
    if (qty) {
        this.reqJSON(`${this.routeCart}/update-item`, 'POST', {
            'key': itemKey,
            'quantity': qty,
        }).then(data => { this.cart = data })
    }

}

在實際進行商品數量的修改前,記得要先確保傳入數量的型別是整數 Integer,不然後續在計算上會發生錯誤,接下來根據 type 參數來修改變數 qty,最後把 itemKeyqty 作為參數,以 POST 方法請求路徑 wp-json/wc/store/v1/cart/update-item 就能更新購物車資料。

接下來當點擊減少按鈕後,就可以更新購物車商品的數量,但我們在畫面上並沒有看到數量的改變,是哪裡發生了問題呢?

五、x-model 資料綁定

Alpine.js 的資料綁定是我最喜歡的功能,以往在製作表單的時候,必須要去接聽欄位的值是否有更新,有的話要取得該欄位的值,然後存到變數裡面以利後續傳送,有了 x-model 之後可以省下許多步驟,它能幫我們把接聽、取值、存值一次搞定。

我們先把數量的輸入框以及增加按鈕的 HTML 補上:

    // 略

    <!--數量-->
    <div>
        <!--減少數量按鈕-->
        <div type="button" class="bg:fade-90:hover" :class="item.quantity === 1 ? 'pointer-events:none opacity:.5':''" @click.prevent="updateQty('decre',item.key,item.quantity,'',$el)">-</div>

        <!--數量輸入框-->
        <input type="text" x-model="item.quantity" @keyup="updateQty('change',item.key,$el.value,item.quantity_limits.maximum,$el);" />

        <!--增加數量按鈕-->
        <div type="button" class="bg:fade-90:hover" :class="item.quantity >= item.quantity_limits.maximum ? 'pointer-events:none opacity:.5':''" @click.prevent="updateQty('incre',item.key,item.quantity,item.quantity_limits.maximum,$el)">+</div>
    </div>

在數量輸入框的地方,除了常見的 type="text" 以及前面提過的 @keyup="updateQty()" 事件綁定外,還多了一個 x-model="item.quantity",多了這個資料綁定代表只要修改 input 裡面的值,在物件 cart.items 裡面的 item.quantity 也會跟著變動,以下圖來看會比較好理解:

https://oberonlai.blog/wp-content/uploads/woocommerce-cat-5/woocommerce-cat-02.jpg

因此遇到表單的需求時,x-model 可以很方便的儲存使用者輸入的資料。回到點選加減按鈕沒有更新數量顯示的問題,原因在於我們是把 x-model 綁定在 input 上面,因此還需要多一個給值的動作來更新數量顯示,updateQty() 方法更新如下:

updateQty(type, itemKey, quantity, quantity_limits = null, el = null) {
    let qty;    

    quantity = parseInt(quantity)
    quantity_limits = parseInt(quantity_limits)

    switch (type) {
        case 'decre':
            qty = (quantity === 1) ? 1 : quantity - 1
            el.parentElement.querySelector('input')._x_model.set(qty)
            break;
        case 'incre':
            qty = (quantity >= quantity_limits) ? quantity_limits : quantity + 1
            el.parentElement.querySelector('input')._x_model.set(qty)
            break;
        case 'change':
            qty = (quantity >= quantity_limits) ? quantity_limits : quantity;
            (qty) ? el._x_model.set(qty) : el._x_model.set(1)
            break;
        default:
            break;
    }

    // 實際請求 Store API 修改購物車內容
    if (qty) {
        this.reqJSON(`${this.routeCart}/update-item`, 'POST', {
            'key': itemKey,
            'quantity': qty,
        }).then(data => { this.cart.items_count = data.items_count })
    }

}

我們看 typeincre 也就是點擊增加按鈕的寫法:

qty = (quantity >= quantity_limits) ? quantity_limits : quantity + 1
el.parentElement.querySelector('input')._x_model.set(qty)

先判斷目前數量是否大於庫存數量,是的話 qty 就等於庫存數量,否則就是用傳入的數量加一,第二行我們選到數量的輸入框,由於該輸入框已經設定 x-model,因此它會自動增加 _x_model 屬性,並且用 set() 方法來設定值,這邊只要帶入變數 qty 就能在點擊增加按鈕後看到數量的改變了。

六、x-mask 過濾輸入資料

在預設情況下 input 可以輸入任何字元以及字數,為了避免使用者不小心輸入到非數字的字元,或是輸入太大的數字,我們有必要來進行輸入資料的過濾。Alpine.js 官方提供一支外掛來協助我們處理這個問題,可以用 x-mask 來設定輸入的格式限制。

安裝方法可以直接使用 CDN 引入或是用 NPM 進行安裝:https://alpinejs.dev/plugins/mask

接下來我們在數量輸入框的地方多增加一個 x-mask 屬性:

        <!--數量輸入框-->
        <input type="text" x-model="item.quantity" @keyup="updateQty('change',item.key,$el.value,item.quantity_limits.maximum,$el);" x-mask="999" />

新增 x-mask="999" 代表的意思是只能輸入三位數的數字,就能讓使用者不能輸入英文或是其他符號,這還可以應用在其他地方,像是限制手機格式為 x-mask="9999-999-999"、限制信用卡卡號 x-mask="9999 9999 9999 9999"、限制日期 x-mask="9999/99/99"等等,也可以用 x-mask.dynamic 設定動態的格式規則。

七、x-show / x-if 控制 HTML 顯示

回到庫存的部分,我們希望在輸入數量超過該商品的庫存時,會顯示如下的文字提示:

https://oberonlai.blog/wp-content/uploads/woocommerce-cat-5/woocommerce-cat-03.jpg

我們先把提示文字的 HTML 準備好,而它只會在商品有設定庫存時才會出現:

    // 略

    <!--數量-->
    <div>
        <!--減少數量按鈕-->
        // 略

        <!--數量輸入框-->
        // 略

        <!--增加數量按鈕-->
        // 略

        <!--庫存提示文字-->
        <template x-if="item.quantity_limits.maximum!==9999">
            <p>庫存上限 <span x-text="item.quantity_limits.maximum"></span></p> 
        </template>
    </div>

這邊可以看到我們用了一個 <template> 來包住庫存提示,並且裡面用了 x-if 這個屬性來判斷該商品是否有設定庫存,當 x-if 裡面的參數為 true 時,瀏覽器會輸出裡面的 <p>...</p>false 的話則不輸出,判斷該商品是否有設定庫存則用 quantity_limits.maximum 決定,如果是 9999 的話代表沒有設定庫存。

因此我們判斷當 quantity_limits.maximum 不等於 9999 的時候才會輸出庫存提示文字,這邊要注意的是 x-if 只能用在 <template> 標籤身上,如果直接用在其他標籤如 <div> 是沒有作用的。

x-if 相似的還有一個叫做 x-show 的屬性,邏輯跟 x-if 一樣,都是當裡面回傳 true 時才會顯示,但差別在於 x-show 會將 HTML 輸出,控制顯示與否的原理是用 CSS 的 display 屬性,也就是說如果 x-show 為 true 時,該標籤會增加 style="display:block",反之為 style="display:none"

我們把 <p> 標籤加入 x-show 來控制顯示隱藏:

        <!--庫存提示文字-->
        <template x-if="item.quantity_limits.maximum!==9999">
            <p x-show="item.quantity>=item.quantity_limits.maximum">庫存上限 <span x-text="item.quantity_limits.maximum"></span></p>    
        </template>

當商品數量大於等於商品庫存時,就會顯示「庫存上限 XX」的文字,這樣就完成庫存提示的功能了。

刪除購物車商品

最後我們來設計移除購物車內商品的功能,需要用到上面介紹過的 x-on 點擊事件,我們設計了一個 removeItem() 方法,傳入 item.key 以作為 Store API 刪除商品的必要參數,HTML 新增刪除按鈕:

<ul>
    <template x-for="item in cart.items" :key="item.id">
        <li>
            // 略

            <!--刪除-->
            <div class="cursor:pointer p:0! abs top:0 right:5 rel@sm" @click.prevent="removeItem(item.key)"></div>
        </li>
    </template>
</ul>

removeItem() 方法實作如下:

removeItem(itemKey) {
    this.reqJSON(`${this.routeCart}/remove-item`, 'POST', {
        'key': itemKey,
    }).then(data => this.cart = data)
},

具體內容就是帶參數 key 以 POST 方法請求路徑 wp-json/wc/store/v1/cart/remove-item,並將回傳結果存回 cart 變數裡面,在顯示的部分我們不需要處理元素的移除,Alpine.js 會自動接聽 cart 變數的異動來重新輸出 HTML,因此當刪除的請求完成後該商品的 <li> 隨即移除,比以往要用 remove() 方便許多。

最後我們處理當購物車內沒有加入任何商品時,會顯示「購物車內沒有商品」的文字提示,用的一樣是 x-show 屬性,只要判斷 items_count 為 0 時顯示即可:

<div x-data="cart">
    <ul>
        <template x-for="item in cart.items" :key="item.id">
            <li>
                // 略
            </li>
        </template>
    </ul>

    <!--購物車內沒有商品的狀態-->
    <div x-show="cart.items_count===0">購物車內沒有商品</div>
</div>

結語

購物車的基本功能到這邊告一段落,但以 WooCommerce 來說還有一堆功能可以整合,包含稅率的計算、依商品數量進行折扣、可變商品的規格顯示等等,以及當 API 請求錯誤時的一些狀態處理,這些就等日後再逐一完善了。

下一個部分我預計處理會員的註冊以及登入功能,我們下一篇繼續吧!

存放庫連結:https://github.com/oberonlai/woocommerce-cat

目錄

賴俊吾 / Oberon Lai
賴俊吾 / Oberon Lai

現為全職 WordPress 工程師,網站開發經歷 11 年,專攻前端工程與 WordPress 佈景主題、外掛客製化開發

訂閱電子報

Hi,我是 Oberon,我會固定在每週五早上發送接案心得以及與 WordPress 相關的電子報,同時也會分享一些實用的開發知識,讓你在 WordPress 的接案路上不孤單!

覺得文章對你有幫助再幫我鼓個掌吧!

相關文章

WooCommerce Notify 支援 Fluent Form 表單發送通知

很開心終於有客戶跟我許願新功能了,第一次加入的完整功能是讓使用 Fluent Forms 表單外掛的站長,能在表單送出後以手機簡訊...

WooCommerce 結帳頁自訂信用卡欄位

最近在接一家有站內付功能的金流商,因此需要在結帳頁整合信用卡資訊欄位,為了避免使用者輸入錯誤,會需要驗證卡號長度、到期日格式、安全...

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料

專注於分享 WordPress 開發、接案技巧、專案管理等自由工作者必備知識與心得

© 2023 想點創意科技有限公司

想點創意科技有限公司 | 統一編號 90516823
Designed by Hend Design | 隱私權政策

訂閱電子報

Hi,我是 Oberon,我會固定在每週五早上發送接案心得以及與 WordPress 相關的電子報,同時也會分享一些實用的開發知識,讓你在 WordPress 的接案路上不孤單!