第九章:前端互動實踐
如果 manifest.json
是 Skin 的身份證,template.html
是它的骨架,那麼 main.js
就是賦予其生命、智慧和反應能力的「大腦和神經系統」。
一個優秀的 main.js
腳本是一個小型的、由資料驅動的應用程式。它的職責是:渲染 UI、管理狀態、處理使用者互動、並與後端引擎進行通訊。本章將介紹實現這些功能的最佳實踐。
1. 存取表單資料 (與引擎的數據接口)
在您的 main.js
執行之前,MoriForms 後端引擎已經將該頁面上所有表單所需的資料,打包成一個全域的 JavaScript 物件,名為 moriformsData
。這是您所有工作的起點。
moriformsData
的結構如下:
JavaScript
window.moriformsData = {
"nonce": "a1b2c3d4e5", // 用於 API 提交的安全權杖
"forms": {
"123": { // 表單 ID
"jsonData": { /* 完整的 Form Schema 2.0 JSON 物件 */ },
"apiEndpoint": "https://yoursite.com/wp-json/MoriForms/v1/forms/123/submit",
"skinOptions": { // 使用者在後台為此 Skin 設定的選項
"primary_color": "#ff0000",
"layout_style": "stacked"
}
},
"456": { // 頁面上的第二個表單
// ...
}
}
};
初始化腳本的 boilerplate (樣板碼):
一個健壯的 main.js 應該能處理同頁面上存在多個表單的情況。
JavaScript
document.addEventListener('DOMContentLoaded', function () {
// 檢查 moriformsData 是否存在
if (typeof moriformsData === 'undefined' || !moriformsData.forms) {
console.warn('MoriForms data not found on this page.');
return;
}
// 遍歷所有需要初始化的表單
for (const formId in moriformsData.forms) {
const formConfig = moriformsData.forms[formId];
const container = document.getElementById('MoriForms-container-' + formId);
if (container) {
// 為每個表單建立一個實例,進行初始化
new MoriFormsSkin(container, formConfig);
}
}
});
// 將我們的邏輯封裝在一個類別中,方便管理
class MoriFormsSkin {
constructor(container, config) {
this.container = container;
this.config = config;
this.state = {}; // 用於儲存所有欄位的當前值
this.render();
this.bindEvents();
}
render() {
// ... 渲染表單的邏輯 ...
console.log('Rendering form:', this.config.jsonData.formName);
}
bindEvents() {
// ... 綁定事件監聽的邏輯 ...
}
}
2. 渲染表單 (動態建立 UI)
不要將所有 HTML 寫死。您應該根據 jsonData.layout
陣列,動態地建立每一個元素。推薦使用「工廠模式」來處理不同類型的元素。
JavaScript
// 在 MoriFormsSkin 類別中
render() {
const schema = this.config.jsonData;
const fieldsContainer = document.createElement('form'); // 使用 <form> 標籤包裹
schema.layout.forEach(elementData => {
const elementNode = this.createElement(elementData);
if (elementNode) {
fieldsContainer.appendChild(elementNode);
}
});
// ... 加上送出按鈕 ...
this.container.appendChild(fieldsContainer);
}
createElement(data) {
// 根據元素類型,呼叫不同的建立函數
switch (data.type) {
case 'text':
case 'email':
return this.createTextField(data);
case 'select':
return this.createSelectField(data);
case 'group':
return this.createGroupContainer(data);
// ... 其他類型
default:
return null;
}
}
createTextField(data) { /* ... 建立 label 和 input ... */ }
// 對於容器,需要遞迴呼叫 createElement
createGroupContainer(data) {
const group = document.createElement('fieldset');
// ... 建立 legend ...
data.children.forEach(childData => {
const childElement = this.createElement(childData);
if (childElement) {
group.appendChild(childElement);
}
});
return group;
}
3. 狀態管理與條件邏輯
表單的互動性來自於對「狀態」的即時反應。您需要:
- 監聽輸入: 在
bindEvents
方法中,為所有輸入欄位綁定input
或change
事件。每當有欄位的值改變時,更新this.state
物件。 - 評估條件: 當一個被依賴的欄位(即出現在
conditions
規則中的fieldId
)的值發生變化時,觸發一個evaluateConditions
的方法。 - 執行動作:
evaluateConditions
方法會遍歷jsonData.conditions
陣列。對於每一條規則,它會根據this.state
中儲存的當前表單值來判斷if
條件是否成立,然後執行then
或else
區塊中定義的動作(例如,為目標元素增刪一個.is-hidden
的 CSS class,或設定其disabled
屬性)。
4. 處理表單提交
這是前端生命週期的最後一步,也是最關鍵的一步。
監聽提交事件:
在 bindEvents 中,監聽 <form>
元素的 submit 事件。
JavaScript
// 在 bindEvents 方法中
const formElement = this.container.querySelector('form');
formElement.addEventListener('submit', (event) => {
event.preventDefault(); // 【重要】阻止瀏覽器預設的提交行為
this.handleSubmit();
});
handleSubmit
方法的實作:
JavaScript
// 在 MoriFormsSkin 類別中
async handleSubmit() {
// 1. 顯示載入中的狀態 (例如,禁用按鈕)
const submitButton = this.container.querySelector('button[type="submit"]');
submitButton.disabled = true;
submitButton.textContent = '傳送中...';
// 2. 收集表單資料 (this.state)
const submissionPayload = {
submissionData: this.state
};
// 3. 使用 Fetch API 發送請求
try {
const response = await fetch(this.config.apiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': moriformsData.nonce // 附上 Nonce 安全權杖
},
body: JSON.stringify(submissionPayload)
});
const responseData = await response.json();
if (!response.ok) { // 處理錯誤 (例如 400, 500)
this.handleErrorResponse(responseData);
} else { // 處理成功 (200 OK)
this.handleSuccessResponse(responseData);
}
} catch (error) { // 處理網路等其他錯誤
console.error('Submission failed:', error);
// 在 UI 上顯示一個通用的網路錯誤訊息
} finally {
// 4. 恢復按鈕狀態
submitButton.disabled = false;
submitButton.textContent = this.config.jsonData.settings.submitButtonText || 'Submit';
}
}
處理 API 回應:
handleSuccessResponse(data)
:- 檢查
data.postSubmitAction.type
。 - 如果是
"success_message"
,則清空表單,並在容器中顯示data.postSubmitAction.content
的內容。 - 如果是
"redirect"
,則執行window.location.href = data.postSubmitAction.url
。
- 檢查
handleErrorResponse(data)
:- 這是處理驗證失敗的地方。
- 遍歷
data.data.errors
物件。 - 對於每一個
fieldId
,找到對應的 HTML 欄位,並在其旁邊或下方顯示錯誤訊息data.data.errors[fieldId]
。 - 清除上一次的舊錯誤訊息。
下一步
您現在已經掌握了編寫一個專業、健壯的前端 Skin 腳本所需的核心知識。從接收資料、渲染 UI,到管理狀態和與後端 API 進行完整的通訊,整個流程都已清晰。
在最後的開發章節中,我們將回到伺服器端,探討如何利用 functions.php
和 WordPress 的 Hooks,為您的 Skin 增加自訂的後端邏輯,實現更深度的功能整合。