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

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

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

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

دليل قديم

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

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

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

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

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

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

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. السطر الأول في didInsertElementwillDestroyElement) هو this._super() وهو ضروري لأننا نقوم بـ إرث (Subclassing) لـ Ember.Component.

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

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

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

أين تذهب من هنا

يتناول الدليل النهائي في هذه السلسلة الاختبار الآلي (Automated Testing).


هذا المستند خاضع لرقمنة الإصدارات (Version Controlled) - اقترح تغييرات على 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 إعجابات