Поля раскрывающегося списка

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

Блок с меткой "выпадающий список:", выпадающее поле с выбранным пунктом "первый" и меткой "элемент".

Тот же блок с открытым выпадающим списком. В выпадающем списке находятся пункты «первый» и «второй».

Тот же блок в свернутом состоянии. На нем есть метка "выпадающий список: первый элемент" и зазубренный правый край, указывающий на то, что он свернут.

Творение

Конструктор выпадающего списка принимает генератор меню и необязательный валидатор . Генератор меню может представлять собой либо массив вариантов (где каждый вариант содержит удобочитаемую часть и нейтральную для языка строку), либо функцию, генерирующую массив вариантов. Удобочитаемая часть каждого варианта может быть строкой, изображением или HTML-элементом, а массив может содержать комбинацию вариантов разных типов.

Простые текстовые выпадающие списки

Откройте выпадающее меню с двумя вариантами текста.

JSON

{
  "type": "example_dropdown",
  "message0": "drop down: %1",
  "args0": [
    {
      "type": "field_dropdown",
      "name": "FIELDNAME",
      "options": [
        [ "first item", "ITEM1" ],
        [ "second item", "ITEM2" ]
      ]
    }
  ]
}

JavaScript

Blockly.Blocks['example_dropdown'] = {
  init: function() {
    this.appendDummyInput()
        .appendField('drop down:')
        .appendField(new Blockly.FieldDropdown([
            ['first item', 'ITEM1'],
            ['second item', 'ITEM2']
        ]), 'FIELDNAME');
  }
};

Разделение удобочитаемой информации от нейтрального к языку ключа позволяет сохранять настройки выпадающего меню между языками. Например, английская версия блока может определяться [['left', 'LEFT'], ['right', 'RIGHT]] а немецкая версия того же блока будет определяться как [['links', 'LEFT'], ['rechts', 'RIGHT]] .

Выпадающие списки изображений

В выпадающем меню могут быть представлены изображения, которые отображаются в виде объектов со свойствами src , width , height и alt .

Выпадающее поле, содержащее изображения и текст.

JSON

{
  "type": "image_dropdown",
  "message0": "flag %1",
  "args0": [
    {
      "type": "field_dropdown",
      "name": "FLAG",
      "options": [
        ["none", "NONE"],
        [{"src": "canada.png", "width": 50, "height": 25, "alt": "Canada"}, "CANADA"],
        [{"src": "usa.png", "width": 50, "height": 25, "alt": "USA"}, "USA"],
        [{"src": "mexico.png", "width": 50, "height": 25, "alt": "Mexico"}, "MEXICO"]
      ]
    }
  ]
}

JavaScript

Blockly.Blocks['image_dropdown'] = {
  init: function() {
    var input = this.appendDummyInput()
        .appendField('flag');
    var options = [
        ['none', 'NONE'],
        [{'src': 'canada.png', 'width': 50, 'height': 25, 'alt': 'Canada'}, 'CANADA'],
        [{'src': 'usa.png', 'width': 50, 'height': 25, 'alt': 'USA'}, 'USA'],
        [{'src': 'mexico.png', 'width': 50, 'height': 25, 'alt': 'Mexico'}, 'MEXICO']
    ];
    input.appendField(new Blockly.FieldDropdown(options), 'FLAG');
  }
};

HTML-выпадающие списки

В качестве варианта может использоваться любой HTML-элемент, если он не слишком большой и не пытается обрабатывать события мыши или клавиатуры. (Вы несете ответственность за соблюдение этих правил — Blockly их не применяет.)

Когда выпадающее меню открыто, в списке отображается HTML-элемент. Когда оно закрыто и элемент выбран в качестве параметра, в списке отображается (в порядке убывания приоритета) атрибут title элемента, его атрибут aria-label или свойство innerText .

Выпадающее поле, содержащее текст и HTML-элементы

JSON

{
  "type": "flags_with_text_dropdown",
  "message0": "flag with text %1",
  "args0": [
    {
      "type": "field_dropdown",
      "name": "FLAG_WITH_TEXT",
      "options": [
        ["x", "X"], // Placeholder. An empty array throws an exception.
      ]
    }
  ],
  // Use an extension to add the HTML element options.
  "extensions": ["flag_with_text_extension"]
}
Blockly.Extensions.register('flag_with_text_extension',
  function() {
    function createFlagWithTextDiv(text, src) {
      const div = document.createElement('div');
      div.setAttribute('style', 'width: 75px;');
      div.setAttribute('title', text);
      const img = document.createElement('img');
      img.setAttribute('src', src);
      img.setAttribute('style', 'height: 25px; display: block; margin: auto;');
      div.appendChild(img);
      const para = document.createElement('p');
      para.innerText = text;
      para.setAttribute('style', 'text-align: center; margin: 5px;');
      div.appendChild(para);
      return div;
    }

    const canadaDiv = createFlagWithTextDiv('Canada', 'canada.png');
    const usaDiv = createFlagWithTextDiv('USA', 'usa.png');
    const mexicoDiv = createFlagWithTextDiv('Mexico', 'mexico.png');
    const options = [
      ['none', 'NONE'],
      [canadaDiv, 'CANADA'],
      [usaDiv, 'USA'],
      [mexicoDiv, 'MEXICO']
    ];
    this.getField('FLAG_WITH_TEXT').setOptions(options);
  });

Это делается с помощью расширения JSON.

JavaScript

function createFlagWithTextDiv(text, src) {
  const div = document.createElement('div');
  div.setAttribute('style', 'width: 75px;');
  div.setAttribute('title', text);
  const img = document.createElement('img');
  img.setAttribute('src', src);
  img.setAttribute('style', 'height: 25px; display: block; margin: auto;');
  div.appendChild(img);
  const para = document.createElement('p');
  para.innerText = text;
  para.setAttribute('style', 'text-align: center; margin: 5px;');
  div.appendChild(para);
  return div;
}

const canadaDiv = createFlagWithTextDiv('Canada', 'canada.png');
const usaDiv = createFlagWithTextDiv('USA', 'usa.png');
const mexicoDiv = createFlagWithTextDiv('Mexico', 'mexico.png');

Blockly.Blocks['flags_with_text_dropdown'] = {
  init: function() {
    const input = this.appendDummyInput()
        .appendField('flag with text');
    const options = [
        ['none', 'NONE'],
        [canadaDiv, 'CANADA'],
        [usaDiv, 'USA'],
        [mexicoDiv, 'MEXICO']
    ];
    input.appendField(new Blockly.FieldDropdown(options), 'FLAG_WITH_TEXT');
  }
};

Динамические выпадающие списки

Выпадающее поле с названиями дней недели

JSON

{
  "type": "dynamic_dropdown",
  "message0": "day %1",
  "args0": [
    {
      "type": "field_dropdown",
      "name": "DAY",
      "options": [
        ["x", "X"], // Placeholder. An empty array throws an exception.
      ]
     }
  ],
  // Use an extension to set the menu function.
  "extensions": ["dynamic_menu_extension"]
}
Blockly.Extensions.register('dynamic_menu_extension',
  function() {
    this.getField('DAY').setOptions(
      function() {
        var options = [];
        var now = Date.now();
        for(var i = 0; i < 7; i++) {
          var dateString = String(new Date(now)).substring(0, 3);
          options.push([dateString, dateString.toUpperCase()]);
          now += 24 * 60 * 60 * 1000;
        }
        return options;
      });
  });

Это делается с помощью расширения JSON.

JavaScript

Blockly.Blocks['dynamic_dropdown'] = {
  init: function() {
    var input = this.appendDummyInput()
      .appendField('day')
      .appendField(new Blockly.FieldDropdown(
        this.generateOptions), 'DAY');
  },

  generateOptions: function() {
    var options = [];
    var now = Date.now();
    for(var i = 0; i < 7; i++) {
      var dateString = String(new Date(now)).substring(0, 3);
      options.push([dateString, dateString.toUpperCase()]);
      now += 24 * 60 * 60 * 1000;
    }
    return options;
  }
};

Вместо списка статических вариантов в выпадающем списке можно также использовать функцию, что позволяет динамически изменять параметры. Функция должна возвращать массив вариантов в том же формате [human-readable-value, language-neutral-key] что и статические варианты. При каждом щелчке по выпадающему списку функция запускается, и параметры пересчитываются.

Сепараторы

Используйте строковый параметр 'separator' , чтобы добавить разделительную линию между пунктами в выпадающем меню.

Выпадающее поле с линией между вторым и третьим вариантами

JSON

{
  "type": "separator_dropdown",
  "message0": "food %1",
  "args0": [
    {
      "type": "field_dropdown",
      "name": "FOOD",
      "options": [
        ["water", "WATER"],
        ["juice", "JUICE"],
        "separator",
        ["salad", "SALAD"],
        ["soup", "SOUP"],
      ]
    }
  ]
}

JavaScript

Blockly.Blocks["separator_dropdown"] = {
  init: function() {
    var input = this.appendDummyInput()
        .appendField("food1");
    var options = [
        ["water", "WATER"],
        ["juice", "JUICE"],
        "separator",
        ["salad", "SALAD"],
        ["soup", "SOUP"],
    ];
    input.appendField(new Blockly.FieldDropdown(options), "FOOD");
  }
};

Сериализация

JSON

JSON-объект для выпадающего списка выглядит следующим образом:

{
  "fields": {
    "FIELDNAME": "LANGUAGE-NEUTRAL-KEY"
  }
}

Где FIELDNAME — это строка, ссылающаяся на выпадающее поле, а value — это значение, которое нужно применить к полю. Значение должно быть ключом опции, не зависящим от языка.

XML

XML-код для выпадающего списка выглядит следующим образом:

<field name="FIELDNAME">LANGUAGE-NEUTRAL-KEY</field>

Атрибут name поля содержит строку, ссылающуюся на выпадающий список, а inner text — это значение, которое нужно применить к полю. inner text должен быть допустимым ключом опции, не зависящим от языка.

Настройка

Свойство Blockly.FieldDropdown.ARROW_CHAR можно использовать для изменения символа Юникода, представляющего стрелку выпадающего списка.

Выпадающее поле с настраиваемой стрелкой

Свойство ARROW_CHAR по умолчанию принимает значение \u25BC (▼) на Android и \u25BE (▾) в остальных случаях.

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

Свойство Blockly.FieldDropdown.MAX_MENU_HEIGHT_VH можно использовать для изменения максимальной высоты меню. Оно определяется в процентах от высоты области просмотра, где область просмотра — это окно.

Значение свойства MAX_MENU_HEIGHT_VH по умолчанию равно 0,45.

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

Сопоставление префикса и суффикса

Если все пункты выпадающего меню имеют общие префиксы и/или суффиксы, эти слова автоматически выносятся за скобки и вставляются как статический текст. Например, вот два способа создать один и тот же блок (первый — без сопоставления суффиксов, второй — с сопоставлением):

Без сопоставления с суффиксом:

JSON

{
  "type": "dropdown_no_matching",
  "message0": "hello %1",
  "args0": [
    {
      "type": "field_dropdown",
      "name": "MODE",
      "options": [
        ["world", "WORLD"],
        ["computer", "CPU"]
      ]
    }
  ]
}

JavaScript

Blockly.Blocks['dropdown_no_matching'] = {
  init: function() {
    var options = [
      ['world', 'WORLD'],
      ['computer', 'CPU']
    ];

    this.appendDummyInput()
        .appendField('hello')
        .appendField(new Blockly.FieldDropdown(options), 'MODE');
  }
};

С использованием суффиксного сопоставления:

JSON

{
  "type": "dropdown_with_matching",
  "message0": "%1",
  "args0": [
    {
      "type": "field_dropdown",
      "name": "MODE",
      "options": [
        ["hello world", "WORLD"],
        ["hello computer", "CPU"]
      ]
    }
  ]
}

JavaScript

Blockly.Blocks['dropdown_with_matching'] = {
  init: function() {
    var options = [
      ['hello world', 'WORLD'],
      ['hello computer', 'CPU']
    ];

    this.appendDummyInput()
        .appendField(new Blockly.FieldDropdown(options), 'MODE');
  }
};

Выпадающее поле с

Одним из преимуществ такого подхода является то, что блок легче перевести на другие языки. В первоначальном коде использовались строки 'hello' , 'world' и 'computer' , тогда как в пересмотренном коде — строки 'hello world' и 'hello computer' . Переводчикам гораздо проще переводить фразы, чем отдельные слова.

Еще одно преимущество этого подхода заключается в том, что порядок слов часто меняется между языками. Представьте себе язык, в котором используются выражения 'world hello' и 'computer hello' . Алгоритм сопоставления суффиксов обнаружит распространенное 'hello' и отобразит его после выпадающего списка.

Однако иногда сопоставление префикса и суффикса не удается. В некоторых случаях два слова всегда должны идти вместе, и префикс не следует выделять. Например, в словах 'drive red car' и 'drive red truck' следует, вероятно, выделить только 'drive' , а не 'drive red' . Вместо обычного пробела можно использовать неразрывный пробел Unicode '\u00A0' , чтобы подавить сопоставление префикса и суффикса. Таким образом, приведенный выше пример можно исправить с помощью 'drive red\u00A0car' и 'drive red\u00A0truck' .

Еще один пример, где сопоставление префиксов/суффиксов не работает, — это языки, в которых отдельные слова не разделяются пробелами. Китайский язык — хороший тому пример. Строка '訪問中國' означает 'visit China' , обратите внимание на отсутствие пробелов между словами. В совокупности последние два символа ( '中國' ) образуют слово 'China' , но если их разделить, они будут означать 'centre' и 'country' соответственно. Чтобы сопоставление префиксов/суффиксов работало в таких языках, как китайский, достаточно вставить пробел там, где должен быть разрыв. Например '訪問 中國' и '訪問 美國' дадут результат "visit [China/USA]" , тогда как '訪問 中 國' и '訪問 美 國' дадут результат "visit [centre/beautiful] country" .

Создание валидатора выпадающего списка

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

Если ваш валидатор вернет что-либо иное, поведение Blockly будет неопределенным, и ваша программа может завершиться с ошибкой.

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

validate: function(newValue) {
  this.getSourceBlock().updateConnections(newValue);
  return newValue;
},

init: function() {
  var options = [
   ['has neither', 'NEITHER'],
   ['has statement', 'STATEMENT'],
   ['has value', 'VALUE'],
  ];

  this.appendDummyInput()
  // Pass the field constructor the options list, the validator, and the name.
      .appendField(new Blockly.FieldDropdown(options, this.validate), 'MODE');
}

validate всегда возвращает переданное ей значение, но при этом вызывает вспомогательную функцию updateConnection , которая добавляет или удаляет поля ввода в зависимости от значения в выпадающем списке:

updateConnections: function(newValue) {
  this.removeInput('STATEMENT', /* no error */ true);
  this.removeInput('VALUE', /* no error */ true);
  if (newValue == 'STATEMENT') {
    this.appendStatementInput('STATEMENT');
  } else if (newValue == 'VALUE') {
    this.appendValueInput('VALUE');
  }
}

Анимированный GIF-файл, демонстрирующий выпадающее поле с тремя элементами: «ни то, ни другое», «утверждение» и «значение». При выборе «ни то, ни другое» поле не имеет полей ввода. При выборе «утверждения» поле имеет поле ввода «утверждение». При подключении «значения» поле имеет поле ввода «значение».