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.
Campo de menu suspenso

Campo suspenso com o editor aberto

Campo suspenso em um bloco recolhido

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

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.

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.

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

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.

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
Seta suspensa
A propriedade Blockly.FieldDropdown.ARROW_CHAR pode ser usada para mudar o
caractere Unicode que representa a seta suspensa.

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.
Altura do menu
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');
}
};

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