富文本编辑器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下载地址:https://www.tiny.cloud/get-tiny/self-hosted/

其中tinyMCE默认为英文,如需中文需要下载语言扩展包,下载地址:https://www.tiny.cloud/get-tiny/language-packages/ 。将zh_CN.js文件复制到langs文件夹下

tinyMCE集成了绝大多数功能,但是调整行高的功能并没有,我们需要额外下载该插件。忘记之前在哪里找到的了,直接放上行高插件,下载地址:tinyMCE富文本编辑器line-height行高插件
该插件默认是德语,我们可以直接修改plugin.min.js文件源码改为中文提示,找到return{type开头的内容,修改为return{type:"listbox",text:"行间距",tooltip:"行间距",...


初始化tinyMCE

工具栏的配置可以参考这篇文章,介绍的很详细:TinyMCE工具栏配置详解

代码如下:
其中valid_elementsvalid_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置为falsewatch的时候只有flagtrue时才调用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==true){
                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

猜你喜欢

转载自blog.csdn.net/tonywu1992/article/details/82953577