Trường thả xuống

Trường thả xuống lưu trữ một chuỗi làm giá trị và một chuỗi làm văn bản. Giá trị này là một khoá không phụ thuộc vào ngôn ngữ, sẽ được dùng để truy cập vào văn bản và sẽ không được dịch khi Blockly chuyển đổi giữa các ngôn ngữ. Văn bản này là một chuỗi ký tự mà con người đọc được và sẽ hiển thị cho người dùng.

Một khối có nhãn "trình đơn thả xuống:", một trường thả xuống có "mục đầu tiên" được chọn và nhãn "mục".

Khối tương tự với trình đơn thả xuống đang mở. Trình đơn thả xuống chứa các mục "first" và "second".

Khối đó sau khi được thu gọn. Nút này có nhãn "drop down: first item" (trình đơn thả xuống: mục đầu tiên) và có một cạnh phải gồ ghề để cho biết nút này đang ở trạng thái thu gọn.

dựa trên xu hướng

Hàm khởi tạo thả xuống nhận một trình tạo trình đơn và một trình xác thực không bắt buộc. Trình tạo trình đơn là một mảng các lựa chọn (trong đó mỗi lựa chọn chứa một phần dễ đọc và một chuỗi không phụ thuộc vào ngôn ngữ) hoặc một hàm tạo ra một mảng các lựa chọn. Phần mà con người có thể đọc được của mỗi lựa chọn có thể là một chuỗi, hình ảnh hoặc phần tử HTML và mảng có thể chứa nhiều lựa chọn thuộc các loại khác nhau.

Trình đơn thả xuống văn bản đơn giản

Mở trình đơn thả xuống có 2 lựa chọn văn bản

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

Việc tách thông tin mà con người có thể đọc được khỏi khoá không phụ thuộc vào ngôn ngữ cho phép chế độ cài đặt của trình đơn thả xuống được giữ nguyên giữa các ngôn ngữ. Ví dụ: phiên bản tiếng Anh của một khối có thể xác định [['left', 'LEFT'], ['right', 'RIGHT]] trong khi phiên bản tiếng Đức của cùng khối đó sẽ xác định [['links', 'LEFT'], ['rechts', 'RIGHT]].

Trình đơn thả xuống về hình ảnh

Các lựa chọn trong trình đơn thả xuống có thể là hình ảnh, được biểu thị dưới dạng các đối tượng có thuộc tính src, width, heightalt.

Trường thả xuống chứa hình ảnh và văn bản

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

Trình đơn thả xuống HTML

Lựa chọn có thể là bất kỳ phần tử HTML nào, miễn là phần tử đó không quá lớn và không cố gắng xử lý các sự kiện chuột hoặc bàn phím. (Bạn có trách nhiệm tuân thủ các quy tắc này – Blockly không thực thi các quy tắc đó.)

Khi trình đơn thả xuống mở ra, danh sách sẽ hiển thị phần tử HTML. Khi đóng và phần tử là lựa chọn đã chọn, danh sách sẽ hiển thị (theo thứ tự ưu tiên giảm dần) thuộc tính title, thuộc tính aria-label hoặc thuộc tính innerText của phần tử.

Trường thả xuống chứa các phần tử văn bản và 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);
  });

Bạn có thể thực hiện việc này bằng cách sử dụng tiện ích 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');
  }
};

Trình đơn thả xuống linh hoạt

Trường thả xuống có các ngày trong tuần

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

Bạn có thể thực hiện việc này bằng cách sử dụng tiện ích 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;
  }
};

Bạn cũng có thể cung cấp một trình đơn thả xuống bằng một hàm thay vì danh sách các lựa chọn tĩnh, cho phép các lựa chọn này thay đổi linh hoạt. Hàm này sẽ trả về một mảng các lựa chọn có cùng định dạng [human-readable-value, language-neutral-key] như các lựa chọn tĩnh. Mỗi khi người dùng nhấp vào trình đơn thả xuống, hàm sẽ chạy và các lựa chọn sẽ được tính toán lại.

Dòng phân cách

Sử dụng chuỗi 'separator' để thêm một dòng giữa các lựa chọn trong trình đơn thả xuống.

Trường thả xuống có một đường kẻ giữa lựa chọn thứ hai và thứ ba

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

Chuyển đổi tuần tự

JSON

JSON cho một trường thả xuống có dạng như sau:

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

Trong đó, FIELDNAME là một chuỗi tham chiếu đến một trường thả xuống và giá trị là giá trị cần áp dụng cho trường. Giá trị này phải là khoá tuỳ chọn không phụ thuộc vào ngôn ngữ.

XML

XML cho một trường thả xuống sẽ có dạng như sau:

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

Trong đó, thuộc tính name của trường chứa một chuỗi tham chiếu đến trường thả xuống và văn bản bên trong là giá trị cần áp dụng cho trường. Văn bản bên trong phải là một khoá tuỳ chọn hợp lệ, không phụ thuộc vào ngôn ngữ.

Tuỳ chỉnh

Bạn có thể dùng thuộc tính Blockly.FieldDropdown.ARROW_CHAR để thay đổi ký tự unicode biểu thị mũi tên thả xuống.

Trường thả xuống có mũi tên tuỳ chỉnh

Thuộc tính ARROW_CHAR mặc định là \u25BC (▼) trên Android và \u25BE (▾) trong trường hợp khác.

Đây là một thuộc tính chung, vì vậy, thuộc tính này sẽ sửa đổi tất cả các trường thả xuống khi được đặt.

Bạn có thể dùng thuộc tính Blockly.FieldDropdown.MAX_MENU_HEIGHT_VH để thay đổi chiều cao tối đa của trình đơn. Được xác định là tỷ lệ phần trăm chiều cao khung nhìn, khung nhìn là cửa sổ.

Thuộc tính MAX_MENU_HEIGHT_VH mặc định là 0,45.

Đây là một thuộc tính chung, vì vậy, thuộc tính này sẽ sửa đổi tất cả các trường thả xuống khi được đặt.

Khớp tiền tố/hậu tố

Nếu tất cả các lựa chọn trong trình đơn thả xuống đều có chung các từ tiền tố và/hoặc hậu tố, thì những từ này sẽ tự động được tách ra và chèn dưới dạng văn bản tĩnh. Ví dụ: dưới đây là 2 cách để tạo cùng một khối (cách đầu tiên không có tính năng so khớp hậu tố và cách thứ hai có):

Không có tính năng khớp hậu tố:

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

Với tính năng khớp hậu tố:

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

Trường thả xuống có

Một ưu điểm của phương pháp này là khối dễ dịch sang các ngôn ngữ khác. Mã trước đó có các chuỗi 'hello', 'world''computer', trong khi mã đã sửa đổi có các chuỗi 'hello world''hello computer'. Người dịch sẽ dễ dàng dịch các cụm từ hơn là dịch từng từ riêng lẻ.

Một ưu điểm khác của phương pháp này là thứ tự từ thường thay đổi giữa các ngôn ngữ. Hãy tưởng tượng một ngôn ngữ sử dụng 'world hello''computer hello'. Thuật toán so khớp hậu tố sẽ phát hiện 'hello' phổ biến và hiển thị hậu tố đó sau danh sách thả xuống.

Tuy nhiên, đôi khi việc so khớp tiền tố/hậu tố không thành công. Có một số trường hợp mà hai từ luôn đi cùng nhau và không nên tách tiền tố. Ví dụ: 'drive red car''drive red truck' chỉ nên có 'drive' được tách ra, chứ không phải 'drive red'. Bạn có thể dùng khoảng trắng không ngắt dòng Unicode '\u00A0' thay cho khoảng trắng thông thường để ngăn chặn trình so khớp tiền tố/hậu tố. Do đó, bạn có thể khắc phục ví dụ trên bằng 'drive red\u00A0car''drive red\u00A0truck'.

Một trường hợp khác mà tính năng so khớp tiền tố/hậu tố không hoạt động là trong các ngôn ngữ không phân tách các từ riêng lẻ bằng khoảng trắng. Tiếng Trung là một ví dụ điển hình. Chuỗi '訪問中國' có nghĩa là 'visit China', lưu ý rằng không có dấu cách giữa các từ. Hai ký tự cuối cùng ('中國') là từ 'China', nhưng nếu tách ra thì chúng sẽ có nghĩa là 'centre''country'. Để tính năng so khớp tiền tố/hậu tố hoạt động trong các ngôn ngữ như tiếng Trung, bạn chỉ cần chèn một khoảng trắng vào vị trí cần ngắt. Ví dụ: '訪問 中國''訪問 美國' sẽ tạo ra "visit [China/USA]", trong khi '訪問 中 國''訪問 美 國' sẽ tạo ra "visit [centre/beautiful] country".

Tạo trình xác thực thả xuống

Giá trị của trường thả xuống là một chuỗi trung lập về ngôn ngữ, vì vậy, mọi trình xác thực phải chấp nhận một chuỗi và trả về một chuỗi là một lựa chọn có sẵn, null hoặc undefined.

Nếu trình xác thực của bạn trả về bất kỳ giá trị nào khác, thì hành vi của Blockly sẽ không xác định và chương trình của bạn có thể gặp sự cố.

Ví dụ: bạn có thể xác định một trường thả xuống có 3 lựa chọn và một trình xác thực như sau:

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 luôn trả về giá trị mà nó được truyền, nhưng nó gọi hàm trợ giúp updateConnection để thêm hoặc xoá các đầu vào dựa trên giá trị của trình đơn thả xuống:

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

Ảnh GIF động cho thấy một trường thả xuống có 3 mục: &quot;không có&quot;, &quot;câu lệnh&quot; và &quot;giá trị&quot;. Khi bạn chọn &quot;không có&quot;, thì sẽ không có đầu vào nào. Khi bạn chọn &quot;statement&quot;, hệ thống sẽ có một đầu vào câu lệnh. Khi &quot;value&quot; được kết nối, nó sẽ có một đầu vào &quot;value&quot;.