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

Pour créer des blocs de procédure personnalisés, vous devez effectuer les opérations suivantes:

  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

Les blocs de définition de procédure et d'appelant 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 davantage 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 le référencement dans un autre).

Vous devez donc 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, via des interfaces utilisateur personnalisées).

@blockly/block-shareable-procedures : les blocs de définition de procédure créent dynamiquement leurs modèles de données de sauvegarde lorsqu'ils sont instanciés dans l'espace de travail. Pour l'implémenter vous-même, vous devez créer le modèle dans init et le supprimer 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.
    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 de 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 le nouveau rendu lors des mises à jour

Votre définition de procédure et vos blocs d'appel 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 les blocs et les modèles sont sérialisés séparément.
  2. Lors du copier-coller d'un bloc de procédure, celui-ci doit sérialiser l'intégralité de l'état de son modèle de procédure afin qu'il puisse être répliqué/dupliqué.

Ces deux opérations sont gérées via saveExtraState et loadExtraState. Notez 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'] = {
  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();
      }
    }
  }
};

Modifier le modèle/la signature de procédure (facultatif)

Vous pouvez également permettre aux utilisateurs de modifier le modèle/la signature de la 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), de champs d'image avec des gestionnaires de clics, d'éléments complètement 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);