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

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

Создание

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

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

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

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');
  }
};

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

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

JSON

{
  "type": "dynamic_dropdown",
  "message0": "day %1",
  "args0": [
    {
      "type": "input_dummy",
      "name": "INPUT"
    }
  ],
  "extensions": ["dynamic_menu_extension"]
}
Blockly.Extensions.register('dynamic_menu_extension',
  function() {
    this.getInput('INPUT')
      .appendField(new Blockly.FieldDropdown(
        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;
        }), 'DAY');
  });

Это делается с помощью расширения 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] что и статические параметры. Каждый раз, когда щелкают раскрывающийся список, запускается функция и параметры пересчитываются.

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

JSON

JSON для раскрывающегося поля выглядит так:

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

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

XML

XML для раскрывающегося поля выглядит так:

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

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

Кастомизация

Свойство 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' . Неразрывный пробел Юникода '\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');
  }
}