Campos do menu suspenso

O campo suspenso armazena uma string como valor e outra como texto. O valor é uma chave neutra em relação ao idioma que será usada para acessar o texto e não será traduzida quando o Blockly for alternado entre idiomas. O texto é uma string legível que será mostrada ao usuário.

Um bloco com o rótulo "drop down:", um campo suspenso com "first" selecionado e o rótulo "item".

O mesmo bloco com o menu suspenso aberto. O menu suspenso contém os itens "first"
e "second".

O mesmo bloco depois de ser recolhido. Ele tem o rótulo "drop down: first item"
e uma borda direita irregular para mostrar que está
encolhido.

Criação

O construtor suspenso usa um gerador de menu e um validador opcional. O gerador de menu é uma matriz de opções (em que cada opção contém uma parte legível por humanos e uma string neutra em relação ao idioma) ou uma função que gera uma matriz de opções. A parte legível de cada opção pode ser uma string, uma imagem ou um elemento HTML, e a matriz pode conter uma mistura de opções de diferentes tipos.

Menus suspensos de texto simples

Abrir menu suspenso com duas opções de texto

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

Manter as informações legíveis separadas da chave neutra em relação ao idioma permite que a configuração do menu suspenso seja preservada entre os idiomas. Por exemplo, uma versão em inglês de um bloco pode definir [['left', 'LEFT'], ['right', 'RIGHT]], enquanto uma versão em alemão do mesmo bloco definiria [['links', 'LEFT'], ['rechts', 'RIGHT]].

Menus suspensos de imagens

As opções em um menu suspenso podem ser imagens, que são representadas como objetos com propriedades src, width, height e alt.

Campo suspenso com imagens e texto

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

Menus suspensos HTML

Uma opção pode ser qualquer elemento HTML, desde que não seja muito grande e não tente processar eventos de mouse ou teclado. É sua responsabilidade seguir essas regras. O Blockly não as impõe.

Quando o menu suspenso está aberto, a lista mostra o elemento HTML. Quando ele está fechado e o elemento é a opção selecionada, a lista mostra (em ordem decrescente de preferência) o atributo title, o atributo aria-label ou a propriedade innerText do elemento.

Campo suspenso com elementos de texto e 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);
  });

Isso é feito usando uma extensão 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');
  }
};

Listas suspensas dinâmicas

Campo suspenso com os dias da semana

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

Isso é feito usando uma extensão 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;
  }
};

Um menu suspenso também pode ser fornecido com uma função em vez de uma lista de opções estáticas, o que permite que as opções sejam dinâmicas. A função precisa retornar uma matriz de opções no mesmo formato [human-readable-value, language-neutral-key] das opções estáticas. Toda vez que o menu suspenso é clicado, a função é executada e as opções são recalculadas.

Separadores

Use a string 'separator' para adicionar uma linha entre as opções em um menu suspenso.

Campo suspenso com uma linha entre a segunda e a terceira
opções

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

Serialização

JSON

O JSON de um campo suspenso tem esta aparência:

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

Em que FIELDNAME é uma string que faz referência a um campo suspenso, e o valor é o valor a ser aplicado ao campo. O valor precisa ser uma chave de opção neutra em relação ao idioma.

XML

O XML de um campo suspenso é assim:

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

Em que o atributo name do campo contém uma string que faz referência a um campo suspenso, e o texto interno é o valor a ser aplicado ao campo. O texto interno precisa ser uma chave de opção válida e neutra em relação ao idioma.

Personalização

A propriedade Blockly.FieldDropdown.ARROW_CHAR pode ser usada para mudar o caractere Unicode que representa a seta suspensa.

Campo suspenso com seta personalizada

A propriedade ARROW_CHAR assume como padrão \u25BC (▼) no Android e \u25BE (▾) em outros casos.

Essa é uma propriedade global, então ela vai modificar todos os campos suspensos quando definida.

A propriedade Blockly.FieldDropdown.MAX_MENU_HEIGHT_VH pode ser usada para mudar a altura máxima do menu. Ela é definida como uma porcentagem da altura da janela de visualização, que é a janela.

O padrão da propriedade MAX_MENU_HEIGHT_VH é 0,45.

Essa é uma propriedade global, então ela vai modificar todos os campos suspensos quando definida.

Correspondência de prefixo/sufixo

Se todas as opções do menu suspenso compartilharem prefixos e/ou sufixos comuns, essas palavras serão automaticamente fatoradas e inseridas como texto estático. Por exemplo, confira duas maneiras de criar o mesmo bloco (a primeira sem correspondência de sufixo e a segunda com):

Sem correspondência de sufixo:

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

Com a correspondência de sufixo:

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

Campo suspenso com

Uma vantagem dessa abordagem é que o bloco é mais fácil de traduzir para outros idiomas. O código anterior tem as strings 'hello', 'world' e 'computer', enquanto o código revisado tem as strings 'hello world' e 'hello computer'. É muito mais fácil para os tradutores traduzir frases do que palavras isoladas.

Outra vantagem dessa abordagem é que a ordem das palavras geralmente muda entre os idiomas. Imagine uma linguagem que usasse 'world hello' e 'computer hello'. O algoritmo de correspondência de sufixos vai detectar o 'hello' comum e mostrar depois do menu suspenso.

No entanto, às vezes, a correspondência de prefixo/sufixo falha. Há alguns casos em que duas palavras devem sempre ficar juntas e o prefixo não deve ser fatorado. Por exemplo, 'drive red car' e 'drive red truck' só devem ter 'drive' fatorado, não 'drive red'. O espaço inseparável Unicode '\u00A0' pode ser usado no lugar de um espaço normal para suprimir o correspondente de prefixo/sufixo. Assim, o exemplo acima pode ser corrigido com 'drive red\u00A0car' e 'drive red\u00A0truck'.

Outro caso em que a correspondência de prefixo/sufixo falha é em idiomas que não separam palavras individuais com espaços. O chinês é um bom exemplo. A string '訪問中國' significa 'visit China'. Observe a falta de espaços entre as palavras. Coletivamente, os dois últimos caracteres ('中國') são a palavra para 'China', mas se forem divididos, significariam 'centre' e 'country', respectivamente. Para que a correspondência de prefixo/sufixo funcione em idiomas como o chinês, basta inserir um espaço onde a quebra deve ocorrer. Por exemplo, '訪問 中國' e '訪問 美國' resultariam em "visit [China/USA]", enquanto '訪問 中 國' e '訪問 美 國' resultariam em "visit [centre/beautiful] country".

Como criar um validador de menu suspenso

O valor de um campo suspenso é uma string neutra em relação ao idioma. Portanto, todos os validadores precisam aceitar uma string e retornar uma string que seja uma opção disponível, null ou undefined.

Se o validador retornar qualquer outra coisa, o comportamento do Blockly será indefinido e o programa poderá falhar.

Por exemplo, você pode definir um campo suspenso com três opções e um validador assim:

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 sempre retorna o valor que foi transmitido, mas chama a função auxiliar updateConnection, que adiciona ou remove entradas com base no valor do menu suspenso:

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

Um GIF animado mostrando um campo suspenso com três itens: &quot;nenhum&quot;, &quot;declaração&quot; e &quot;valor&quot;. Quando &quot;nenhum&quot; é selecionado, não há entradas. Quando &quot;declaração&quot; é selecionada, ela tem uma entrada de declaração. Quando &quot;value&quot; está conectado, ele tem uma entrada &quot;value&quot;.