Ящик для инструментов

Панель инструментов — это место, где пользователи получают блоки. Обычно он отображается на одной стороне рабочей области. Иногда у него есть категории, иногда нет.

На этой странице основное внимание уделяется тому, как указать структуру вашего набора инструментов (т. е. какие категории он имеет и какие блоки они содержат). Если вам нужны дополнительные сведения о том, как изменить пользовательский интерфейс вашего набора инструментов, ознакомьтесь с лабораторной работой по настройке кода набора инструментов Blockly и беседой об API Toolbox 2021 .

Форматы

Blockly позволяет вам указать структуру вашего набора инструментов, используя несколько различных форматов. В новом рекомендуемом формате используется JSON, а в старом формате — XML.

Вот различные способы указания вышеуказанного набора инструментов:

JSON

Начиная с выпуска за сентябрь 2020 года наборы инструментов можно определять с помощью 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>

XML-строка

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

Категории

Блоки на панели инструментов могут быть организованы по категориям.

Вот способы определения вышеуказанного набора инструментов, который имеет две категории («Управление» и «Логика»), каждая из которых содержит блоки:

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>

Вложенные категории

Категории могут быть вложены в другие категории. Вот две категории верхнего уровня («Основная» и «Пользовательская»), вторая из которых содержит две подкатегории, каждая из которых содержит блоки:

Обратите внимание, что категория может содержать как подкатегории , так и блоки. В приведенном выше примере «Пользовательский» имеет две подкатегории («Перемещение» и «Поворот»), а также собственный блок («Начало»).

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>

Динамические категории

Динамические категории — это категории, которые динамически заполняются на основе функции каждый раз, когда они открываются.

Blockly поддерживает это, позволяя вам связать категорию с функцией через зарегистрированный строковый ключ. Функция должна возвращать определение содержимого категории (включая блоки, кнопки, метки и т. д.). Содержимое можно указать в формате JSON или XML, хотя рекомендуется использовать JSON.

Также обратите внимание, что функции предоставляется целевая рабочая область в качестве параметра, поэтому блоки в вашей динамической категории могут основываться на состоянии рабочей области.

JSON

Начиная с выпуска за сентябрь 2021 г., вы можете указывать состояние блоков без использования '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

До выпуска за сентябрь 2021 г. вам приходилось использовать свойство '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', // 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);

После того как функции динамической категории связаны со строковым ключом (он же зарегистрированный), вы можете назначить этот строковый ключ custom свойству определения категории, чтобы сделать категорию динамической.

JSON

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

XML

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

Встроенные динамические категории

Blockly предоставляет три встроенные динамические категории.

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>

Примечание. Слово «процедура» используется во всем коде Blockly, но слово «функция» оказалось более понятным для студентов. Извините за несоответствие.

Отключение

Отключенная категория не позволит пользователю открыть ее, и она будет пропущена при навигации с помощью клавиатуры.

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

Когда категория отключена, к элементу DOM добавляется свойство 'disabled' , которое позволяет вам управлять внешним видом отключенной категории.

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

Прячется

Скрытая категория не будет отображаться как часть панели инструментов. Скрытые категории позже можно будет отобразить с помощью JavaScript.

JSON

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

XML

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

JavaScript

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

Расширение

Это применимо только к категориям, которые содержат другие вложенные категории .

Расширенная категория покажет вам ее подкатегории. По умолчанию вложенные категории свернуты, и их нужно щелкнуть, чтобы развернуть.

JSON

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

XML

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

Стиль

Blockly предоставляет пользовательский интерфейс категорий по умолчанию, а также некоторые основные параметры стиля. Если вам нужна информация о том, как выполнить более сложное оформление/настройку пользовательского интерфейса, ознакомьтесь с лабораторной работой по настройке кода Blockly и беседой об API-интерфейсах Toolbox 2021 .

Темы

Темы позволяют вам указать все цвета вашего рабочего пространства одновременно, включая цвета наших категорий.

Чтобы использовать их, вам необходимо связать свою категорию с определенным стилем категории:

JSON

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

XML

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

Цвета

Вы также можете указать цвет напрямую, но это не рекомендуется. Цвет представляет собой строковое число (0–360), определяющее оттенок. Обратите внимание на британское написание.

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>

Обратите внимание, что мы также поддерживаем использование локализуемых ссылок на цвета .

Категория CSS

Если вам нужна более мощная настройка, Blockly также позволяет вам указывать классы CSS для различных элементов пользовательского интерфейса по умолчанию. Затем вы можете использовать CSS для их стилизации.

К следующим типам элементов могут быть применены классы CSS:

  • контейнер — класс родительского элемента div для категории. blocklyToolboxCategory по умолчанию.
  • row — класс элемента div, содержащий метку и значок категории. blocklyTreeRow по умолчанию.
  • icon — класс значка категории. blocklyTreeIcon по умолчанию.
  • label — класс метки категории. blocklyTreeLabel по умолчанию.
  • selected — класс, который добавляется в категорию при ее выборе. По умолчанию blocklyTreeSelected .
  • openicon — класс, добавляемый к значку, когда категория имеет вложенные категории и открыта. По умолчанию blocklyTreeIconOpen .
  • Closedicon — класс, добавляемый к значку, когда категория имеет вложенные категории и закрыта. По умолчанию blocklyTreeIconClosed .

Вот как вы указываете классы, используя любой формат:

JSON

Установите класс CSS для определенного типа элемента, используя свойство cssConfig.

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

XML

Установите класс CSS для определенного типа элемента, добавив к нему «css-».

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

Доступ

Программный доступ к категории можно получить двумя способами. Вы можете получить к нему доступ по индексу (где 0 — верхняя категория):

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

Или по идентификатору:

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

Если идентификатор указан в определении панели инструментов:

JSON

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

XML

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

Предустановленные блоки

Определение набора инструментов может содержать блоки, поля которых имеют значения по умолчанию, или блоки, которые уже соединены вместе.

Вот четыре блока:

  1. Простой блок logic_boolean без предустановленных значений:
  2. Блок math_number , который был изменен для отображения числа 42 вместо значения по умолчанию 0:
  3. Блок controls_for , к которому подключены три блока math_number :
  4. Блок math_arithmetic , к которому подключены два теневых блока math_number :

Вот определение набора инструментов, содержащее эти четыре блока:

JSON

Начиная с выпуска за сентябрь 2021 г., вы можете указывать состояние блоков без использования '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

До выпуска за сентябрь 2021 г. вам приходилось использовать свойство 'blockxml' , чтобы указать состояние блоков.

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

Записывать эти определения вручную может быть... немного муторно. Вместо этого вы можете загрузить блоки в рабочую область, а затем запустить следующий код, чтобы получить определения. Эти вызовы работают, поскольку набор инструментов использует тот же формат блоков, что и система сериализации.

JSON

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

XML

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

Вы также можете удалить свойства x , y и id , поскольку они игнорируются панелью инструментов.

Теневые блоки

Теневые блоки — это блоки-заполнители, выполняющие несколько функций:

  • Они указывают значения по умолчанию для своего родительского блока.
  • Они позволяют пользователям вводить значения напрямую, без необходимости извлечения числового или строкового блока.
  • В отличие от обычного блока, они заменяются, если пользователь помещает блок поверх них.
  • Они информируют пользователя о типе ожидаемого значения.

Отключенные блоки

Отключенные блоки нельзя перетащить из панели инструментов. Блоки можно отключать по отдельности, используя необязательное свойство 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>

Вы также можете программно отключить или включить блок, используя setEnabled .

Переменные поля

Поля переменных, возможно, придется указывать по-разному, когда они находятся в наборе инструментов, и когда они просто сериализованы.

В частности, когда поля переменных обычно сериализуются в JSON, они содержат только идентификатор переменной, которую они представляют, поскольку имя и тип переменной сериализуются отдельно. Однако наборы инструментов не содержат этой информации, поэтому ее необходимо включить непосредственно в поле переменной.

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

Сепараторы

Добавление разделителя между любыми двумя категориями создаст линию и дополнительное пространство между двумя категориями.

Вы можете изменить класс разделителя в определении вашего набора инструментов JSON или XML.

JSON

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

XML

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

Добавление разделителя между любыми двумя блоками создаст разрыв между блоками. По умолчанию каждый блок отделен от своего нижнего соседа на 24 пикселя. Это разделение можно изменить с помощью атрибута «пробел», который заменит пробел по умолчанию.

Это позволяет создавать логические группы блоков в наборе инструментов.

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>

Кнопки и метки

Вы можете разместить кнопку или метку в любом месте панели инструментов, где можно разместить блок.

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>

Вы можете указать имя класса CSS, которое будет применяться к вашей кнопке или метке. В приведенном выше примере первая метка использует собственный стиль, а вторая — стиль по умолчанию.

Кнопки должны иметь функции обратного вызова; этикеток не должно быть. Чтобы установить обратный вызов для данного нажатия кнопки, используйте

yourWorkspace.registerButtonCallback(yourCallbackKey, yourFunction).

Ваша функция должна принимать в качестве аргумента кнопку, на которую была нажата кнопка. Кнопка «Создать переменную...» в категории переменных — хороший пример кнопки с обратным вызовом.

Изменение панели инструментов

Приложение может изменить блоки, доступные в панели инструментов, в любое время с помощью одного вызова функции:

workspace.updateToolbox(newTree);

Как и во время первоначальной настройки, newTree может быть деревом узлов, строковым представлением или объектом JSON. Единственное ограничение состоит в том, что режим нельзя изменить; то есть, если в первоначально определенном наборе инструментов были категории, то новый набор инструментов также должен иметь категории (хотя категории могут меняться). Аналогично, если изначально определенный набор инструментов не имел категорий, то и новый набор инструментов может не иметь категорий.

Содержимое одной категории можно обновить следующим образом:

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

Где FlyoutContents может быть списком блоков, определенных с использованием JSON, дерева узлов или строкового представления.

Имейте в виду, что в настоящее время обновление набора инструментов приводит к незначительному сбросу пользовательского интерфейса:

  • В наборе инструментов без категорий любые поля, измененные пользователем (например, раскрывающийся список), вернутся к значениям по умолчанию.

Вот живая демонстрация дерева с категориями и группами блоков.