在寫 Rails 的時候,是不是很常遇到連結因為 turbo-link 的關係沒有作用呢?
不管是用原生的 JS 還是 React 或是 Vue 都很常遇到這個問題
Rails 團隊開發了一個輕型的 JS 框架,叫做 Stimulus JS
Stimulus JS 與 Turbo-Links 非常搭配,可以讓我們避免 turbo-link 的問題發生
我們也不需要在 JS 抓取許多的 DOM 元素,然後監聽事件等等
只要用乾淨、簡單的語法就能輕鬆抓取以及設定事件
我們先在 javascript 資料夾底下建立一個 navBar_controller.js 檔案
並且掛上 connect
方法
connect
方法:當我們在元素中掛上對應的 data-controller ,在頁面載入的時候,就會觸發 connect
方法,不過只會在載入的時候觸發一次我們就在 navBar_controller 先加上 connect ,先測試這個 controller 是否有效果
在使用 controller 要注意的一點是, controller 的作用域只限於他掛載元素的區塊(例如 div 內),出了這個區塊就沒作用
# app/javascript/controllers/navBar_controller.js
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
connect() {
console.log("hey");
}
}
在 navbar 裡面掛上這個 controller
# app/views/layouts/application.html.erb
...
<nav class="flex justify-end w-full py-2 pr-4 text-right text-white bg-slate-500" data-controller="navBar">
...
接著我們就可以重新整理頁面,並且按下檢查,看一下 console 是否有 hey
都沒問題後,我們就可以開始來做互動效果了
假設我們今天要做一個互動效果,加入購物車後,
要顯示購物車目前的品項總共多少錢
我們就得用 data-target 去抓顯示購物車的這個元素
要設定的 attribute 名稱是 data-{controller名稱}-target
這樣才會知道是哪個 controller 的 target
# app/views/layouts/application.html.erb
...
<div class="mr-8 cursor-pointer">購物車(<span
data-navBar-target="cart">0</span>)</div>
...
接下來我們來看一下有沒有真的抓到這個購物車
到 controller 中定義 target
static targets
是固定寫法,後面用陣列來放置要定義的 target 名稱
當我們定義好後,他就會去抓取 data-navBar-target 名稱為 cart 的元素
# app/javascript/controllers/navBar_controller.js
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["cart"];
...
}
將它印出
# app/javascript/controllers/navBar_controller.js
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["cart"];
connect() {
console.log(this.cartTarget);
}
}
這邊的 this 與我們在香草 JS 中所學的 this 是不一樣的東西,
在 Stimulus JS 當中, this 指的是當前的 controller
成功印出來之後,我們就要做下一步了
接著我們要來設定商品的購物車按鈕了
當使用者按下購物車後,商品金額會自動加總到購物車中
我們先來抓取商品的金額
不過商品頁是在另一個 erb 檔案中,我們必須要另外設定 controller
# app/views/drinks/show.html.erb
...
<div data-controller="product">
...
<div class="mb-10 text-lg">
價錢:
<span data-product-target="price"><%= @drink.price %></span>
</div>
...
</div>
</div>
再來我們要來追蹤事件
在商品頁面(product)購物車按鈕被點下(click)時,商品金額(price)會加總到 Navbar 的購物車裡面(另一個 controller: navBar)
這種跨 controller 的事件該怎麼做
我們要用 dispatch
來做
dispatch
有點像是廣播的概念,當動作觸發時,會廣播給另一個 controller 知道,並且觸發另一個 controller 的 action
我們先設定購物車按鈕觸發 click 動作的事件
<div data-controller="product">
...
<%= link_to '加入購物車', cart_items_path(id: @drink.product.id), class: 'p-2 text-white border-2 rounded-lg bg-slate-600', data: { turbo_method: :post, turbo_frame: "cart", action: "click->product#addPrice" } %>
...
</div>
到 product_controller 中設定 addPrice 方法
題外話,若 connect 沒有用到就可以刪掉了
# app/javascript/controllers/product_controller.js
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["price"];
addPrice() {
console.log(this.priceTarget);
}
}
有印出東西後我們就可以來綁定跨區事件了
當加入購物車按鈕點擊下去時,將事件綁定為全域事件
Navbar 的購物車監聽到全域事件被觸發時就會連帶觸發
# app/javascript/controllers/product_controller.js
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["price"];
addPrice() {
const event = new CustomEvent("addToCart");
window.dispatchEvent(event);
}
}
Navbar 的購物車需要設定監聽動作
# app/views/layouts/application.html.erb
<div class="mr-8 cursor-pointer" data-navBar-target="cart" data-action="addToCart@window->navBar#addToCart">購物車(0)</div>
接著我們要到 navBar 的 controller 中設定 addToCart 方法
在設定之前可以先印出一個東西看看設定是否正確
# app/javascript/controllers/navBar_controller.js
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["cart"];
addToCart() {
console.log("hey");
}
}
這邊就有一個問題了,我要怎麼知道商品價錢?商品價錢可是在其他的 controller 中噎
不用擔心,在綁定全域事件的同時,我們可以傳參數進來
# app/javascript/controllers/product_controller.js
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["price"];
addPrice() {
const event = new CustomEvent("addToCart", {
detail: { price: this.priceTarget.textContent }
});
window.dispatchEvent(event);
}
}
接著我們在 navBar_controller 中印出參數
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["cart"];
addToCart(e) {
console.log(e.detail);
}
}
成功印出後,我們就可以來做最後一步了
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["cart"];
addToCart(e) {
this.cartTarget.textContent =
parseInt(this.cartTarget.textContent) + parseInt(e.detail.price);
}
}
當我們點選加入購物車時,就會計算金額進去囉