Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support injecting Blockly into shadowDom #1114

Open
bamartin-ua opened this issue May 19, 2017 · 19 comments
Open

Support injecting Blockly into shadowDom #1114

bamartin-ua opened this issue May 19, 2017 · 19 comments
Labels
internal External contributions not accepted issue: feature request Describes a new feature and why it should be added

Comments

@bamartin-ua
Copy link

bamartin-ua commented May 19, 2017

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?

@rachel-fenichel
Copy link
Collaborator

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

@bamartin-ua
Copy link
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.

@rachel-fenichel
Copy link
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.

@Quincy515
Copy link

Quincy515 commented Jul 22, 2017

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

@BusbyActual
Copy link

I'm experiencing this issue in angularJS as well.

@arunsoman
Copy link

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')})
}
});

@rachel-fenichel rachel-fenichel added the issue: bug Describes why the code or behaviour is wrong label Jun 21, 2018
@0xAnon101
Copy link

any progress yet on this one ?

@abserari
Copy link

abserari commented Mar 3, 2021

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;
}

@letrthang
Copy link

letrthang commented Sep 6, 2021

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

@EmilePerron
Copy link

This issue is indeed a major blocker for every project that uses web components (which are being made more popular by Google's own Lit).

Just removing the validation check in the original post won't fix the issue however. Blockly injects styles and scripts into the document, and these won't work if the editor is within the shadow DOM.

Are there any plans for fixing this issue? Or any avenues of development to help contributors work on a PR?

@pauceano
Copy link

pauceano commented Dec 3, 2021

With a small hack, it looks as if blockly works well in a shadow DOM.
Blockly saves all styles in an array: Blockly.Css.CONTENT
They are 'registered' in the array by the different modules and they are injected in the document when the workspace is injected with the function Blockly.Css.inject() in css.js.
If, before injecting the workspace, you call a function like this one, in this.blocklyStyle you will have all the styles:

getBlocklyCSS() {
var text = Blockly.Css.CONTENT.join('\n');
const pathToMedia = "/media"; // If you are interested in sounds define here the path for media
var mediaPath = pathToMedia.replace(/[\/]$/, '');
this.blocklyStyle = text.replace(/<<>>/g, mediaPath);
};

and in the render() function you can do something like:

return html <style>${this.blocklyStyle}</style>

I am pretty sure that this solution is not optimized at all, but, together with the removal of the check for the blockly container in Blockly.inject makes a non invasive hack. Actually, you can do a new Blockly.inject function and then you do not need to change any code. So it works in the compressed version without any compilation.

@maribethb maribethb changed the title shadow dom causes "Uncaught Error: container is not in current document." Support injecting Blockly into shadowDom Feb 15, 2023
@maribethb maribethb added issue: feature request Describes a new feature and why it should be added internal External contributions not accepted and removed issue: bug Describes why the code or behaviour is wrong type: question labels Feb 15, 2023
@jogibear9988
Copy link

jogibear9988 commented Dec 12, 2023

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

@BeksOmega BeksOmega added the issue: triage Issues awaiting triage by a Blockly team member label Dec 12, 2023
@jogibear9988
Copy link

jogibear9988 commented Dec 12, 2023

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

@jogibear9988
Copy link

jogibear9988 commented Dec 15, 2023

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

@BeksOmega
Copy link
Collaborator

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!

@jogibear9988
Copy link

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)

@BeksOmega
Copy link
Collaborator

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.

@jogibear9988
Copy link

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

@maribethb
Copy link
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.

@maribethb maribethb removed the issue: triage Issues awaiting triage by a Blockly team member label Jan 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
internal External contributions not accepted issue: feature request Describes a new feature and why it should be added
Projects
None yet
Development

No branches or pull requests