使用 vue-tribute 實作標記功能
這篇文章主要說明如何透過 vue-tribute 來實作網頁上的標記功能(@mention),先備知識必須要已經基本會使用 Vue。
開始使用 vue-tribute
vue-tribute @ GitHub
vue-tribute 是將 ES6 Native 的 tribute 經過 Vue.js 的寫法包裝後的套件,方便支援 Vue.js 框架的寫法。
安裝 vue-tribute
我個人是習慣使用 npm 安裝套件。
1npm install vue-tribute --save 2
使用 vue-tribute
我們可以在 vue-tribute 中使用 input
、textarea
、contenteditable
等 HTML 標籤,當我們輸入內容時,vue-tribute 會存取選項 tributeOptions
裡的屬性與方法,呈現出 @mention 後的畫面與功能。
1<vue-tribute :options="tributeOptions"> 2 <input type="text" placeholder="@..." /> 3</vue-tribute> 4
如果想要對輸入框做進一步的處理,像是讓文字變色、轉為連結等功能,可以使用 contenteditable
來完成。
1<vue-tribute :options="tributeOptions"> 2 <div contenteditable="true" id="myElement" placeholder="輸入留言"></div> 3</vue-tribute> 4
參數選項設定
tributeOptions
是 vue-tribute 的參數選項,這個參數選項在 vue-tribute 與 tribute 套件中都是相同的,因為 vue-tribute 的參數設定就是沿用 tribute 的設定。
以下列出一些我自己常用的參數:
values
:唯一的必填參數,就是你要用來搜尋的資料trigger
:觸發條件,可使用符號或字串selectTemplate
:選取後新增到輸入框裡面的內容,如果不是使用contenteditable
元素,這邊就只能寫入純文字哩menuItemTemplate
:標記清單所呈現的組合樣板,以 Twitter 的 @mention 功能來說,就會呈現頭像、名稱、ID 等資料lookup
:通常放字串即可,但若要透過資料裡的多個屬性來搜尋,可以把key
相加來做複合搜尋requireLeadingSpace
:觸發前要有空格allowSpaces
:@mention 的內容允許空格menuItemLimit
:清單最多呈現多少筆篩選結果menuShowMinLength
:觸發前至少要打幾個字
1tributeOptions: { 2 values: [], 3 trigger: '@', 4 selectTemplate(item) { 5 return `<span><a href="http://twitter.com/${item.original.id}">@${item.original.name}</a></span>`; 6 }, 7 menuItemTemplate(item) { 8 return `<span>${item.original.name}</span>`; 9 }, 10 lookup(item) { 11 return item.friend_name + item.friend_suuid; 12 }, 13 requireLeadingSpace: true, 14 allowSpaces: false, 15 menuItemLimit: 10, 16 menuShowMinLength: 1, 17}, 18
這是一個簡易的參數設定範例,若有其他需求可以參考 所有參數 的說明。
處理標記結果
接著,我們要來處理 @mention 得到的結果。
由於 vue-tribute 沒有提供方法來綁定與監聽選取到的項目,所以我們只好從選取後的文字框當中,將標記到的對象給篩選出來。
篩選出所有的標記對象
要擷取使用者這則留言總共標記了哪些人,我們可以直接用正規表達式篩選出 @ 後面的文字,並將結果以陣列傳回後端。
1// 計算留言 (content) 裡面標記了哪些人 2const str = this.content; 3const pattern = /\B@([a-z0-9_-]+)/gi; // 透過正規表達式查找符合規則的字段 4const arr = str.match(pattern); // ['@sean', '@sealman'] 5let result = []; 6if (arr) { 7 result = arr.map((item) => item.substr(1)); // ['sean', 'sealman'] 8} 9
將文字轉換成連結
透過正規表達式,我們也可以將標記文字轉為連結,像是將 @vuejs 這個標記,轉換為 Twitter 使用者的個人頁面連結,像是 http://twitter.com/vuejs。
1// 將 @mention 轉為 twitter.com/mention 帳號的 Link 2const replaceContent = content.replace( 3 /\B@([a-z0-9_-]+)/gi, // 可位於開頭 or 左右有空格,可包含 _ 與 - 符號 4 '<a href="http://twitter.com/$1">@$1</a>', 5); 6
Troubleshooting
根據過往的開發經驗,如果需求比較複雜,或是想要實作出像是留言板等功能較完善的輸入框的話,輸入框可能就會選用 contenteditable
元素。不過 contenteditable
在使用時可能會遇到一些問題,以下就是我自己在實作中遇到過的幾個坑。
Contenteditable 滑鼠游標自動 Focus 至最後方
使用 contenteditable
時,如果有 Focus 游標的需求,無法直接透過一般的 focus()
完成,需要使用到 Selection
與 Range
屬性。
在 Stack Overflow 上 Nico Burns 在 javascript - How to move cursor to end of contenteditable entity - Stack Overflow 這篇問題裡提到的解法,應該是目前可行的解法。
不過若
contenteditable
裡面有放其他元素,在 Node 節點上可能要再做判斷
1function setEndOfContenteditable(contentEditableElement) { 2 var range, selection; 3 if (document.createRange) { 4 range = document.createRange(); 5 range.selectNodeContents(contentEditableElement); 6 range.collapse(false); 7 selection = window.getSelection(); 8 selection.removeAllRanges(); 9 selection.addRange(range); 10 } else if (document.selection) { 11 range = document.body.createTextRange(); 12 range.moveToElementText(contentEditableElement); 13 range.collapse(false); 14 range.select(); 15 } 16} 17
接著,我們透過 setEndOfContenteditable
觸發整個方法,完成將游標移動到最後面的功能。
1const elem = document.querySelector('.input-area'); 2this.setEndOfContenteditable(elem); // 游標移到最後面 3
Contenteditable 複製貼上時限制為純文字
若要防止使用者貼上文字以外的內容,可以加上以下程式碼,防止使用者貼上任何內容。
在 Javascript trick for 'paste as plain text` in execCommand - Stack Overflow 這個問題串的最佳解答,算是比起另一個網路上的熱門答案更簡單、更好理解,且不容易出現 Bug 的寫法。
1editor.addEventListener('paste', function (e) { 2 e.preventDefault(); 3 var text = (e.originalEvent || e).clipboardData.getData('text/plain'); 4 document.execCommand('insertHTML', false, text); 5}); 6
以上資源是我自己整理過後的筆記,若有錯誤歡迎隨時和我聯繫