Drop-down Menus

Drop-down menus have a lot of hidden functionality and can be somewhat complicated fields to configure.

Language Neutrality

JSON

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

JavaScript

init: function() {
  input.appendField('hello');
  var options = [['world', 'WORLD'], ['computer', 'CPU']];
  input.appendField(new Blockly.FieldDropdown(options), 'MODE');
}

Each dropdown menu is created with a list of menu options. Each option is made up of two strings. The first is the human-readable text to display. The second is a string constant which is used when saving the option to XML. This separation allows a dropdown menu's setting to be preserved between languages. For instance an English version of a block may define [['left', 'LEFT'], ['right', 'RIGHT']] while a German version of the same block would define [['links', 'LEFT'], ['rechts', 'RIGHT']].

Prefix/Suffix Matching

If all the menu options of a dropdown menu share common prefix and/or suffix words, these words are automatically factored out and inserted as static text. For example, the same "hello world" code above could be rewritten like this:

JSON

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

JavaScript

init: function() {
  var options = [['hello world', 'WORLD'], ['hello computer', 'CPU']];
  input.appendField(new Blockly.FieldDropdown(options), 'MODE');
}

One advantage of this approach is that the block is easier for to translate into other languages. The earlier code has the strings 'hello', 'world', and 'computer', whereas the revised code has the strings 'hello world' and 'hello computer'. Translators have a much easier time translating phrases than words in isolation.

Another advantage of this approach is that word order often changes between languages. Imagine a language that used 'world hello' and 'computer hello'. The suffix matching algorithm will detect the common 'hello' and display it after the drop-down.

However, sometimes the prefix/suffix matching fails. There are some cases where two words should always go together and the prefix should not be factored out. For example 'drive red car' and 'drive red truck' should arguably only have 'drive' factored out, not 'drive red'. The Unicode non-breaking space '\u00A0' may be used in place of a regular space to suppress the prefix/suffix matcher. Thus the above example can be fixed with 'drive red\u00A0car' and 'drive red\u00A0truck'.

Another place where prefix/suffix matching fails is in languages that do not separate individual words with spaces. Chinese is a good example. The string '訪問中國' means 'visit China', note the lack of spaces between words. Collectively, the last two characters ('中國') are the word for 'China', but if split they would individually they would mean 'centre' and 'country' respectively. To make prefix/suffix matching work in languages such as Chinese, just insert a space where the break should be. For example '訪問 中國' and '訪問 美國' would result in "visit [China/USA]", whereas '訪問 中 國' and '訪問 美 國' would result in "visit [centre/beautiful] country".

Images

JSON

{
  "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

init: function() {
  input.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');
}

Options in a dropdown menu may also be images instead of text. Image objects are specified with src, width, height, and alt properties. Note that although a dropdown can have a mix of some text options and some image options, an individual option cannot currently have both an image and text.

Dynamic Menu

Instead of providing a static list of options, one can provide a function that returns a list of options when called. Every time the menu is opened, the function is called and the options are recalculated. Here is an example that creates a menu with the days of the week starting with today:

function dynamicOptions() {
  var options = [];
  var now = Date.now();
  for (var i = 0; i < 7; i++) {
    options.push([String(new Date(now)).substring(0, 3), 'DAY' + i]);
    now += 24 * 60 * 60 * 1000;
  }
  return options;
}
var dropdown = new Blockly.FieldDropdown(dynamicOptions);
input.appendField(dropdown, 'DATE');

If today is Tuesday, running dynamicOptions would return:
[['Tue', 'DAY0'], ['Wed', 'DAY1'], ['Thu', 'DAY2'], ['Fri', 'DAY3'], ['Sat', 'DAY4'], ['Sun', 'DAY5'], ['Mon', 'DAY6']]

Prefix/suffix matching does not occur for dynamic menus.

Change Handler

Optionally, the FieldDropdown constructor can also take a second argument which becomes the validation function or change handler. The math.js file has a couple of good examples. See math_number_property where changing a dropdown modifies the block's shape. See math_on_list where changing a dropdown modifies the block's output check type.

Change handlers on fields are not called when a block is instantiated from XML (created, loaded, duplicated).