Node views with Vue

Introduction

Using Vanilla JavaScript can feel complex if you are used to work in Vue. Good news: You can use regular Vue components in your node views, too. There is just a little bit you need to know, but let’s go through this one by one.

Render a Vue component

Here is what you need to do to render Vue components inside your editor:

  1. Create a node extension
  2. Create a Vue component
  3. Pass that component to the provided VueNodeViewRenderer
  4. Register it with addNodeView()
  5. Configure Tiptap to use your new node extension

This is how your node extension could look like:

import { Node } from '@tiptap/core'
import { VueNodeViewRenderer } from '@tiptap/vue-2'
import Component from './Component.vue'

export default Node.create({
  // configuration …

  addNodeView() {
    return VueNodeViewRenderer(Component)
  },
})

There is a little bit of magic required to make this work. But don’t worry, we provide a wrapper component you can use to get started easily. Don’t forget to add it to your custom Vue component, like shown below:

<template>
  <node-view-wrapper>
    Vue Component
  </node-view-wrapper>
</template>

Got it? Let’s see it in action. Feel free to copy the below example to get started.

That component doesn’t interact with the editor, though. Time to wire it up.

Access node attributes

The VueNodeViewRenderer which you use in your node extension, passes a few very helpful props to your custom Vue component. One of them is the node prop. Add this snippet to your Vue component to directly access the node:

props: {
  node: {
    type: Object,
    required: true,
  },
},

That enables you to access node attributes in your Vue component. Let’s say you have added an attribute named count to your node extension (like we did in the above example) you could access it like this:

this.node.attrs.count

Update node attributes

You can even update node attributes from your node, with the help of the updateAttributes prop passed to your component. Just add this snippet to your component:

props: {
  updateAttributes: {
    type: Function,
    required: true,
  },
},

Pass an object with updated attributes to the function:

this.updateAttributes({
  count: this.node.attrs.count + 1,
})

And yes, all of that is reactive, too. A pretty seamless communication, isn’t it?

Adding a content editable

There is another component called NodeViewContent which helps you adding editable content to your node view. Here is an example:

<template>
  <node-view-wrapper class="dom">
    <node-view-content class="content-dom" />
  </node-view-wrapper>
</template>

<script>
import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2'

export default {
  components: {
    NodeViewWrapper,
    NodeViewContent,
  },
}
</script>

You don’t need to add those class attributes, feel free to remove them or pass other class names. Try it out in the following example:

Keep in mind that this content is rendered by Tiptap. That means you need to tell what kind of content is allowed, for example with content: 'inline*' in your node extension (that’s what we use in the above example).

The NodeViewWrapper and NodeViewContent components render a <div> HTML tag (<span> for inline nodes), but you can change that. For example <node-view-content as="p"> should render a paragraph. One limitation though: That tag must not change during runtime.

All available props

For advanced use cases, we pass a few more props to the component.

editor

The editor instance.

node

Access the current node.

decorations

An array of decorations.

selected

true when there is a NodeSelection at the current node view.

extension

Access to the node extension, for example to get options.

getPos()

Get the document position of the current node.

updateAttributes()

Update attributes of the current node.

deleteNode()

Delete the current node.

Here is the full list of what props you can expect:

<template>
  <node-view-wrapper />
</template>

<script>
import { NodeViewWrapper } from '@tiptap/vue-2'

export default {
  components: {
    NodeViewWrapper,
  },

  props: {
    // the editor instance
    editor: {
      type: Object,
    },

    // the current node
    node: {
      type: Object,
    },

    // an array of decorations
    decorations: {
      type: Array,
    },

    // `true` when there is a `NodeSelection` at the current node view
    selected: {
      type: Boolean,
    },

    // access to the node extension, for example to get options
    extension: {
      type: Object,
    },

    // get the document position of the current node
    getPos: {
      type: Function,
    },

    // update attributes of the current node
    updateAttributes: {
      type: Function,
    },

    // delete the current node
    deleteNode: {
      type: Function,
    },
  },
}
</script>

If you just want to have all (and TypeScript support) you can import all props:

// Vue 3
import { defineComponent } from 'vue'
import { nodeViewProps } from '@tiptap/vue-3'
export default defineComponent({
  props: nodeViewProps,
})

// Vue 2
import Vue from 'vue'
import { nodeViewProps } from '@tiptap/vue-2'
export default Vue.extend({
  props: nodeViewProps,
})

Dragging

To make your node views draggable, set draggable: true in the extension and add data-drag-handle to the DOM element that should function as the drag handle.