富文字編輯器TinyMCE在vue2.x中的使用以及動態繫結(解決雙向繫結後游標跳到最左側問題)
寫在前面
專案重構老管理後臺,使用vue作為前端框架,對於一直使用jsp+jquery開發管理後臺的我還是挺頭疼的… 之後在vue官網學習了一下基礎知識外加向前端同事討教,粗略瞭解了vue的使用,如果文中有錯誤地方請多多包涵。
由於不是專業的前端開發,所以並沒有使用vue-cli
腳手架,只是用的原生vue。
下面是最終效果圖:
引入vue和tinyMCE
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
tinyMCE集成了絕大多數功能,但是調整行高的功能並沒有,我們需要額外下載該外掛。忘記之前在哪裡找到的了,直接放上行高外掛,下載地址:
該外掛預設是德語,我們可以直接修改plugin.min.js
檔案原始碼改為中文提示,找到return{type
開頭的內容,修改為return{type:"listbox",text:"行間距",tooltip:"行間距",...
初始化tinyMCE
工具欄的配置可以參考這篇文章,介紹的很詳細:TinyMCE工具欄配置詳解
程式碼如下:
其中valid_elements
、valid_style
是將合法標籤以及屬性加入白名單的,我這裡寫的是根據策劃要求來的,因人而異。官方文件說明:valid_elements,valid_style
tinymce. init({
selector: '#mytextarea',
language: 'zh_CN',
menubar: false,
valid_elements :"p[style],span[style],ul,ol,li,strong/b,em,h1,h2,h3,h4,h5,h6",
valid_style:{
"*":"color,font_size,text-align,line-height"
},
plugins: "code,textcolor,lists,lineheight,fullscreen",
toolbar: [
'undo redo | formatselect | bold italic forecolor fontsizeselect | alignleft aligncenter alignright | bullist numlist lineheightselect | code fullscreen | removeformat '
]
});
之後在html裡引入這個textarea就可以了
其中通過tinyMCE.activeEditor.getContent()
以及tinyMCE.activeEditor.setContent(val)
可以手動獲取、設定tinyMCE的值
<div><textarea id="mytextarea" v-model="model.content"></textarea></div>
其中需要注意的小細節:toolbar
中的|
是用來分割工具欄的,|
和工具欄的名稱中間必須有空格
,否則無法識別該按鈕。
<textarea>
外面一定要用<div>
套一層,否則無法正常顯示
元件化tinyMCE並設定雙向繫結
為了方便使用,我們使用vue component將tinyMCE元件化。同時為了滿足實時監控富文字內容的需求,不能採用最後在ajax方法中手動get/set內容的策略了,需要用v-model實時雙向繫結。
定義傳入的欄位props: ['value']
,用來接收父元件的值,當編輯器檢測到有變化時呼叫$emit(‘input’, value)將資料寫出到父元件
具體程式碼如下:
Vue.component('tinymce', {
props: ['value'],
watch:{
value(val){
tinyMCE.activeEditor.setContent(val);
}
},
mounted: function(){
var component = this;
tinymce.init({
target: this.$el.children[0],
language: "zh_CN",
menubar: false,
branding: false,
valid_elements: "p[style],span[style],ul,ol,li,strong/b,em,h1,h2,h3,h4,h5,h6",
valid_style: {
"*":"color,font_size,text-align,line-height"
},
plugins: "code,textcolor,lists,lineheight,fullscreen",
toolbar: [
'undo redo | formatselect | bold italic forecolor fontsizeselect | alignleft aligncenter alignright | bullist numlist lineheightselect | code fullscreen | removeformat '
],
setup: function(editor) {
editor.on('input change undo redo execCommand KeyUp', function(e) {
component.$emit('input', editor.getContent());
})
}
});
},
template: `<div><textarea style="height:300px" v-model="value"></textarea></div>`
});
解決游標移動問題(10.19更新,解決中文輸入法無法輸入問題)
雖然我們已經做到了tinyMCE展示並且可以雙向繫結資料,但是輸入的時候會發現游標一直回到最左側。這是因為我們輸入文字時觸發$emit('input', editor.getContent())
修改了value,這樣就又會進入watch
,從而再次呼叫tinyMCE.activeEditor.setContent(val)
。這裡我參考了前端手札——vue元件vue-tinymce開發經驗分享這篇文章,文中採用了這裡解決辦法是當前編輯不讓觸發editor.seContent()就不會導致游標更新
。作者定義了一個status
用來記錄不同狀態,但是我這裡測試後不可以-.-,同時作者還提出了一種思路:(當然還有其他方法,比如記錄游標位置)
。於是我就按照這種思路去解決游標跳動問題,感覺更簡單一些,供大家參考。
我們查閱tinyMCE的api文件,發現有一個BookmarkManager,它可以記錄位置並移動。
getBookmark(type:Number, normalized:Boolean):Object
Returns a bookmark location for the current selection. This bookmark object can then be used to restore the selection after some content modification to the document.
moveToBookmark(bookmark:Object):Boolean
Restores the selection to the specified bookmark.
其中getBookmark
的兩個引數官方文件中並沒有具體說明,但是經過試驗,第一個引數必須使用2
才生效。其實也是google tinymce cursor jump 然後在stackoverflow上偶爾看見的,大家有興趣可以具體瞭解下Preserve caret/bookmark position in tiny while using setContent
var bm=tinyMCE.activeEditor.selection.getBookmark(2);
tinyMCE.activeEditor.setContent(val);
tinyMCE.activeEditor.selection.moveToBookmark(bm);
這樣整個tinyMCE的構建以及資料雙向繫結就完成了,最後在需要使用tinyMCE的頁面呼叫該元件就可以了。
10.19更新
之前考慮的採用bookmark防止游標移動策略還是會有問題,因為每次修改編輯器的值其實還是會觸發watch
,從而呼叫tinyMCE.activeEditor.setContent(val)
,我只是在前後記錄、移動了游標位置,保證輸入時游標不會跳到最左側。但在chrome瀏覽器下,輸入中文時拼音會先填充在文字框裡,如下圖:
這樣就會觸發input
從而觸發watch
裡的tinyMCE.activeEditor.setContent(val)
,導致輸入法消失。所以這樣來看,我們在編輯器裡操作時是就是不應該去觸發tinyMCE.activeEditor.setContent(val)
!
我最終的做法是加入一個flag
標誌,在編輯器觸發input undo redo execCommand
事件時將flag
置為false
,watch
的時候只有flag
為true
時才呼叫tinyMCE.activeEditor.setContent(val)
,並且最終都置回true
ps:後來決定不監聽change
是因為change
事件需要失去焦點並且文字變化時觸發,這樣會導致先觸發input
再觸發change
,而由於資料已經在input
事件中修改,所以change
不會呼叫watch
重新把flag
置為true
,所以這時候的flag
就定格在了false
。
這個方法應該也不是太好,但是我不能在這一個功能上再耗太多時間了,就先這樣處理了,基本滿足要求了。
完整程式碼
- tinyMCE元件js
Vue.component('tinymce', {
props: ['value'],
data(){
return{
flag:true
}
},
watch:{
value(val){
if(this.flag){
tinyMCE.activeEditor.setContent(val);
}
this.flag=true;
}
},
mounted: function(){
var component = this;
tinymce.init({
target: this.$el.children[0],
language: "zh_CN",
menubar: false,
branding: false,
valid_elements: "p[style],span[style],ul,ol,li,strong/b,em,h1,h2,h3,h4,h5,h6",
valid_style: {
"*":"color,font_size,text-align,line-height"
},
plugins: "code,textcolor,lists,lineheight,fullscreen",
toolbar: [
'undo redo | formatselect | bold italic forecolor fontsizeselect | alignleft aligncenter alignright | bullist numlist lineheightselect | code fullscreen | removeformat '
],
setup: function(editor) {
editor.on('input undo redo execCommand', function(e) {
component.flag=false;
component.$emit('input', editor.getContent());
})
}
});
},
template: `<div><textarea style="height:300px" v-model="value"></textarea></div>`
});
- 需要引入tinyMCE的頁面
......
<body>
<div id="test">
<tinymce v-model="content"></tinymce>
</div>
<!--vue-->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!--下載的tinyMCE-->
<script src="../plugins/tinymce/tinymce.min.js"></script>
<!--tinyMCE元件js-->
<script src="../js/tinymceTemplate.js"></script>
<script>
new Vue({
el:"#test",
data(){
return{
content:""
}
},
mounted:function(){
//TODO
},
methods:{
//TODO
}
});
</script>
</body>
寫在最後
ok 大功告成!
對於一個後端開發接觸vue以及一些外掛的使用還是很有必要的,用了才發現比以前用jsp jquery方便多了,真香!不過畢竟還是小白,用的也是非常的基礎,還需不斷努力。
ps:使用vue時發現有一款整合在chrome上的vue開發外掛,可以實時監測vue中的資料,並且可以手動修改,很方便。附上鍊接:https://github.com/vuejs/vue-devtools