IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    前端手札——vue组件vue-tinymce开发经验分享

    帕奇式发表于 2017-06-01 17:10:02
    love 0

    唠叨

    最近公司在开发一个社交管理后台,看一遍线框图后发现需要富文本编辑器我便找会上两年开发的vue-tinymce组件,可惜的是组件支持还是vue1,所以这个组件需要升级支持vue2。然后有朋友问我为何不用现有的?因为看一圈回来发觉比较不靠谱的啊,全部都需要赋予id值(明明可以内部处理的为何要外部传入?),实在看不下去结果还是完善自己写的这个没多少收藏的库吧:)

    关于 vue-tinymce

    vue-tinymce 只是基于tinymce封装的vue组件,让用vue的同学能快速使用tinymce富文本编辑器。

    过程

    从tinymce开始

    接下来分享一些开发过程中的一些问题,首先要学会初次化,我们先来看看tinymce的官方例子:

    <!DOCTYPE html>
    <html>
    <head>
      <script src="https://cloud.tinymce.com/stable/tinymce.min.js"></script>
    </head>
    <body>
      <textarea>Next, get a free TinyMCE Cloud API key!</textarea>
      <script>
        tinymce.init({
            selector:'textarea'
            //or
            // target: document.querySelector('textarea')
        });
      </script>
    </body>
    </html>

    能看出tinymce需要引入全局才能使用,就没其他方式了?于是我找了一下npmjs.org有的有的,可以用import引入。

    于是不用想立马写个例子试试

    # index.html
    <!DOCTYPE html>
    <html>
    <body>
      <textarea>Next, get a free TinyMCE Cloud API key!</textarea>
      <script src="dist/main.js"></script>
    </body>
    </html>
    
    # main.js
    import tinymce form 'tinymce';
    tinymce.init({
        selector:'textarea'
        //or
        // target: document.querySelector('textarea')
    });

    结果发现tinymce是加载出来了,但是样式和图标那些没了...好吧不折腾还是直接引入吧目前来说问题不大。(看看其他库都是直接引入,我不折腾算是对了)

    好了,第一步完成了,接下来第二步是获得/设定富文本内容,来看看以下代码:

    # main.js
    tinymce.init({
        selector: 'textarea',
        // 获得editor,当有多个textarea实例时会多次调用setup
        setup: (editor)=> {
            // 初次化编辑器
            editor.on('init', ()=>{
                // 设置默认值
                editor.setContent('<p>Default Value!</p>');
                // 注册事件
                editor.on('input change undo redo', ()=>{
                    // 获得编辑结果
                    console.log(editor.getContent());
                });
            });
        }
    })

    上面这段是已总结怎样获得或设置富文本内容,tinymce知道怎样用就能开始写vue组件。

    需要怎样的vue组件

    作为组件配置当然可以自己设定的固需要setting的传入,可能也需要在初次化动手再自定义一些功能所以加上setup,再来是获得editor进行处理一些富文本数据。起步代码是这样的:

    <template>
        <textarea :id="id"></textarea>
    </template>
    <script>
    export default {
        props: ['setting','setup', 'value'],
        data(){ return {id:'tinymce', editor:null}; }
        mounted(){
            const setting = {
                ...this.setting,
                {
                    selector: `#${this.id}`,
                    setup: (editor)=> {
                        this.setup(editor);
                        this.editor = editor;
                        editor.on('init', ()=>{
                            editor.setContent(this.value);
                            editor.on('input change undo redo', ()=>{
                                this.value = editor.getContent();
                            });
                        });
                    }
                }
            };
            tinymce.init(setting);
        },
        beforeDestroy: function(){
            tinymce.remove(this.id);
        }
    }
    </script>

    自管理id

    对比其他vue-tinymce组件都要传入id我感到很不解,因为根本没这个必要,所以接下来先解决id自管理问题。

    export default {
        ...
        // 这里我用render写在template绑定:id一样可以
        render(createElement){
            return createElement('textarea', {
                attrs: {
                    id: this.id
                }
            });
        },
        data(){
            return {
                //生成id
                id: 'vue-tinymce-'+Date.now(),
            }
        }
        ...
    }

    支持v-model双向绑定

    这个简单,只要传入字段(props)包含value,使用v-model就能从value获得绑定数据,然后当富文本编辑器数据跟新时使用$emit('input', value)方法便能通知变化跟新value。

    export default {
        ...
        watchs:{
            value(val){
                // 当传入值变化时跟新富文本内容
                tinymce.get(this.id).setContent(val);
            }
        },
        mounted(){
            const setting = {
                ...this.setting,
                {
                    selector: `#${this.id}`,
                    setup: (editor)=> {
                        this.setup(editor);
                        this.editor = editor;
                        editor.on('init', ()=>{
                            editor.setContent(this.value);
                            editor.on('input change undo redo', ()=>{
                                this.$emit('input', editor.getContent());
                            });
                        });
                    }
                }
            };
            tinymce.init(setting);
        }
        ...
    }

    到这里将近完成了,可惜这次问题静静地出现了:输入一个字光标就刷到最前面。接下来得解决这问题,思路我猜应该是editor的input事件触发$emit('input')然后进入watch调用了editor.setContent()方法后导致光标更新,这里解决办法是当前编辑不让触发editor.seContent()就不会导致光标更新(当然还有其他方法,比如记录光标位置)。

    const INIT = 0;
    const INPUT = 1;
    const CHANGED = 2;
    
    export default {
        ...
        watchs:{
            value(val){
                // 只在外部引起变化时才跟新编辑器
                if(this.status === CHANGED || selt.status === INIT) return this.status = INPUT;
                tinymce.get(this.id).setContent(val);
            }
        },
        mounted(){
            const setting = {
                ...this.setting,
                {
                    selector: `#${this.id}`,
                    setup: (editor)=> {
                        this.setup(editor);
                        this.editor = editor;
                        editor.on('init', ()=>{
                            editor.setContent(this.value);
                            editor.on('input change undo redo', ()=>{
                                // 只在用户输入导致事件相应时才更新value数据
                                if(this.status === INPUT || this.status === INIT) return this.status = CHANGED;
                                this.$emit('input', editor.getContent());
                            });
                        });
                    }
                }
            };
            tinymce.init(setting);
        }
        ...
    }

    当value从外部更新时才更新编辑器内容,编辑器触发的内容更新并不需要绕一圈回来再更新编辑器,这样便能解决光标问题。

    结果

    就在这vue-tinymce,一些细节不补充,建议看源码。以下是使用方法:

    安装

    $ npm i -D lpreterite/vue-tinymce

    使用

    # index.html
    <div id="app">
      <vue-tinymce
        ref="tinymce"
        v-model="content"
        :setting="setting">
      </vue-tinymce>
    </div>
    <!-- in last -->
    <script src="node_modules/tinymce/tinymce.min.js"></script>
    
    # main.js
    import Vue from 'vue';
    import VueTinymce from 'vue-tinymce.vue';
    
    new Vue({
        el: '#app',
        data: function(){
            return {
                content: '<p>html content</p>',
                setting: {
                    height: 200,
                    language_url: "langs/zh_CN.js",
                    block_formats: "Paragraph=p;Heading 1=h1;Heading 2=h2;Heading 3=h3;Heading 4=h4;Heading 5=h5;Heading 6=h6;"
                }
            }
        }
    })

    目录结构

    dist/
    - index.html
    - main.js
    - lang/
        -zh_CN.js
    node_modules/
    - tinymce/

    遇到的问题

    刚开始想写vue2组件我跑了一圈github也没发现比较好的例子,构建工具及配置直接能用的并没有,参考的倒是找到一些。webpack配置算是个麻烦事,想尽量简化工作就得动动脑子。

    vue-cli是个好东西,能帮你快速创建项目,如想创建vue的单页项目可以这样使用:

    $ vue init webpack-simple my-product

    可是没见到有快速创建vue组件的项目,所以这里我写了一个lpreterite/vue-component-project提供给大家使用。

    使用方法:

    $ vue init lpreterite/vue-component-project my-vue-component
    

    一路回车之后会提示

       vue-cli · Generated "my-vue-component".
    
       To get started:
       
         cd my-vue-component
         npm install
         npm run dev 
         npm run hot.

    项目就这样创建好来👌,剩下交给你们发挥。

    这遍文章算是把手上这个大项目的副产品吧 :) 。希望日后有点时间继续分享其他在经验及一些大项目下的组件,欢迎评论和PR!



沪ICP备19023445号-2号
友情链接