Caja de herramientas

La caja de herramientas es el lugar donde los usuarios obtienen bloques. Por lo general, se muestra en un lado del espacio de trabajo. A veces, tiene categorías y otras no.

Esta página se enfoca principalmente en cómo especificar la estructura de tu caja de herramientas (es decir, qué categorías tiene y qué bloques contiene). Si quieres obtener más detalles para cambiar la IU de tu caja de herramientas, consulta el codelab Cómo personalizar una caja de herramientas de Blockly y la charla sobre las APIs de Toolbox de 2021.

Formatos

Blockly te permite especificar la estructura de tu caja de herramientas con diferentes formatos. El nuevo formato recomendado usa JSON, y el anterior, XML.

Estas son las diferentes maneras en las que puedes especificar la caja de herramientas anterior:

JSON

A partir de la versión de septiembre de 2020, las cajas de herramientas se pueden definir con JSON.

var toolbox = {
    "kind": "flyoutToolbox",
    "contents": [
      {
        "kind": "block",
        "type": "controls_if"
      },
      {
        "kind": "block",
        "type": "controls_whileUntil"
      }
    ]
  };
var workspace = Blockly.inject('blocklyDiv', {toolbox: toolbox});

XML

<xml id="toolbox" style="display: none">
  <block type="controls_if"></block>
  <block type="controls_whileUntil"></block>
</xml>
<script>
  var workspace = Blockly.inject('blocklyDiv',
      {toolbox: document.getElementById('toolbox')});
</script>

String XML

var toolbox = '<xml>' +
    '<block type="controls_if"></block>' +
    '<block type="controls_whileUntil"></block>' +
    '</xml>';
var workspace = Blockly.inject('blocklyDiv', {toolbox: toolbox});

Categorías

Los bloques de la caja de herramientas pueden organizarse en categorías.

Estas son las formas en que podrías definir la caja de herramientas anterior, que tiene dos categorías ("Control" y "Lógica"), cada una de las cuales contiene bloques:

JSON

{
  "kind": "categoryToolbox",
  "contents": [
    {
      "kind": "category",
      "name": "Control",
      "contents": [
        {
          "kind": "block",
          "type": "controls_if"
        },
      ]
    },
    {
      "kind": "category",
      "name": "Logic",
      "contents": [
        {
          "kind": "block",
          "type": "logic_compare"
        },
        {
          "kind": "block",
          "type": "logic_operation"
        },
        {
          "kind": "block",
          "type": "logic_boolean"
        }
      ]
    }
  ]
}

XML

<xml id="toolbox" style="display: none">
  <category name="Control">
    <block type="controls_if"></block>
  <category name="Logic">
    <block type="logic_compare"></block>
    <block type="logic_operation"></block>
    <block type="logic_boolean"></block>
  </category>
</xml>

Categorías anidadas

Las categorías pueden estar anidadas dentro de otras categorías. Estas son dos categorías de nivel superior ("Principal" y "Personalizada"), la segunda de las cuales contiene dos subcategorías, cada una de las cuales contiene bloques:

Ten en cuenta que es posible que una categoría contenga subcategorías y bloques. En el ejemplo anterior, “Personalizado” tiene dos subcategorías (“Mover” y “Giro”), así como un bloque propio (“inicio”).

JSON

{
  "kind": "categoryToolbox",
  "contents": [
    {
      "kind": "category",
      "name": "Core",
      "contents": [
        {
          "kind": "block",
          "type": "controls_if"
        },
        {
          "kind": "block",
          "type": "logic_compare"
        },
      ]
    },
    {
      "kind": "category",
      "name": "Custom",
      "contents": [
        {
          "kind": "block",
          "type": "start"
        },
        {
          "kind": "category",
          "name": "Move",
          "contents": [
            {
              "kind": "block",
              "type": "move_forward"
            }
          ]
        },
        {
          "kind": "category",
          "name": "Turn",
          "contents": [
            {
              "kind": "block",
              "type": "turn_left"
            }
          ]
        }
      ]
    }
  ]
}

XML

<xml id="toolbox" style="display: none">
  <category name="Core">
    <block type="controls_if"></block>
    <block type="logic_compare"></block>
  </category>
  <category name="Custom">
    <block type="start"></block>
    <category name="Move">
      <block type="move_forward"></block>
    </category>
    <category name="Turn">
      <block type="turn_left"></block>
    </category>
  </category>
</xml>

Categorías dinámicas

Las categorías dinámicas son categorías que se vuelven a propagar de forma dinámica en función de una función cada vez que se abren.

Blockly lo admite, ya que te permite asociar una categoría con una función a través de una clave de string registrada. La función debe mostrar una definición del contenido de una categoría (incluidos los bloques, los botones, las etiquetas, etcétera). El contenido se puede especificar como JSON o XML, aunque se recomienda usar JSON.

Además, ten en cuenta que la función proporciona el lugar de trabajo de destino como parámetro, por lo que los bloques de tu categoría dinámica pueden basarse en el estado del lugar de trabajo.

JSON

A partir de la versión de septiembre de 2021, puedes especificar el estado de los bloques sin usar 'blockxml'.

// Returns an array of objects.
var coloursFlyoutCallback = function(workspace) {
  // Returns an array of hex colours, e.g. ['#4286f4', '#ef0447']
  var colourList = getPalette();
  var blockList = [];
  for (var i = 0; i < colourList.length; i++) {
    blockList.push({
      'kind': 'block',
      'type': 'colour_picker',
      'fields': {
        'COLOUR': colourList[i]
      }
    });
  }
  return blockList;
};

// Associates the function with the string 'COLOUR_PALETTE'
myWorkspace.registerToolboxCategoryCallback(
    'COLOUR_PALETTE', coloursFlyoutCallback);

JSON anterior

Antes de la versión de septiembre de 2021, tenías que usar la propiedad 'blockxml' para especificar el estado de los bloques.

// Returns an array of objects.
var coloursFlyoutCallback = function(workspace) {
  // Returns an array of hex colours, e.g. ['#4286f4', '#ef0447']
  var colourList = getPalette();
  var blockList = [];
  for (var i = 0; i < colourList.length; i++) {
    blockList.push({
      'kind': 'block',
      'type': 'colour_picker', // Type is optional if you provide blockxml
      'blockxml': '<block type="colour_picker">' +
          '<field name="COLOUR">' + colourList[i] + '</field>' +
          '</block>'
    });
  }
  return blockList;
};

// Associates the function with the string 'COLOUR_PALETTE'
myWorkspace.registerToolboxCategoryCallback(
    'COLOUR_PALETTE', coloursFlyoutCallback);

XML

// Returns an arry of XML nodes.
var coloursFlyoutCallback = function(workspace) {
  // Returns an array of hex colours, e.g. ['#4286f4', '#ef0447']
  var colourList = getPalette();
  var blockList = [];
  for (var i = 0; i < colourList.length; i++) {
    var block = document.createElement('block');
    block.setAttribute('type', 'colour_picker');
    var field = document.createElement('field');
    field.setAttribute('name', 'COLOUR');
    field.innerText = colourList[i];
    block.appendChild(field);
    blockList.push(block);
  }
  return blockList;
};

// Associates the function with the string 'COLOUR_PALETTE'
myWorkspace.registerToolboxCategoryCallback(
    'COLOUR_PALETTE', coloursFlyoutCallback);

Después de que las funciones de categoría dinámica se asocien con una clave de string (es decir, registrada), puedes asignar esta clave de string a la propiedad custom de tu definición de categoría para que la categoría sea dinámica.

JSON

{
  "kind": "category",
  "name": "Colours",
  "custom": "COLOUR_PALETTE"
}

XML

<category name="Colours" custom="COLOUR_PALETTE"></category>

Categorías dinámicas integradas

Blockly proporciona tres categorías dinámicas integradas.

  • 'VARIABLE' crea una categoría para las variables sin tipo.
  • 'VARIABLE_DYNAMIC' crea una categoría para las variables escritas. Tiene botones para crear cadenas, números y colores.
  • 'PROCEDURE' crea una categoría para los bloques de funciones.

JSON

{
  "kind": "category",
  "name": "Variables",
  "custom": "VARIABLE"
},
{
  "kind": "category",
  "name": "Variables",
  "custom": "VARIABLE_DYNAMIC"
},
{
  "kind": "category",
  "name": "Functions",
  "custom": "PROCEDURE"
}

XML

<category name="Variables" custom="VARIABLE"></category>
<category name="Variables" custom="VARIABLE_DYNAMIC"></category>
<category name="Functions" custom="PROCEDURE"></category>

Nota: La palabra "procedimiento" se usa en toda la base de código de Blockly, pero la palabra "función" es más comprensible para los estudiantes. Lamentamos la discrepancia.

Inhabilitando

Una categoría inhabilitada no permitirá que el usuario la abra y se omitirá durante la navegación con el teclado.

var category = toolbox.getToolboxItems()[0];
category.setDisabled('true');

Cuando se inhabilita una categoría, se agrega una propiedad 'disabled' al elemento del DOM, lo que te permite controlar el aspecto de una categoría inhabilitada.

.blocklyToolboxCategory[disabled="true"] {
  opacity: .5;
}

Ocultando

Una categoría oculta no se mostrará como parte de la caja de herramientas. Las categorías ocultas se pueden mostrar más adelante a través de JavaScript.

JSON

{
  "kind": "category",
  "name": "...",
  "hidden": "true"
}

XML

<category name="..." hidden="true"></category>

JavaScript

var category = toolbox.getToolboxItems()[0];
category.hide();
// etc...
category.show();

Expandiendo

Esto solo se aplica a las categorías que contienen otras categorías anidadas.

Una categoría expandida mostrará sus subcategorías. De forma predeterminada, las categorías anidadas se contraen y se debe hacer clic en ellas para expandirse.

JSON

{
  "kind": "category",
  "name": "...",
  "expanded": "true"
}

XML

<category name="..." expanded="true"></sep>

Diseño

Blockly proporciona una IU de categorías predeterminada y, con ella, algunas opciones básicas de diseño. Si quieres obtener información para aplicar ajustes de estilo o configuración más avanzados a la IU, consulta el codelab Cómo personalizar una caja de herramientas de Blockly y la charla sobre las APIs de Toolbox de 2021.

Temas

Los temas te permiten especificar todos los colores de tu lugar de trabajo a la vez, incluidos los colores de nuestras categorías.

Para usarlas, debes asociar tu categoría con un estilo de categoría en particular:

JSON

{
  "kind": "category",
  "name": "Logic",
  "categorystyle": "logic_category"
}

XML

<category name="Logic" categorystyle="logic_category"></category>

Colores

También puedes especificar el color directamente, pero no se recomienda. El color es un número en cadena (0-360) que especifica el matiz. Presta atención al deletreo británico.

JSON

{
  "contents": [
    {
      "kind": "category",
      "name": "Logic",
      "colour": "210"
    },
    {
      "kind": "category",
      "name": "Loops",
      "colour": "120"
    }
  ]
}

XML

<xml id="toolbox" style="display: none">
  <category name="Logic" colour="210">...</category>
  <category name="Loops" colour="120">...</category>
  <category name="Math" colour="230">...</category>
  <category name="Colour" colour="20">...</category>
  <category name="Variables" colour="330" custom="VARIABLE"></category>
  <category name="Functions" colour="290" custom="PROCEDURE"></category>
</xml>

Ten en cuenta que también admitimos el uso de referencias de color localizables.

Categoría CSS

Si quieres una personalización más potente, Blockly también te permite especificar clases de CSS para diferentes elementos de la IU predeterminada. Luego, puedes usar CSS para diseñarlas.

Se pueden aplicar clases de CSS a los siguientes tipos de elementos:

  • container: Es la clase del div superior de la categoría. El valor predeterminado es blocklyToolboxCategory.
  • row: Es la clase del elemento div que contiene el ícono y la etiqueta de categoría. El valor predeterminado es blocklyTreeRow.
  • icon: Es la clase para el ícono de categoría. blocklyTreeIcon predeterminado.
  • label: Es la clase para la etiqueta de categoría. blocklyTreeLabel predeterminado.
  • selected: La clase que se agrega a la categoría cuando se selecciona. El valor predeterminado es blocklyTreeSelected.
  • openicon: Es la clase que se agrega a un ícono cuando la categoría tiene categorías anidadas y está abierta. blocklyTreeIconOpen predeterminado.
  • Closedicon: Es la clase que se agrega a un ícono cuando la categoría tiene categorías anidadas y se cierra. blocklyTreeIconClosed predeterminado.

Y así es como especificas las clases usando cualquiera de los dos formatos:

JSON

Configura la clase de CSS de un tipo de elemento particular con la propiedad cssConfig.

{
  "kind": "category",
  "name": "...",
  "cssConfig": {
    "container": "yourClassName"
  }
}

XML

Configura la clase de CSS de un tipo de elemento en particular agregando “css-” al elemento.

<category name="..." css-container="yourClassName"></category>

Acceso

Existen dos maneras de acceder a una categoría de manera programática. Puedes acceder a él mediante un índice (donde 0 es la categoría principal):

var category = toolbox.getToolboxItems()[0];

O por ID:

var category = toolbox.getToolboxItemById('categoryId');

Donde el ID se especifica en la definición de la caja de herramientas:

JSON

{
  "kind": "category",
  "name": "...",
  "toolboxitemid": "categoryId"
}

XML

<category name="..." toolboxitemid="categoryId"></category>

Bloques predeterminados

La definición de la caja de herramientas puede contener bloques que tienen campos configurados en un valor predeterminado o bloques que ya están conectados entre sí.

Aquí hay cuatro bloques:

  1. Un bloque logic_boolean simple sin valores predeterminados:
  2. Un bloque math_number que se modificó para mostrar el número 42 en lugar del valor predeterminado 0:
  3. Un bloque controls_for con tres bloques math_number conectados:
  4. Un bloque math_arithmetic con dos bloques sombras math_number conectados a él:

A continuación, se muestra una definición de la caja de herramientas que contiene estos cuatro bloques:

JSON

A partir de la versión de septiembre de 2021, puedes especificar el estado de los bloques sin usar 'blockxml'.

{
  "kind": "flyoutToolbox",
  "contents": [
    {
      "kind": "block",
      "type": "logic_boolean"
    },
    {
      "kind": "block",
      "type": "math_number",
      "fields": {
        "NUM": 42
      }
    },
    {
      "kind": "block",
      "type": "controls_for",
      "inputs": {
        "FROM": {
          "block": {
            "type": "math_number",
            "fields": {
              "NUM": 1
            }
          }
        },
        "TO": {
          "block": {
            "type": "math_number",
            "fields": {
              "NUM": 10
            }
          }
        },
        "BY": {
          "block": {
            "type": "math_number",
            "fields": {
              "NUM": 1
            }
          }
        },
      }
    },
    {
      "kind": "block",
      "type": "math_arithmetic",
      "fields": {
        "OP": "ADD"
      },
      "inputs": {
        "A": {
          "shadow": {
            "type": "math_number",
            "fields": {
              "NUM": 1
            }
          }
        },
        "B": {
          "shadow": {
            "type": "math_number",
            "fields": {
              "NUM": 1
            }
          }
        }
      }
    },
  ]
}

JSON anterior

Antes de la versión de septiembre de 2021, tenías que usar la propiedad 'blockxml' para especificar el estado de los bloques.

{
  "kind": "flyoutToolbox",
  "contents": [
    {
      "kind": "block",
      "type": "logic_boolean"
    },
    {
      "kind": "block",
      "blockxml":
          '<block type="math_number">' +
          '<field name="NUM">42</field>' +
          '</block>'
    },
    {
      "kind": "block",
      "blockxml":
          '<block type="controls_for">' +
            '<value name="FROM">' +
              '<block type="math_number">' +
                '<field name="NUM">1</field>' +
              '</block>' +
            '</value>' +
            '<value name="TO">' +
              '<block type="math_number">' +
                '<field name="NUM">10</field>' +
              '</block>' +
            '</value>' +
            '<value name="BY">' +
              '<block type="math_number">' +
                '<field name="NUM">1</field>' +
              '</block>' +
            '</value>' +
          '</block>'
    },
    {
      "kind": "block",
      "blockxml":
          '<block type="math_arithmetic">' +
            '<field name="OP">ADD</field>' +
            '<value name="A">' +
              '<shadow type="math_number">' +
                '<field name="NUM">1</field>' +
              '</shadow>' +
            '</value>' +
            '<value name="B">' +
              '<shadow type="math_number">' +
                '<field name="NUM">1</field>' +
              '</shadow>' +
            '</value>' +
          '</block>'
    },
  ]
}

XML

<xml id="toolbox" style="display: none">
  <block type="logic_boolean"></block>

  <block type="math_number">
    <field name="NUM">42</field>
  </block>

  <block type="controls_for">
    <value name="FROM">
      <block type="math_number">
        <field name="NUM">1</field>
      </block>
    </value>
    <value name="TO">
      <block type="math_number">
        <field name="NUM">10</field>
      </block>
    </value>
    <value name="BY">
      <block type="math_number">
        <field name="NUM">1</field>
      </block>
    </value>
  </block>

  <block type="math_arithmetic">
    <field name="OP">ADD</field>
    <value name="A">
      <shadow type="math_number">
        <field name="NUM">1</field>
      </shadow>
    </value>
    <value name="B">
      <shadow type="math_number">
        <field name="NUM">1</field>
      </shadow>
    </value>
  </block>
</xml>

Escribir estas definiciones a mano puede ser... un poco molesto. En su lugar, puedes cargar tus bloques en un lugar de trabajo y, luego, ejecutar el siguiente código para obtener las definiciones. Estas llamadas funcionan porque la caja de herramientas usa para los bloques el mismo formato que el sistema de serialización.

JSON

console.log(Blockly.serialization.workspaces.save(Blockly.getMainWorkspace()));

XML

console.log(Blockly.Xml.workspaceToDom(Blockly.getMainWorkspace()));

También puedes quitar las propiedades x, y y id, ya que la caja de herramientas las ignora.

Bloques de sombras

Los bloques de sombra son bloques de marcador de posición que realizan varias funciones:

  • Indican los valores predeterminados de su bloque superior.
  • Permiten que los usuarios escriban valores directamente sin necesidad de recuperar un bloque de números o cadenas.
  • A diferencia de un bloque normal, se reemplazan si el usuario suelta un bloque encima de él.
  • Informan al usuario el tipo de valor esperado.

Bloques inhabilitados

No se pueden arrastrar los bloques inhabilitados de la caja de herramientas. Los bloques se pueden inhabilitar de forma individual con la propiedad opcional disabled.

JSON

{
  "kind": "flyoutToolbox",
  "contents": [
    {
      "kind": "block",
      "type":"math_number"
    },
    {
      "kind": "block",
      "type": "math_arithmetic"
    },
    {
      "kind": "block",
      "type": "math_single",
      "disabled": "true"
    }
  ]
}

XML

<xml id="toolbox" style="display: none">
  <block type="math_number"></block>
  <block type="math_arithmetic"></block>
  <block type="math_single" disabled="true"></block>
</xml>

También puedes inhabilitar o habilitar un bloqueo de manera programática con setEnabled.

Campos de variables

Es posible que los campos variables deban especificarse de manera diferente cuando están en una caja de herramientas, en lugar de cuando simplemente se serializan.

En particular, cuando los campos variables se serializan normalmente a JSON, solo contienen el ID de la variable que representan, ya que el nombre y el tipo de la variable se serializan por separado. Sin embargo, las cajas de herramientas no contienen esa información, por lo que debe incluirse directamente en el campo de variable.

{
  "kind": "flyoutToolbox",
  "content": [
    {
      "type": "controls_for",
      "fields": {
        "VAR": {
          "name": "index",
          "type": "Number"
        }
      }
    }
  ]
}

Separadores

Si agregas un separador entre dos categorías cualesquiera, se creará una línea y un espacio adicional entre las dos categorías.

Puedes cambiar la clase del separador en la definición de la caja de herramientas JSON o XML.

JSON

{
  "kind": "sep",
  "cssConfig": {
    "container": "yourClassName"
  }
}

XML

<sep css-container="yourClassName"></sep>

Agregar un separador entre dos bloques cualesquiera creará un espacio entre los bloques. De forma predeterminada, cada bloque está separado de su vecino inferior por 24 píxeles. Esta separación se puede cambiar con el atributo “gap”, que reemplazará el intervalo predeterminado.

Esto te permite crear grupos de bloques lógicos en la caja de herramientas.

JSON

{
  "kind": "flyoutToolbox",
  "contents": [
    {
      "kind": "block",
      "type":"math_number"
    },
    {
      "kind": "sep",
      "gap": "32"
    },
    {
      "kind": "block",
      "blockxml": "<block type='math_arithmetic'><field name='OP'>ADD</field></block>"
    },
    {
      "kind": "sep",
      "gap": "8"
    },
    {
      "kind": "block",
      "blockxml": "<block type='math_arithmetic'><field name='OP'>MINUS</field></block>"
    }
  ]
}

XML

<xml id="toolbox" style="display: none">
  <block type="math_number"></block>
  <sep gap="32"></sep>
  <block type="math_arithmetic">
    <field name="OP">ADD</field>
  </block>
  <sep gap="8"></sep>
  <block type="math_arithmetic">
    <field name="OP">MINUS</field>
  </block>
</xml>

Botones y etiquetas

Puedes colocar un botón o una etiqueta en cualquier lugar donde puedas colocar un bloque de la caja de herramientas.

JSON

{
  "kind": "flyoutToolbox",
  "contents": [
    {
      "kind": "block",
      "type":"logic_operation"
    },
    {
      "kind": "label",
      "text": "A label",
      "web-class": "myLabelStyle"
    },
    {
      "kind": "label",
      "text": "Another label"
    },
    {
      "kind": "block",
      "type": "logic_negate"
    },
    {
      "kind": "button",
      "text": "A button",
      "callbackKey": "myFirstButtonPressed"
    },
    {
      "kind": "block",
      "type": "logic_boolean"
    }
  ]
}

XML

<xml id="toolbox" style="display: none">
  <block type="logic_operation"></block>
  <label text="A label" web-class="myLabelStyle"></label>
  <label text="Another label"></label>
  <block type="logic_negate"></block>
  <button text="A button" callbackKey="myFirstButtonPressed"></button>
  <block type="logic_boolean"></block>
</xml>
    <style>
    .myLabelStyle>.blocklyFlyoutLabelText {
      font-style: italic;
      fill: green;
    }
    </style>

Puedes especificar un nombre de clase CSS para aplicarlo a tu botón o etiqueta. En el ejemplo anterior, la primera etiqueta usa un diseño personalizado, mientras que la segunda usa el diseño predeterminado.

Los botones deben tener funciones de devolución de llamada, pero las etiquetas no. Para configurar la devolución de llamada de un clic determinado en un botón, usa

yourWorkspace.registerButtonCallback(yourCallbackKey, yourFunction).

Tu función debería aceptar como argumento el botón en el que se hizo clic. El botón "Crear variable..." en la categoría de variables es un buen ejemplo de un botón con una devolución de llamada.

Cómo cambiar la Caja de herramientas

La aplicación puede cambiar los bloques disponibles en la caja de herramientas en cualquier momento con una sola llamada a función:

workspace.updateToolbox(newTree);

Al igual que durante la configuración inicial, newTree puede ser un árbol de nodos, una representación de string o un objeto JSON. La única restricción es que el modo no se puede cambiar; es decir, si hay categorías en la caja de herramientas definida inicialmente, la caja de herramientas nueva también debe tener categorías (aunque las categorías pueden cambiar). Del mismo modo, si la caja de herramientas definida inicialmente no tenía ninguna categoría, es posible que la caja de herramientas nueva no tenga ninguna.

El contenido de una sola categoría se puede actualizar de las siguientes maneras:

var category = workspace.getToolbox().getToolboxItems()[0];
category.updateFlyoutContents(flyoutContents);

En este caso, FlyoutContents puede ser una lista de bloques definidos mediante JSON, un árbol de nodos o una representación de cadena.

Ten en cuenta que, en este momento, actualizar la caja de herramientas causa algunos restablecimientos menores de la IU:

  • En una caja de herramientas sin categorías, cualquier campo modificado por el usuario (como un menú desplegable) se revertirá a la configuración predeterminada.

Esta es una demostración en vivo de un árbol con categorías y grupos de bloques.