Campos desplegables

El campo desplegable almacena una cadena como valor y otra como texto. El valor es una clave neutral en cuanto al idioma que se usará para acceder al texto y no se traducirá cuando se cambie de idioma en Blockly. El texto es una cadena legible por humanos que se mostrará al usuario.

Un bloque con la etiqueta “menú desplegable”, un campo de menú desplegable con “primero” seleccionado y la etiqueta “elemento”.

El mismo bloque con el menú desplegable abierto. El menú desplegable contiene los elementos "primero" y "segundo".

El mismo bloque después de contraerse. Tiene la etiqueta “primer elemento del menú desplegable” y un borde derecho irregular para mostrar que está colapsado.

Creación

El constructor de menús desplegables toma un generador de menús y un validador opcional. El generador de menús tiene mucha flexibilidad, pero, en esencia, es un array de opciones, cada una de las cuales contiene una parte legible por humanos y una cadena neutral en cuanto al idioma.

Menús desplegables de texto simple

Menú desplegable abierto con dos opciones de texto

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

Mantener la información legible por humanos separada de la clave neutral en cuanto al idioma permite que se conserve la configuración del menú desplegable entre los idiomas. Por ejemplo, una versión en inglés de un bloque puede definir [['left', 'LEFT'], ['right', 'RIGHT]], mientras que una versión en alemán del mismo bloque definiría [['links', 'LEFT'], ['rechts', 'RIGHT]].

Menús desplegables de imágenes

Las opciones de un menú desplegable también pueden ser imágenes en lugar de texto. Los objetos de imagen se especifican con las propiedades src, width, height y alt.

Ten en cuenta que, si bien un menú desplegable puede tener una combinación de opciones de texto y de imagen, una opción individual no puede contener una imagen y texto al mismo tiempo.

Campo desplegable que contiene imágenes y texto

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

Menús desplegables dinámicos

Campo desplegable con los días de la semana

JSON

{
  "type": "dynamic_dropdown",
  "message0": "day %1",
  "args0": [
    {
      "type": "input_dummy",
      "name": "INPUT"
    }
  ],
  "extensions": ["dynamic_menu_extension"]
}
Blockly.Extensions.register('dynamic_menu_extension',
  function() {
    this.getInput('INPUT')
      .appendField(new Blockly.FieldDropdown(
        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;
        }), 'DAY');
  });

Esto se hace con una extensión de 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;
  }
};

También se puede proporcionar un menú desplegable con una función en lugar de una lista de opciones estáticas, lo que permite que las opciones sean dinámicas. La función debe mostrar un array de opciones en el mismo formato [human-readable-value, language-neutral-key] que las opciones estáticas. Cada vez que se hace clic en el menú desplegable, se ejecuta la función y se vuelven a calcular las opciones.

Separadores

Usa la cadena 'separator' para agregar una línea entre las opciones de un menú desplegable.

Campo desplegable con una línea entre la segunda y la tercera opción

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

Serialización

JSON

El JSON de un campo desplegable se ve de la siguiente manera:

{
  "fields": {
    "FIELDNAME": "LANGUAGE-NEUTRAL-KEY"
  }
}

En el que FIELDNAME es una cadena que hace referencia a un campo desplegable y el valor es el valor que se aplicará al campo. El valor debe ser una clave de opción independiente del idioma.

XML

El XML de un campo desplegable se ve de la siguiente manera:

<field name="FIELDNAME">LANGUAGE-NEUTRAL-KEY</field>

En el que el atributo name del campo contiene una cadena que hace referencia a un campo desplegable y el texto interno es el valor que se aplicará al campo. El texto interno debe ser una clave de opción válida sin idioma.

Personalización

La propiedad Blockly.FieldDropdown.ARROW_CHAR se puede usar para cambiar el carácter Unicode que representa la flecha del menú desplegable.

Campo desplegable con flecha personalizada

El valor predeterminado de la propiedad ARROW_CHAR es \u25BC (▼) en Android y \u25BE (▾) en otros casos.

Esta es una propiedad global, por lo que modificará todos los campos del menú desplegable cuando se establezca.

La propiedad Blockly.FieldDropdown.MAX_MENU_HEIGHT_VH se puede usar para cambiar la altura máxima del menú. Se define como un porcentaje de la altura del viewport, siendo la ventana el viewport.

El valor predeterminado de la propiedad MAX_MENU_HEIGHT_VH es 0.45.

Esta es una propiedad global, por lo que modificará todos los campos del menú desplegable cuando se establezca.

Coincidencia de prefijos o sufijos

Si todas las opciones del menú desplegable comparten palabras de prefijo o sufijo comunes, estas se eliminan automáticamente y se insertan como texto estático. Por ejemplo, estas son dos formas de crear el mismo bloque (la primera sin coincidencia de sufijos y la segunda con):

Sin concordancia de sufijos:

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 concordancia de sufijos:

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

Campo desplegable con

Una ventaja de este enfoque es que el bloque es más fácil de traducir a otros idiomas. El código anterior tiene las cadenas 'hello', 'world' y 'computer', mientras que el código revisado tiene las cadenas 'hello world' y 'hello computer'. Los traductores tienen más facilidad para traducir frases que palabras aisladas.

Otra ventaja de este enfoque es que el orden de las palabras suele cambiar entre los idiomas. Imagina un lenguaje que use 'world hello' y 'computer hello'. El algoritmo de coincidencia de sufijos detectará el 'hello' común y lo mostrará después del menú desplegable.

Sin embargo, a veces, la coincidencia de prefijos o sufijos falla. Hay algunos casos en los que dos palabras siempre deben ir juntas y el prefijo no debe eliminarse. Por ejemplo, se podría argumentar que 'drive red car' y 'drive red truck' solo deberían tener 'drive' factorizado, no 'drive red'. Se puede usar el espacio indentado '\u00A0' de Unicode en lugar de un espacio normal para suprimir el comparador de prefijos o sufijos. Por lo tanto, el ejemplo anterior se puede corregir con 'drive red\u00A0car' y 'drive red\u00A0truck'.

Otro lugar en el que falla la coincidencia de prefijos o sufijos es en los idiomas que no separan palabras individuales con espacios. El chino es un buen ejemplo. La cadena '訪問中國' significa 'visit China'. Ten en cuenta la falta de espacios entre las palabras. En conjunto, los últimos dos caracteres ('中國') son la palabra para 'China', pero si se dividen, significarían 'centre' y 'country', respectivamente. Para que la coincidencia de prefijos o sufijos funcione en idiomas como el chino, solo debes insertar un espacio donde debería estar la pausa. Por ejemplo, '訪問 中國' y '訪問 美國' darían como resultado "visit [China/USA]", mientras que '訪問 中 國' y '訪問 美 國' darían como resultado "visit [centre/beautiful] country".

Cómo crear un validador de menú desplegable

El valor de un campo desplegable es una cadena neutral en cuanto al idioma, por lo que cualquier validador debe aceptar una cadena y mostrar una cadena que sea una opción disponible, null o undefined.

Si tu validador muestra cualquier otro valor, el comportamiento de Blockly no se define y es posible que tu programa falle.

Por ejemplo, podrías definir un campo desplegable con tres opciones y un validador de la siguiente manera:

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 siempre muestra el valor que se le pasó, pero llama a la función auxiliar updateConnection, que agrega o quita entradas según el valor del menú desplegable:

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 animado que muestra un campo desplegable con tres elementos: “ni”, “sentencia” y “valor”. Cuando se selecciona &quot;ninguno&quot;, no tiene entradas. Cuando se selecciona “statement”, tiene una entrada de sentencia. Cuando &quot;value&quot; está conectado, tiene una entrada &quot;value&quot;.