Wrapping Quill Editor in a Vue Component

Quill is one of the best WYSIWYG editors today. By integrating it with Vue, we can make it even more powerful. Let’s take a look, how to wrap a Quill editor in a Vue component!

Getting Started

Our goal, to make a component called <editor> and bind the v-model functionality on it. We need this if we want to integrate our component easily with forms. But first things first, let’s create the component:

// Editor.vue

<script>
    export default {
        props: {
            value: {
                type: String,
                default: ''
            }
        },
 
        data() {
            return {
                editor: null
            };
        }
    }
</script>

<template>
    <div ref="editor" v-html="value"></div>
</template>
Note, we need the value prop for handling any initial value from the v-model binding.

We can register our component globally:

Vue.component('editor', require('./components/Editor'));

Now we can reference the component with the <editor> tag. Let’s move on and add Quill to the component.

Installing and Importing Quill

We can install Quill via npm. Just run npm install quill to install it to your project. After it’s done, we can import it at the top of our component.

<script>
    import Quill from 'quill';

    export default {
        // ...
    }
</script>
Don’t forget to pull in the CSS as well!

Initializing Quill and Defining Events

The best place to initialize a new Quill instance (at least for us in this case) is Vue’s mounted method. For initializing Quill, we need to pass the configuration to the constructor. For now, we can keep it very simple, but you can extend and modify it as you need.

Also, it’s good to know Quill is using the contenteditable attribute, which means we can’t use the usual input or change events in the usual way. But Quill has a built-in event that we can listen for, but we need to add the listener in the mounted method as well.

As a final touch, it’s a nice place to add any default that value comes from the v-model. Here we can set the initial content of the editor using the value property.

mounted() {
    this.editor = new Quill(this.$refs.editor, {
        modules: {
            toolbar: [
                [{ header: [1, 2, 3, 4, false] }],
                ['bold', 'italic', 'underline']
            ]
        },
        theme: 'bubble',
        formats: ['bold', 'underline', 'header', 'italic']
    });

    this.editor.root.innerHTML = this.value;

    // We will add the update event here
    this.editor.on('text-change', () => {});
}
We are passing a Vue reference to the Quill constructor because we can omit any classes or IDs. This way we can have multiple components without any conflict.

We have the Quill instance now, and everything works the way we want. Now move on and take a look at the update method, where we make sure v-model will work well with the component.

Integrating v-model with the Component

It’s up to you if you want to use the full HTML of the content or just the text. Mostly, the HTML way is the one. But let’s take a look at both. We will wrap the logic in the update method, but first just let’s see the code.

// Only text handling
this.$emit('input', this.editor.getText());

By default, if the editor has no content, Quill adds some HTML to the editor: <p><br></p>. Now, imagine if we have some validating at the back-end and we want to provide some error message if the input is empty. Since we want to send the HTML with the form, it never will be empty because of Quill. So, maybe we won’t have any text, but there will be some HTML all the time.

To fix this, we need to check first if there is any content or not, then emit the data we want:

// HTML handling as well
this.$emit('input', this.editor.getText() ? this.editor.root.innerHTML : '');

Wrap It Up

Finally, let’s put the pieces together and see the full component:

<script>
    import Quill from 'quill';

    export default {
        props: {
            value: {
                type: String,
                default: ''
            }
        }, 

        data() {
            return {
                editor: null
            };
        },
        mounted() {
            this.editor = new Quill(this.$refs.editor, {
                modules: {
                    toolbar: [
                        [{ header: [1, 2, 3, 4, false] }],
                        ['bold', 'italic', 'underline']
                    ]
                },
                theme: 'bubble',
                formats: ['bold', 'underline', 'header', 'italic']
            });

            this.editor.root.innerHTML = this.value;

            this.editor.on('text-change', () => this.update());
        },

        methods: {
            update() {
                this.$emit('input', this.editor.getText() ? this.editor.root.innerHTML : '');
            }
        }
    }
</script>

<template>
    <div ref="editor"></editor>
</template>

Usage and Final Notes

We can use the component like it would be input. Also, it handles the default value comes from the model we bound to the component. Simply we can use this markup, and it’s ready to go:

<editor v-model="model"></editor>

We can see it’s a very simple component, but yet powerful. If you want to extend with any functionality, you can easily do it. Also, you can use Quill’s nice and fluent API, that can be a big help.

You can find Quill’s documentation at this link: https://quilljs.com/docs/quickstart/. It provides some excellent examples of its flexibility and clean API.

Special thanks for the following recource(s): Icon made by Freepik from www.flaticon.com