Skip to content

Support injecting Blockly into shadowDom #1114

Closed
Closed

Description

@bamartin-ua

So I'm trying to put blockly in a webcomponent, and I'm getting Uncaught Error: container is not in current document. Looking at the source code, this appears to be a deliberate line in Blockly.inject() that checks to make sure you're not using shadow-dom.

// Verify that the container is in document.
if (!goog.dom.contains(document, container)) {
  throw 'Error: container is not in current document.';
}

what's the reason for this? is there some principled reason we shouldn't be using blockly in a webcomponent?

Activity

  1. rachel-fenichel commented on May 19, 2017

    @rachel-fenichel
    Collaborator

    Here's an old forum discussion from people trying to do the same thing.

  2. bamartin-ua commented on May 19, 2017

    @bamartin-ua
    Author

    @rachel-fenichel it looks like the consensus they came to was "it's impossible just put it in an iframe" which doesn't sound like much of a solution.

  3. rachel-fenichel commented on May 19, 2017

    @rachel-fenichel
    Collaborator

    I linked to the forum post because they discussed specific things that broke when you put Blockly in a shadow DOM.

    First things first though - I've discovered the rendering issue is almost certainly due to Blockly inserting its CSS into the document head, where it has no effect on the contents of a shadowroot.

    While trying to debug my attempt to fix that I've also discovered I can't HTML Import 'blockly_uncompressed.js' as it has a trio of 'document.write' commands at the end which don't work because HTML Imports are async (well this is optional with native support but polymer's polyfill uses xhr anyway). Even if these didn't error they still wouldn't work with blockly in a shadowroot.

    Strangely I don't see this problem with the compressed code - are these statements optimised out by Closure I wonder? In which case how does Blockly get initialised?
    (The answer is yes, using compressed Blockly means it doesn't use document.write.)

    and

    The biggest stumbling block has been trying to get categories to work - unfortunately the Closure TreeControl isn't expecting to be used within a shadowRoot, so I've had to put in a bit of a hack with a DomHelper to work around the limitations. This mostly works, the only thing that doesn't is <hr> separators - I haven't managed to track down why that is yet.

  4. Quincy515 commented on Jul 22, 2017

    @Quincy515

    can you help me? I don't know how to fix my code.
    thank you

    import React from 'react'
    import Blockly from 'node-blockly'
    const toolbox = `
             <xml>
               <block type="controls_if"></block>
               <block type="controls_whileUntil"></block>
             </xml>` 
    class BlocklyDiv extends React.Component {
        componentDidMount() {
            var workspace = Blockly.inject(this.blocklyDiv,{toolbox: toolbox});
        }
        render() {
            return (
                <div>
                    <h2>BlocklyDiv</h2>
                    <div id="blocklyContainer">
                        <div id="blocklyDiv" ref={ref => this.blocklyDiv = ref} ></div>
                    </div>
                </div>
            )
        }
    }
    export default BlocklyDiv
    
  5. BusbyActual commented on May 2, 2018

    @BusbyActual

    I'm experiencing this issue in angularJS as well.

  6. arunsoman commented on May 24, 2018

    @arunsoman

    Same issue

      Blockly.inject @ blockly_compressed.js:1645
      (anonymous) @ VM663:1
      connectedCallback @ blocklyBlock.html:31
      (anonymous) @ blocklyBlock.html:7

    Code below

    window.customElements.define('blockly-block' ,class extends HTMLElement{
    constructor(){
    super();
    const template = document.createElement('template');
    template.innerHTML=<div id="blocklyDiv" style="position: absolute"></div> <xml id="toolbox" style="display: none"> <block type="controls_if"></block> <block type="logic_compare"></block> <block type="controls_repeat_ext"></block> <block type="math_number"> <field name="NUM">123</field> </block> <block type="math_arithmetic"></block> <block type="text"></block> <block type="text_print"></block> </xml>;

    this.attachShadow({mode: 'open'});
    this.shadowRoot.appendChild(template.content.cloneNode(true));
    

    }

    connectedCallback(){
    console.log("connected");
    this.workspace = Blockly.inject(this.shadowRoot.getElementById('blocklyDiv'),{toolbox:this.shadowRoot.getElementById('toolbox')})
    }
    });

  7. 0xAnon101 commented on Nov 25, 2020

    @0xAnon101

    any progress yet on this one ?

  8. abserari commented on Mar 3, 2021

    @abserari
    workspace = {
      const container = document.querySelector('#blockly_div');
      removeAllChildNodes(container);
    
      const workspace = Blockly.inject('blockly_div', {
        media: 'https://unpkg.com/blockly/media/',
        toolbox: defaultToolboxConfiguration(Blockly),
        grid: { spacing: 20, length: 3, colour: '#ccc', snap: true },
        zoom: {
          controls: true,
          wheel: true,
          startScale: 1.0,
          maxScale: 2.0,
          minScale: 1.0,
          scaleSpeed: 0.4
        },
        trashcan: true
      });
    
      return workspace;
    }
  9. letrthang commented on Sep 6, 2021

    @letrthang

    @BeksOmega please put this issue to higher priority. It is more than 4 years old :)

  10. 4 remaining items

  11. jogibear9988 commented on Dec 12, 2023

    @jogibear9988

    I solved it atm. with a hack like this:

       const workspace = Blockly.inject(this.blocklyDiv, { toolbox: toolbox,  /* renderer: 'zelos' */ });
    
        let style1 = document.getElementById('blockly-renderer-style-geras-classic');
        this.shadowRoot.appendChild(style1);
        let style2 = document.getElementById('blockly-common-style');
        this.shadowRoot.appendChild(style2);
    

    means after injecting blockly, I search the document for the styles and move them to my component

  12. jogibear9988 commented on Dec 12, 2023

    @jogibear9988

    I already did a little bit better fix:

        if (!IobrokerWebuiBlocklyScriptEditor.blocklyStyle1) {
            IobrokerWebuiBlocklyScriptEditor.blocklyStyle1 = <HTMLStyleElement>document.getElementById('blockly-renderer-style-zelos-classic');
            this.shadowRoot.appendChild(IobrokerWebuiBlocklyScriptEditor.blocklyStyle1);
            IobrokerWebuiBlocklyScriptEditor.blocklyStyle2 = <HTMLStyleElement>document.getElementById('blockly-common-style');
            this.shadowRoot.appendChild(IobrokerWebuiBlocklyScriptEditor.blocklyStyle2);
        } else {
            this.shadowRoot.appendChild(IobrokerWebuiBlocklyScriptEditor.blocklyStyle1.cloneNode(true));
            this.shadowRoot.appendChild(IobrokerWebuiBlocklyScriptEditor.blocklyStyle2.cloneNode(true));
        }
    

    but there are also problems when using blockly in shadow dom, for example the overlay input for enter text is at the wrong position. There may be more, this is my first day testing this.

    But at least I could get it to render and I could drag/drop items...

    See in the image blockly inside of shadow dom:
    image

  13. jogibear9988 commented on Dec 15, 2023

    @jogibear9988

    There are 2 problems at the moment with blockly in ShadowRoot:

    ToolTips, they are shown at a complete wrong position:
    image

    Input fields for texts, are shown at wrong position, and maybe below the component in the dom:
    image

    (you see the textbox where I enter "lll" is below the element wich hosts the blockly component

  14. BeksOmega commented on Dec 15, 2023

    @BeksOmega
    Contributor

    From @jogibear9988 on #7717 (comment)
    Question:

    I'd like to work on official webcomponent support. But there will be refactorings needed, cause you could have multiple blockly instance with different containers for example for tooltips, ....)

    Is this something wich will be accepted later? What are the opinions about this?

    Maybe look at my draft with already some changes : #7718

    Could you provide some more details about why shadow dom is important to your project? We have a lot of things we are planning to do in Q1 and I'm not sure that we have time to prioritize this :/ (including review). It would be very helpful to have more details about if and where this is blocking you!

  15. jogibear9988 commented on Dec 16, 2023

    @jogibear9988

    ShadowDom is important for me, as my complete UI is based on WebComponents wich use shadowdom.

    At the moment I could solve my usage with this code...

      import { BaseCustomWebComponentConstructorAppend, html, css } from '@node-projects/base-custom-webcomponent';
      import toolbox from './IobrokerWebuiBlocklyToolbox.js'
      
      export class IobrokerWebuiBlocklyScriptEditor extends BaseCustomWebComponentConstructorAppend {
      
          static readonly template = html`
              <div id="blocklyDiv" style="position: absolute; width: 100%; height: 100%;"></div>
          `;
      
          static readonly style = css`
              :host {
                  box-sizing: border-box;
                  position: absolute;
                  height: 100%;
                  width: 100%;
              }`;
      
          static readonly is = 'iobroker-webui-blockly-script-editor';
      
          blocklyDiv: HTMLDivElement;
          workspace: any;
          static blocklyStyle1: CSSStyleSheet;
          static blocklyStyle2: CSSStyleSheet;
          resizeObserver: ResizeObserver;
      
          constructor() {
              super();
              super._restoreCachedInititalValues();
      
              this.blocklyDiv = this._getDomElement<HTMLDivElement>('blocklyDiv');
      
              this.createBlockly();
          }
      
          createBlockly() {
              //@ts-ignore
              this.workspace = Blockly.inject(this.blocklyDiv, {
                  toolbox: toolbox,
                  renderer: 'zelos',
                  trashcan: true,
                  zoom: {
                      controls: true,
                      wheel: false,
                      startScale: 0.7,
                      maxScale: 3,
                      minScale: 0.3,
                      scaleSpeed: 1.2,
                      pinch: false
                  },
                  move: {
                      scrollbars: {
                          horizontal: true,
                          vertical: true
                      },
                      drag: true,
                      wheel: true
                  }
              });
      
              if (!IobrokerWebuiBlocklyScriptEditor.blocklyStyle1) {
                  IobrokerWebuiBlocklyScriptEditor.blocklyStyle1 = new CSSStyleSheet();
                  //@ts-ignore
                  IobrokerWebuiBlocklyScriptEditor.blocklyStyle1.replaceSync(<HTMLStyleElement>document.getElementById('blockly-renderer-style-zelos-classic').innerText);
                  IobrokerWebuiBlocklyScriptEditor.blocklyStyle2 = new CSSStyleSheet();
                  //@ts-ignore
                  IobrokerWebuiBlocklyScriptEditor.blocklyStyle2.replaceSync(<HTMLStyleElement>document.getElementById('blockly-common-style').innerText);
              }
              this.shadowRoot.adoptedStyleSheets = [IobrokerWebuiBlocklyScriptEditor.blocklyStyle1, IobrokerWebuiBlocklyScriptEditor.blocklyStyle2, IobrokerWebuiBlocklyScriptEditor.style];
      
              //@ts-ignore
              const zoomToFit = new ZoomToFitControl(this.workspace);
              zoomToFit.init();
          }
      
          ready() {
              //@ts-ignore
              Blockly.svgResize(this.workspace);
      
              this.resizeObserver = new ResizeObserver((entries) => {
                  //@ts-ignore
                  Blockly.svgResize(this.workspace)
              });
              this.resizeObserver.observe(this);
          }
      
          public save(): any {
              //@ts-ignore
              const state = Blockly.serialization.workspaces.save(this.workspace);
              return state;
          }
      
          public load(data: any) {
              //@ts-ignore
              Blockly.serialization.workspaces.load(data, this.workspace);
          }
      }
      customElements.define(IobrokerWebuiBlocklyScriptEditor.is, IobrokerWebuiBlocklyScriptEditor)
    

    but I feels very hacky for me atm. So it is not so critical as it works . I could work on this, the Blockly Codebase seems very easy to understand, but at first there needs to be a consense how some of the issues could & should be solved (I created a list of the TODOs in the other issue)

  16. BeksOmega commented on Dec 18, 2023

    @BeksOmega
    Contributor

    Hiya @jogibear9988 so it sounds like this isn't strictly blocking you. So we probably won't get to it in the first 3 months of 2024. But I've added it to the triage list so the triage team can get back to you and confirm!

    Really appreciate you offering to work on this! But like you said first there needs to be a consensus, and I don't think we have bandwidth to make those design decisions at the moment.

  17. jogibear9988 commented on Dec 18, 2023

    @jogibear9988

    Yeah, atm. it is workin with the dirty hacks.
    If there is time for discussions, I'm glad to help and discuss whats needed for webcomponents

  18. maribethb commented on Jan 17, 2024

    @maribethb
    Contributor

    Hi @jogibear9988 unfortunately our team doesn't have the cycles right now to look at webcomponents. We love that you're willing to help with this issue though. If you'd like to put together a proposal or other information about what you'd need for webcomponents, that would be great, but we also won't have the cycles to review that kind of proposal right now either. It would be a while before we are able to review any kind of proposal and take action on it. Thanks again for your interest in this issue. For now, it's going back to our backlog.

  19. added theissue type on Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    internalExternal contributions not accepted

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions