إضافة مكونات Ember إلى Discourse

في الدليل السابق، أوضحت كيفية إعداد كل من جانب الخادم وجانب العميل في Discourse للاستجابة لطلب.

ننصحك الآن بقراءة توثيق مكونات Ember: Introducing Components - Components - Ember Guides

قبل كتابة مكوّن، تحقق من مجموعة واجهة المستخدم التي تحتوي على مكونات وأدوات مساعدة ومعدّلات مشتركة؛ فمعظم عناصر التحكم الشائعة موجودة بالفعل هناك.

الدليل القديم

في هذا الدليل، سأقوم بإنشاء مكوّن Ember جديد كطريقة لتغليف جافاسكريبت من طرف ثالث. سيكون هذا مشابهًا لـ فيديو على يوتيوب قمت بإنشائه منذ فترة، والذي قد تجده مفيدًا، لكن هذه المرة سيكون مخصصًا لـ Discourse وكيفية تنظيم الملفات في مشروعنا.

لماذا المكونات؟

Handlebars هي لغة بسيطة ومغرية إلى حد كبير. فهي تتكون من HTML عادي مع بعض الأجزاء الديناميكية. هذا سهل التعلّم وممتاز للإنتاجية، لكنه ليس جيدًا جدًا لإعادة استخدام الكود. إذا كنت تطبّق تطبيقًا كبيرًا مثل Discourse، فستجد أنك ترغب في إعادة استخدام بعض الأشياء نفسها مرة بعد مرة.

المكونات هي حل Ember لهذه المشكلة. دعنا ننشئ مكوّنًا سيعرض وجبتنا الخفيفة بطريقة أنظف.

إنشاء مكوّن جديد

يجب أن تحتوي أسماء المكونات على شرطة صغيرة (dash). سأختار fancy-snack كاسم لهذا المكوّن. دعنا ننشئ قالبنا:

app/assets/javascripts/admin/templates/components/fancy-snack.hbs

<div class="fancy-snack-title">
  <h1>{{snack.name}}</h1>
</div>

<div class="fancy-snack-description">
  <p>{{snack.description}}</p>
</div>

الآن، لاستخدام مكوّننا، سنقوم باستبدال قالب admin/snack الموجود لدينا بما يلي:

app/assets/javascripts/admin/templates/snack.hbs

{{fancy-snack snack=model}}

يمكننا الآن إعادة استخدام مكوّن fancy-snack في أي قالب آخر، فقط عن طريق تمرير النموذج (model) كما هو مطلوب.

إضافة كود جافاسكريبت مخصص

بجانب إمكانية إعادة الاستخدام، المكونات في Ember رائعة لإضافة جافاسكريبت مخصص، jQuery، وأكواد خارجية أخرى بأمان. فهي تمنحك التحكم في وقت إدراج المكوّن في الصفحة، ووقت إزالته. للقيام بذلك، نعرّف Ember.Component مع بعض الكود:

app/assets/javascripts/admin/components/fancy-snack.js

export default Ember.Component.extend({
  didInsertElement() {
    this._super();
    this.$().animate({ backgroundColor: "yellow" }, 2000);
  },

  willDestroyElement() {
    this._super();
    this.$().stop();
  },
});

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

دعنا نشرح ما يحدث هنا:

  1. عندما يُعرض المكوّن على الصفحة، سيقوم باستدعاء didInsertElement

  2. السطر الأول في didInsertElement (وwillDestroyElement) هو this._super() وهو ضروري لأننا نقوم ببناء فئة فرعية من Ember.Component.

  3. يتم تنفيذ الحركة باستخدام دالة animate في jQuery.

  4. أخيرًا، يتم إلغاء الحركة في خطاف willDestroyElement، الذي يُستدعى عندما يُزال المكوّن من الصفحة.

قد تتساءل لماذا نهتم بـ willDestroyElement على الإطلاق؛ السبب هو أنه في تطبيق جافاسكريبت طويل العمر مثل Discourse، من المهم تنظيف ما نقوم به، حتى لا تسرّب الذاكرة أو نترك أشياء تعمل في الخلفية. في هذه الحالة، نوقف الحركة، مما يخبر مؤقتات jQuery أنها لا تحتاج إلى العمل بعد الآن لأن المكوّن لم يعد مرئيًا على الصفحة.

إلى أين نذهب من هنا

الدليل الأخير في هذه السلسلة يتناول الاختبار الآلي.


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

18 إعجابًا

Hi, how do i extend a discourse component thru a plugin? Can you give me some points. Thanks

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

Generally we prefer you don’t extend Discourse plugins, and you stick to plugin outlets or using the widget decoration API to add stuff.

But if you must, you can create an initializer and use Ember’s extend code. Here’s an example that extends an Ember object.

4 إعجابات

Tried with initializer but didnt worked. What i actually want to do is to add 2 more classNames and some actions:

import { withPluginApi } from 'discourse/lib/plugin-api';

function initializeComponentTopicList(api) {
  // extend component from jsapp/components/topic-list.js.es6
  const TopicList = api.container.lookupFactory('component:topic-list');

  TopicList.extend({
    classNames: ['topic-list', 'round', 'table'],
    actions: {
        clickMe: function() {
            console.log('click');
        }
    }
  });
};

export default {
  name: "extend-for-component-topic-list",

  initialize() {
    withPluginApi('0.1', initializeComponentTopicList);
  }
};

And by “didnt worked”, i mean that topic list completly disappeared from page.
Thank you

Were there any logs in the console?

Nope, no logs at all. However i managed to fix it this way. I hope it will help someone.

import { default as TopicList } from 'discourse/components/topic-list';
import { withPluginApi } from 'discourse/lib/plugin-api';

function initializeComponentTopicList(api) {
  TopicList.reopen({
    classNames: ['topic-list', 'round', 'table'],
  });
};

export default {
  name: "extend-for-component-topic-list",

  initialize() {
    withPluginApi('0.1', initializeComponentTopicList);
  }
};
3 إعجابات

I just ran into the same problem. Add the following as a css/html customisation and observe empty user cards:

<script type="text/discourse-plugin" version="0.5">
    api.container.lookupFactory('component:user-card-contents')
</script>
إعجابَين (2)

سيكون من الجيد رؤية وثائق محدثة هنا، والتي يمكن أن تشير إلى وثائق مكون Glimmer.

3 إعجابات