新手指南:使用 Alpine.js 實現前端標籤輸入功能
更新日期: 2025 年 1 月 27 日
本文為 Django 標籤功能 系列文,第 3 篇
- 新手指南:在 Django 中實作標籤功能
- Django 新手指南:django-taggit 簡介
- 新手指南:使用 Alpine.js 實現前端標籤輸入功能 👈所在位置
- 新手指南|Django 標籤功能|後端資料儲存邏輯
- 新手指南:Django 標籤顯示功能解析
- Djano 標籤編輯頁|前、後端設定說明
- 使用 Django 建立標籤分類頁:新手教學
在網頁表單中,標籤輸入是一種常見的功能,可以幫助用戶添加多個關鍵字或分類,以描述內容或過濾搜索結果。
本篇文章將以簡單易懂的方式,帶你逐步了解如何用 Alpine.js 實現這樣的功能。
功能概述
這段代碼實現了一個動態標籤輸入功能,主要特點如下:
- 新增標籤: 用戶可以輸入標籤,按下 Enter 後將其添加到列表中。
- 刪除標籤: 每個標籤都附有一個刪除按鈕,用於移除該標籤。
- 隱藏輸出: 用戶輸入的標籤最終以逗號分隔的形式存入隱藏輸入欄位,便於後端處理。
接下來,我們將分段說明這段代碼的結構與邏輯。
<div x-data="{ tags: [], newTag: '' }" class="flex flex-col space-y-2 mt-4">
<div class="flex items-center">
<label for="tags" class="text-gray-700 font-medium">
<strong>
標籤:
</strong>
</label>
<span class="text-red-500 text-sm ml-1">*必填 (輸入完按下 Enter) </span>
</div>
<!-- 輸入標籤的欄位 -->
<input type="text" x-model="newTag" @keydown.enter.prevent="
if (newTag.trim() !== '' && !tags.includes(newTag.trim())) {
tags.push(newTag.trim());
newTag = '';
}
" placeholder="<範例> 影片剪輯 | 字幕設計" class="bg-white text-gray-800 border border-gray-300 rounded-md p-2 w-full" />
<!-- 已輸入的標籤顯示 -->
<div class="flex flex-wrap gap-2 mt-2">
<template x-for="(tag, index) in tags" :key="index">
<div class="flex items-center bg-indigo-500 text-white text-sm px-3 py-1 rounded-full">
<span x-text="tag"></span>
<button type="button" @click="tags.splice(index, 1)"
class="ml-2 py-1 text-white hover:text-red-300 focus:outline-none">
X
</button>
</div>
</template>
</div>
<!-- 隱藏的標籤輸出,用於送出到 Django -->
<input type="hidden" name="tags" :value="tags.join(',')" />
</div>
HTML 結構與數據綁定
這段代碼的核心是 Alpine.js 的數據綁定 和 HTML 的結構設計。
整個功能區域用 <div>
包裹並綁定了 Alpine.js 的狀態:
<div x-data="{ tags: [], newTag: '' }" class="flex flex-col space-y-2 mt-4">
Alpine.js 的狀態管理
x-data
:定義了一個物件,來存放所有與標籤功能相關的數據。tags
:儲存當前所有已添加的標籤,是一個陣列。newTag
:用來綁定用戶在輸入框中輸入的標籤內容。
顯示與樣式結構
這裡採用了 Tailwind CSS 來進行樣式設計,主要的排版與樣式:
flex
和space-y-2
:建立垂直方向的彈性布局,讓每個子元素之間有固定的間距。mt-4
:設置距離頂部的外邊距。
輸入框功能詳解
在標籤功能中,輸入框負責接收用戶輸入並處理新增邏輯。
以下是代碼的詳細說明:
輸入框代碼
<input
type="text"
x-model="newTag"
@keydown.enter.prevent="
if (newTag.trim() !== '' && !tags.includes(newTag.trim())) {
tags.push(newTag.trim());
newTag = '';
}
"
placeholder="<範例> 影片剪輯 | 字幕設計"
class="bg-white text-gray-800 border border-gray-300 rounded-md p-2 w-full"
/>
x-model
雙向綁定
x-model
是 Alpine.js 提供的一個核心功能,用於實現 雙向數據綁定。
在這段代碼中:
<input
type="text"
x-model="newTag"
...
/>
- 綁定
newTag
變數:- 當用戶在輸入框中鍵入內容時,輸入的值會自動更新到
newTag
。 - 反之,如果 JavaScript 程式中修改了
newTag
的值(如清空輸入框的操作),輸入框中的內容也會即時同步。
- 當用戶在輸入框中鍵入內容時,輸入的值會自動更新到
作用:x-model
的雙向綁定消除了手動操作 DOM 的需求,用戶在輸入框的輸入行為可以自動影響到程式內部的狀態。
範例:
- 當用戶輸入 “字幕設計” 時,
newTag
的值就會變為"字幕設計"
。 - 當用戶完成操作後程式執行
newTag = ''
,輸入框的內容也會自動清空。
按下 Enter 新增標籤
在這段代碼中,輸入框還監聽用戶的 Enter 鍵事件,用來新增標籤:
@keydown.enter.prevent="
if (newTag.trim() !== '' && !tags.includes(newTag.trim())) {
tags.push(newTag.trim());
newTag = '';
}
"
功能解析:
- 監聽 Enter 鍵:
@keydown.enter.prevent
是 Alpine.js 中的事件綁定語法:keydown.enter
:只在用戶按下 Enter 鍵時觸發。.prevent
:阻止表單的默認提交行為,避免頁面刷新。
- 新增標籤邏輯: 當用戶按下 Enter 後,程式執行以下邏輯檢查和操作:
newTag.trim() !== ''
:
確保用戶輸入的內容不是空值或只有空白字元。如果條件不成立,不執行後續邏輯。
範例:" "
→ 經過trim()
後變成""
,不會新增標籤。"字幕設計 "
→ 經過trim()
後變成"字幕設計"
,通過條件檢查。
!tags.includes(newTag.trim())
:
避免用戶輸入重複的標籤。該條件檢查tags
陣列中是否已經存在用戶輸入的標籤。如果條件不成立(標籤已存在),則不執行新增。
範例:- 當
tags = ["影片剪輯"]
時,輸入"影片剪輯"
不會新增。 - 當
tags = ["字幕設計"]
時,輸入"影片剪輯"
會通過檢查並新增。
- 當
tags.push(newTag.trim())
:
通過條件檢查後,將標籤加入到tags
陣列。trim()
確保標籤內容不包含首尾多餘的空白字元。newTag = ''
:
新增標籤後,清空newTag
的值,從而清空輸入框,讓用戶可以繼續輸入新的標籤。
提示與樣式設計
輸入框還設置了一些視覺樣式和提示,讓用戶更容易使用該功能:
placeholder
提示:placeholder="<範例> 影片剪輯 | 字幕設計"
- 當輸入框為空時,會顯示占位提示文字,例如「影片剪輯 | 字幕設計」,幫助用戶理解應輸入的內容格式。
- 當用戶開始輸入時,這段提示文字會自動消失。
- CSS 樣式設計: 使用 Tailwind CSS 提供的實用工具類,快速設計出美觀且實用的輸入框樣式:
- 白色背景(
bg-white
): 提升輸入框的可見性和對比度。 - 灰色邊框(
border border-gray-300
): 增強輸入框的邊界感,幫助用戶辨識其可交互性。 - 圓角設計(
rounded-md
): 提供柔和的視覺效果,避免尖銳的邊緣。 - 內部填充(
p-2
): 增加輸入框內文字與邊框的距離,提升可讀性和輸入體驗。 - 全寬設置(
w-full
): 讓輸入框自適應父容器寬度,適應不同的螢幕尺寸。
- 白色背景(
完整的輸入框功能運作流程
- 用戶輸入標籤內容,例如「字幕設計」。
- 按下 Enter 鍵觸發事件,程式檢查:
- 是否為空值。
- 是否與現有標籤重複。
- 如果通過檢查:
- 新標籤會被新增到
tags
陣列。 - 輸入框清空,等待用戶輸入下一個標籤。
- 新標籤會被新增到
- 標籤內容即時同步更新到網頁視圖,並可以通過刪除按鈕進行操作。
這段代碼的實現不僅簡潔高效,還考慮到了用戶體驗,特別是輸入框的即時反饋和清晰的提示,為整個功能的實現提供了良好的基礎。
標籤顯示與刪除功能
x-for
循環渲染
Alpine.js 提供的 x-for
指令 用於迭代數據陣列並在視圖中動態生成元素。在這段代碼中:
<template x-for="(tag, index) in tags" :key="index">
<div class="flex items-center bg-indigo-500 text-white text-sm px-3 py-1 rounded-full">
<span x-text="tag"></span>
<button
type="button"
@click="tags.splice(index, 1)"
class="ml-2 py-1 text-white hover:text-red-300 focus:outline-none">
X
</button>
</div>
</template>
x-for="(tag, index) in tags"
:- 對
tags
陣列中的每一個元素(即標籤)進行迭代。 tag
:表示當前迭代的標籤值。index
:表示當前標籤在陣列中的索引值。- 每次迭代會生成一個新的
<div>
元素,對應一個標籤。
- 對
:key="index"
:- 設置唯一鍵,告訴瀏覽器每個標籤的唯一性,方便高效地更新 DOM。
- 避免渲染錯誤: 如果沒有設置唯一鍵,Alpine.js 在更新標籤時可能會錯誤地重複渲染或刪除標籤。
範例: 假設 tags
陣列的值為 ["影片剪輯", "字幕設計"]
,x-for
會渲染以下 HTML 結構:
<div>
<div class="flex items-center bg-indigo-500 text-white text-sm px-3 py-1 rounded-full">
<span>影片剪輯</span>
<button> X </button>
</div>
<div class="flex items-center bg-indigo-500 text-white text-sm px-3 py-1 rounded-full">
<span>字幕設計</span>
<button> X </button>
</div>
</div>
補充::key 值的作用是什麼?
在大多數前端框架(包括 Alpine.js、Vue.js 等)中,當數據改變時,框架會幫助你「重新更新畫面」。
為了讓這個更新過程高效,框架需要知道哪些元素應該保持不變、哪些需要新增或刪除。
如果不設置
:key
,框架如何處理?
- 當標籤只是顯示字串(如 “影片剪輯” 或 “字幕設計”),這些字串對框架來說只是 表面數據,它不知道字串背後的身份。
- 沒有
:key
,框架會用標籤在數組中的「順序位置」來猜測它的身份。例如:
- 索引
0
對應的是第一個標籤,索引1
對應的是第二個標籤。- 如果數據變動(比如刪除),框架可能會誤判:
- 假如刪掉了第一個標籤,框架可能直接「重新排列」所有標籤,將第二個標籤移動到第一個位置,並嘗試覆蓋它的內容。
這就可能導致畫面閃爍或顯示錯誤,因為框架不知道哪些標籤是真正被刪除的,哪些只是位置變動。
設置
:key
如何解決問題?
:key="index"
或其他唯一值,相當於為每個標籤分配了一個獨特的「身份證」。當數據更新時,框架可以直接根據
key
來判斷每個標籤的真實身份,而不是單純依賴字串或位置。用
:key
的更新邏輯:假設
tags
陣列為:["影片剪輯", "字幕設計"]
,並設置了:key
。
- 初始狀態:
- 標籤 1(
key: 0
):”影片剪輯”。- 標籤 2(
key: 1
):”字幕設計”。- 刪除 “影片剪輯”(
key: 0
):
- 框架根據
key: 0
確認它需要刪除的是第一個標籤。- 同時,
key: 1
對應的標籤(”字幕設計”)保持不變,無需重新渲染。為什麼不能直接用字串當
key
?你可能會問:「為什麼不用字串本身當作
key
呢,比如:key="tag"
?」這其實是可行的,但只有在確保標籤的字串內容是絕對 唯一 並且 不會改變 的情況下才安全。例如:
- 當標籤是
"影片剪輯"
和"字幕設計"
,它們是唯一的,用:key="tag"
是沒問題的。但是如果:
- 標籤的內容可能重複(例如多次輸入同樣的字串);
- 標籤的內容可能被編輯或動態改變;
那麼用字串作為
key
就會導致問題,因為框架會誤判這些重複或變動的標籤是同一個元素,從而造成渲染混亂。
標籤的外觀設計
每個標籤的顯示樣式設計精簡而實用,以下是主要的樣式亮點:
- 背景與文字顏色:
- 背景為紫色(
bg-indigo-500
): 強調標籤,讓其從頁面其他元素中脫穎而出。 - 文字為白色(
text-white
): 與深色背景形成對比,提升可讀性。
- 背景為紫色(
- 圓形邊框(
rounded-full
):- 圓角設計讓標籤看起來更加柔和,模仿「膠囊式」設計風格。
- 提升用戶視覺體驗。
- 字體與間距:
- 字體大小(
text-sm
): 小字體適合顯示標籤,避免佔用過多空間。 - 內邊距(
px-3 py-1
): 保持文字與邊框之間的距離,讓標籤看起來緊湊且易於辨識。
- 字體大小(
- 彈性布局(
flex items-center
):- 使用 Flexbox 將文字與刪除按鈕對齊在同一行。
- 提升排版整齊度,避免文字與按鈕出現錯位。
範例: 單個標籤的設計如下:
<div class="flex items-center bg-indigo-500 text-white text-sm px-3 py-1 rounded-full">
<span>字幕設計</span>
<button> X </button>
</div>
刪除標籤功能
每個標籤旁都設有刪除按鈕,用於從 tags
陣列中移除該標籤。
刪除按鈕代碼:
<button
type="button"
@click="tags.splice(index, 1)"
class="ml-2 py-1 text-white hover:text-red-300 focus:outline-none">
X
</button>
功能解析:
- 點擊事件綁定:
@click="tags.splice(index, 1)"
:tags.splice(index, 1)
是 JavaScript 的原生方法,用於從tags
陣列中移除索引為index
的標籤。1
表示只刪除一個元素(即當前標籤)。
範例: 假設 tags
為 ["影片剪輯", "字幕設計", "動畫設計"]
,當用戶點擊第二個標籤(”字幕設計”)的刪除按鈕時,執行以下操作:
tags.splice(1, 1);
刪除後的 tags
變為:["影片剪輯", "動畫設計"]
。
刪除按鈕樣式設計
刪除按鈕的設計兼顧視覺效果與交互體驗:
- 按鈕顏色:
- 默認狀態下文字為白色(
text-white
),與標籤背景顏色保持一致。 - 滑鼠懸停時變為紅色(
hover:text-red-300
),提示刪除操作。
- 默認狀態下文字為白色(
- 間距與對齊:
ml-2
: 設置刪除按鈕與標籤文字之間的左邊距,避免緊貼標籤內容。py-1
: 增加上下內邊距,讓按鈕點擊範圍更大。
- 去除默認樣式:
focus:outline-none
: 去除按鈕獲得焦點時的默認邊框樣式,保持整潔。
範例: 單個按鈕的設計如下:
<button
type="button"
@click="tags.splice(index, 1)"
class="ml-2 py-1 text-white hover:text-red-300 focus:outline-none">
X
</button>
隱藏輸出以整合後端
隱藏的輸入欄位
隱藏輸入欄位的目的是將標籤數據封裝起來,不直接顯示在前端頁面中,但仍可以被提交到後端進行處理。
代碼:
<input type="hidden" name="tags" :value="tags.join(',')" />
功能解析:
type="hidden"
:- 這個屬性讓輸入框對用戶不可見,但它仍然是表單的一部分,可以在表單提交時攜帶數據到後端。
- 例如,網頁中即使看不到這個欄位,當用戶提交表單時,它的值會自動被包含在請求中。
:value="tags.join(',')"
:tags
是用戶輸入的標籤數據,存儲在 Alpine.js 的陣列變數中(例如:["影片剪輯", "字幕設計"]
)。.join(',')
方法會將這個陣列轉換為一個逗號分隔的字符串:- 範例:
["影片剪輯", "字幕設計"]
→"影片剪輯,字幕設計"
- 範例:
- 將這樣的字符串設置為隱藏輸入欄位的值,方便後端處理。
範例: 假設用戶輸入了以下標籤:
tags = ["影片剪輯", "字幕設計", "動畫設計"];
則隱藏輸入欄位的值會是:
<input type="hidden" name="tags" value="影片剪輯,字幕設計,動畫設計" />
方便後端接收數據
隱藏的輸入欄位通過表單提交時,數據會被以鍵值對的形式傳遞到後端進行處理。
name="tags"
:- 在表單提交時,這個隱藏欄位的值會使用
name="tags"
的名稱傳遞到後端。 - 後端接收到的數據類似於以下格式:
tags=影片剪輯,字幕設計,動畫設計
- 在表單提交時,這個隱藏欄位的值會使用
- 後端處理數據的便利性:
- 由於標籤數據是用逗號分隔的字符串形式,後端可以直接將其轉換為陣列或進行分割處理。
- 用於後端的應用場景:
- 存儲到資料庫: 可以直接將標籤存入資料庫中的一個字段(比如一個
tags
欄位),以逗號分隔的形式存儲。 - 進一步處理: 後端也可以將這些標籤用於分類搜索、推薦內容或其他業務邏輯。
- 存儲到資料庫: 可以直接將標籤存入資料庫中的一個字段(比如一個
結合前端與後端的完整流程
以下是前後端協同工作的完整數據流:
- 用戶在前端頁面中輸入標籤(如:「影片剪輯」、「字幕設計」)。
- 每次輸入完畢後,標籤會動態添加到
tags
陣列中。 tags
陣列被自動轉換為逗號分隔的字符串,設置為隱藏輸入欄位的值。- 當用戶提交表單時,隱藏輸入欄位的值會和其他表單數據一起提交到後端。
- 後端接收
tags
欄位的數據,進行解析或存儲。
結語
透過以上步驟,我們完成了一個功能完整的標籤輸入區域。
本範例結合 Alpine.js 和 Tailwind CSS,讓整個流程簡單高效。如果你是新手,這是學習如何將 JavaScript 框架與現代 CSS 工具結合的一個很好起點。