Champs de menu déroulant

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.

Bloc avec le libellé "menu déroulant :", champ de menu déroulant avec "premier" sélectionné et libellé "élément".

Le même bloc avec le menu déroulant ouvert. Le menu déroulant contient les éléments "first" (premier) et "second" (deuxième).

Le même bloc après avoir été réduit. Il comporte le libellé "drop down: first item" (menu déroulant : premier élément) et un bord droit dentelé pour indiquer qu'il est 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

Ouvrir un menu déroulant avec deux options de texte

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.

Champ de menu déroulant contenant des images et du texte

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.

Champ déroulant contenant des éléments de texte et 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);
  });

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

Champ déroulant avec les jours de la semaine

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.

Champ déroulant avec une ligne entre la deuxième et la troisième option

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

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

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.

Champ de liste déroulante avec flèche personnalisée

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.

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

Champ de liste déroulante avec

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

GIF animé montrant un champ de menu déroulant avec trois éléments : &quot;ni l&#39;un ni l&#39;autre&quot;, &quot;déclaration&quot; et &quot;valeur&quot;. Lorsque l&#39;option &quot;Aucun&quot; est sélectionnée, il n&#39;y a aucune entrée. Lorsque &quot;statement&quot; (déclaration) est sélectionné, une entrée de déclaration est disponible. Lorsqu&#39;une &quot;valeur&quot; est connectée, elle dispose d&#39;une entrée &quot;value&quot; (valeur).