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.
Pole rozwijane
Pole rozwijane z otwartym edytorem
Pole wyboru w zwiniętym bloku
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
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
.
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
.
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
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.
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
Strzałka w dół
Właściwość Blockly.FieldDropdown.ARROW_CHAR
może służyć do zmiany znaku Unicode reprezentującego strzałkę menu.
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.
Wysokość menu
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');
}
};
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'
i 'computer'
, a w zmienionym – ciągi znaków 'hello world'
i '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'
i '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'
i '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'
i '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'
i '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 '訪問 中國'
i '訪問 美國'
dałyby w wyniku "visit [China/USA]"
, a '訪問 中 國'
i '訪問 美 國'
– "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');
}
}