Professional Documents
Culture Documents
עבודה
עבודה
األمر إلى أمر معقد ،فافصله إلى أجزاء أبسط واربطها ببعضها بأكثر الطرق وضو ًحا ،فالمعماري
األمهر هو من يستطيع تحويل المعقد إلى بسيط.
يمكن تقسيم واجهة المستخدم إلى مك ّونات مرئية لكلٍّ منها موقعه في الصفحة ،وتُنفِّذ مهمةً محددةً بعناية
وتكون مفصولةً عن بعضها البعض ،لنلق نظرةً على موقع ويب ،وليكن تويتر مثاًل ،يُقسم الموقع إلى
مكوّنات:
المكون إ ًذا؟ يأتي ذلك من حسن البديهة والخبرة والمنطق ،حيث تش ًكل المك ّونات عادةً
ِّ كيف نحدد ما هو
كيانات مرئيةً نميزها وفقًا لوظيفتها وكيفية تتفاعلها مع الصفحة ،ففي حالة الصفحة السابقة ،سنجد أنها
تحوي وحدات يلعب كل منها دوره الخاص ،ومن المنطق إ ًذا أن نجعلها مكوّنات.
للمكون:
ِّ
تذكر ّ
أن فكرة الكائن ككل ال تفرض وجود أي شيء خاص.
تتواجد الكثير من المنصات وإطارات العمل التي تقدم وسائل إلنشاء المك ّونات ،وتستخدم أصناف CSS
باإلضافة إلى ترتيبات أخرى لتعزيز المك ّونات وتحديد مجال تطبيق CSSوتغليف .DOM
مكونات الويب بإمكانيات تصفّح مدمجةً معها وال حاجة لمحاولة تقليدها بعد اآلن.
تزوّدنا ِّ
عناصر مخصصة :Custom elements لتعريف عناصر HTMLمخصصة.
شجرة DOMمخفية :Shadow DOM إلنشاء شجرة داخلية للمكوّن مخفية عن بقية
المكوّنات.
مجال لتطبيق أوراق التنسيق :CSS Scoping لتعريف قواعد تنسيق يمكن تطبيقها على
الشجرة الداخلية لمكوّن.
إعادة توجيه الحدث Event retargeting وغيره من األمور الثانوية :لتحسين قدرة المكوّنات
على التالؤم مع عملية التطوير.
كل الميزات المدعومة جيدًا لمك ّونات الويب جيدة في األمور التي تقدر على تنفيذها.
عناصر HTMLالمخصصة
يمكننا إنشاء عناصر HTMLمخصصة يصفها الصنف الذي ننشئه لهذا الغرض مز ّودًا بتوابعه
وخصائصه وأحداثه الخاصة ،وسنتمكن من استخدام العنصر ضمن عناصر HTMLاألصلية بعد أن
نع ّرفه ،وهذا أمر جيد ،فعلى الرغم من غنى HTMLبالعناصر ،إال أنها ال تحوي عناصرًا مثل عالمة
جداول سهلة التعامل >easy-tabs< أو سير دائري منزلق >sliding-carousel< أو
الرفع بطريقة جذابة >beautiful-upload< أو أي عناصر أخرى قد نحتاجها.
يمكن تعريف العناصر المخصصة ضمن أصناف خاصة ،واستخدامها كما لو أنها جزء من ،HTML
ويوجد نوعان من هذه العناصر المخصصة ،وهما:
عناصر مخصصة ذاتية التصرف :Autonomousوهي عناصر جديدة توّسع عمل الصنف .1
المجرّد.HTMLElement
عناصر أصلية مخصصة :customizedتوسّع العناصر األصلية ،إلنجاز أزرار مخصصة .2
مبنية على أساس الصنف HTMLButtonElement مثاًل .
سنغطي أواًل العناصر ذاتية التصرف ،ثم سننتقل إلى العناصر المخصصة ،وإلنشاء عنصر مخصص ال
ب ّد من إخبار المتصفح عن جملة من التفاصيل حوله ،مثل كيف سيُظهره؟ وماذا سيفعل عند إضافته أو
إزالته من الصفحة؟ يجري ذلك بإنشاء صنف له توابعه الخاصة ،وسيكون األمر بسيطًا ،فهي عدة توابع
فقط وجميعها اختيارية.
}
{ )(disconnectedCallback
//يستدعيه المتصفح عند حذف عنصر ويمكن استدعاؤه عدة مرات إن أضيف
أو حذف العنصر
}
{ )(adoptedCallback
ينقل العنصر إلى صفحة أخرى
يستدعى عندما ُ
ُ //
}
و my-element فبعض األسماء مثل،"-" يجب أن يحتوي اسم العنصر المخصص على الواصلة
وذلك لمنع،مرفوض myelement لكن االسم، هي أسماء مقبولة،super-button
. األصليةHTML التضارب مع عناصر
لكنه ال يقدم أي تنسيق للبيانات التي، إلظهار الوقت والتاريخ >time< يُستخدم العنصر األصلي
الذي يعرض الوقت بتنسيق جميل يأخذ اللغة >time-formatted< لننشئ إ ًذا العنصر،يعطيها
:بالحسبان
>script<
class TimeFormatted extends HTMLElement { // (1)
{ )(connectedCallback
let date = new Date(this.getAttribute('datetime') ||
;Date.now())
تشير ( )1إلى الصنف تابع واحد فقط هو ،connectedCallback)( حيث يستدعيه .1
المتصفح عندما يُضاف العنصر إلى الصفحة ،أو عندما يكتشف مفسّر HTMLوجوده،
ويستخدم العنصر تابع تنسيق البيانات المدمج Intl.DateTimeFormat الذي يلقى دع ًما جيدًا
من معظم المتصفحات لعرض الوقت بتنسيق جميل.
تعني ( )2أنه ال ب ّد من تسجيل العنصر الجديد باستخدام األمر( .2
).customElements.define(tag, class
تعني ( )3أنه يمكن اآلن استخدام العنصر في أي مكان. .3
ّ
إن السبب بسيط ،ألنه من المبكر جدًا تصيير المك ّون عند استدعاء ،constructor فعندما يُنشأ
العنصر في هذه المرحلة ،فلن يُعالجه المتصفح أو يسند إليه السمات الخاصة به،
فاستدعاء getAttribute سيعيد القيمة "الشيء" ،null وبالتالي لن نتمكن من تصيير شيء،
ولو فكرنا باألمر قلياًل فسنجد ّ
أن هذا األداء أفضل لتأخير العمل حتى يكون كل شيء جاه ًزا.
يقع الحدث connectedCallback عندما يُضاف العنصر إلى الصفحة ،ولن يُضاف إلى
عنصر آخر مثل ابن فقط ،بل سيغدو عمليًا جز ًءا من الصفحة ،وهكذا سنتمكن من بناء شجرة
DOMمنفصلة ،وإنشاء عناصر وتحضيرها للعمل الحقًا ،وستصيّر هذه العناصر فعليًا عندما تُضاف
إلى الصفحة.
مراقبة السمات
<>script
{ class TimeFormatted extends HTMLElement
)render() { // (1
|| )'let date = new Date(this.getAttribute('datetime
))(;Date.now
;customElements.define("time-formatted", TimeFormatted)
>script/<
>script<
setInterval(() => elem.setAttribute('datetime', new Date()),
1000); // (5)
>script/<
.>time-formatted< ) أنه قد نُقل منطق التصيير إلى التابع المساعد1( ُقصد بـ َ ي .1
.) أن هذا التابع يُستدعى مباشرةً في اللحظة التي يضاف فيها العنصر إلى الصفحة2( تعني .2
عند حدوث أي تغير attributeChangedCallback ) أنه يقع الحدث3( تعني .3
observedAttributes)( في السمات التي مررت على شكل قائمة إلى
.) أنه يُعاد تصيير العنصر بعد ذلك4( تعني .4
.) أنه في النهاية يمكننا إنشاء مؤقت مباشر بكل سهولة5( تعني .5
تسلسل التصيير
عندما يبني مفسِّر HTMLالشجرة ،DOMتعالَج العناصر الواحد تلو اآلخر واألب قبل االبن ،فلو كان
لدينا العنصر:
<>outer><inner></inner></outer
<>script
{ customElements.define('user-info', class extends HTMLElement
{ )(connectedCallback
)*( alert(this.innerHTML); // empty
}
});
<>script/
<>user-infoمحمد<>user-info/
إذا نفّذت الشيفرة السابقة فستظهر الرسالة alert فارغة ،والسبب في ذلك هو عدم وجود عناصر
أبناء في هذه المرحلة ،وستكون الشجرة DOMغير مكتملة ،يربط مفسِّر HTMLالعنصر
المخصص >user-info< وسيستأنف العمل بعدها لربط أبنائه لكنه لم يفعلها بعد.
إذا أردنا تمرير معلومات إلى عنصر مخصص ،فيمكننا استخدام السمات فهي متاحة مباشرةً ،وإذا كنا
بحاجة فعاًل إلى األبناء ،فيمكن تأجيل الوصول إليهم باستخدام الحدثsetTimeout دون تأخير
زمني .Zero-delay
<>script
{ customElements.define('user-info', class extends HTMLElement
{ )(connectedCallback
)) // ;setTimeout(() => alert(this.innerHTMLمحمد (*)
}
});
<>script/
<>user-info>John</user-info
وهكذا ستُظهر الرسالة alert في السطر (*) الكلمة "محمد" طالما أننا ننفذ األمر دون تزامن ،وذلك
بعد اكتمال تفسير شيفرة ،HTMLويمكن معالجة األبناء إذا اقتضى األمر ثم إنهاء مرحلة التهيئة ،لكن
هذا الحل ليس مثاليًا.
فإذا استخدمت العناصر المخصصة الحدث لتهيئة نفسها ،فإنها ستصطف بحيث يقع
الحدث setTimeout للعنصر الخارجي ثم الداخلي ،وبالتالي ستنهي العناصر الخارجية تهيئة
نفسها قبل الداخلية ،لنشرح ذلك من خالل المثال التالي:
<>script
{ customElements.define('user-info', class extends HTMLElement
{ )(connectedCallback
)`;alert(`${this.id} connected.
))`;setTimeout(() => alert(`${this.id} initialized.
}
});
<>script/
<>"user-info id="outer
<>user-info id="inner"></user-info
<>user-info/
تسلسل الخرج:
يمكننا أن نرى بوضوح أن العنصر الخارجي سينهي تهيئة نفسه (الخطوة )3قبل الداخلي (الخطوة ،)4
وال يوجد استدعاء مدمج يمكن أن يقع بعد أن يصبح العنصر ضمن الشجرة ،كما يمكن تنفيذ استدعاء
مثل هذا عند الحاجة بأنفسنا ،حيث يمكن للعناصر الداخلية إرسال أحداث
مثل initialized تستمع إليها العناصر الخارجية وتتفاعل معها.
العناصر األصلية المخصصة
ليس للعناصر المخصصة -مثل ->time-formatted< أي دالالت تتعلق بها ،فهي غير
معروفة لمحركات البحث مثاًل ،وال تستطيع األجهزة التي تدعم مبدأ الوصول السهل accessibility
التعامل معها .قد تكون هذه األشياء مهمةً ،فقد يرغب مح ّرك البحث بمعرفة أن هذا العنصر سيظهر
ص ا من األزرار ،فلماذا ال يمكننا إعادة استخدام وظيفة الزر األصلي<
الوقت ،فإذا أنشأنا نوعًا خا ً
>button؟
يمكننا توسيع وتخصيص العناصر األصلية بالوراثة من أصنافها ،فاألزرار مثاًل هي نسخ عن
الصنف ،HTMLButtonElement وإلنشاء عنصر يرث منه ويوسّعه عليك باتباع اآلتي:
يمكن أن تشترك وسوم مختلفة بصنف ،DOMلذلك سنحتاج إلى استخدام التعليمة.extends
لكي نستخدم أخي ًرا العنصر المخصص ،استخدم الوسم األصلي النظامي ،>button< لكن .1
أضف إليه الصفة.is="hello-button"
<>button is="hello-button">...</button
<>script
//عند ضغطه " "helloالزر الذي يظهر
{ class HelloButton extends HTMLButtonElement
{ )(constructor
;)(super
))"!;this.addEventListener('click', () => alert("Hello
}
}
يوسِّع الزر الجديد الزر األصلي ،وبالتالي سيحتفظ بتنسيق وميزات الزر األصلي مثل
الصفة.disabled
قوالب HTML
يمكن نظريًا إنشاء أي عناصر مخفية ضمن ملف HTMLلتخزين شيفرة ،HTMLفما الغاية من<
>template؟
أواًل :قد يحتوي على أي شيفرة HTMصالحة ،حتى تلك التي تتطلب عادةً وسم إغالق ،Closing tag
فيمكن أن نضع ضمنها أسطر جدول:>tr<
<>template
<>tr
<>td>Contents</td
<>tr/
<>template/
إدراج القالب
<>"template id="tmpl
<>script
)";alert("Hello
<>script/
<>div class="message">Hello, world!</div
<>template/
<>script
)';let elem = document.createElement('div
);document.body.append(elem
//سيعمل اآلن السكربت الموجود ضمن القالب
<>script/
:>template< الخفية في الفصل السابق باستخدام القالبDOM دعونا نعيد كتابة مثال شجرة
>"template id="tmpl<
>style> p { font-weight: bold; } </style<
>p id="message"></p<
>template/<
>script<
{ )(elem.onclick = function
;elem.attachShadow({mode: 'open'})
)*( // ;elem.shadowRoot.append(tmpl.content.cloneNode(true))
elem.shadowRoot.getElementById('message').innerHTML = "Hello
;"!from the shadows
;}
>script/<
>"div id="elem<
shadow-root#
>style> p { font-weight: bold; } </style<
>p id="message"></p<
>div/<
خالصة
لها وسم خاص،Autonomous األول عناصر مخصصة ذاتية التصرف:للعناصر المخصصة نوعان
: وإليك تخطيط التعريف لهذه العناصر،HTMLElement جديد وتوسع الصنف
أما النوع الثاني ،فهو عناصر أصلية مع َّدلة :customizedتوّسع عناصرًا أصليةً موجودة ،وتتطلب
وسيطًا ثالثًا للدالة ،.define وإلى الصفة is="..." ضمن وسمها.
تدعم معظم المتصفحات العناصر المخصصة جيدًا ،كما يوجد موائم polyfillأي تعويض نقص الدعم
للمتصفحات غير المدعومة.
مهام إلنجازها