Pola menu

Pole menu przechowuje ciąg znaków jako wartość i ciąg znaków jako tekst. Wartość jest kluczem niezależnym od języka, który będzie używany do uzyskiwania dostępu do tekstu i nie będzie tłumaczony, gdy Blockly będzie przełączany między językami. Tekst to zrozumiały dla człowieka ciąg znaków, który będzie wyświetlany użytkownikowi.

Blok z etykietą „menu:”, pole menu z wybraną opcją „pierwszy” i etykietą „element”.

Ten sam blok z otwartym menu. Menu zawiera elementy „first” i „second”.

Ten sam blok po zwinięciu. Ma etykietę „menu: pierwszy element” i poszarpany prawy brzeg, co oznacza, że jest zwinięte.

na podstawie trendów

Konstruktor menu przyjmuje generator menu i opcjonalny walidator. Generator menu to tablica opcji (gdzie każda opcja zawiera czytelną dla człowieka część i ciąg znaków niezależny od języka) lub funkcja, która generuje tablicę opcji. Część każdej opcji czytelna dla człowieka może być ciągiem znaków, obrazem lub elementem HTML, a tablica może zawierać mieszankę opcji różnych typów.

Proste menu tekstowe

Otwórz menu z 2 opcjami tekstowymi

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

Oddzielenie informacji czytelnych dla człowieka od klucza niezależnego od języka umożliwia zachowanie ustawień menu w różnych językach. Na przykład angielska wersja bloku może definiować [['left', 'LEFT'], ['right', 'RIGHT]], a niemiecka wersja tego samego bloku może definiować [['links', 'LEFT'], ['rechts', 'RIGHT]].

Menu obrazów

Opcje w menu rozwijanym mogą być obrazami, które są reprezentowane jako obiekty z właściwościami src, width, height i alt.

Pole wyboru zawierające obrazy i tekst

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 HTML

Opcją może być dowolny element HTML, o ile nie jest zbyt duży i nie próbuje obsługiwać zdarzeń myszy ani klawiatury. (Obowiązek przestrzegania tych reguł spoczywa na Tobie – Blockly nie wymusza ich stosowania).

Gdy menu się otworzy, na liście pojawi się element HTML. Gdy jest zamknięta, a element jest wybraną opcją, lista wyświetla (w kolejności od najbardziej preferowanych) atrybut title elementu, jego atrybut aria-label lub jego właściwość innerText.

Pole wyboru zawierające tekst i elementy 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);
  });

Odbywa się to za pomocą rozszerzenia 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');
  }
};

Dynamiczne menu

Pole wyboru z dniami tygodnia

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

Odbywa się to za pomocą rozszerzenia 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;
  }
};

Menu rozwijane może też zawierać funkcję zamiast listy statycznych opcji, co umożliwia dynamiczne wyświetlanie opcji. Funkcja powinna zwracać tablicę opcji w takim samym formacie [human-readable-value, language-neutral-key] jak opcje statyczne. Za każdym razem, gdy klikniesz menu, funkcja zostanie uruchomiona, a opcje zostaną przeliczone.

Separatory

Użyj ciągu znaków 'separator', aby dodać linię między opcjami w menu.

Pole rozwijane z linią między drugą a trzecią opcją

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

Publikacja w odcinkach

JSON

Kod JSON pola menu wygląda tak:

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

gdzie FIELDNAME to ciąg znaków odwołujący się do pola menu, a wartość to wartość, którą należy zastosować w polu. Wartość powinna być kluczem opcji niezależnym od języka.

XML

Kod XML pola wyboru wygląda tak:

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

Gdy atrybut name pola zawiera ciąg znaków odwołujący się do pola menu, a tekst wewnętrzny jest wartością, którą należy zastosować w polu. Tekst wewnętrzny powinien być prawidłowym kluczem opcji niezależnym od języka.

Dostosowywanie

Właściwość Blockly.FieldDropdown.ARROW_CHAR może służyć do zmiany znaku Unicode reprezentującego strzałkę menu.

Pole wyboru ze strzałką niestandardową

Właściwość ARROW_CHAR ma domyślnie wartość \u25BC (▼) na Androidzie i \u25BE (▾) w innych przypadkach.

Jest to właściwość globalna, więc po jej ustawieniu wszystkie pola menu zostaną zmodyfikowane.

Właściwość Blockly.FieldDropdown.MAX_MENU_HEIGHT_VH może służyć do zmiany maksymalnej wysokości menu. Jest on określany jako procent wysokości widocznego obszaru, czyli okna.

Właściwość MAX_MENU_HEIGHT_VH ma domyślnie wartość 0,45.

Jest to właściwość globalna, więc po jej ustawieniu wszystkie pola menu zostaną zmodyfikowane.

Dopasowywanie prefiksów i sufiksów

Jeśli wszystkie opcje w menu mają wspólny prefiks lub sufiks, te słowa są automatycznie wyodrębniane i wstawiane jako tekst statyczny. Oto 2 sposoby utworzenia tego samego bloku (pierwszy bez dopasowywania sufiksów, a drugi z nim):

Bez dopasowywania sufiksów:

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

Z dopasowaniem sufiksu:

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

Pole wyboru z

Zaletą tego podejścia jest to, że blok łatwiej przetłumaczyć na inne języki. W poprzednim kodzie występują ciągi znaków 'hello', 'world''computer', a w zmienionym – ciągi znaków 'hello world''hello computer'. Tłumaczom znacznie łatwiej jest tłumaczyć frazy niż pojedyncze słowa.

Kolejną zaletą tego podejścia jest to, że kolejność słów często zmienia się w zależności od języka. Wyobraź sobie język, w którym używa się symboli 'world hello''computer hello'. Algorytm dopasowywania sufiksów wykryje wspólny sufiks 'hello' i wyświetli go po menu.

Czasami jednak dopasowywanie prefiksu lub sufiksu się nie udaje. W niektórych przypadkach dwa słowa powinny zawsze występować razem, a przedrostek nie powinien być wyodrębniany. Na przykład w przypadku wartości 'drive red car''drive red truck' należy prawdopodobnie wyłączyć tylko wartość 'drive', a nie 'drive red'. Zamiast zwykłej spacji można użyć spacji nierozdzielającej Unicode '\u00A0', aby wyłączyć dopasowywanie prefiksów i sufiksów. Powyższy przykład można poprawić za pomocą znaczników 'drive red\u00A0car''drive red\u00A0truck'.

Dopasowywanie prefiksów i sufiksów nie działa też w przypadku języków, w których poszczególne słowa nie są oddzielane spacjami. Dobrym przykładem jest język chiński. Ciąg znaków '訪問中國' oznacza 'visit China'. Zwróć uwagę na brak spacji między wyrazami. Ostatnie 2 znaki ('中國') razem oznaczają 'China', ale rozdzielone oznaczają odpowiednio 'centre''country'. Aby dopasowywanie prefiksów i sufiksów działało w językach takich jak chiński, wystarczy wstawić spację w miejscu, w którym ma nastąpić podział. Na przykład '訪問 中國''訪問 美國' dałyby w wyniku "visit [China/USA]", a '訪問 中 國''訪問 美 國' – "visit [centre/beautiful] country".

Tworzenie weryfikatora menu

Wartość pola menu to ciąg znaków niezależny od języka, więc wszystkie walidatory muszą akceptować ciąg znaków i zwracać ciąg znaków będący dostępną opcją, null lub undefined.

Jeśli walidator zwróci coś innego, działanie Blockly jest nieokreślone i program może się zawiesić.

Możesz na przykład zdefiniować pole wyboru z 3 opcjami i walidatorem w ten sposób:

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 zawsze zwraca przekazaną wartość, ale wywołuje funkcję pomocniczą updateConnection, która dodaje lub usuwa dane wejściowe na podstawie wartości menu:

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

Animowany GIF przedstawiający pole wyboru z 3 opcjami: „ani”, „oświadczenie” i „wartość”. Jeśli wybierzesz opcję „ani jedno, ani drugie”, nie będzie ona miała żadnych danych wejściowych. Gdy wybierzesz „statement”, pojawi się pole do wpisania oświadczenia. Gdy element „value” (wartość) jest połączony, ma wejście „value” (wartość).