تحويل النوافذ المنبثقة من وحدات التحكم القديمة إلى واجهة برمجة تطبيقات مكوّن DModal الجديد

:information_source: إذا كنت تقوم بتنفيذ نافذة منبثقة (Modal) جديدة، فراجع الوثائق الرئيسية هنا. يصف هذا الموضوع كيفية هجرة نافذة منبثقة قائمة على المتحكم (Controller) إلى واجهة برمجة التطبيقات الجديدة القائمة على المكونات (Component-based API).

في الماضي، كان Discourse يستخدم واجهة برمجة تطبيقات قائمة على متحكمات Ember (Ember-Controller-based API) لعرض النوافذ المنبثقة. لاستدعاء النافذة المنبثقة، كنت تمرر نصاً يحتوي على اسم المتحكم إلى showModal(). تحت الغطاء، كان هذا يعتمد على واجهة Route#renderTemplate الخاصة بـ Ember، والتي تم إيقافها (deprecated) في Ember 3.x وستُحذف في Ember 4.x.

لتمكين Discourse من الترقية إلى Ember 4.x وما بعده، قمنا بمقدمة واجهة برمجة تطبيقات جديدة قائمة على المكونات للنوافذ المنبثقة. تعتنق هذه الواجهة الجديدة أنماط التصميم “الإعلانية” (declarative) الخاصة بـ Ember، وتهدف إلى تقديم دلالات نظيفة لـ DDAU (البيانات للأسفل والإجراءات للأعلى).

الخطوة 1: نقل الملفات

انقل ملف JS الخاص بالمتحكم وملف القالب إلى مجلد /components/modal. هذا يجعلهما “مكوّناً في نفس الموضع” (colocated component) يمكن استيراده تماماً مثل أي وحدة JS أخرى.

الخطوة 2: تحديث ملف JS

ثم، حدّث تعريف مكوّن JS ليتوارث من @ember/component بدلاً من @ember/controller [1]. احذف خليط ModalFunctionality (mixin) وحدّث أي استخدامات لدواله وفقاً للجداول أدناه:

قبل بعد
flash() و clearFlash() أنشئ خاصية flash في مكوّنك ومررها إلى الحجة @flash في <DModal>. بشكل افتراضي، سيتم تنسيق التنبيه بفئة alert وهي نسخة من فئة ‘error’، ولكن يمكن تجاوز ذلك باستخدام الحجة @flashType.
showModal() استورد دالة showModal من discourse/lib/show-modal
إجراء closeModal استدعِ الحجة closeModal التي يتم تمريرها تلقائياً إلى مكوّنك

كانت متحكمات النوافذ المنبثقة القديمة تعيش “إلى الأبد”، مما meant أنه كان علينا تنظيف الحالة يدوياً. مع واجهة برمجة التطبيقات الجديدة القائمة على المكونات، سيتم إنشاء المكوّن وتدميره عند إظهار/إخفاء النافذة المنبثقة. في كثير من الحالات، يعني ذلك أن خطافات دورة الحياة (lifecycle hooks) القديمة لم تعد مطلوبة.

إذا كنت لا تزال بحاجة إلى بعض المنطق المعتمد على دورة الحياة، استخدم هذا الجدول:

قبل بعد
onShow() استخدم دورة حياة مكوّن Ember القياسية (init() أو معدّل Ember)
afterRender استخدم دورة حياة مكوّن Ember القياسية (init() أو معدّل Ember)
beforeClose() أنشئ غلافاً (wrapper) حول الحجة @closeModal التي يتم تمريرها إلى مكوّنك. مرر مرجعاً إلى غلاف الإغلاق الخاص بك إلى DModal مثل <DModal @closeModal={{this.myCloseModalWrapper}}>
onClose() استخدم دورة حياة مكوّن Ember القياسية (willDestroy() أو معدّل Ember)

الخطوة 3: تحديث القالب

استبدل الغلاف <DModalBody> بـ <DModal>. أضف بعض السمات الجديدة:

  • مرر الحجة الجديدة @closeModal
  • أضف فئة (class) صريحة. لمطابقة السلوك القديم، خذ اسم ملف المتحكم وأضف -modal.

على سبيل المثال، إذا كان اسم متحكم النافذة المنبثقة close-topic.js، فستبدو استدعاء <DModal> الجديد شيئاً مثل هذا:

<DModal @closeModal={{@closeModal}} class="close-topic-modal">

إذا تضمن استدعاء DModalBody أي حجج أخرى، حدّثها بناءً على الجدول أدناه:

قبل بعد
@title="title_key" @title={{i18n "title_key"}}
@rawTitle="translated title" @title="translated title"
@subtitle="subtitle_key" @subtitle={{i18n "subtitle_key"}}
@rawSubtitle="translated subtitle" @subtitle="translated subtitle"
@class @bodyClass
@modalClass استخدم صيغة الأقواس الزاوية مع سمة html عادية: <DModal class="blah">
@titleAriaElementId استخدم صيغة الأقواس الزاوية مع سمة html عادية: <DModal aria-labelledby="blah">
@dismissable, @submitOnEnter, @headerClass بدون تغيير

إذا كان هناك أي محتوى تذييل (footer) يُعرض بعد مكوّن <DModalBody> القديم، فاستخدم الكتلة المسمية الجديدة <:footer> لإدخاله داخل <DModal>. عند استخدام أي كتل مسمية، يجب تغليف محتوى الجسم في <:body></:body>. على سبيل المثال:

<DModal @closeModal={{@closeModal}}>
  <:body>
    مرحباً عالماً، هذا هو محتوى النافذة المنبثقة
  </:body>
  <:footer>
    هذا هو محتوى التذييل. سيتم إضافة غلاف `.modal-footer`
    تلقائياً
  </:footer>
</DModal>

الخطوة 4: تحديث مواضع استدعاء showModal

سابقاً، كانت النوافذ المنبثقة تُعرض باستخدام واجهة showModal، التي كانت تأخذ نصاً (اسم المتحكم) وعددًا من الخيارات (opts). كانت تعيد نسخة من المتحكم يمكن التحكم بها:

import showModal from "discourse/lib/show-modal";

export default class extends Component {
  showMyModal() {
    const controller = showModal("my-modal", {
      title: "My Modal Title",
      modalClass: "my-modal-class",
      model: { topic: this.topic },
    });

    controller.set("updateTopic", this.updateTopic);
  });
}

لإظهار نوافذ منبثقة جديدة قائمة على المكونات، يجب حقن خدمة ‘modal’ (أو الوصول إليها باستخدام شيء مثل getOwner(this).lookup("service:modal")) واستدعاء دالة show().

تأخذ show() مرجعاً إلى فئة المكوّن الجديد كحجة أولى. الخيار الوحيد المدعوم لا يزال هو ‘model’، والذي يمكن استخدامه لتمرير جميع البيانات/الإجراءات المطلوبة لنافذتك المنبثقة.

لن يُعاد أي مرجع لنسخة المكوّن. بدلاً من ذلك، تعيد show() وعداً (promise) سيُحل (resolve) عند إغلاق النافذة المنبثقة. سيحل الوعد بأي بيانات تم تمريرها إلى @closeModal.

import MyModal from "discourse/components/my-modal";
import { service } from "@ember/service";

export default class extends Component {
  @service modal;

  showMyModal() {
    this.modal.show(MyModal, {
      model: { topic: this.topic, updateTopic: this.updateTopic },
    });
  });
}

بديلاً عن ذلك، هاجر إلى واجهة البرمجة الإعلانية الموصوفة في وثائق DModal الرئيسية.

يمكن تكرار وظيفة الخيارات القديمة على النحو التالي:

خيار showModal القديم الحل
admin غير قابل للتطبيق للمكوّن - احذفه
templateName غير قابل للتطبيق للمكونات - احذفه
title انقله إلى <DModal @title={{i18n "blah"}}>
titleTranslated انقله إلى <DModal @title="blah">. يمكن حسابه بناءً على بيانات من model إذا لزم الأمر
modalClass انقله إلى <DModal class="blah">
titleAriaElementId انقله إلى <DModal aria-labelledby="blah">
panels استخدم الكتلة المسمية <:headerBelowTitle> لتنفيذ التبويبات في مكوّنك (مثال)
model بدون تغيير

الخطوة 5: الاختبارات

يجب أن تبقى الاختبارات في الغالب كما هي. أكثر المشاكل شيوعاً هي:

  • لم تعد النوافذ المنبثقة لها فئة افتراضية بناءً على اسمها. يجب تحديد الفئات صراحةً في القالب (انظر بداية الخطوة 3)

  • لم يعد غلاف d-modal موجوداً في DOM عند إغلاق النافذة المنبثقة. للتحقق من إغلاق جميع النوافذ المنبثقة، استخدم فحصاً مثل assert.dom('.d-modal').doesNotExist()

الربح!

يجب أن تعمل نافذتك المنبثقة الآن كما كانت من قبل. للاستفادة بشكل أكبر من واجهة البرمجة الجديدة، قد ترغب في النظر في استبدال استدعاءات showModal باستراتيجية إعلانية، وتحويل نافذتك المنبثقة لتكون مكوّن Glimmer.

أمثلة

إليك بعض الأمثلة على التزامات (commits) التي توضح تحويل بعض نوافذ Discourse الأساسية إلى الواجهة الجديدة:


هذا المستند خاضع للتحكم في الإصدارات - اقترح تغييرات على github.


  1. يُنصح باستخدام مكونات Ember الكلاسيكية (Classic Ember Components) في هذا الدليل لأنها وفّرت أسهل مسار للهجرة من متحكمات Ember. لكن للنوافذ المنبثقة البسيطة، أو إذا كنت مستعداً لقضاء بعض الوقت في إعادة الهيكلة، فإن مكونات Glimmer الحديثة هي الخيار الأفضل. ↩︎

20 إعجابًا

هذا يبدو رائعًا حقًا. يمنحني الأمل في أنني أستطيع تحويل نوافذي المنبثقة إلى Ember 4. أنا بالكاد أفهم كود Ember الذي أكتبه، لذا فإن كتابة وثائق يمكنني فهمها ليس بالأمر السهل. شكرًا جزيلاً على هذا.

8 إعجابات

شكرا على البرنامج التعليمي! كانت الأمثلة مفيدة للغاية. تمكنت من إصلاح نافذة المكون الإضافي المخصصة المعطلة في غضون ساعة.

4 إعجابات

أنا أعمل على هذا التحويل حاليًا، ولكني أواجه مشكلة:

في السابق، لم يكن لدينا وحدة تحكم/تعريف JavaScript مطابق للنافذة المنبثقة، وكنا قادرين على عرض النافذة المنبثقة من خلال showModal($HBS_FILE_NAME). نظرًا لأن show() الجديد يتطلب تمرير مكون، أحتاج إلى تقديم هذا التعريف JavaScript (هل هذا افتراض صحيح؟).

لقد أضفت شيئًا مثل:

import Component from '@glimmer/component';

export default class SomeModal extends Component {

  constructor() {
    super(...arguments);
    console.log('Modal constructor')
  }
}

ولدي ملف .hbs السابق (مع التغييرات المطلوبة لـ DModal) كلاهما في دليل /components/modal بنفس اسم الملف. عند محاولة عرض النافذة المنبثقة (عبر getOwner(this).lookup("service:modal").show(SomeModal)), أرى سجل المُنشئ الخاص بي مطبوعًا في وحدة التحكم، ولكن النافذة المنبثقة لا يتم عرضها.

هل هناك أي تكوين آخر مطلوب في وحدة التحكم/تعريف JavaScript لهذا التغيير؟ أي توجيه سيكون موضع تقدير كبير!

لا تحتاج إليه إذا كنت لا تضيف أي كود.

يمكنك الاكتفاء بملف .hbs.

على سبيل المثال، discourse-templates لا يحتوي على ملف جافاسكريبت مقابل لقالب المقبض الخاص بالنافذة المنبثقة.

هل قمت بتكييف قالب المقبض الخاص بك باتباع التعليمات؟

هل هناك أي أخطاء في وحدة التحكم؟

إعجابَين (2)

شكراً على الملاحظات! خطأ كبير :facepalm: من جهتي، لقد نقلت الملفات إلى الدليل .../discourse/templates/components/modal بدلاً من .../discourse/components/modal. الأمور تعمل كما هو متوقع الآن (مع أو بدون وحدة التحكم .js)، شكراً لك!

3 إعجابات

هل يمكنك أن تريني كيف يمكنني استدعاء showModal() من نص برمجي داخل ملف head_tag.html من فضلك؟ في حالتي، أحتاج إلى استخدام

document.querySelector(".actions .double-button .toggle-like");

لالتقاط حدث النقر، والتحقق من الشرط، ثم عرض نافذة منبثقة مخصصة.

إعجاب واحد (1)

أقدر حقًا الجهد الذي بذلته هنا لتوثيق هذا بوضوح يا ديفيد!

لقد تمكنت تقريبًا من إزالة الإهمال لـ 3.2 بعد ظهر اليوم في أكبر المكونات الإضافية لدينا.

3 إعجابات

كيف يمكنك الآن الوصول إلى نافذة منبثقة موجودة في core لتعديلها؟

في الماضي، استخدمت هذا (الذي لم يعد يعمل):
api.modifyClass("controller:poll-ui-builder", {

في هذه الحالة بالذات، يبدو أن اسم الفئة هذا تم تعريفه بشكل جيد ولم يتغير.

إعجابَين (2)

اعتمادًا على ما تحتاج إلى تعديله، أعتقد أن أفضل حل هو استخدام PluginOutlet لحقن الكود المخصص الخاص بك، أو PluginOutlet Wrapper لاستبدال/عرض التنفيذ الأساسي بشكل شرطي. (يمكنك تقديم طلب سحب لإضافة منفذ إذا لم يكن متاحًا)

إذا كنت ترغب حقًا في استخدام modifyClass، فلا يزال ذلك ممكنًا، فالنافذة المنبثقة هي الآن مكون وهي متداخلة في components/modal لذا ستصل إليها مثل:

api.modifyClass("component:modal/poll-ui-builder", {
   pluginId: "your-custom-plugin-id",

   // insert custom code
});
4 إعجابات