[note] stimulus
本篇範例的 import 路徑使用的是舊版寫法(import { Controller } from 'stimulus')。Stimulus 2.0+ 起套件已改名為 @hotwired/stimulus,新版應改用:
import { Controller } from '@hotwired/stimulus';
請在使用前確認專案的 Stimulus 版本。
本篇內容擷取自:Stimulus 手冊 - 繁體中文 @ Andyyou
基本使用
<!-- snippets -->
<div data-controller="hello">
<input data-target="hello.name" type="text" />
<button data-action="click->hello#greet">Greet</button>
</div>
// snippets
// src/controllers/hello_controller.js
import { Controller } from 'stimulus';
export default class extends Controller {
static targets = ['name']; // 將 data-target 的名稱加到定義列表中
// 當 Controller 和 DOM 綁定時會觸發 connect
connect() {
this.element; // 取得被綁定的 DOM 元素
}
// 透過 DOM 上的 data-action="click->hello#greet" 觸發
greet() {
this.nameTarget; // Object, 取得第一個符合的 target
this.nameTargets; // Array, 取得所有符合的 targets
this.hasNameTarget; // Boolean, 檢查是否有符合的 target
}
}
data-controller:主要用來設定繫結或停止繫結 Stimulus 的 controllers。data-action:用來設定事件(events),即元素該觸發 controller 裡的哪個方法(action)。data-target:則是協助我們在 controller 影響的範圍內方便的操作特定元素。
定義 controller 並檢查是否成功綁定到 HTML 元素
keywords: data-controller
<!-- public/index.html -->
<!-- data-controller -->
<div data-controller="hello">
<input type="text" />
<button>Greet</button>
</div>
connect() 這個方法會在 Stimulus 完成 controller 和 DOM 綁定的時候被呼叫
// src/controllers/hello_controller.js
import { Controller } from 'stimulus';
export default class extends Controller {
connect() {
console.log('Hello, Stimulus!', this.element);
}
}
action 回應 DOM 事件
keywords: data-action
在 Stimulus 裡,controller 的方法我們叫做 action:
<!-- public/index.html -->
<!-- data-action -->
<div data-controller="hello">
<input type="text" />
<button data-action="click->hello#greet">Greet</button>
<!-- multiple data-action if needed -->
<div data-action="mouseover->monkey#mouseOver mouseout->monkey#mouseOut"></div>
</div>
其中 data-action 的值 click->hello#greet 稱為動作描述子(action descriptor)。其實就是一個設定格式。下面是格式的說明:
click為綁定的事件hello為 controller 識別名稱greet為調用的方法(action)
當我們 click 時,即會出發 hello controller 裡的 greet action。
// src/controllers/hello_controller.js
import { Controller } from 'stimulus';
export default class extends Controller {
greet() {
console.log('Hello, Stimulus!', this.element);
}
}
Action 預設事件可省略
上述 button 的 action 也可以省略寫成 <button data-action="hello#greet">Greet</button>,這是因為 Stimulus 定義了 click 是 <button> 的預設事件。
其他特定的元素也有預設事件,下列是完整的列表:
| 元素 | 預設事件 |
|---|---|
| a | click |
| button | click |
| form | submit |
| input | change |
| input type=submit | click |
| select | change |
| textarea | change |
target 為重要元素建立 controller 的參考屬性
keywords: data-target, this.nameTarget, this.nameTargets, this.hasNameTarget
<!-- public/index.html -->
<!-- data-target -->
<div data-controller="hello">
<input data-target="hello.name" type="text" />
<button data-action="click->hello#greet">Greet</button>
</div>
觀察到 data-target 的值為 hello.name 又稱為目標元素描述子(target descriptor)。其實就是一個設定格式。下面是格式的說明:
hello為 controller 是識別名稱name為 target 名稱
接著透過 static targets = ['name'] ,把 name 加入到 controller 目標元素的定義列表中,Stimulus 會自動建立一個 this.nameTarget 屬性,這個屬性會參考到符合條件的第一個目標元素。我們就可以直接使用這個屬性來讀取元素的值,用它來產生我們的問候句。
// src/controllers/hello_controller.js
import { Controller } from 'stimulus';
export default class extends Controller {
static targets = ['name']; // 將 data-target 的名稱加到定義列表中
greet() {
const element = this.nameTarget;
const name = element.value;
console.log(`Hello, ${name}!`);
}
}
如上面的範例我們的 "name" target 名稱會產生下面這些屬性:
this.nameTarget等於在 controller 影響的範圍內找到符合source的第一個元素,如果沒有任何元素符合,當我們存取屬性會產生錯誤。this.nameTargets所有在 controller 範圍內符合source的元素this.hasNameTarget判斷是否有符合name元素,如果有就是true,沒有則是false
狀態管理與 Data API
keywords: data-<controller-name>-<attribute-name>, this.data.get(), this.data.has(), this.data.set()
一個 Stimulus 應用程式的狀態基本上會存在 DOM 的屬性上;controller 基本上不管理狀態。命名的方式是使用 data-<controller-name>-<attribute-name>
<!-- 直接在 DOM 上面透過 data 屬性帶入狀態 -->
<!-- data-<controller-name>-<attribute-name> -->
<div data-controller="slideshow" data-slideshow-index="1"></div>
注意
data-slideshow-index必須要放在 controller 的根元素上。- 因為
data-slide-show-index是 HTML 的 attribute 所以只能是字串,若要存成陣列,則需透過split()和join()處理;若是物件則需要先透過JSON.stringify()等方法。
由於 data 屬性太常被使用,因此若按上面 慣例命名的話,除了過去使用 el.dataset 或 el.getAttribute 的做法外,在 Stimulus 中可以直接使用 this.data.get('<attribute-name>'):
// ./slideshow_controller
initialize() {
// const index = parseInt(this.element.dataset.slideshowIndex)
// const index = parseInt(this.element.getAttribute('data-slideshow-index'))
const index = parseInt(this.data.get("index"))
this.showSlide(index)
}
使 controller 的狀態與 DOM 上的狀態保持一致
this.data.has('<attribute-name>')假如 controller 根元素有data-slideshow-index屬性的話,會回傳true。this.data.get('<attribute-name>')取得根元素上data-slideshow-index屬性值。this.data.set('<attribute-name>', <value>)將index的值設定到根元素的data-slideshow-index屬性上。
// ./slideshow_controller.js
import { Controller } from 'stimulus';
export default class extends Controller {
static targets = ['slide'];
initialize() {
this.showCurrentSlide();
}
previous() {
this.index--;
}
next() {
this.index++;
}
showCurrentSlide() {
this.slideTargets.forEach((el, i) => {
el.classList.toggle('slide--current', this.index == i);
});
}
get index() {
return parseInt(this.data.get('index'));
}
set index(value) {
this.data.set('index', value);
this.showCurrentSlide();
}
}