ส่วนขยายและการจําลอง

ส่วนขยายคือฟังก์ชันที่ทำงานในแต่ละบล็อกของประเภทหนึ่งๆ เมื่อมีการสร้างการบล็อก ซึ่งมักจะเพิ่มการกำหนดค่าหรือพฤติกรรมที่กำหนดเองลงในการบล็อก

การเปลี่ยนแปลงเป็นส่วนขยายชนิดพิเศษที่เพิ่มการทำให้เป็นอนุกรมที่กำหนดเอง และบางครั้ง UI ลงในบล็อก

ส่วนขยาย

ส่วนขยายคือฟังก์ชันที่ทำงานในแต่ละบล็อกของประเภทหนึ่งๆ เมื่อมีการสร้างการบล็อก โดยอาจเพิ่มการกำหนดค่าที่กำหนดเอง (เช่น การตั้งค่าเคล็ดลับเครื่องมือของบล็อก) หรือลักษณะการทำงานที่กำหนดเอง (เช่น การเพิ่ม 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 ยังมอบวิธีที่สะดวกสำหรับสถานการณ์ที่คุณต้องการเพิ่มพร็อพเพอร์ตี้/ฟังก์ชันตัวช่วยบางรายการลงในบล็อก แต่ไม่เรียกใช้โดยทันที วิธีนี้จะช่วยคุณลงทะเบียนออบเจ็กต์ mixin ที่มีพร็อพเพอร์ตี้/เมธอดเพิ่มเติมทั้งหมด จากนั้นออบเจ็กต์มิกซ์จะรวมไว้ในฟังก์ชันที่นำมิกซ์นั้นไปใช้ทุกครั้งที่มีการสร้างอินสแตนซ์ของประเภทการบล็อกที่ระบุ

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 จะเปลี่ยนรูปร่าง แต่จะปรับตามช่องแบบเลื่อนลง ซึ่งมีค่าต่างๆ ที่ทำให้เป็นอนุกรมอยู่แล้ว ดังนั้น โดยใช้เพียงเครื่องมือตรวจสอบช่องและไม่จำเป็นต้องมีตัวเปลี่ยนแปลง

ดูข้อมูลเพิ่มเติมเกี่ยวกับสถานการณ์ที่ต้องการการเปลี่ยนแปลงและเวลาที่ไม่ต้องใช้ได้ในหน้าการทำให้เป็นอนุกรม

นอกจากนี้ Mutator ยังมี UI ในตัวให้ผู้ใช้เปลี่ยนรูปร่างของบล็อกได้ด้วยหากคุณระบุวิธีการเพิ่มเติม

ฮุกการทำให้เป็นอนุกรม

เครื่องมือจำลองมีฮุกอนุกรม 2 คู่ที่ใช้ได้ ฮุกคู่หนึ่งใช้งานได้กับระบบอนุกรม JSON ใหม่ ส่วนอีกคู่หนึ่งใช้งานได้กับระบบการเรียงลำดับ XML แบบเก่า คุณต้องระบุคู่เหล่านี้อย่างน้อย 1 คู่

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 ที่ไม่บังคับด้วย ซึ่งใช้โดยบล็อกอ้างอิงสถานะที่ทำให้เป็นอนุกรมโดยซีเรียลไลเซอร์อื่น (เช่น การสำรองข้อมูลโมเดลข้อมูล) พารามิเตอร์จะส่งสัญญาณว่าสถานะที่อ้างอิงไม่พร้อมใช้งานเมื่อการบล็อกได้รับการดีซีเรียลไลซ์ ดังนั้นบล็อกจึงควรทำให้สถานะการสนับสนุนทั้งหมดเป็นอนุกรม ตัวอย่างเช่น กรณีนี้จะเป็นจริงเมื่อมีการทำให้บล็อกหนึ่งๆ มีการเรียงลำดับ หรือเมื่อมีการคัดลอกและวางบล็อก

กรณีการใช้งานทั่วไปสำหรับกรณีนี้มี 2 กรณีดังนี้

  • เมื่อโหลดแต่ละบล็อกเข้าไปในพื้นที่ทำงานที่ไม่มีโมเดลข้อมูลสนับสนุนอยู่ จะมีข้อมูลเพียงพอที่จะสร้างโมเดลข้อมูลใหม่
  • เมื่อมีการคัดลอกและวางบล็อก ระบบจะสร้างโมเดลข้อมูลสนับสนุนใหม่อยู่เสมอแทนที่จะอ้างอิงโมเดลที่มีอยู่

การบล็อกที่ใช้ค่านี้คือการบล็อก @blockly/block-shareable-procedures โดยปกติจะมีการเรียงอันดับการอ้างอิงไปยังโมเดลข้อมูลสนับสนุน ซึ่งจะจัดเก็บสถานะไว้ แต่ถ้าพารามิเตอร์ doFullSerialization เป็นจริง ค่าเหล่านี้จะทำให้สถานะทั้งหมดเป็นอนุกรม การบล็อกขั้นตอนที่แชร์ได้จะใช้วิธีนี้ในการตรวจสอบว่าเมื่อคัดลอกและวาง จะเป็นการสร้างโมเดลข้อมูลสนับสนุนใหม่แทนการอ้างอิงโมเดลที่มีอยู่

MutationToDom และ domToMutation

mutationToDom และ domToMutation คือฮุกอนุกรมที่ทำงานร่วมกับระบบการทำให้เป็นอนุกรม XML แบบเก่า ใช้ hook เหล่านี้เมื่อจำเป็นเท่านั้น (เช่น คุณกำลังทำงานกับฐานของโค้ดเก่าที่ยังไม่ได้ย้ายข้อมูล) มิฉะนั้น ให้ใช้ 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

ตะขอ UI

หากคุณมีฟังก์ชันบางอย่างเป็นส่วนหนึ่งของการเปลี่ยนแปลง Blockly จะเพิ่ม UI "เปลี่ยนแปลง" เริ่มต้นในบล็อก

คุณไม่จำเป็นต้องใช้ UI นี้หากต้องการเพิ่มการเรียงอันดับเพิ่มเติม คุณสามารถใช้ UI ที่กำหนดเอง เช่น ปลั๊กอินบล็อกเครื่องหมายบวกที่มีให้ หรือจะใช้ UI ไม่ได้เลยก็ได้

เขียนและแยกข้อมูล

UI เริ่มต้นต้องใช้ฟังก์ชัน 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 ที่ใช้ได้กับ UI เริ่มต้นได้ด้วย ฟังก์ชันนี้จะเปิดโอกาสให้คุณเชื่อมโยงรายการย่อยของบล็อกหลัก (ซึ่งมีอยู่ในพื้นที่ทำงานหลัก) กับบล็อกย่อยที่มีในพื้นที่ทำงานของการเปลี่ยนรูปแบบ จากนั้นคุณจะใช้ข้อมูลนี้เพื่อตรวจสอบว่า 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: อาร์เรย์ทางเลือกของประเภทบล็อก (เป็นสตริง) ที่จะเพิ่มลงในฟลายเอตใน UI การเปลี่ยนแปลงเริ่มต้น หากมีการกําหนดวิธีการ UI ไว้ด้วย

โปรดทราบว่าการบล็อกแต่ละประเภทสามารถเปลี่ยนแปลงได้เพียง 1 รายการเท่านั้น ซึ่งต่างจากส่วนขยาย

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

ฟังก์ชันตัวช่วย

โดยเครื่องมือกลายพันธุ์อาจบันทึกฟังก์ชันตัวช่วยควบคู่กับมิกซ์อิน ฟังก์ชันนี้จะทำงานในแต่ละบล็อกของประเภทที่กำหนดหลังจากที่สร้างขึ้นและเพิ่มMixinObj แล้ว ใช้เพื่อเพิ่มทริกเกอร์หรือเอฟเฟกต์เพิ่มเติมให้กับการเปลี่ยนแปลงได้

ตัวอย่างเช่น คุณสามารถเพิ่มเครื่องมือช่วยเหลือในบล็อกที่มีลักษณะเหมือนลิสต์รายการซึ่งกำหนดจำนวนเริ่มต้นของรายการดังนี้

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