question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Unable to use inside Shadow Dom and Webcomponents

See original GitHub issue

We wanted to use EditorJS with customElements (we use lit-html internally). Our reasons behind this is because we wanted to isolate the editor styles away from the main dom so that our CMS styles don’t make the editor content look strange. This way we can also customize the CSS nicely so that the end user looks at the formatting and text with about the same styles as they would on the frontend.

We previously used a Iframe for this (which still works), but we would like to avoid that if possible.

With the editor in the customElement, we get errors like these: editorjs_shadowdom

Here is the code that creates the customElement:

import '@editorjs/editorjs/dist/editor.js';
import {LitElement, html, css} from 'lit-element';


class HTMLEngine {
  constructor(inputEl, templateSelector, editorConfig) {
    this.inputEl = inputEl;
    this.editorConfig = editorConfig;
  }

  read() {
    let doc = new DOMParser().parseFromString(this.inputEl.value, 'text/html');
    var dataTemplate = doc.querySelector('[data-editor-data]');
    if (dataTemplate) {
      var jsonData = dataTemplate.dataset.editorData;
      return JSON.parse(jsonData);
    }
    return null;
  }

  write(outputData) {
    var editorData = JSON.stringify(outputData);
    var renderedBlocks = '';
    outputData.blocks.forEach((block) => {
      renderedBlocks += this.renderBlock(block);
    });
    this.inputEl.value = `<template data-editor-data='${editorData}'></template>
      ${renderedBlocks}`;
  }

  renderBlock(block) {
    return this.editorConfig.tools[block.type].HTMLGenerator(block.data) + '\n';
  }
}


class OstinatoEditorWidget extends LitElement {
  static get properties() {
    return {
      saveTo: { type: String },
      editorConfig: { type: String },
      editorFramePath: { type: String },
    }
  }

  constructor() {
    super();
    this.saveTo = '';
    this.editorConfig = '';
    this.editorFramePath = '';
  }

  connectedCallback() {
    super.connectedCallback();
    this.saveToEl = document.querySelector(this.saveTo);
    // Now import our editor config.
    import(this.editorConfig).then((m) => {
      this.config = m.editorConfig;
      this.engine = new HTMLEngine(
        this.saveToEl,
        '[editorjs-data]',
        this.config);
      this.initEditor();
    });
  }

  initEditor() {
    this.config.data = this.engine.read();
    this.config.holder = this.shadowRoot.getElementById('editor');

    this.config.onChange = function() {
      this.editor.save().then((outputData) => {
        if (outputData) this.engine.write(outputData);
      });
    }.bind(this);

    console.log(this.config);

    this.editor = new EditorJS(this.config);
  }

  static get styles() {
    return css`
      :host {
        display: block;
        width: 100%;
      }

      #editor {
        width: 100%;
        box-shadow: 0 0 1px 2px #e3e3e3;
      }
    `;
  }

  render() {
    return html`
      <div id="editor"></div>
    `;
  }
}

customElements.define('ostinato-editor-widget', OstinatoEditorWidget);

And this is how we are using the element:

{% load staticfiles %}

<textarea name="{{ widget.name }}"
  {% include "django/forms/widgets/attrs.html" %}
  style="display: none;">{% if widget.value %}{{ widget.value }}{% endif %}</textarea>

<ostinato-editor-widget
  saveTo='[name="{{ widget.name }}"]'
  editorConfig="{{ config_module }}">
</ostinato-editor-widget>

Edit: Oh and here is the config that is used.

export const editorConfig = {
  initialBlock: "paragraph",
  minHeight: "400px",
  autoFocus: true,

  tools: {
    header: {
      class: Header,
      config: {
        placeholder: 'Header Text'
      },
      shortcut: 'CMD+SHIFT+H',
      HTMLGenerator: (data) => `<h${data.level}>${data.text}</h${data.level}>`,
    },

    paragraph: {
      class: Paragraph,
      shortcut: 'CMD+SHIFT+P',
      HTMLGenerator: (data) => `<p>${data.text}</p>`,
    },

    list: {
      class: List,
      inlineToolbar: true,
      shortcut: 'CMD+SHIFT+L',
      HTMLGenerator: (data) => {
        let tagname = data.style.charAt(0) + 'l';
        var renderItem = (item) => { return `<li>${item}</li>`; }
        var items = '';
        data.items.forEach((item) => { items += renderItem(item); });
        return `<${tagname}>${items}</${tagname}>`;
      },
    },

    quote: {
      class: Quote,
      inlineToolbar: true,
      config: {
        quotePlaceholder: 'Enter a quote',
        captionPlaceholder: 'Caption or Author',
      },
      shortcut: 'CMD+SHIFT+O',
      HTMLGenerator: (data) => {
        return `<blockquote style="quote-${data.alignment}">
          <p class="quote-text">${data.text}</p>
          <p class="quote-caption">${data.caption}</p>
        </blockquote>`;
      },
    },

    warning: {
      class: Warning,
      inlineToolbar: true,
      shortcut: 'CMD+SHIFT+W',
      config: {
        titlePlaceholder: 'Warning Title',
        messagePlaceholder: 'Warning Message',
      },
      HTMLGenerator: (data) => {
        return `<div class="warning">
            <p class="warning-title">${data.title}</p>
            <p class="warning-message">${data.message}</p>
          </div>`;
      },
    },

    marker: {
      class:  Marker,
      shortcut: 'CMD+SHIFT+M'
    },

    code: {
      class:  CodeTool,
      shortcut: 'CMD+SHIFT+C',
      HTMLGenerator: (data) => { return `<code>${data.code}</code>`; }
    },

    delimiter: {
      class: Delimiter,
      HTMLGenerator: () => { return `<div class="ce-delimiter"></div>` }
    },

    inlineCode: {
      class: InlineCode,
      shortcut: 'CMD+SHIFT+C'
    },

    table: {
      class: Table,
      inlineToolbar: true,
      shortcut: 'CMD+ALT+T',
      HTMLGenerator: (data) => {
        var rows = '';
        data.content.forEach((row) => {
          var cells = '';
          row.forEach((cell) => { cells += '<td>' + cell + '</td>'; })
          rows += '<tr>' + cells + '</tr>';
        });
        return '<table>' + rows + '</table>';
      }
    },
  },
};

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:5
  • Comments:13 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
aletorradocommented, Apr 6, 2021

Any update on this?

Btw, it’s specially annoying that plugins doesn’t use any encapsulation nor namespacing for class names, so it’s very easy to mess up. Now that Shadow Dom is widely supported across all major browsers, it would be awesome to embrace it for editor.js internals as well!

1reaction
andrewebdevcommented, Jan 11, 2020

@gohabereg ok so it seems id didn’t just have anything to do with the tools. I attempted to use it without specifying any tools in my config, just to test. And the editor still doesn’t load.

editorjs_shadowdom2

Read more comments on GitHub >

github_iconTop Results From Across the Web

Unable to use inside Shadow Dom and Webcomponents #1881
I found an easy workaround to be able to use editorJS inside a web component. When designing your web component with your favorite...
Read more >
Using shadow DOM - Web Components | MDN
Using shadow DOM. An important aspect of web components is encapsulation — being able to keep the markup structure, style, and behavior ...
Read more >
Not able use Bootstrap within Shadow Root of Custom Elements
I am new to web components and shadow root. I am not sure whether using bootstrap inside the shadow root is good approach...
Read more >
Working With Web Components (Shadow DOM) - HeadSpin
Web Components help encapsulate styling and functionality for particular components, enabling sharing and reusing them within and across ...
Read more >
Shadow DOM v1 - Self-Contained Web Components
Shadow DOM composes different DOM trees together using the <slot> element. Slots are placeholders inside your component that users can fill with ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found