下拉菜单字段

下拉字段将字符串存储为值,并将字符串存储为文本。该值是一个与语言无关的键,用于访问文本,并且在 Blockly 切换语言时不会被翻译。该文本是直观易懂的字符串,将显示给用户。

一个带有标签“drop down:”的块,一个选择了“first”的下拉字段,以及标签“item”。

下拉菜单处于打开状态的同一代码块。下拉菜单包含“first”和“second”这两个项。

折叠后的同一代码块。它带有“下拉菜单:第一个项目”标签,右边缘呈锯齿状,表示它处于收起状态。

创建

下拉菜单构造函数会接收菜单生成器和可选的验证器。菜单生成器可以是选项数组(其中每个选项都包含人类可读的部分和语言中立的字符串),也可以是生成选项数组的函数。每个选项的人类可读部分可以是字符串、图片或 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]]

图片下拉菜单

下拉菜单中的选项可以是图片,这些图片表示为具有 srcwidthheightalt 属性的对象。

包含图片和文字的下拉字段

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 是引用下拉字段的字符串,而相应的值是要应用于该字段的值。该值应为与语言无关的选项键。

XML

下拉字段的 XML 如下所示:

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

其中,字段的 name 属性包含引用下拉菜单字段的字符串,而内部文本是要应用于该字段的值。内部文本应为有效的语言中性选项键。

自定义

Blockly.FieldDropdown.ARROW_CHAR 属性可用于更改表示下拉箭头的 Unicode 字符。

带有自定义箭头的下拉字段

ARROW_CHAR 属性在 Android 上默认为 \u25BC (▼),在其他情况下默认为 \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"

创建下拉验证器

下拉菜单字段的值是与语言无关的字符串,因此任何验证器都必须接受字符串并返回可用选项nullundefined

如果验证器返回任何其他内容,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,显示一个下拉字段,其中包含三个项:“neither”“statement”和“value”。如果选择“两者均不”,则没有输入。如果选择“陈述”,则会显示陈述输入框。当“value”已连接时,它会有一个“value”输入。