Le champ de menu déroulant stocke une chaîne de caractères comme valeur et une chaîne de caractères comme texte. La valeur est une clé neutre en termes de langue qui sera utilisée pour accéder au texte et ne sera pas traduite lorsque Blockly passera d'une langue à une autre. Le texte est une chaîne lisible qui sera affichée à l'utilisateur.
Champ de menu déroulant
Champ déroulant avec l'éditeur ouvert
Champ de menu déroulant dans un bloc réduit
Création
Le constructeur de menu déroulant accepte un générateur de menu et un validateur facultatif. Le générateur de menu est soit un tableau d'options (où chaque option contient une partie lisible par l'utilisateur et une chaîne neutre en termes de langue), soit une fonction qui génère un tableau d'options. La partie lisible par l'utilisateur de chaque option peut être une chaîne, une image ou un élément HTML. Le tableau peut contenir un mélange d'options de différents types.
Menus déroulants de texte simple
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');
}
};
En séparant les informations lisibles par l'homme de la clé neutre en termes de langue, le paramètre du menu déroulant peut être conservé d'une langue à l'autre. Par exemple, une version anglaise d'un bloc peut définir [['left', 'LEFT'], ['right',
'RIGHT]]
, tandis qu'une version allemande du même bloc définirait [['links',
'LEFT'], ['rechts', 'RIGHT]]
.
Menus déroulants d'images
Les options d'un menu déroulant peuvent être des images, qui sont représentées sous forme d'objets avec les propriétés src
, width
, height
et 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 déroulants HTML
Une option peut être n'importe quel élément HTML, à condition qu'il ne soit pas trop volumineux et qu'il ne tente pas de gérer les événements de souris ou de clavier. (Il vous incombe de respecter ces règles. Blockly ne les applique pas.)
Lorsque le menu déroulant est ouvert, la liste affiche l'élément HTML. Lorsqu'il est fermé et que l'élément est l'option sélectionnée, la liste affiche (par ordre décroissant de préférence) l'attribut title
, l'attribut aria-label
ou la propriété innerText
de l'élément.
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);
});
Pour ce faire, vous devez utiliser une extension 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');
}
};
Menus déroulants dynamiques
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;
});
});
Pour ce faire, vous devez utiliser une extension 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 déroulant peut également être fourni avec une fonction au lieu d'une liste d'options statiques, ce qui permet aux options d'être dynamiques. La fonction doit renvoyer un tableau d'options au même format [human-readable-value, language-neutral-key]
que les options statiques. Chaque fois que l'utilisateur clique sur le menu déroulant, la fonction est exécutée et les options sont recalculées.
Séparateurs
Utilisez la chaîne 'separator'
pour ajouter une ligne entre les options d'un menu déroulant.
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");
}
};
Sérialisation
JSON
Le code JSON d'un champ de menu déroulant se présente comme suit :
{
"fields": {
"FIELDNAME": "LANGUAGE-NEUTRAL-KEY"
}
}
Où FIELDNAME
est une chaîne faisant référence à un champ de menu déroulant, et la valeur est celle à appliquer au champ. La valeur doit être une clé d'option neutre en termes de langue.
XML
Le code XML d'un champ de menu déroulant se présente comme suit :
<field name="FIELDNAME">LANGUAGE-NEUTRAL-KEY</field>
Où l'attribut name
du champ contient une chaîne faisant référence à un champ de liste déroulante, et où le texte interne est la valeur à appliquer au champ. Le texte interne doit être une clé d'option neutre valide.
Personnalisation
Flèche du menu déroulant
La propriété Blockly.FieldDropdown.ARROW_CHAR
peut être utilisée pour modifier le caractère Unicode représentant la flèche du menu déroulant.
La propriété ARROW_CHAR
est définie par défaut sur \u25BC
(▼) sur Android et sur \u25BE
(▾) dans les autres cas.
Il s'agit d'une propriété globale. Par conséquent, elle modifiera tous les champs de menu déroulant lorsqu'elle sera définie.
Hauteur du menu
La propriété Blockly.FieldDropdown.MAX_MENU_HEIGHT_VH
peut être utilisée pour modifier la hauteur maximale du menu. Elle est définie comme un pourcentage de la hauteur de la fenêtre d'affichage.
La propriété MAX_MENU_HEIGHT_VH
est définie par défaut sur 0,45.
Il s'agit d'une propriété globale. Par conséquent, elle modifiera tous les champs de menu déroulant lorsqu'elle sera définie.
Correspondance des préfixes/suffixes
Si toutes les options du menu déroulant partagent des mots de préfixe et/ou de suffixe communs, ces mots sont automatiquement factorisés et insérés en tant que texte statique. Par exemple, voici deux façons de créer le même bloc (la première sans correspondance de suffixe et la seconde avec) :
Sans correspondance des suffixes :
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');
}
};
Avec la correspondance de suffixe :
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');
}
};
L'un des avantages de cette approche est que le bloc est plus facile à traduire dans d'autres langues. Le code précédent contient les chaînes 'hello'
, 'world'
et 'computer'
, tandis que le code révisé contient les chaînes 'hello world'
et 'hello computer'
. Les traducteurs ont beaucoup plus de facilité à traduire des expressions que des mots isolés.
Un autre avantage de cette approche est que l'ordre des mots change souvent d'une langue à l'autre. Imaginez une langue qui utilise 'world hello'
et 'computer hello'
.
L'algorithme de correspondance des suffixes détecte le 'hello'
commun et l'affiche après le menu déroulant.
Toutefois, il arrive que la correspondance du préfixe/suffixe échoue. Dans certains cas, deux mots doivent toujours aller ensemble et le préfixe ne doit pas être supprimé.
Par exemple, 'drive red car'
et 'drive red truck'
ne devraient, à juste titre, avoir que 'drive'
comme facteur commun, et non 'drive red'
. L'espace insécable Unicode '\u00A0'
peut être utilisé à la place d'un espace standard pour supprimer l'outil de mise en correspondance des préfixes/suffixes. L'exemple ci-dessus peut donc être corrigé avec 'drive red\u00A0car'
et 'drive red\u00A0truck'
.
La correspondance des préfixes/suffixes échoue également dans les langues qui ne séparent pas les mots individuels par des espaces. Le chinois est un bon exemple. La chaîne '訪問中國'
signifie 'visit China'
. Notez l'absence d'espaces entre les mots.
Collectivement, les deux derniers caractères ('中國'
) forment le mot 'China'
, mais s'ils sont séparés, ils signifient respectivement 'centre'
et 'country'
. Pour que la correspondance des préfixes/suffixes fonctionne dans des langues telles que le chinois, il vous suffit d'insérer un espace à l'endroit où la coupure doit avoir lieu. Par exemple, '訪問 中國'
et '訪問 美國'
donneront "visit [China/USA]"
, tandis que '訪問 中 國'
et '訪問 美 國'
donneront "visit [centre/beautiful] country"
.
Créer un validateur de liste déroulante
La valeur d'un champ de liste déroulante est une chaîne neutre en termes de langue. Par conséquent, tous les validateurs doivent accepter une chaîne et renvoyer une chaîne qui est une option disponible, null
ou undefined
.
Si votre validateur renvoie autre chose, le comportement de Blockly est indéfini et votre programme peut planter.
Par exemple, vous pouvez définir un champ de menu déroulant avec trois options et un validateur comme suit :
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
renvoie toujours la valeur qui lui a été transmise, mais il appelle la fonction d'assistance updateConnection
qui ajoute ou supprime des entrées en fonction de la valeur du menu déroulant :
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');
}
}