Il campo menu a discesa memorizza una stringa come valore e una stringa come testo. Il valore è una chiave indipendente dalla lingua che verrà utilizzata per accedere al testo e non verrà tradotta quando Blockly passa da una lingua all'altra. Il testo è una stringa leggibile che verrà mostrata all'utente.
Campo menu a discesa

Campo menu a discesa con l'editor aperto

Campo menu a discesa sul blocco compresso

Creazione
Il costruttore del menu a discesa accetta un generatore di menu e uno strumento di convalida facoltativo . Il generatore di menu è un array di opzioni (dove ogni opzione contiene una parte leggibile e una stringa indipendente dalla lingua) o una funzione che genera un array di opzioni. La parte leggibile di ogni opzione può essere una stringa, un'immagine o un elemento HTML e l'array può contenere un mix di opzioni di tipi diversi.
Menu a discesa di testo semplice

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');
}
};
Mantenere le informazioni leggibili separate dalla chiave indipendente dalla lingua consente di conservare l'impostazione del menu a discesa tra le lingue. Ad
esempio, una versione inglese di un blocco può definire [['left', 'LEFT'], ['right',
'RIGHT]], mentre una versione tedesca dello stesso blocco definirebbe [['links',
'LEFT'], ['rechts', 'RIGHT]].
Menu a discesa di immagini
Le opzioni in un menu a discesa possono essere immagini, rappresentate come oggetti con proprietà 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');
}
};
Menu a discesa HTML
Un'opzione può essere qualsiasi elemento HTML, purché non sia troppo grande e non tenti di gestire eventi del mouse o della tastiera. (È tua responsabilità rispettare queste regole: Blockly non le applica.)
Quando il menu a discesa è aperto, l'elenco mostra l'elemento HTML. Quando è chiuso e l'elemento è l'opzione selezionata, l'elenco mostra (in ordine decrescente di preferenza) l'attributo title dell'elemento, l'attributo aria-label o la proprietà innerText.

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);
});
Questa operazione viene eseguita utilizzando un'estensione 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');
}
};
Menu a discesa dinamici

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;
});
});
Questa operazione viene eseguita utilizzando un'estensione 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;
}
};
A un menu a discesa può essere fornita anche una funzione anziché un elenco di opzioni statiche, il che consente di rendere dinamiche le opzioni. La funzione deve restituire un array di opzioni nello stesso formato [human-readable-value, language-neutral-key] delle opzioni statiche. Ogni volta che si fa clic sul menu a discesa, la funzione viene eseguita e le opzioni vengono ricalcolate.
Separatori
Utilizza la stringa 'separator' per aggiungere una riga tra le opzioni in un menu a discesa.

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");
}
};
Serializzazione
JSON
Il JSON per un campo menu a discesa è il seguente:
{
"fields": {
"FIELDNAME": "LANGUAGE-NEUTRAL-KEY"
}
}
Dove FIELDNAME è una stringa che fa riferimento a un campo menu a discesa e il valore è il valore da applicare al campo. Il valore deve essere una chiave di opzione indipendente dalla lingua.
XML
L'XML per un campo menu a discesa è il seguente:
<field name="FIELDNAME">LANGUAGE-NEUTRAL-KEY</field>
Dove l'attributo name del campo contiene una stringa che fa riferimento a un campo menu a discesa e il testo interno è il valore da applicare al campo. Il testo interno deve essere una chiave di opzione indipendente dalla lingua valida.
Personalizzazione
Freccia menu a discesa
La proprietà Blockly.FieldDropdown.ARROW_CHAR può essere utilizzata per modificare il carattere Unicode che rappresenta la freccia del menu a discesa.

Per impostazione predefinita, la proprietà ARROW_CHAR è \u25BC (▼) su Android e \u25BE (▾) in caso contrario.
Questa è una proprietà globale, quindi se impostata modificherà tutti i campi menu a discesa.
Altezza del menu
La proprietà Blockly.FieldDropdown.MAX_MENU_HEIGHT_VH può essere utilizzata per modificare l'altezza massima del menu. È definita come percentuale dell'altezza della finestra, che è la finestra.
Per impostazione predefinita, la proprietà MAX_MENU_HEIGHT_VH è 0,45.
Questa è una proprietà globale, quindi se impostata modificherà tutti i campi menu a discesa.
Corrispondenza di prefissi/suffissi
Se tutte le opzioni del menu a discesa condividono parole di prefisso e/o suffisso comuni, queste parole vengono automaticamente fattorizzate e inserite come testo statico. Ad esempio, ecco due modi per creare lo stesso blocco (il primo senza corrispondenza di suffissi e il secondo con):
Senza corrispondenza di suffissi:
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');
}
};
Con corrispondenza di suffissi:
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');
}
};

Un vantaggio di questo approccio è che il blocco è più facile da tradurre in altre lingue. Il codice precedente contiene le stringhe 'hello', 'world' e
'computer', mentre il codice rivisto contiene le stringhe 'hello world' e
'hello computer'. I traduttori hanno molta più facilità a tradurre frasi che parole isolate.
Un altro vantaggio di questo approccio è che l'ordine delle parole spesso cambia da una lingua all'altra. Immagina una lingua che utilizza 'world hello' e 'computer hello'.
L'algoritmo di corrispondenza dei suffissi rileverà il 'hello' comune e lo visualizzerà
dopo il menu a discesa.
A volte, però, la corrispondenza di prefissi/suffissi non riesce. In alcuni casi, due parole devono sempre andare insieme e il prefisso non deve essere fattorizzato.
Ad esempio 'drive red car' e 'drive red truck' dovrebbero avere solo
'drive' fattorizzato, non 'drive red'. Lo spazio unificatore Unicode
space '\u00A0' può essere utilizzato al posto di uno spazio normale per sopprimere il
matcher di prefissi/suffissi. Pertanto, l'esempio precedente può essere corretto con
'drive red\u00A0car' e 'drive red\u00A0truck'.
Un altro caso in cui la corrispondenza di prefissi/suffissi non riesce è nelle lingue che non separano le singole parole con spazi. Il cinese è un buon esempio. La stringa
'訪問中國' significa 'visit China', nota l'assenza di spazi tra le parole.
Collettivamente, gli ultimi due caratteri ('中國') sono la parola per 'China',
ma se divisi significherebbero rispettivamente 'centre' e 'country'. Per far funzionare la corrispondenza di prefissi/suffissi in lingue come il cinese, basta inserire uno spazio dove deve essere presente l'interruzione. Ad esempio '訪問 中國' e
'訪問 美國' genererebbero "visit [China/USA]", mentre '訪問 中 國' e
'訪問 美 國' genererebbero "visit [centre/beautiful] country".
Creazione di uno strumento di convalida del menu a discesa
Il valore di un campo menu a discesa è una stringa indipendente dalla lingua, quindi tutti i validatori devono
accettare una stringa e restituire una stringa che sia un'opzione disponibile, null, o
undefined.
Se il validatore restituisce qualsiasi altro valore, il comportamento di Blockly non è definito e il programma potrebbe avere un arresto anomalo.
Ad esempio, puoi definire un campo menu a discesa con tre opzioni e uno strumento di convalida come questo:
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 restituisce sempre il valore che gli è stato passato, ma chiama la funzione helper updateConnection che aggiunge o rimuove gli input in base al valore del menu a discesa:
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');
}
}
