Caixa de ferramentas

A caixa de ferramentas é o lugar em que os usuários recebem bloqueios. Geralmente, ele é exibido em um lado do espaço de trabalho. Às vezes, há categorias, mas às vezes não.

O foco desta página é principalmente como especificar a estrutura da sua caixa de ferramentas, ou seja, quais categorias ela tem e quais blocos elas contêm. Se você quiser mais detalhes sobre como mudar a interface da caixa de ferramentas, confira o codelab Como personalizar uma caixa de ferramentas do Blockly e a palestra sobre APIs da caixa de ferramentas 2021.

Formatos

O Blockly permite especificar a estrutura da caixa de ferramentas usando alguns formatos diferentes. O novo formato recomendado usa JSON, e o antigo usa XML.

Veja diferentes maneiras de especificar a caixa de ferramentas acima:

JSON

A partir da versão de setembro de 2020, as ferramentas podem ser definidas usando 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});

Categorias

Os blocos da caixa de ferramentas podem ser organizados em categorias.

Veja como definir a caixa de ferramentas acima, que tem duas categorias ("Controle" e "Lógica"), cada uma contendo blocos:

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>

Categorias aninhadas

As categorias podem ser aninhadas dentro de outras categorias. Veja a seguir duas categorias de nível superior ("Principal" e "Personalizado"). A segunda contém duas subcategorias, e cada uma delas contém blocos:

É possível que uma categoria contenha subcategorias e blocos. No exemplo acima, "Personalizado" tem duas subcategorias ("Mover" e "Virar"), além de um bloco próprio ("start").

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>

Categorias dinâmicas

Categorias dinâmicas são preenchidas dinamicamente com base em uma função sempre que são abertas.

O Blockly oferece suporte a isso, permitindo que você associe uma categoria a uma função por meio de uma chave de string registrada. A função precisa retornar uma definição do conteúdo de uma categoria (incluindo blocos, botões, rótulos etc.). O conteúdo pode ser especificado como JSON ou XML, embora JSON seja recomendado.

Além disso, a função recebe o espaço de trabalho de destino como um parâmetro, portanto, os blocos na categoria dinâmica podem ser baseados no estado do espaço de trabalho.

JSON

A partir da versão de setembro de 2021, é possível especificar o estado dos blocos sem 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 antigo

Antes da versão de setembro de 2021, era necessário usar a propriedade 'blockxml' para especificar o estado dos blocos.

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

Depois que as funções de categoria dinâmica são associadas a uma chave de string (também conhecida como registrada), é possível atribuir essa chave de string à propriedade custom da definição da categoria para tornar a categoria dinâmica.

JSON

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

XML

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

Categorias dinâmicas integradas

O Blockly fornece três categorias dinâmicas integradas.

  • 'VARIABLE' cria uma categoria para variáveis não digitadas.
  • 'VARIABLE_DYNAMIC' cria uma categoria para variáveis digitadas. Ele tem botões para criar strings, números e cores.
  • 'PROCEDURE' cria uma categoria para blocos de função.

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>

Observação: a palavra "procedimento" é usada em toda a base de código do Blockly, mas a palavra "função" foi mais compreendida pelos estudantes. Lamentamos a divergência.

Desativando

Uma categoria desativada não permite que um usuário abra a categoria e será ignorada durante a navegação pelo teclado.

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

Quando uma categoria é desativada, uma propriedade 'disabled' é adicionada ao elemento DOM, o que permite controlar a aparência de uma categoria desativada.

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

Ocultando

Uma categoria oculta não será mostrada como parte da caixa de ferramentas. As categorias ocultas podem ser exibidas posteriormente em JavaScript.

JSON

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

XML

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

JavaScript

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

Abrindo

Isso se aplica somente a categorias que contêm outras categorias aninhadas.

Uma categoria expandida mostrará suas subcategorias. Por padrão, as categorias aninhadas são recolhidas e precisam ser clicadas para serem expandidas.

JSON

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

XML

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

Estilo

O Blockly fornece uma IU de categorias padrão e algumas opções básicas de estilo. Se quiser informações sobre como fazer um estilo/configuração mais avançados da interface, confira o codelab Como personalizar uma caixa de ferramentas do Blockly e a palestra sobre APIs da caixa de ferramentas 2021.

Temas

Os temas permitem especificar todas as cores do espaço de trabalho de uma só vez, incluindo as cores das nossas categorias.

Para usá-los, você precisa associar sua categoria a um estilo específico:

JSON

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

XML

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

Cores

Também é possível especificar a cor diretamente, mas isso não é recomendado. A cor é um número stringificado (0 a 360) que especifica a matiz. Observe a ortografia britânica.

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>

Também oferecemos compatibilidade com o uso de referências de cores localizáveis.

CSS da categoria

Se você quiser uma personalização mais eficiente, o Blockly também permite especificar classes CSS para diferentes elementos da IU padrão. Depois, use CSS para definir o estilo.

Os seguintes tipos de elementos podem ter classes CSS aplicadas:

  • container - a classe do div pai da categoria. blocklyToolboxCategory padrão.
  • row - a classe do div que contém o ícone e o rótulo da categoria. blocklyTreeRow padrão.
  • icon: a classe do ícone de categoria. blocklyTreeIcon padrão.
  • label - a classe do rótulo da categoria. blocklyTreeLabel padrão.
  • selected - A classe que é adicionada à categoria quando ela é selecionada. blocklyTreeSelected padrão.
  • openicon: a classe adicionada a um ícone quando a categoria tem categorias aninhadas e está aberta. blocklyTreeIconOpen padrão.
  • closedicon: a classe adicionada a um ícone quando a categoria tem categorias aninhadas e está fechada. blocklyTreeIconClosed padrão.

Veja como especificar as classes usando um dos formatos:

JSON

Defina a classe CSS de um tipo de elemento específico usando a propriedade cssConfig.

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

XML

Defina a classe CSS de um tipo de elemento específico adicionando "css-" como prefixo.

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

Como acessar

Há duas maneiras de acessar uma categoria de maneira programática. Você pode acessá-lo por índice (em que 0 é a categoria superior):

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

Ou por ID:

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

Onde o ID é especificado na definição da caixa de ferramentas:

JSON

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

XML

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

Blocos predefinidos

A definição da caixa de ferramentas pode conter blocos com campos definidos como um valor padrão ou blocos que já estão conectados.

Aqui estão quatro blocos:

  1. Um bloco logic_boolean simples sem valores predefinidos:
  2. Um bloco math_number que foi modificado para exibir o número 42 em vez do padrão de 0:
  3. Um bloco controls_for com três blocos math_number conectados a ele:
  4. Um bloco math_arithmetic que tem dois math_number blocos de sombra conectados a ele:

Aqui está uma definição de caixa de ferramentas que contém estes quatro blocos:

JSON

A partir da versão de setembro de 2021, é possível especificar o estado de bloqueios sem 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 antigo

Antes da versão de setembro de 2021, era necessário usar a propriedade 'blockxml' para especificar o estado dos blocos.

{
  "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>

Escrever essas definições à mão pode ser... um pouco difícil. Em vez disso, é possível carregar os blocos em um espaço de trabalho e executar o código a seguir para ver as definições. Essas chamadas funcionam porque a caixa de ferramentas usa o mesmo formato para blocos que o sistema de serialização.

JSON

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

XML

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

Você também pode remover as propriedades x, y e id, porque elas são ignoradas pela caixa de ferramentas.

Blocos de sombra

Os blocos de sombra são marcadores de posição que executam várias funções:

  • Eles indicam os valores padrão do bloco pai.
  • Elas permitem que os usuários digitem valores diretamente sem precisar buscar um bloco de números ou strings.
  • Ao contrário de um bloco normal, eles são substituídos quando o usuário deixa um bloco em cima deles.
  • Eles informam o usuário o tipo de valor esperado.

Blocos desativados

Blocos desativados não podem ser arrastados da caixa de ferramentas. Os blocos podem ser desativados individualmente usando a propriedade disabled opcional.

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>

Também é possível desativar ou ativar programaticamente um bloqueio usando setEnabled.

Campos variáveis

Talvez os campos de variáveis precisem ser especificados de forma diferente quando estão em uma caixa de ferramentas e quando são simplesmente serializados.

Em particular, quando os campos de variáveis normalmente são serializados para JSON, eles contêm apenas o ID da variável que representam, porque o nome e o tipo da variável são serializados separadamente. No entanto, as caixas de ferramentas não contêm essas informações, portanto, elas precisam ser incluídas diretamente no campo de variável.

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

Separadores

Adicionar um separador entre duas categorias vai criar uma linha e um espaço extra entre elas.

Você pode alterar a classe do separador na definição da caixa de ferramentas JSON ou XML.

JSON

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

XML

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

Adicionar um separador entre dois blocos criará uma lacuna entre eles. Por padrão, cada bloco é separado de seu vizinho inferior em 24 pixels. Essa separação pode ser alterada usando o atributo "gap", que substituirá a lacuna padrão.

Isso permite criar grupos lógicos de blocos na caixa de ferramentas.

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>

Botões e rótulos

É possível colocar um botão ou um rótulo em qualquer lugar onde seja possível colocar um bloco na caixa de ferramentas.

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>

É possível especificar um nome de classe CSS para aplicar ao botão ou rótulo. No exemplo acima, o primeiro rótulo usa um estilo personalizado, enquanto o segundo usa o estilo padrão.

Os botões precisam ter funções de callback, mas os rótulos não. Para definir o retorno de chamada para um determinado clique de botão, use

yourWorkspace.registerButtonCallback(yourCallbackKey, yourFunction).

A função precisa aceitar como argumento o botão que foi clicado. O botão "Criar variável..." na categoria da variável é um bom exemplo de botão com callback.

Como mudar a caixa de ferramentas

O aplicativo pode alterar os blocos disponíveis na caixa de ferramentas a qualquer momento com uma única chamada de função:

workspace.updateToolbox(newTree);

Como aconteceu durante a configuração inicial, newTree pode ser uma árvore de nós, uma representação de string ou um objeto JSON. A única restrição é que o modo não pode ser alterado. Ou seja, se houver categorias na caixa de ferramentas definida inicialmente, a nova caixa de ferramentas também precisará ter categorias, embora elas possam mudar. Da mesma forma, se a caixa de ferramentas definida inicialmente não tinha nenhuma categoria, ela pode não ter nenhuma.

O conteúdo de uma única categoria pode ser atualizado da seguinte maneira:

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

Em que flyoutContents pode ser uma lista de blocos definidos usando JSON, uma árvore de nós ou uma representação de string.

No momento, a atualização da caixa de ferramentas causa algumas redefinições de interface:

  • Em uma caixa de ferramentas sem categorias, todos os campos alterados pelo usuário (como uma lista suspensa) serão revertidos para o padrão.

Veja uma demonstração ao vivo de uma árvore com categorias e grupos de blocos.