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

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

في الماضي، كان Discourse يستخدم واجهة برمجة تطبيقات قائمة على Ember-Controller لعرض النوافذ المنبثقة. لاستدعاء النافذة المنبثقة، كنت تمرر سلسلة تحتوي على اسم المتحكم إلى showModal(). تحت الغطاء، كان هذا يستخدم واجهة برمجة تطبيقات Route#renderTemplate الخاصة بـ Ember، وهي واجهة قديمة الإصدار في 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]. قم بإزالة mixin ModalFunctionality وحدّث أي استخدامات لدوالها وفقًا للجدول أدناه:

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

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

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

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

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

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

  • مرّر الوسيطة الجديدة @closeModal
  • أضف فئة صريحة. لمطابقة السلوك القديم، خذ اسم ملف المتحكم وأضف -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 دون تغيير

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

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

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

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

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) سيتم حله عند إغلاق النافذة المنبثقة. سيتم حل الوعد بأي بيانات تم تمريرها إلى @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 الكلاسيكية في هذا الدليل لأنها توفر أسهل مسار ترحيل من متحكمات 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 إعجابات