Campi del menu a discesa

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.

Un blocco con l'etichetta "menu a discesa:", un campo a discesa con "primo" selezionato
e l'etichetta "elemento".

Lo stesso blocco con il menu a discesa aperto. Il menu a discesa contiene gli elementi "primo"
e "secondo".

Lo stesso blocco dopo essere stato compresso. Ha l'etichetta "menu a discesa: primo elemento"
e un bordo destro frastagliato per indicare che è
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

Apri il menu a discesa con due opzioni di testo

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.

Campo a discesa contenente immagini e testo

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.

Campo a discesa contenente testo ed elementi 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);
  });

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

Campo del menu a discesa con i giorni della settimana

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.

Campo a discesa con una linea tra la seconda e la terza opzione

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

La proprietà Blockly.FieldDropdown.ARROW_CHAR può essere utilizzata per modificare il carattere Unicode che rappresenta la freccia del menu a discesa.

Campo a discesa con freccia personalizzata

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.

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

Campo a discesa con

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

Una GIF animata che mostra un campo a discesa con tre elementi: &quot;nessuno&quot;,
&quot;istruzione&quot; e &quot;valore&quot;. Quando è selezionata l&#39;opzione &quot;Nessuno&quot;, non sono presenti input. Quando
&quot;statement&quot; è selezionato, ha un input di istruzione. Quando &quot;value&quot; è connesso, ha un input &quot;value&quot;.