Benutzerdefinierte Prozedurblöcke erstellen

Für das Erstellen von benutzerdefinierten Prozedurblöcken sind folgende Schritte erforderlich:

  1. Installieren Sie das Plug-in @blockly/block-shareable-procedures, wie auf der Seite Verfahren verwenden beschrieben.
  2. Verwenden Sie das JSON-Serialisierungssystem, wie auf der Übersichtsseite erläutert.

Datenmodelle zum Arbeitsbereich hinzufügen

Sowohl Prozedurdefinitions- als auch Prozeduraufrufblöcke verweisen auf ein unterstützendes Datenmodell, das die Signatur der Prozedur definiert (Name, Parameter und Rückgabe). Dies ermöglicht mehr Flexibilität bei der Gestaltung Ihrer Anwendung (z.B. können Prozeduren in einem Arbeitsbereich definiert und in einem anderen referenziert werden).

Das bedeutet, dass Sie die Prozedurdatenmodelle dem Arbeitsbereich hinzufügen müssen, damit Ihre Blöcke funktionieren. Dafür gibt es viele Möglichkeiten (z.B. benutzerdefinierte Benutzeroberflächen).

Die @blockly/block-shareable-procedures erstellen die unterstützenden Datenmodelle dynamisch durch Prozedurdefinitionsblöcke, wenn sie im Arbeitsbereich instanziiert werden. Wenn Sie dies selbst implementieren möchten, erstellen Sie das Modell in init und löschen es in 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());
  }
}

Informationen zu den Blöcken zurückgeben

Die Prozedurdefinition und Prozeduraufrufblöcke müssen die Methoden getProcedureModel, isProcedureDef und getVarModels implementieren. Mit diesen Hooks ruft Blockly Informationen zu Ihren Prozedurblöcken ab.

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 [];
  },
};

Re-Rendering bei Updates auslösen

Die Prozedurdefinition und die Prozeduraufrufblöcke müssen die Methode doProcedureUpdate implementieren. Dies ist der Hook, den die Datenmodelle aufrufen, um den Prozedurblöcken mitzuteilen, dass sie sich selbst neu rendern sollen.

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

Benutzerdefinierte Serialisierung hinzufügen

Die Serialisierung von Prozedurblöcken muss zwei separate Vorgänge ausführen.

  1. Beim Laden aus JSON müssen die Blöcke einen Verweis auf ihr unterstützendes Datenmodell abrufen, da die Blöcke und Modelle separat serialisiert sind.
  2. Beim Kopieren und Einfügen eines Prozedurblocks muss der Block den gesamten Status seines Prozedurmodells serialisieren, damit er repliziert/dupliziert werden kann.

Beides wird über saveExtraState und loadExtraState abgewickelt. Beachten Sie, dass benutzerdefinierte Prozedurblöcke nur bei Verwendung des JSON-Serialisierungssystems unterstützt werden, sodass wir nur JSON-Serialisierungs-Hooks definieren müssen.

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

Prozedurmodell/-signatur optional ändern

Sie können den Nutzern auch die Möglichkeit hinzufügen, das Verfahrensmodell/-signatur zu ändern. Durch den Aufruf der Methoden insertParameter, deleteParameter oder setReturnTypes werden die Blockierungen automatisch über doProcedureUpdate neu gerendert.

Zu den Optionen zum Erstellen von UIs zum Ändern des Prozedurmodells gehören die Verwendung von Mutatoren (die von den integrierten Prozedurblöcken verwendet werden), Bildfelder mit Klick-Handlern, etwas, das Blockly vollständig extern ist, usw.

Blöcke zur Toolbox hinzufügen

Die Kategorie der integrierten dynamischen Verfahren von Blockly bezieht sich speziell auf die integrierten Prozedurblöcke von Blockly. Damit Sie auf Ihre Blöcke zugreifen können, müssen Sie Ihre eigene benutzerdefinierte dynamische Kategorie definieren und Ihrer Toolbox hinzufügen.

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