建立自訂程序區塊

如要建立自訂程序區塊,您必須:

  1. 按照使用程序頁面的說明,安裝 @blockly/block-shareable-procedures 外掛程式。
  2. 使用 JSON 序列化系統,如總覽頁面所述。

將資料模型新增至工作區

程序定義和程序呼叫端都會參照備份資料模型,該模型定義了程序的簽名 (名稱、參數和回傳)。如此一來,您在設計應用程式時可以享有更多彈性 (例如,您可以在一個工作區中定義程序,並在另一個工作區中參照)。

這表示您需要將程序資料模型新增至工作區,區塊才能運作。有許多方法可以達到這個目標 (例如自訂 UI)。

@blockly/block-shareable-procedures 透過程序定義區塊在執行個體執行個體化時,動態建立其支援資料模型。如要自行實作,請在 init 中建立模型,並在 destroy 中刪除該模型。

import {ObservableProcedureModel} from '@blockly/block-shareable-procedures';

Blockly.Blocks['my_procedure_def'] = {
  init: function() {
    this.model = new ObservableProcedureModel('default name');
    this.workspace.getProcedureMap().add(model);
    // etc...
  }

  destroy: function() {
    // Optionally:
    // Destroy the model when the definition block is deleted.
    this.workpace.getProcedureMap().delete(model.getId());
  }
}

傳回封鎖條件相關資訊

程序定義和程序呼叫區塊需要實作 getProcedureModelisProcedureDefgetVarModels 方法。這些掛鉤是 Blockly 程式碼,用來取得程序區塊的相關資訊。

Blockly.Blocks['my_procedure_def'] = {
  getProcedureModel() {
    return this.model;
  },

  isProcedureDef() {
    return true;
  },

  getVarModels() {
    // If your procedure references variables
    // then you should return those models here.
    return [];
  },
};

Blockly.Blocks['my_procedure_call'] = {
  getProcedureModel() {
    return this.model;
  },

  isProcedureDef() {
    return false;
  },

  getVarModels() {
    // If your procedure references variables
    // then you should return those models here.
    return [];
  },
};

更新時觸發重新算繪

程序定義和程序呼叫區塊需要實作 doProcedureUpdate 方法。這就是資料模型呼叫,用來指示程序區塊自行重新轉譯。

Blockly.Blocks['my_procedure_def'] = {
  doProcedureUpdate() {
    this.setFieldValue('NAME', this.model.getName());
    this.setFieldValue(
        'PARAMS',
        this.model.getParameters()
            .map((p) => p.getName())
            .join(','));
    this.setFieldValue(
        'RETURN', this.model.getReturnTypes().join(',');
  }
};

Blockly.Blocks['my_procedure_call'] = {
  doProcedureUpdate() {
    // Similar to the def block above...
  }
};

新增自訂序列化作業

程序區塊的序列化作業必須分別執行兩項工作。

  1. 從 JSON 載入時,區塊需要取得備份資料模型的參照,因為區塊和模型會分開進行序列化。
  2. 複製及貼上程序區塊時,區塊必須序列化其程序模型的整個狀態,才能複製/複製該區塊。

這兩個項目都是透過 saveExtraStateloadExtraState 處理。再次請注意,只有在使用 JSON 序列化系統時才支援自訂程序區塊,因此我們只需要定義 JSON 序列化掛鉤。

import {
    ObservableProcedureModel,
    ObservableParameterModel,
    isProcedureBlock
} from '@blockly/block-shareable-procedures';

Blockly.Blocks['my_procedure_def'] = {
  saveExtraState() {
    return {
      'procedureId': this.model.getId(),

      // These properties are only necessary for pasting.
      'name': this.model.getName(),
      'parameters': this.model.getParameters().map((p) => {
        return {name: p.getName(), p.getId()};
      }),
      'returnTypes': this.model.getReturnTypes(),
    };
  },

  loadExtraState(state) {
    const id = state['procedureId']
    const map = this.workspace.getProcedureMap();

    // Grab a reference to the existing procedure model.
    if (this.model.getId() != id && map.has(id) &&
        (this.isInsertionMarker || this.noBlockHasClaimedModel_(id))) {
      // Delete the existing model (created in init).
      this.workspace.getProcedureMap().delete(model.getId());
      // Grab a reference to the new model.
      this.model = this.workspace.getProcedureMap()
          .get(state['procedureId']);
      this.doProcedureUpdate();
      return;
    }

    // There is no existing procedure model (we are likely pasting), so
    // generate it from JSON.
    this.model
        .setName(state['name'])
        .setReturnTypes(state['returnTypes']);
    for (const [i, param] of state['parameters'].entries()) {
      this.model.insertParameter(
          i,
          new ObservableParameterModel(
              this.workspace, param['name'], param['id']));
    }
  },

  // We don't want to reference a model that some other procedure definition
  // is already referencing.
  noBlockHasClaimedModel_(procedureId) {
    const model =
      this.workspace.getProcedureMap().get(procedureId);
    return this.workspace.getAllBlocks(false).every(
      (block) =>
        !isProcedureBlock(block) ||
        !block.isProcedureDef() ||
        block.getProcedureModel() !== model);
  },
};

Blockly.Blocks['my_procedure_call'] = {
  saveExtraState() {
    return {
      'procedureId': this.model.getId(),
    };
  },

  loadExtraState(state) {
    // Delete our existing model (created in init).
    this.workspace.getProcedureMap().delete(model.getId());
    // Grab a reference to the new model.
    this.model = this.workspace.getProcedureMap()
        .get(state['procedureId']);
    if (this.model) this.doProcedureUpdate();
  },

  // Handle pasting after the procedure definition has been deleted.
  onchange(event) {
    if (event.type === Blockly.Events.BLOCK_CREATE &&
        event.blockId === this.id) {
      if(!this.model) { // Our procedure definition doesn't exist =(
        this.dispose();
      }
    }
  }
};

視需要修改程序模型/簽名

您也可以新增讓使用者修改程序模型/簽名的功能。呼叫 insertParameterdeleteParametersetReturnTypes 方法會自動觸發區塊重新轉譯 (透過 doProcedureUpdate)。

建立 UI 以修改程序模型的選項包括:使用變異器 (內建程序區塊使用)、具有點擊處理常式的映像檔欄位、完全屬於 Blockly 的外部項目等。

在工具箱中新增方塊

Blockly 的內建動態程序類別僅適用於 Blockly 的內建程序區塊。因此,如要存取區塊,您必須定義自己的自訂動態類別,然後將其新增至工具箱

const proceduresFlyoutCallback = function(workspace) {
  const blockList = [];
  blockList.push({
    'kind': 'block',
    'type': 'my_procedure_def',
  });
  for (const model of
        workspace.getProcedureMap().getProcedures()) {
    blockList.push({
      'kind': 'block',
      'type': 'my_procedure_call',
      'extraState': {
        'procedureId': model.getId(),
      },
    });
  }
  return blockList;
};

myWorkspace.registerToolboxCategoryCallback(
    'MY_PROCEDURES', proceduresFlyoutCallback);