Creating a foreignObject text field

85 views
Skip to first unread message

Jeremiah Saunders

unread,
Aug 28, 2024, 11:20:07 AM8/28/24
to Blockly
I'm trying to create a custom code editor as a field within a block. It would eventually become more than just a span.

I'm having a hard time with sizing and whatnot.

Each piece of text is place on a new line (I assume the span feels squished to 0px width and as such the only reason text is shown is due to overflow)

I'd expect this to work with any element, such as an img.

Also, rather than resizing the borderRect, I think it's better to override isFullBlockField, which says to ask the forum before overriding.

Here is what I've got so far. It's filled with a bunch of hacky workarounds.

import * as Blockly from 'blockly/core';

export class CodeField extends Blockly.Field {
    constructor(value, validator = null) {
        super(value, validator);
        this.SERIALIZABLE = true;
    }

    override initView() {
        this.createBorderRect_();
        this.textElement_ = Blockly.utils.dom.createSvgElement('foreignObject', {}, this.fieldGroup_);
        if (this.getConstants()!.FIELD_TEXT_BASELINE_CENTER) {
            this.textElement_.setAttribute('dominant-baseline', 'central');
        }
        let span = document.createElement('span');
        span.innerText = this.value_;
        this.textElement_.setAttribute('y', '0');
        this.textElement_.appendChild(span);
        setTimeout(() => {
            this.textElement_.setAttribute('width', String(span.offsetWidth));
            this.textElement_.setAttribute('height', String(span.offsetHeight));
            this.borderRect_.setAttribute('height', String(span.offsetHeight));
            this.size_.height = span.offsetHeight;
        }, 0);
    }

    override positionTextElement_(xOffset: number, contentWidth: number) {
        super.positionTextElement_(xOffset, contentWidth);
        this.textElement_.setAttribute('y', '0');
    }

}

Blockly.fieldRegistry.register('field_code', CodeField);

Screenshot_12.png
Thanks all,
Jeremiah

Christopher Allen

unread,
Aug 29, 2024, 11:35:33 AM8/29/24
to blo...@googlegroups.com
Hello Jeremiah,

I'm trying to create a custom code editor as a field within a block. It would eventually become more than just a span.

I'm having a hard time with sizing and whatnot.

Have you looked at the multiline input field plugin?  It has to solve a similar problem to the one you are encountering—though it does not use a foreignObject for the purpose.  The workspace comment view and bubble do use foreignObject but do not attempt to automatically resize the comment to fit the contents.

Also, rather than resizing the borderRect, I think it's better to override isFullBlockField, which says to ask the forum before overriding.

The isFullBlockField method was created so that Blockly (with the Zelos renderer) could support Scratch-style colour blocks, where the entire block would be in the chosen colour, not just a field within the block (as with the Geras and Thrasos renderers).  You could override this (with similar precautions to those taken by FieldColour) if you want the entire block to be the editor, but we would recommend against this, as it would be inconsistent with how most other text input fields look, and would probably only be viable for blocks that had no other fields or inputs (i.e., were "simple reporters" like the existing number and boolean blocks).

A couple of observations about your code:

export class CodeField extends Blockly.Field {

You may wish to consider inheriting from Blockly.FieldInput, which is intended to handle strings (and/or numbers) specifically, rather than Field, which is more general-purpose.

        this.textElement_ = Blockly.utils.dom.createSvgElement('foreignObject', {}, this.fieldGroup_);
        if (this.getConstants()!.FIELD_TEXT_BASELINE_CENTER) {
            this.textElement_.setAttribute('dominant-baseline', 'central');
        }
        let span = document.createElement('span');
        span.innerText = this.value_;
        this.textElement_.setAttribute('y', '0');
        this.textElement_.appendChild(span);
        setTimeout(() => {
            this.textElement_.setAttribute('width', String(span.offsetWidth));
            this.textElement_.setAttribute('height', String(span.offsetHeight));
            this.borderRect_.setAttribute('height', String(span.offsetHeight));
            this.size_.height = span.offsetHeight;
        }, 0);

What is the purpose of the setTimeout call here?  Are you waiting for the span to render so you can measure its width?  I think you will need to ask Blockly to re-render your field after you adjust its size.  We have some documentation about render management that might be helpful here.


Best wishes,

Christopher

Mark Friedman

unread,
Aug 29, 2024, 3:49:20 PM8/29/24
to blo...@googlegroups.com
Jeremiah,

  A couple of years ago I developed a Blockly field for code editing.  It was for use by Gamefroot, a really nice cloud based platform for making 2D games based on Blockly and PhaserJS (among other things).  I've wanted to open source the code for the field and make a Blockly plugin for it, but never got around to it.  Seeing your post inspired me to get going on it. I haven't packaged it as a plugin yet, but I've put the code in a GitHub repo (here, though note that it will likely move to a Gamefroot based repo when it is updated and finally packaged up).  Feel free to use the code for your block, wait for it to become available as a Blockly plugin, or just use the code as a guide for developing your own field.

  A couple of notes about the code.  It is based on an old version of Blockly, so it might need some changes to work with the latest version.  It uses Codemirror for its core functionality.  

  There are also a couple of caveats/bugs.  The combination of Blockly, foreignObject and Codemirror (or maybe a bug with my code) has issues on Safari, so the code currently falls back to a simple multiline field if it detects that it is running in Safari.  Also the field currently also has issues if the Blockly workspace is scaled, so you might want to disable the workspace's zoom option.

  Feel free to ask questions about the code.  It's been awhile since I worked on it but hopefully I can answer them.

  Hope this helps.

-Mark


--
You received this message because you are subscribed to the Google Groups "Blockly" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blockly+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/blockly/77c0d91b-e34e-44b6-ae95-7104f48a85c4n%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages