שדות נפתחים

שדה התפריט הנפתח שומר מחרוזת כערך ומחרוזת כטקסט. הערך הוא מפתח ניטרלי לשפה שישמש לגישה לטקסט ולא יתורגם כשעוברים בין שפות ב-Blockly. הטקסט הוא מחרוזת שאנשים יכולים לקרוא, שתוצג למשתמש.

בלוק עם התווית 'תפריט נפתח:', שדה של תפריט נפתח עם האפשרות 'ראשון' שנבחרה, והתווית 'פריט'.

אותו בלוק עם התפריט הנפתח פתוח. התפריט הנפתח מכיל את הפריטים 'first' ו-'second'.

אותו בלוק אחרי שהוא מכווץ. הוא כולל את התווית 'תפריט נפתח: הפריט הראשון' וקצה ימני משונן שמראה שהוא מכווץ.

יצירה

הפונקציה ליצירת תפריט נפתח מקבלת מחולל תפריטים ומאמת אופציונלי. מחולל התפריט הוא מערך של אפשרויות (שכל אחת מהן מכילה חלק שקריא למשתמשים ומחרוזת שאינה תלויה בשפה) או פונקציה שמייצרת מערך של אפשרויות. החלק שקריא לבני אדם בכל אפשרות יכול להיות מחרוזת, תמונה או רכיב HTML, והמערך יכול להכיל שילוב של אפשרויות מסוגים שונים.

תפריטים נפתחים עם טקסט פשוט

פתיחת תפריט נפתח עם שתי אפשרויות טקסט

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

הפרדת המידע שניתן לקריאה על ידי בני אדם מהמפתח הניטרלי לשפה מאפשרת לשמור את ההגדרה של התפריט הנפתח בין שפות שונות. לדוגמה, בגרסה באנגלית של בלוק יכול להיות מוגדר [['left', 'LEFT'], ['right', 'RIGHT]], ובגרסה בגרמנית של אותו בלוק יכול להיות מוגדר [['links', 'LEFT'], ['rechts', 'RIGHT]].

תפריטים נפתחים של תמונות

אפשרויות בתפריט נפתח יכולות להיות תמונות, שמיוצגות כאובייקטים עם המאפיינים src, width, height ו-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');
  }
};

תפריטים נפתחים של HTML

אפשר להשתמש בכל רכיב HTML כאפשרות, כל עוד הוא לא גדול מדי ולא מנסה לטפל באירועים של עכבר או מקלדת. (באחריותכם לפעול בהתאם לכללים האלה – Blockly לא אוכפת אותם).

כשהתפריט הנפתח פתוח, הרשימה מציגה את רכיב ה-HTML. כשהתפריט סגור והרכיב הוא האפשרות שנבחרה, מוצגת הרשימה (בסדר יורד לפי העדפה) של מאפיין title של הרכיב, מאפיין aria-label או מאפיין innerText.

שדה נפתח שמכיל טקסט ורכיבי 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);
  });

הפעולה הזו מתבצעת באמצעות תוסף 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');
  }
};

תפריטים נפתחים דינמיים

שדה של תפריט נפתח עם ימים בשבוע

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

הפעולה הזו מתבצעת באמצעות תוסף 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;
  }
};

אפשר גם לספק תפריט נפתח עם פונקציה במקום רשימה של אפשרויות סטטיות, וכך האפשרויות יכולות להיות דינמיות. הפונקציה צריכה להחזיר מערך של אפשרויות באותו פורמט [human-readable-value, language-neutral-key] של אפשרויות סטטיות. בכל פעם שלוחצים על התפריט הנפתח, הפונקציה מופעלת והאפשרויות מחושבות מחדש.

מפרידים

כדי להוסיף קו בין האפשרויות בתפריט נפתח, משתמשים במחרוזת 'separator'.

שדה נפתח עם קו בין האפשרות השנייה לשלישית

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

סידור הפרקים

JSON

קובץ ה-JSON של שדה תפריט נפתח נראה כך:

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

כאשר FIELDNAME הוא מחרוזת שמפנה לשדה של תפריט נפתח, והערך הוא הערך שרוצים להחיל על השדה. הערך צריך להיות מפתח אפשרות ניטרלי מבחינת שפה.

XML

קוד ה-XML של שדה נפתח נראה כך:

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

כאשר המאפיין name של השדה מכיל מחרוזת שמפנה לשדה של תפריט נפתח, והטקסט הפנימי הוא הערך שיוחל על השדה. הטקסט הפנימי צריך להיות מפתח אפשרות תקין שאינו תלוי בשפה.

התאמה אישית

אפשר להשתמש במאפיין Blockly.FieldDropdown.ARROW_CHAR כדי לשנות את תו ה-Unicode שמייצג את החץ של התפריט הנפתח.

שדה של תפריט נפתח עם חץ מותאם אישית

ערך ברירת המחדל של המאפיין ARROW_CHAR הוא \u25BC (▼) ב-Android ו-\u25BE (▾) בכל מקרה אחר.

זוהי מאפיין גלובלי, ולכן היא תשנה את כל שדות התפריט הנפתח כשהיא מוגדרת.

אפשר להשתמש במאפיין Blockly.FieldDropdown.MAX_MENU_HEIGHT_VH כדי לשנות את הגובה המקסימלי של התפריט. הוא מוגדר כאחוז מגובה אזור התצוגה, כאשר אזור התצוגה הוא החלון.

ערך ברירת המחדל של המאפיין MAX_MENU_HEIGHT_VH הוא 0.45.

זוהי מאפיין גלובלי, ולכן היא תשנה את כל שדות התפריט הנפתח כשהיא מוגדרת.

התאמה של קידומת/סיומת

אם לכל האפשרויות בתפריט הנפתח יש מילה משותפת בתחילתן או בסופן, המילה הזו תוסר אוטומטית ותוצג כטקסט סטטי. לדוגמה, הנה שתי דרכים ליצור את אותו בלוק (הראשונה בלי התאמה של סיומת, והשנייה עם):

ללא התאמה של סיומות:

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

עם התאמה לפי סיומת:

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

שדה נפתח עם

יתרון אחד של הגישה הזו הוא שקל יותר לתרגם את הבלוק לשפות אחרות. בקוד הקודם יש את המחרוזות 'hello', 'world' ו-'computer', ואילו בקוד המתוקן יש את המחרוזות 'hello world' ו-'hello computer'. למתרגמים קל יותר לתרגם משפטים מאשר מילים בודדות.

יתרון נוסף של הגישה הזו הוא שסדר המילים משתנה לעיתים קרובות בין שפות. תדמיינו שפה שמשתמשת ב-'world hello' וב-'computer hello'. אלגוריתם ההתאמה של הסיומת יזהה את המשותף 'hello' ויציג אותו אחרי התפריט הנפתח.

עם זאת, לפעמים ההתאמה של הקידומת או הסיומת נכשלת. יש מקרים שבהם שתי מילים צריכות להופיע תמיד יחד, ולא צריך להסיר את הקידומת. לדוגמה, ל-'drive red car' ול-'drive red truck' לדעתנו צריך להיות רק 'drive' כגורם משותף, ולא 'drive red'. אפשר להשתמש ברווח שאינו חוצה שורה ב-Unicode‏ '\u00A0' במקום ברווח רגיל כדי למנוע את ההתאמה של הקידומת או הסיומת. לכן, אפשר לתקן את הדוגמה שלמעלה באמצעות 'drive red\u00A0car' ו-'drive red\u00A0truck'.

מקום נוסף שבו ההתאמה של קידומות או סיומות לא מצליחה הוא בשפות שבהן אין רווחים בין המילים. דוגמה טובה לכך היא סינית. המחרוזת '訪問中國' פירושה 'visit China', שימו לב שאין רווחים בין המילים. ביחד, שני התווים האחרונים ('中國') הם המילה 'China', אבל אם מפצלים אותם הם מייצגים את 'centre' ו-'country' בהתאמה. כדי שהתאמה של קידומת או סיומת תפעל בשפות כמו סינית, פשוט מוסיפים רווח במקום שבו אמור להיות מעבר שורה. לדוגמה, '訪問 中國' ו-'訪問 美國' יניבו את התוצאה "visit [China/USA]", ואילו '訪問 中 國' ו-'訪問 美 國' יניבו את התוצאה "visit [centre/beautiful] country".

יצירת אמצעי לאימות רשימה נפתחת

הערך של שדה נפתח הוא מחרוזת שאינה תלויה בשפה, ולכן כל אמצעי האימות צריכים לקבל מחרוזת ולהחזיר מחרוזת שהיא אפשרות זמינה, null או undefined.

אם כלי האימות מחזיר משהו אחר, ההתנהגות של Blockly לא מוגדרת והתוכנית עלולה לקרוס.

לדוגמה, אפשר להגדיר שדה של תפריט נפתח עם שלוש אפשרויות ועם אמצעי אימות כמו זה:

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 תמיד מחזירה את הערך שהועבר אליה, אבל היא קוראת לפונקציית העזר updateConnection שמוסיפה או מסירה קלט על סמך הערך של התפריט הנפתח:

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 מונפש שמציג שדה נפתח עם שלושה פריטים: &#39;אף אחד מהם&#39;, &#39;הצהרה&#39; ו&#39;ערך&#39;. אם בוחרים באפשרות &#39;אף אחד מהם&#39;, לא מוזנים נתונים. כשבוחרים באפשרות statement (הצהרה), מוצג קלט של הצהרה. כשמחברים את value, יש לו קלט value.