كود

طريقة بناء وتصميم Widget احترافي: من الفكرة إلى مكوّن قابل لإعادة الاستخدام

2026-08-27 · بواسطة Manus AI

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

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

1. ما الذي يجعل الـWidget احترافيًا؟

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

المحورقرار احترافيعلامة خطر
الوظيفةمهمة واحدة قابلة للشرح في جملةالودجت يحاول أن يكون صفحة كاملة
البياناتخصائص وأحداث موثقةقراءة عشوائية من global variables
التصميميعمل في عرض ضيق وعريضأبعاد ثابتة تكسر الصفحة
التجربةloading وerror وempty وsuccess واضحةمساحة بيضاء أو رسالة غامضة
الدمجلا يفرض CSS أو مكتبات على الموقعأنماط عامة مثل `.button` تتسرب للخارج

2. اكتب مواصفة الودجت قبل رسمه

قبل فتح Figma أو كتابة class، اكتب مواصفة قصيرة. سمِّ المستخدم، واللحظة التي سيستخدم فيها الودجت، والنتيجة التي يجب أن يراها. مثال: “يعرض حالة خدمة من endpoint عام، ويخبر المستخدم بآخر تحديث، ويعطي رسالة قابلة للفهم إذا تعذر الاتصال”. هذه الجملة تمنعك من إضافة زر لا يخدم الهدف أو تحميل بيانات لا يحتاجها المستخدم.

ورقة المواصفة في سبع خانات

  1. اسم الودجت وهدفه في جملة واحدة.
  2. خصائص الإدخال: نص، رقم، رابط، أو JSON محدود الشكل.
  3. الأحداث التي قد يحتاج الموقع الأب إلى استقبالها.
  4. الحالات المرئية: loading، success، empty، error، disabled.
  5. الحد الأدنى للعرض والحد الأقصى المعقول له.
  6. مصدر البيانات، وهل يحتاج إذنًا أو مفتاحًا أو لا.
  7. ما الذي لن يفعله الإصدار الأول.

3. صمّم API صغيرة لا تحوّل الودجت إلى تطبيق كامل

إذا استخدمت Web Components، فالمكوّن يمكن تعريفه كعنصر HTML مخصص وتسجيله في المتصفح عبر customElements.define() [1]. اجعل أسماء الخصائص قليلة ومفهومة، وميّز بين البيانات التي يقرأها الودجت والأحداث التي يطلقها. لا تجعل الموقع الأب يعبث بعناصر داخلية لا تضمن بقاءها.

<ignis-status
  endpoint="/api/status"
  label="حالة الخدمة"
  refresh="60000">
</ignis-status>

في المثال السابق، العقد واضحة: الودجت يملك endpoint وlabel وrefresh، أما الموقع الأب فلا يحتاج معرفة أسماء عناصره الداخلية. لو احتاج الأب إلى معرفة النتيجة، أطلق حدثًا مخصصًا مثل status-change مع بيانات صغيرة وآمنة بدل تمرير كائن ضخم أو كشف تفاصيل التنفيذ.

4. اختر بنية قابلة لإعادة الاستخدام

Web Components تجمع تقنيات متعاونة: Custom Elements لتعريف العنصر، Shadow DOM لعزل الشجرة والأنماط، وHTML templates وslots لإعادة استخدام البنية وإتاحة نقاط تركيب. توضح MDN أن Shadow DOM يحد من تصادم المعرفات والأنماط بين المكوّن والصفحة المضيفة [1]، لكنه ليس بديلًا عن تصميم دلالي أو وصول جيد.

const template = document.createElement('template');
template.innerHTML = `
  <style>
    :host { display:block; font: 500 0.95rem system-ui; }
    .card { padding:1rem; border:1px solid #ddd; border-radius:14px; }
    [part="value"] { font-size:1.6rem; }
  </style>
  <article class="card" aria-live="polite">
    <h2 part="label"></h2>
    <p part="value">—</p>
    <p part="message" hidden></p>
  </article>`;

استخدم :host لتحديد السلوك العام، وخصائص مثل part إذا كنت تريد السماح بتخصيص محدود من الخارج. لا تعتمد على selectors عامة داخل Shadow DOM، ولا تفترض أن الموقع المضيف يستخدم نفس نظام الألوان أو نفس الخط.

5. ابنِ دورة حياة واضحة

يحتاج المكوّن إلى مكان واحد لإنشاء الـShadow Root، ومكان واحد لبدء التشغيل، ودالة render صغيرة تعكس الحالة الحالية. في Custom Elements تُستخدم connectedCallback() عند إدخال العنصر إلى الصفحة، ويمكن استخدام attributeChangedCallback() لمراقبة خصائص معلنة [2]. لا تبدأ طلبات الشبكة من constructor قبل أن يصبح العنصر متصلًا.

class IgnisStatus extends HTMLElement {
  static observedAttributes = ['endpoint', 'label'];

  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.append(template.content.cloneNode(true));
    this.state = { status: 'idle', value: null, message: '' };
  }

  connectedCallback() {
    this.render();
    this.load();
  }

  attributeChangedCallback() {
    if (this.isConnected) this.load();
  }
}

الفكرة الأهم هي أن الحالة تكون مصدر الحقيقة. لا تغيّر عشرة عناصر من أماكن مختلفة ثم تحاول معرفة ما حدث؛ حدّث state واحدة، وبعدها ارسم الواجهة. هذا يجعل اختبار loading وerror أسهل، ويمنع بقاء رسالة قديمة بعد نجاح طلب جديد.

6. صمّم الحالات قبل تفاصيل الألوان

أغلب الودجتات تبدو جيدة في success فقط، ثم تنهار عندما ينقطع الإنترنت أو لا توجد بيانات. ارسم لكل حالة نصًا وارتفاعًا متوقعًا. الحالة الفارغة ليست خطأ؛ هي نتيجة صحيحة تحتاج شرحًا. أما الخطأ فيجب أن يوضح ما يستطيع المستخدم فعله، مثل المحاولة مرة أخرى، من غير كشف stack trace.

الحالةما يراه المستخدمقرار التنفيذ
Loadingعنوان ثابت ومؤشر هادئلا تغيّر ارتفاع البطاقة بعشوائية
Successالقيمة ووقت التحديثاستخدم تنسيقًا مفهومًا للأرقام والتاريخ
Emptyلا توجد نتائج مع سبب مختصرلا تعرض قيمة وهمية كأنها حقيقة
Errorرسالة مفهومة وزر إعادة المحاولةسجّل التفاصيل للمطور فقط
Disabledسبب عدم التفاعللا تجعل اللون هو الإشارة الوحيدة

7. اجعل التصميم متجاوبًا من داخل الودجت

صمّم الودجت كجزء قد يعيش في sidebar أو داخل بطاقة أو على شاشة هاتف. استخدم وحدات مرنة ولفّ النص الطويل، ولا تضع width: 600px كحل سريع. إذا احتجت breakpoint، اجعله مرتبطًا بعرض المكوّن لا بعرض الشاشة متى كان ذلك ممكنًا، واختبره داخل حاوية ضيقة فعلًا.

احفظ تسلسلًا بصريًا ثابتًا: label صغير، value واضح، message مساعد، ثم action عند الحاجة. لا تضف الظلال والحدود لكل عنصر؛ يكفي سطح واحد وحدود هادئة وتباين يوضح ما يمكن الضغط عليه. لو كان الودجت جزءًا من نظام تصميم، عرّف custom properties مثل --ignis-accent بدل نسخ ألوان المشروع المضيف داخل كل سطر.

8. الوصول ليس إضافة لاحقة

الودجت التفاعلي يجب أن يعمل بلوحة المفاتيح وقارئ الشاشة. استخدم HTML دلاليًا قبل ARIA، واجعل زر إعادة المحاولة زرًا حقيقيًا، وامنح كل معلومة اسمًا واضحًا. توصي مراجع الوصول إلى الودجتات بأن تكون الحالة المعلنة مفهومة دون الاعتماد على اللون أو الحركة [5] [6]. اجعل focus ظاهرًا، ولا تحبس المستخدم داخل Shadow DOM من غير سبب.

استخدم aria-live="polite" فقط للمعلومة التي تتغير ويحتاج المستخدم إلى معرفتها، ولا تجعل كل إعادة رسم إعلانًا صوتيًا. إذا كان الودجت يحتوي dialog أو listbox أو tabs، فلا تخترع سلوكًا من الذاكرة؛ راجع نمط الوصول المناسب واختبر التنقل بالأسهم وEscape وTab.

9. البيانات والأمان والخصوصية

لا تضع أسرارًا داخل Widget يعمل في المتصفح؛ أي قيمة تصل إلى JavaScript في العميل يمكن للمستخدم رؤيتها. استخدم endpoint عام محدود أو اجعل الطلب يمر عبر خادمك عندما تكون هناك صلاحيات. تحقّق من شكل البيانات القادمة قبل عرضها، واستخدم textContent للنصوص بدل إدخال HTML غير موثوق.

function readStatus(payload) {
  if (!payload || typeof payload !== 'object') {
    throw new Error('Invalid status response');
  }
  return {
    value: String(payload.value ?? 'غير متاح'),
    updatedAt: Number.isFinite(payload.updatedAt)
      ? payload.updatedAt
      : null
  };
}

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

10. الأداء: اجعل العمل عند الحاجة

لا تبدأ أكثر من طلب عند تغيير attribute، ولا تترك timer يعمل بعد إزالة العنصر. خزّن مؤقت التحديث، وألغِ الطلب السابق عند بدء طلب جديد باستخدام AbortController. حمّل الكود عند الحاجة إذا كان الودجت نادر الظهور، ولا تضع مكتبة كاملة من أجل وظيفة يمكن تنفيذها بواجهة المتصفح.

async load() {
  this.controller?.abort();
  this.controller = new AbortController();
  this.setState({ status: 'loading' });
  try {
    const response = await fetch(this.endpoint, {
      signal: this.controller.signal,
      headers: { Accept: 'application/json' }
    });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    this.setState({ status: 'success', data: readStatus(await response.json()) });
  } catch (error) {
    if (error.name !== 'AbortError') this.setState({ status: 'error' });
  }
}

قِس بدل التخمين: حجم JavaScript، عدد الطلبات، وقت ظهور المحتوى، وما إذا كان الودجت يسبب layout shift. لا تجعل animation مستمرة أو polling سريعًا لمجرد أن البطاقة تبدو حية؛ حدث البيانات حسب الحاجة وبحدود معقولة.

11. اختبر الودجت في صفحة غريبة عنه

الاختبار الحقيقي ليس داخل demo الذي صممته. أنشئ صفحة اختبار فيها CSS عدواني، خط مختلف، اتجاه RTL، حاوية 240px، شبكة عالية الكثافة، وبطء شبكة. جرّب أكثر من نسخة من الودجت في الصفحة نفسها، لأن المتغيرات العامة والـIDs الثابتة تظهر عيوبها عند التكرار.

الاختبارالسؤالالنجاح
Keyboardهل يمكن الوصول لكل action؟ترتيب focus منطقي ومؤشر ظاهر
Screen readerهل الحالة مفهومة؟العنوان والقيمة والتغيير مسموعون بوضوح
Networkماذا يحدث مع 500 أو timeout؟error مفهومة وإعادة المحاولة تعمل
Responsiveهل ينهار داخل 240px؟لا قص أفقي ولا نص خارج البطاقة
Multiple instancesهل تختلط الحالات؟كل نسخة مستقلة عن الأخرى
Reduced motionهل تحترم إعداد المستخدم؟لا حركة ضرورية عند طلب تقليلها

12. طريقة نشر وتوثيق الودجت

اكتب README يبدأ بمثال نسخ ولصق، ثم جدول الخصائص والأحداث، ثم الحالات والمشاكل المعروفة. اذكر دعم المتصفحات، ومتطلبات endpoint، وحجم الحزمة، وطريقة تغيير الألوان. لا تجعل المستخدم يبحث داخل المصدر ليعرف أن refresh بالمللي ثانية أو أن تغيير endpoint يعيد التحميل.

عند إصدار نسخة جديدة، اكتب changelog مختصرًا، واختبر المثال الموجود في README نفسه. إذا كسرت اسم attribute أو شكل event، ارفع major version أو وفر فترة توافق. المكوّن القابل لإعادة الاستخدام ينجح عندما يستطيع مطور آخر استخدامه من الوثيقة، لا عندما ينجح عند صاحبه فقط.

خطة تنفيذ من يومين

الفترةالمهمةالناتج
الساعات 1–2المواصفة والـAPI والحالاتورقة عقد ورسمة بسيطة
الساعات 3–5Markup دلالي ونسخة staticواجهة قابلة للفحص دون بيانات حقيقية
الساعات 6–8Custom Element وShadow DOMنسخة يمكن تضمينها في صفحة ثانية
اليوم الثاني صباحًاالبيانات والأخطاء والأداءloading/error/retry مع إلغاء الطلبات
اليوم الثاني مساءًالوصول والاختبار والتوثيقREADME وdemo وchecklist إصدار

مراجع رسمية

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

  1. MDN — Web Components
  2. MDN — Using custom elements
  3. MDN — Using shadow DOM
  4. MDN — Using templates and slots
  5. MDN — Accessible web applications and widgets
  6. web.dev — Accessibility for web developers