I'm trying to create a tool where you can create HTML pages using Blockly blocks. I already have a page that shows my workspace and my self-created block. Now I want to write a script that gets the code from the workspace. Normally there is a workspaceToCode
method in the Blockly library. Unfortunately I can't access any Blockly methods or really anything Blockly-related in my index.html.
I've looked up similar projects and can't seem to find any differences. I'm loading blockly_compressed.js
, blocks_compressed.js
and javascript_compressed.js
. And because it shows me a workspace with "functioning" blocks I'm pretty sure that the paths are correct.
See below what I tried and thanks in advance for your help:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="styles.css">
<style>
...
</style>
<script src="node_modules/blockly/blockly_compressed.js"></script>
<script src="node_modules/blockly/blocks_compressed.js"></script>
<script src="node_modules/blockly/msg/en.js"></script>
<script src="node_modules/blockly/javascript_compressed.js"></script>
</head>
<body>
...
<script src="html_blocks.js"></script>
<script src="html_generator.js"></script>
<script src="main.js"></script>
<script>
function update(event) {
var code = HtmlGenerator.workspaceToCode(workspace);
document.getElementById('HTMLCodeDiv').innerText = code;
}
workspace.addChangeListener(update);
</script>
</body>
</html>
The error it is giving is "unresolved function or method" for the workspaceToCode
method as well as the addChangeListener
method.
Since you haven't shown all of your code, I can't provide a precise answer to explain exactly what's gone wrong for you here, but I can say that Blockly in a classical (non-module) script tag adds itself to the window
as follows:
// ...
} else { // Browser
var factoryExports = factory();
root.Blockly = factoryExports;
}
// ...
where root
is window
(by way of this
) and factory()
is the entire Blockly code. All Blockly functions are namespaced inside of the window.Blockly
object, so there is no such window.workspace
variable that would be created unless one of your other scripts (not shown) created this and attached it to the window.
If you open your browser console, you can type Blockly.
and see the list of available properties that were imported by the scripts. The other Blockly scripts simply attach more properties to the global Blockly
object that was created by the first script tag. Blockly.Workspace
and Blockly.workspaceToCode
are some of these properties, and you can call Blockly.inject
to create a workspace.
For example,
const blocklyWorkspace = Blockly.inject("blockly-container", {
toolbox: document.getElementById("toolbox")
});
document.querySelector("button")
.addEventListener("click", e => {
const code = Blockly.JavaScript
.workspaceToCode(blocklyWorkspace);
console.log(code);
});
#blockly-container {
height: 100vh;
}
xml {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/blockly/8.0.0/blockly.min.js" integrity="sha512-m19pjKFpHlhFqUAWB49IQt7ip1P7UDKyV0k0f7UGnN8pXSLFjtvsrRcDlwRw+ZhaNeqQTwHwE9+CJgPAWUyA9Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<button>Workspace to code</button>
<div id="blockly-container"></div>
<xml xmlns="https://developers.google.com/blockly/xml" id="toolbox">
<block type="text_print"></block>
<block type="text"></block>
</xml>
It strikes me as an antipattern to use node_modules
in script tags like this, even though some of the Blockly examples do this. Usually, you'd use a bundler of some sort (webpack, parcel, vite, browserify, rollup, etc) to allow you to dynamically import the code using modules (example below). Or else keep your build without any local dependencies and use a CDN and take advantage of client caching (as shown above). Using node_modules
directly seems like the worst of both worlds, especially without a minification build.
For example, you can use parcel to build your app for the web. A bundler makes it easy to use node_modules
without specifying the paths. You can develop using modules rather than legacy UMD script tags, which help you organize the project into chunks and avoid polluting the window with shared data.
The example below is contrived for clarity, but hopefully you can extrapolate the approach (or something similar) to your project.
index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<style>
#blockly-container {
height: 100vh;
}
xml {
display: none;
}
</style>
</head>
<body>
<div id="blockly-container"></div>
<xml xmlns="https://developers.google.com/blockly/xml" id="toolbox">
<block type="text_print"></block>
<block type="text"></block>
</xml>
<xml xmlns="https://developers.google.com/blockly/xml" id="start-blocks">
<block type="text_print" id="N4+B!H6xh[=wx]z^LqGk" x="38" y="38">
<value name="TEXT">
<shadow type="text" id="~uw6Vr9}hxZS-=a(Zjt{">
<field name="TEXT">hello world</field>
</shadow>
</value>
</block>
</xml>
<script src="src/index.js" type="module"></script>
</body>
</html>
src/index.js
:
import Blockly from "blockly";
import generateCode from "./generate-code";
const blocklyWorkspace = Blockly.inject("blockly-container", {
toolbox: document.getElementById("toolbox")
});
Blockly.Xml.domToWorkspace(
document.getElementById("start-blocks"),
blocklyWorkspace
);
console.log(generateCode(blocklyWorkspace));
src/generate-code.js
:
import Blockly from "blockly";
export default blocklyWorkspace =>
Blockly.JavaScript
.workspaceToCode(blocklyWorkspace);
package.json
:
{
"scripts": {
"start": "node node_modules/parcel/lib/cli index.html"
},
"dependencies": {
"blockly": "^8.0.3"
},
"devDependencies": {
"parcel": "^2.6.2"
}
}
Building and running:
npm i
npm start
Now, navigate to (by default) http://localhost:1234 (or whatever parcel
tells you on the console) and begin developing.