Il campo 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 cambia lingua. Il testo è una stringa leggibile che verrà visualizzata dall'utente.
Campo a discesa
Campo a discesa con editor aperto
Campo a discesa nel blocco compresso
Creazione
Il costruttore del menu a discesa accetta un generatore di menu e un validatore facoltativo. Il generatore di menu è un array di opzioni (in cui ogni opzione contiene una parte leggibile e una stringa indipendente dalla lingua) o una funzione che genera un array di opzioni. La parte leggibile da persone 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 con 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 in inglese di un blocco potrebbe definire [['left', 'LEFT'], ['right',
'RIGHT]]
, mentre una versione in tedesco dello stesso blocco definirebbe [['links',
'LEFT'], ['rechts', 'RIGHT]]
.
Menu a discesa delle immagini
Le opzioni di un menu a discesa possono essere immagini, rappresentate come oggetti con le 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;
}
};
Un menu a discesa può anche essere fornito con una funzione anziché un elenco di opzioni statiche, il che consente alle opzioni di essere dinamiche. La funzione deve restituire un
array di opzioni nello stesso formato [human-readable-value, language-neutral-key]
delle opzioni statiche. Ogni volta che viene fatto clic sul menu a discesa, la funzione viene
eseguita e le opzioni vengono ricalcolate.
Separatori
Utilizza la stringa 'separator'
per aggiungere una linea 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 codice JSON per un campo a discesa è il seguente:
{
"fields": {
"FIELDNAME": "LANGUAGE-NEUTRAL-KEY"
}
}
dove FIELDNAME
è una stringa che fa riferimento a un campo a discesa e
il valore è il valore da applicare al campo. Il valore deve essere una chiave di opzione indipendente dalla lingua.
XML
Il codice XML per un campo 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
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.
La proprietà ARROW_CHAR
ha come valore predefinito \u25BC
(▼) su Android e \u25BE
(▾)
altrimenti.
Si tratta di una proprietà globale, quindi, una volta impostata, modificherà tutti i campi 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 dell'area visibile, ovvero la finestra.
La proprietà MAX_MENU_HEIGHT_VH
ha come valore predefinito 0,45.
Si tratta di una proprietà globale, quindi, una volta impostata, modificherà tutti i campi a discesa.
Corrispondenza prefisso/suffisso
Se tutte le opzioni del menu a discesa condividono parole di prefisso e/o suffisso comuni, queste parole vengono automaticamente estratte e inserite come testo statico. Ad esempio, ecco due modi per creare lo stesso blocco (il primo senza corrispondenza dei suffissi e il secondo con):
Senza corrispondenza dei 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 la corrispondenza suffisso:
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');
}
};
Uno dei vantaggi 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 cambia spesso tra le lingue. Immagina una lingua che utilizzasse 'world hello'
e 'computer hello'
.
L'algoritmo di corrispondenza dei suffissi rileverà il 'hello'
comune e lo visualizzerà
dopo il menu a discesa.
Tuttavia, a volte la corrispondenza del prefisso/suffisso non riesce. Esistono alcuni casi in cui
due parole devono sempre essere unite e il prefisso non deve essere separato.
Ad esempio, 'drive red car'
e 'drive red truck'
dovrebbero avere solo 'drive'
come fattore, non 'drive red'
. Lo spazio unificato non separabile '\u00A0'
può essere utilizzato al posto di uno spazio normale per eliminare 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 con prefisso/suffisso non funziona è quello delle 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.
Gli ultimi due caratteri ('中國'
) insieme formano la parola 'China'
,
ma se separati significano rispettivamente 'centre'
e 'country'
. Per far funzionare
la corrispondenza di prefissi/suffissi in lingue come il cinese,
basta inserire uno spazio dove deve avvenire l'interruzione. Ad esempio, '訪問 中國'
e
'訪問 美國'
darebbero come risultato "visit [China/USA]"
, mentre '訪問 中 國'
e
'訪問 美 國'
darebbero come risultato "visit [centre/beautiful] country"
.
Creazione di un validatore di menu a discesa
Il valore di un campo a discesa è una stringa indipendente dalla lingua, quindi tutti i validatori devono
accettare una stringa e restituirne una che sia un'opzione disponibile, null
o
undefined
.
Se il validatore restituisce altro, il comportamento di Blockly non è definito e il programma potrebbe arrestarsi in modo anomalo.
Ad esempio, puoi definire un campo a discesa con tre opzioni e un validator 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');
}
}