תוספים וגורמים לשינוי

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

מוטטור הוא סוג מיוחד של תוסף שמוסיף לבלוק עריכה בסדרה בהתאמה אישית, ולפעמים גם ממשק משתמש.

תוספים

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

// This extension sets the block's tooltip to be a function which displays
// the parent block's tooltip (if it exists).
Blockly.Extensions.register(
    'parent_tooltip_extension',
    function() { // this refers to the block that the extension is being run on
      var thisBlock = this;
      this.setTooltip(function() {
        var parent = thisBlock.getParent();
        return (parent && parent.getInputsInline() && parent.tooltip) ||
            Blockly.Msg.MATH_NUMBER_TOOLTIP;
      });
    });

התוספים צריכים להיות בסטטוס 'רישום' כדי שאפשר יהיה לשייך אותם למפתח מחרוזת. לאחר מכן תוכלו להקצות את מפתח המחרוזת הזה למאפיין extensions בהגדרת JSON של סוג הבלוק, כדי להחיל את התוסף על הבלוק.

{
 //...,
 "extensions": ["parent_tooltip_extension",]
}

אפשר גם להוסיף כמה תוספים בבת אחת. שימו לב שהמאפיין extensions חייב להיות מערך, גם אם מחילים רק תוסף אחד.

{
  //...,
  "extensions": ["parent_tooltip_extension", "break_warning_extension"],
}

מיקסנים

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

Blockly.Extensions.registerMixin('my_mixin', {
  someProperty: 'a cool value',

  someMethod: function() {
    // Do something cool!
  }
))`

ב-JSON אפשר להפנות למפתחות מחרוזות המשויכים למיקסים, בדיוק כמו לכל תוסף אחר.

{
 //...,
 "extensions": ["my_mixin"],
}

מוטטורים

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

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

בדף serialization מפורט מידע נוסף על מקרים שבהם יש צורך במוטציה ומתי לא.

המוטטורים מספקים גם ממשק משתמש מובנה שמאפשר למשתמשים לשנות את צורות הבלוקים, אם מספקים כמה שיטות אופציונליות.

קטעי הוק (hooks) לסידור בסידרה

למוטטורים יש שני זוגות של קטעי הוק (hooks) לסדרה. זוג אחד של תגי hook פועל עם מערכת העריכה החדשה של JSON והשני עובד עם המערכת הישנה של סידורי XML. יש לספק לפחות אחד מהצמדים האלה.

SaveExtraState ו-loadExtraState

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

// These are the serialization hooks for the lists_create_with block.
saveExtraState: function() {
  return {
    'itemCount': this.itemCount_,
  };
},

loadExtraState: function(state) {
  this.itemCount_ = state['itemCount'];
  // This is a helper function which adds or removes inputs from the block.
  this.updateShape_();
},

קובץ ה-JSON שמתקבל ייראה כך:

{
  "type": "lists_create_with",
  "extraState": {
    "itemCount": 3 // or whatever the count is
  }
}
אין מדינה

אם הבלוק במצב ברירת המחדל כאשר הוא נערך, השיטה saveExtraState יכולה להחזיר את הערך null כדי לציין זאת. אם השיטה saveExtraState מחזירה null, לא יתווסף נכס extraState ל-JSON. כך ניתן לשמור קבצים בגודל קטן.

סריאליזציה מלאה ונתוני גיבוי

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

שני תרחישים נפוצים לדוגמה:

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

חלק מהבלוקים האלה כוללים את הבלוקים @blockly/block-shareable-procedures. בדרך כלל הם עורכים הפניה למודל נתונים גיבוי, שבו מצבם נשמר. אבל אם הפרמטר doFullSerialization מוגדר כ-true, הוא מסדר את כל המצבים שלו. הבלוקים של התהליכים שניתנים לשיתוף משתמשים בכך כדי לוודא שכאשר הם מעתיקים ומדביקים הם יוצרים מודל חדש של נתוני גיבוי, במקום להתייחס למודל קיים.

mutationToDom ו-domToMutation

mutationToDom ו-domToMutation הם קטעי סריאליזציה שפועלים עם המערכת הישנה של סריאליזציה של XML. כדאי להשתמש ב-hooks האלה רק אם צריך (למשל, אתם עובדים על בסיס קוד ישן שעדיין לא הועבר). אחרת, כדאי להשתמש ב-saveExtraState וב-loadExtraState.

הקוד mutationToDom מחזיר צומת XML שמייצג את המצב הנוסף של הבלוק, ו-domToMutation מקבל את אותו צומת XML ומחיל את המצב על הבלוק.

// These are the old XML serialization hooks for the lists_create_with block.
mutationToDom: function() {
  // You *must* create a <mutation></mutation> element.
  // This element can have children.
  var container = Blockly.utils.xml.createElement('mutation');
  container.setAttribute('items', this.itemCount_);
  return container;
},

domToMutation: function(xmlElement) {
  this.itemCount_ = parseInt(xmlElement.getAttribute('items'), 10);
  // This is a helper function which adds or removes inputs from the block.
  this.updateShape_();
},

ה-XML שיתקבל ייראה כך:

<block type="lists_create_with">
  <mutation items="3"></mutation>
</block>

אם הפונקציה mutationToDom מחזירה את הערך null, לא יתווסף שום רכיב ל-XML.

קטעי הוק (hooks) לממשק משתמש

אם אתם מספקים פונקציות מסוימות כחלק מהמוטציה, blockly תוסיף לבלוק שלכם ממשק משתמש מסוג "mutator" שמוגדר כברירת מחדל.

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

כתיבה ופירוק

ממשק המשתמש שמוגדר כברירת מחדל תלוי בפונקציות compose ו-decompose.

decompose "מפוצץ" את הבלוק לבלוקי משנה קטנים יותר, שאפשר להזיז, להוסיף ולמחוק. הפונקציה אמורה להחזיר 'בלוק עליון', שהוא הבלוק הראשי בסביבת העבודה של השינוי שאליו מתחברים בלוקי המשנה.

לאחר מכן, compose מפרש את התצורה של בלוקי המשנה ומשתמש בהם כדי לשנות את הבלוק הראשי. הפונקציה הזו אמורה לקבל את "הבלוק העליון" שמוחזר על ידי decompose כפרמטר.

שימו לב שהפונקציות האלה "מתמזגות" עם הבלוק שעבר שינוי, כך שאפשר להשתמש ב-this כדי לבצע הפניה לבלוק הזה.

// These are the decompose and compose functions for the lists_create_with block.
decompose: function(workspace) {
  // This is a special sub-block that only gets created in the mutator UI.
  // It acts as our "top block"
  var topBlock = workspace.newBlock('lists_create_with_container');
  topBlock.initSvg();

  // Then we add one sub-block for each item in the list.
  var connection = topBlock.getInput('STACK').connection;
  for (var i = 0; i < this.itemCount_; i++) {
    var itemBlock = workspace.newBlock('lists_create_with_item');
    itemBlock.initSvg();
    connection.connect(itemBlock.previousConnection);
    connection = itemBlock.nextConnection;
  }

  // And finally we have to return the top-block.
  return topBlock;
},

// The container block is the top-block returned by decompose.
compose: function(topBlock) {
  // First we get the first sub-block (which represents an input on our main block).
  var itemBlock = topBlock.getInputTargetBlock('STACK');

  // Then we collect up all of the connections of on our main block that are
  // referenced by our sub-blocks.
  // This relates to the saveConnections hook (explained below).
  var connections = [];
  while (itemBlock && !itemBlock.isInsertionMarker()) {  // Ignore insertion markers!
    connections.push(itemBlock.valueConnection_);
    itemBlock = itemBlock.nextConnection &&
        itemBlock.nextConnection.targetBlock();
  }

  // Then we disconnect any children where the sub-block associated with that
  // child has been deleted/removed from the stack.
  for (var i = 0; i < this.itemCount_; i++) {
    var connection = this.getInput('ADD' + i).connection.targetConnection;
    if (connection && connections.indexOf(connection) == -1) {
      connection.disconnect();
    }
  }

  // Then we update the shape of our block (removing or adding iputs as necessary).
  // `this` refers to the main block.
  this.itemCount_ = connections.length;
  this.updateShape_();

  // And finally we reconnect any child blocks.
  for (var i = 0; i < this.itemCount_; i++) {
    connections[i].reconnect(this, 'ADD' + i);
  }
},

saveConnections

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

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

saveConnections: function(topBlock) {
  // First we get the first sub-block (which represents an input on our main block).
  var itemBlock = topBlock.getInputTargetBlock('STACK');

  // Then we go through and assign references to connections on our main block
  // (input.connection.targetConnection) to properties on our sub blocks
  // (itemBlock.valueConnection_).
  var i = 0;
  while (itemBlock) {
    // `this` refers to the main block (which is being "mutated").
    var input = this.getInput('ADD' + i);
    // This is the important line of this function!
    itemBlock.valueConnection_ = input && input.connection.targetConnection;
    i++;
    itemBlock = itemBlock.nextConnection &&
        itemBlock.nextConnection.targetBlock();
  }
},

מבצע רישום

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

// Function signature.
Blockly.Extensions.registerMutator(name, mixinObj, opt_helperFn, opt_blockList);

// Example call.
Blockly.Extensions.registerMutator(
    'controls_if_mutator',
    { /* mutator methods */ },
    undefined,
    ['controls_if_elseif', 'controls_if_else']);
  • name: מחרוזת לשיוך למשתנה כדי שניתן יהיה להשתמש בה ב-JSON.
  • mixinObj: אובייקט שמכיל את שיטות השינוי השונות. למשל saveExtraState וגם loadExtraState.
  • opt_helperFn: פונקציית עזרה אופציונלית שתרוץ בבלוק לאחר שילוב של המיקס.
  • opt_blockList: מערך אופציונלי של סוגי בלוקים (כמחרוזות) שייתווספו ל-Flyout בממשק המשתמש של שינוי ברירת המחדל, אם גם שיטות ממשק המשתמש מוגדרות.

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

{
  //...
  "mutator": "controls_if_mutator"
}

פונקציית עזרה

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

לדוגמה, אפשר להוסיף כלי עזר לבלוק דמוי רשימה, שמגדיר את מספר הפריטים הראשוני:

var helper = function() {
  this.itemCount_ = 5;
  this.updateShape();
}