Michael Bates
Michael Bates

Reputation: 1934

Insert Vue component into CKEditor 5 editable body

TLDR; I want to insert a compiled Vue JS component into the CKEditor editable body, as part of the downcasting process of a plugin.


I have a custom CKEditor image plugin, for a CMS-style site. The user inserts an image by selecting their image from an image picker (outside CKEditor), and then a CKEditor command is executed to insert the image:

const model = this.editor.model;

model.change(writer => {
  const imageElement = writer.createElement('custom-image');
  const insertAtSelection = findOptimalInsertionPosition(model.document.selection, model);
  model.insertContent(imageElement, insertAtSelection);
});

There is a plugin which registers the 'custom-image' element into the schema, inside the plugin's init method:

const schema = this.editor.model.schema;
schema.register('custom-image', {
  isObject: true,
  isBlock: true,
  allowIn: '$root'
});

Then, there is a downcast handler registered (also inside the plugin's init method):

const conversion = this.editor.conversion;
// A similar handler is used for editingDowncast
conversion.for('dataDowncast').elementToElement({
  model: 'custom-image',
  view: (modelElement, viewWriter) => {
    return writer.createContainerElement('div', {
      class: 'custom-image',
    });
  }
});

Now, I have a Vue component, with a reference to the DOM node I would like to insert inside the newly created div.custom-image (ContainerElement).

I've tried creating a handler on the downcastDispatcher, like so:

this.editor.editing.downcastDispatcher.on("insert:custom-image", (evt, data, conversionApi) => {
  const el = ... // Vue component is created here
  const viewElement = conversionApi.mapper.toViewElement(data.item);
  const domNode = view.domConverter.mapViewToDom(viewElement);
  domNode.appendChild(el);
});

The problem is 1) viewElement doesn't seem to have been added to the DOM yet (so mapViewToDom returns undefined), and 2) if I wait for viewElement to be added to the DOM (using setTimeout, a nasty hack), the contents of the Vue component are mostly stripped, because the schema doesn't allow for them.

The Vue component that is being added is quite complex, and talks to the application outside CKEditor. Recreating the component using CKEditor's model would be quite messy. It is also used in the view-side of the application, so it would be nice to be able to reuse the same component as part of the editing and viewing part of the app.

So, what would be the best way to solve this issue?

Upvotes: 0

Views: 1181

Answers (1)

zamber
zamber

Reputation: 938

In short - CKEditor5 was designed to prevent this.

CKEditor 5 implements a custom data model. In order to load data to this model you need to have view -> model converters for each piece of the content that you want your editor to support. Then, you need model -> view converters in order to make this content editable (it needs to be rendered in the editor for editing). Finally, you need to configure the schema and sometimes customize certain features like Enter so they know the meaning of this content that you loaded into the editor and how to modify it.

In other words, because of the data model a feature needs to implement the full life-cycle of a specific piece of content (tag, attribute, etc.) which it handles – from data loading, through rendering for editing, editing itself and data retrieval.

via https://github.com/ckeditor/ckeditor5/issues/705 via https://stackoverflow.com/a/47775840/1225741

I've got a similar use case but with React. In my case I create a custom iframe element to show charts rendered by my webapp but it's not viable in the long run, given that the editor may have dozens of charts loaded. It gets heavy and unusable quite fast.

Currently I'm investigating Web Components as a possible solution for this limitation. AFAIK it Should Work™ but I yet have to code a demo up to confirm this approach.

Did you find a way to solve your issue Michael?

Upvotes: 1

Related Questions