الإضافات ومفاتيح التبديل

الإضافات هي دوال تعمل على كل كتلة من نوع معيّن عند إنشاء الكتلة. وغالبًا ما تضيف هذه الضبط أو السلوك المخصّص إلى مجموعة.

أداة التعديل هي نوع خاص من الإضافات تضيف تسلسلاً مخصّصًا، وأحيانًا واجهة المستخدم، إلى مجموعة معيّنة.

الإضافات

الإضافات هي دوال تعمل على كل كتلة من نوع معيّن عند إنشاء الكتلة. يمكن أن يضيف المشرفون إعدادات مخصّصة (مثل ضبط تلميح الحظر) أو سلوكًا مخصّصًا (مثل إضافة أداة معالجة حدث إلى مجموعة أدوات الحظر).

// 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"],
}

خلطات

يوفّر حظر أيضًا طريقة مناسبة في الحالات التي تريد فيها إضافة بعض الخصائص/دوال المساعدة إلى كتلة معيّنة بدون تفعيلها على الفور. ويعمل ذلك من خلال السماح لك بتسجيل كائن mixin يحتوي على جميع السمات/الطرق الإضافية. ثم يتم لف كائن 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 في الشكل، لكنّها تتغير استنادًا إلى حقل قائمة منسدلة يتم تسجيل قيمته مسبقًا. وبالتالي، يمكنه فقط استخدام أداة التحقّق من الحقل، ولا يحتاج إلى أداة تغيير.

راجع صفحة التسلسل التسلسلية للحصول على مزيد من المعلومات حول الحالات التي تحتاج فيها إلى أداة تحويل وتلك التي لا تحتاج إليها.

توفر المتغيرات أيضًا واجهة مستخدم مضمنة للمستخدمين لتغيير أشكال القوالب إذا قدمت بعض الطرق الاختيارية.

عناصر جذب التسلسل

تشتمل المبدلات على زوجين من أدوات جذب التسلسل التي تعمل معها. يعمل أحدهما مع نظام تسلسل 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 مختلف (مثل نماذج البيانات الاحتياطية). تشير المَعلمة إلى أنّ الحالة المُشار إليها لن تكون متاحة عند إلغاء تسلسل الكتلة، لذا من المفترض أن يتضمّن الحظر كل حالة الدعم نفسها على نحو تسلسلي. على سبيل المثال، هذا صحيح عندما تكون كتلة فردية متسلسلة، أو عند نسخ كتلة.

هناك حالتا استخدام شائعتان لذلك هما:

  • عند تحميل كتلة فردية في مساحة عمل لا يتوفّر فيها نموذج البيانات الاحتياطية، فإنّها تتضمّن معلومات كافية في حالتها الخاصة لإنشاء نموذج بيانات جديد.
  • عندما يتم نسخ كتلة ما، فإنها تنشئ دائمًا نموذج بيانات احتياطية جديدًا بدلاً من الإشارة إلى نموذج موجود.

بعض عمليات الحظر التي تستخدم هذا هي عمليات الحظر @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 تعرض قيمة فارغة، لن تتم إضافة أي عنصر إضافي إلى XML.

خطافات واجهة المستخدم

إذا كنت تقدّم دوال معيّنة كجزء من المُبدِّل، سيضيف بشكل حظر واجهة مستخدم "أداة تبديل" تلقائية إلى المجموعة.

لن تحتاج إلى استخدام واجهة المستخدم هذه إذا كنت تريد إضافة تسلسل إضافي. يمكنك استخدام واجهة مستخدم مخصّصة، مثل المكوّن الإضافيblocks-plus-minus الذي يوفّره، أو يمكنك عدم استخدام أي واجهة مستخدم على الإطلاق.

تكوينها وتفكيكها

تعتمد واجهة المستخدم التلقائية على الدالتَين 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، تستدعي هذه الدالة قبل استدعاء 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: مصفوفة اختيارية من أنواع الكتل (كسلاسل) ستتم إضافتها إلى القائمة المنبثقة في واجهة المستخدم المتغيّرة التلقائية، إذا تم أيضًا تحديد طرق واجهة المستخدم.

تجدر الإشارة إلى أنّه على عكس الإضافات، قد يحتوي كل نوع حظر على مفتاح تبديل واحد فقط.

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

دالة مساعِدة

إلى جانب المزج، قد يُسجل المبدل وظيفة مساعد. يتم تشغيل هذه الدالة على كل كتلة من النوع المحدد بعد إنشائها وإضافة MixinObj. ويمكن استخدامه لإضافة مشغلات أو تأثيرات إضافية إلى طفرة.

على سبيل المثال، يمكنك إضافة مساعد إلى الكتلة شبيهة بالقائمة التي تعين العدد الأولي من العناصر:

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