การกลายพันธุ์

ตัวแปรคือมิกซ์อินที่เพิ่มการจัดรูปแบบเพิ่มเติม (สถานะเพิ่มเติมที่บันทึกและโหลด) ลงในบล็อก เช่น บล็อก controls_if และ list_create_with ในตัวต้องมีการจัดรูปแบบเพิ่มเติมเพื่อให้บันทึกจํานวนอินพุตที่มีได้ นอกจากนี้ยังอาจเพิ่ม UI เพื่อให้ผู้ใช้เปลี่ยนรูปร่างของบล็อกได้

การกลายพันธุ์ 3 รูปแบบของบล็อกสร้างรายการ ได้แก่ ไม่มีอินพุต 3 อินพุต และ 5 อินพุต

การกลายพันธุ์ 2 รูปแบบของบล็อก if/do ได้แก่ if-do และ if-do-else-if-do-else

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

บล็อก `math_number_property` ที่มีการตั้งค่าเมนูแบบเลื่อนลงเป็น "even" และมีอินพุตค่าเดี่ยว บล็อก `math_number_property` ที่มีการตั้งค่าเมนูแบบเลื่อนลงเป็น `divisible by` และมีอินพุตค่า 2 รายการ

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

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

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

บล็อก if-do ที่เปิดบับเบิลตัวแปลง ซึ่งช่วยให้ผู้ใช้เพิ่มอนุประโยค else-if และ
else ลงในบล็อก if-do ได้

คุณไม่จำเป็นต้องใช้ UI นี้หากต้องการเพิ่มการแปลงเป็นอนุกรมเพิ่มเติม คุณอาจใช้ UI ที่กําหนดเอง เช่น ปลั๊กอิน blocks-plus-minus ให้มา หรือจะใช้ 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 ฟังก์ชันจะเชื่อมต่อรายการย่อยของบล็อกหลักอีกครั้งอย่างถูกต้องเมื่อมีการ reorganize บล็อกย่อย

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 ด้วย

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

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

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

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

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

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