單元 24 · Blockly 應用整合與序列化

toolbox 深度、Blockly.inject、JSON/XML、執行生成碼

24.1 把 Blockly 嵌進你的頁面

載入 Blockly定義 toolboxBlockly.inject生成/執行
最小整合
<div id="blocklyDiv" style="height: 480px; width: 600px;"></div>
<script>
  const toolbox = {kind: 'categoryToolbox', contents: [
    {kind: 'category', name: '控制', colour: '120', contents: [
      {kind: 'block', type: 'controls_if'},
      {kind: 'block', type: 'controls_repeat_ext'}
    ]},
    {kind: 'category', name: '變數', colour: '330',
     custom: 'VARIABLE'}
  ]};

  const workspace = Blockly.inject('blocklyDiv', {
    toolbox: toolbox,
    grid: {spacing: 20, length: 3},
    zoom: {controls: true, wheel: true},
    trashcan: true
  });
</script>
inject 的回傳值就是 workspace:之後生成程式碼、序列化、監聽事件都靠它。

24.2 toolbox JSON 深度

元件kind用途
工具箱根categoryToolbox含多個分類
分類category一組積木(可設 name/colour)
積木block單一積木(type + fields 預設值)
分隔線sep分類間分隔
動態分類custom: 'VARIABLE'變數/函式等動態清單
帶預設值的積木
{kind: 'block', type: 'alarm_when',
 fields: {SENSOR: 'temperature', THRESHOLD: 40}}
// 拖出來就是「temperature > 40」的預設狀態

24.3 序列化:儲存與載入

JSON(新 API,推薦)
import * as Blockly from 'blockly/core';
import {save, load} from 'blockly/serialization/workspaces';

// 儲存
const state = save(workspace);
localStorage.setItem('myWorkspace', JSON.stringify(state));

// 載入
const loaded = JSON.parse(localStorage.getItem('myWorkspace'));
load(loaded, workspace);
XML(傳統,仍相容)
import {workspaceToDom, domToWorkspace} from 'blockly/xml';

const xml = Blockly.Xml.workspaceToDom(workspace);
const text = Blockly.Xml.domToText(xml);   // 儲存成字串
// 載入:
const dom = Blockly.Xml.textToDom(text);
Blockly.Xml.domToWorkspace(dom, workspace);
選哪個:新專案建議用 JSON(`blockly/serialization/workspaces`)——結構化、未來支援佳;XML 為相容舊版而存在。

24.4 生成並執行程式碼

執行策略
// 1. 生成
const code = Blockly.JavaScript.workspaceToCode(workspace);

// 2. 執行(瀏覽器,注意安全)
try { new Function(code)(); }
catch (e) { console.error(e); }

// 3. 或輸出給使用者複製
document.getElementById('output').textContent = code;

// 4. 或送後端/硬體(POST code 字串)
fetch('/api/run', {method: 'POST', body: code});
安全提醒:`eval`/`new Function` 執行使用者程式有 XSS/RCE 風險。線上工具建議只「顯示程式碼」,執行移轉到後端沙箱或目標硬體。

24.5 事件監聽與變更偵測

範例
// 工作區任何變更都觸發
workspace.addChangeListener((e) => {
  if (e.isUiEvent) return;          // 忽略純 UI 事件
  const code = Blockly.JavaScript.workspaceToCode(workspace);
  updatePreview(code);              // 即時預覽
  autosave(workspace);              // 自動存檔
});
即時預覽:「拼積木 → 右邊即時顯示程式碼」就是靠 change listener。這是 Blockly 應用最常見的 UX。

24.6 Worked Example:完整迷你應用

範例
「積木 → JSON 設定」工具(完整流程):
1. 載入 blockly + 自訂 JsonGenerator(單元 23)
2. inject:toolbox 含 alarm_when 等自訂積木
3. change listener:每次變更 → jsonGenerator.workspaceToCode()
4. 顯示在右側 pre#output
5. 「儲存」→ JSON 序列化存 localStorage
6. 「下載」→ 把 JSON 設定存成 .json 檔

完成標竿:
  使用者拼「temperature > 40 → sound」→
  即時看到 JSON → 下載 → 韌體直接套用。

24.7 練習

  1. 用 categoryToolbox 建立含 3 個分類的工具箱。
  2. 實作「儲存/載入」按鈕(JSON 序列化)。
  3. 說明 `eval` 執行程式碼的風險與替代方案。
看完這單元你應該能說出:
  • 用 categoryToolbox JSON 定義完整工具箱。
  • 用 Blockly.inject 設定工作區並注入。
  • 用 JSON/XML 序列化儲存與載入工作區。
  • 生成程式碼並在應用中執行。

延伸閱讀