下拉式選單欄位

下拉式選單欄位會將字串儲存為值,並將字串儲存為文字。這個值是與語言無關的鍵,用於存取文字,且在 Blockly 切換語言時不會翻譯。這段文字是使用者容易理解的字串,會顯示給使用者。

標籤為「drop down:」的方塊、選取「first」的下拉式選單欄位,以及「item」標籤。

下拉式選單開啟時的相同方塊。下拉式選單包含「first」和「second」項目。

收合後的相同區塊。標籤為「drop down: first item」,右側邊緣呈鋸齒狀,表示已收合。

創作

下拉式選單建構函式會採用選單產生器和選用的驗證器。選單產生器可以是選項陣列 (每個選項都包含使用者可讀的部分和與語言無關的字串),也可以是產生選項陣列的函式。每個選項的人類可讀部分可以是字串、圖片或 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 字元。

含自訂箭頭的下拉式選單欄位

在 Android 上,ARROW_CHAR 屬性預設為 \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:顯示下拉式欄位,內含「兩者皆非」、「陳述」和「值」三項。選取「兩者皆非」時,不會有任何輸入內容。選取「陳述式」時,系統會顯示陳述式輸入欄位。連結「值」時,該節點會顯示「值」輸入內容。