Créer des blocs de procédure personnalisée

Pour créer des blocs de procédure personnalisée, vous devez:

  1. Installez le plug-in @blockly/block-shareable-procedures, comme décrit sur la page Utiliser les procédures.
  2. Utilisez le système de sérialisation JSON, comme expliqué sur la page de présentation.

Ajouter des modèles de données à l'espace de travail

La définition de la procédure et les blocs d'appelants de procédure font référence à un modèle de données de sauvegarde qui définit la signature de la procédure (nom, paramètres et retour). Cela offre plus de flexibilité dans la conception de votre application (par exemple, vous pouvez autoriser la définition de procédures dans un espace de travail et la référence à un autre).

Cela signifie que vous devrez ajouter les modèles de données de procédure à l'espace de travail pour que vos blocs fonctionnent. Vous pouvez le faire de nombreuses façons (par exemple, à l'aide d'UI personnalisées).

Pour ce faire, @blockly/block-shareable-procedures crée des modèles de données de sauvegarde de manière dynamique lorsque des blocs de définition de procédure sont instanciés dans l'espace de travail. Pour l'implémenter vous-même, créez le modèle dans init et supprimez-le dans 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.

    // Insertion markers reference the model of the original block.
    if (this.isInsertionMarker()) return;
    this.workpace.getProcedureMap().delete(model.getId());
  }
}

Afficher des informations sur les blocs

Votre définition de procédure et vos blocs d'appel de procédure doivent implémenter les méthodes getProcedureModel, isProcedureDef et getVarModels. Il s'agit des hooks que le code Blockly utilise pour obtenir des informations sur vos blocs de procédure.

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

Déclencher un nouveau rendu lors des mises à jour

Votre définition de procédure et vos blocs d'appels de procédure doivent implémenter la méthode doProcedureUpdate. Il s'agit du hook que les modèles de données appellent pour indiquer à vos blocs de procédure de s'afficher à nouveau.

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

Ajouter une sérialisation personnalisée

La sérialisation des blocs de procédure doit effectuer deux opérations distinctes.

  1. Lors du chargement à partir d'un fichier JSON, vos blocs doivent récupérer une référence à leur modèle de données de sauvegarde, car ils sont sérialisés séparément.
  2. Lors du copier-coller d'un bloc de procédure, le bloc doit sérialiser l'intégralité de l'état de son modèle de procédure afin qu'il puisse être répliqué.

Ces deux opérations sont gérées via saveExtraState et loadExtraState. Notez à nouveau que les blocs de procédure personnalisée ne sont compatibles qu'avec le système de sérialisation JSON. Il nous suffit donc de définir des hooks de sérialisation JSON.

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

Blockly.Blocks['my_procedure_def'] = {
  // When doFullSerialization is true, we should serialize the full state of
  // the model.
  saveExtraState(doFullSerialization) {
    const state = Object.create(null);
    state['procedureId']: this.model.getId();

    if (doFullSerialization) {
      state['name'] = this.model.getName();
      state['parameters'] = this.model.getParameters().map((p) => {
        return {name: p.getName(), p.getId()};
      });
      state['returnTypes'] = this.model.getReturnTypes();

      // Flag for deserialization.
      state['createNewModel'] = true;
    }

    return state;
  },

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

    if (map.has(id) && !state['createNewModel']) {
      // Delete the existing model (created in init).
      map.delete(this.model.getId());
      // Grab a reference to the model we're supposed to reference.
      this.model = map.get(id);
      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']));
    }
    this.doProcedureUpdate();
  },
};

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

Modifier le modèle de procédure (facultatif)

Vous pouvez également ajouter la possibilité pour les utilisateurs de modifier le modèle de procédure. L'appel des méthodes insertParameter, deleteParameter ou setReturnTypes déclenche automatiquement le rerendu de vos blocs (via doProcedureUpdate).

Les options de création d'interfaces utilisateur permettant de modifier le modèle de procédure incluent l'utilisation de mutateurs (utilisés par les blocs de procédure intégrés), des champs d'image avec des gestionnaires de clics, des éléments entièrement externes à Blockly, etc.

Ajouter des blocs à la boîte à outils

La catégorie de procédure dynamique intégrée de Blockly est spécifique aux blocs de procédure intégrés de Blockly. Pour accéder à vos blocs, vous devez donc définir votre propre catégorie dynamique personnalisée et l'ajouter à votre boîte à outils.

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