Campos desplegables

El campo desplegable almacena una cadena como su valor y una cadena como su texto. El valor es una clave independiente del idioma que se usará para acceder al texto y no se traducirá cuando se cambie el idioma de Blockly. El texto es una cadena legible 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 "first" y "second".

El mismo bloque después de contraerse. Tiene la etiqueta "drop down first item" y un borde derecho irregular para mostrar que está contraído.

Creación

El constructor del menú desplegable toma un generador de menú y un validador opcional. El generador de menús tiene mucha flexibilidad, pero es básicamente un array de opciones, en el que cada opción contiene una parte legible para las personas y una cadena independiente del 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 para las personas separada de la clave independiente del idioma permite que se conserve la configuración del menú desplegable entre 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 opciones de imágenes, actualmente una opción individual no puede contener una imagen y texto.

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 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 devolver 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.

Serialización

JSON

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

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

Aquí, FIELDNAME es una cadena que hace referencia a un campo de menú 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>

Donde 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 independiente del 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

La propiedad ARROW_CHAR tiene el valor predeterminado \u25BC (▼) en Android y \u25BE (▾) en otros casos.

Esta es una propiedad global, por lo que modificará todos los campos desplegables cuando se configure.

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, que es la ventana.

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 desplegables cuando se configure.

Coincidencia de prefijos y sufijos

Si todas las opciones del menú desplegable comparten palabras de prefijo o sufijo comunes, estas palabras se factorizan automáticamente y se insertan como texto estático. Por ejemplo, aquí tienes dos formas de crear el mismo bloque (el primero sin coincidencia de sufijos y el segundo con ella):

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 coincidencia de sufijo:

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'. A los traductores les resulta mucho más fácil 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 usara '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 se debe factorizar. Por ejemplo, 'drive red car' y 'drive red truck' solo deberían tener factorizado 'drive', no 'drive red'. El espacio Unicode sin interrupción '\u00A0' se puede usar 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 caso en el que falla la coincidencia de prefijos y sufijos es en los idiomas que no separan las palabras individuales con espacios. El chino es un buen ejemplo. La cadena '訪問中國' significa 'visit China'. Ten en cuenta que no hay espacios entre las palabras. En conjunto, los dos últimos caracteres ('中國') forman la palabra 'China', pero, si se separan, significarían 'centre' y 'country', respectivamente. Para que la coincidencia de prefijos y sufijos funcione en idiomas como el chino, solo inserta un espacio donde debería estar la separación. 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 independiente del idioma, por lo que cualquier validador debe aceptar una cadena y devolver una cadena que sea una opción disponible, null o undefined.

Si tu validador devuelve 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 devuelve el valor que se le pasó, pero llama a la función de ayuda 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');
  }
}

Un GIF animado que muestra un campo desplegable con tres elementos: &quot;ninguno&quot;, &quot;declaración&quot; y &quot;valor&quot;. Cuando se selecciona &quot;ninguno&quot;, no tiene entradas. Cuando se selecciona &quot;declaración&quot;, tiene una entrada de declaración. Cuando &quot;value&quot; está conectado, tiene una entrada &quot;value&quot;.