مقدمة
تفتقر الويب إلى حد كبير إلى التعبير. لمعرفة ما أقصده، يمكنك إلقاء نظرة على تطبيق ويب "حديث" مثل Gmail:
لا شيء عصري في حساء <div>
. ومع ذلك، هذه هي الطريقة التي ننشئ بها تطبيقات الويب. إنّه أمر حزين.
ألا يجب أن نطلب المزيد من منصتنا؟
ترميز مثير. لنبدأ
يوفر لنا HTML أداة ممتازة لتنظيم بنية مستند، ولكن تقتصر مفرداته على العناصر التي يحدّدها معيار HTML.
ماذا لو لم تكن علامات ترميز Gmail سيئة؟ ماذا لو كانت جميلة:
<hangout-module>
<hangout-chat from="Paul, Addy">
<hangout-discussion>
<hangout-message from="Paul" profile="profile.png"
profile="118075919496626375791" datetime="2013-07-17T12:02">
<p>Feelin' this Web Components thing.
<p>Heard of it?
</hangout-message>
</hangout-discussion>
</hangout-chat>
<hangout-chat>...</hangout-chat>
</hangout-module>
كم هو ممتع! هذا التطبيق منطقي تمامًا أيضًا. هذا الإعلان مفيد وسهل الفهم، والأفضل من ذلك أنّه يمكن الحفاظ عليه. ستعرف أنت أو أنا في المستقبل بالضبط ما يفعله فقط من خلال فحص العمود الفقري التعريفي.
الخطوات الأولى
العناصر المخصّصة: تسمح لمطوّري الويب بتحديد أنواع جديدة من عناصر HTML. هذه المواصفة هي إحدى العديد من العناصر الأساسية الجديدة لواجهة برمجة التطبيقات التي يتم طرحها ضمن مظلة مكونات الويب، ولكن من المحتمل أنّها الأكثر أهمية. لا توجد مكونات الويب بدون الميزات التي يتم إلغاء قفلها بواسطة العناصر المخصصة:
- تحديد عناصر HTML/DOM جديدة
- إنشاء عناصر تمتد من عناصر أخرى
- تجميع الوظائف المخصصة معًا بشكل منطقي في علامة واحدة
- توسيع واجهة برمجة التطبيقات لعناصر DOM الحالية
تسجيل عناصر جديدة
تم إنشاء العناصر المخصصة باستخدام document.registerElement()
:
var XFoo = document.registerElement('x-foo');
document.body.appendChild(new XFoo());
الوسيطة الأولى لدالة document.registerElement()
هي اسم علامة العنصر.
يجب أن يحتوي الاسم على شرطة (-). على سبيل المثال، <x-tags>
و<my-element>
و<my-awesome-app>
هي أسماء صالحة، في حين أنّ <tabs>
و<foo_bar>
غير صالحَين. ويسمح هذا التقييد للمحلل اللغوي بتمييز العناصر المخصصة من العناصر العادية ولكنه يضمن أيضًا توافق إعادة التوجيه عند إضافة علامات جديدة إلى HTML.
الوسيطة الثانية هي كائن (اختياري) يصف prototype
للعنصر.
هذا هو المكان المناسب لإضافة وظائف مخصّصة (مثل السمات والطُرق العامة) إلى العناصر.
مزيد من المعلومات حول ذلك لاحقًا
يتم اكتساب العناصر المخصّصة من HTMLElement
تلقائيًا. وبالتالي، فإنّ المثال السابق يعادل:
var XFoo = document.registerElement('x-foo', {
prototype: Object.create(HTMLElement.prototype)
});
تُعلِم عملية الاستدعاء document.registerElement('x-foo')
المتصفّح بالعنصر الجديد،
وتُعرِض دالة انشائية يمكنك استخدامها لإنشاء نُسخ من <x-foo>
.
ويمكنك بدلاً من ذلك استخدام الأساليب الأخرى لإنشاء مثيل العناصر إذا لم تكن تريد استخدام الدالة الإنشائية.
عناصر التمديد
تتيح لك العناصر المخصّصة توسيع عناصر HTML الحالية (المضمّنة) بالإضافة إلى غيرها من العناصر المخصّصة. لتوسيع عنصر ما، يجب تمرير registerElement()
إلى اسم
وprototype
العنصر ليتم اكتسابه منه.
توسيع العناصر الأصلية
لنفترض أنّك غير راضٍ عن "عادل" <button>
. وترغب في تعزيز إمكاناته لتصبح "زرًا ضخمًا". لتوسيع عنصر <button>
،
أنشئ عنصرًا جديدًا يكتسب prototype
عنصر HTMLButtonElement
وextends
اسم العنصر. في هذه الحالة، "button":
var MegaButton = document.registerElement('mega-button', {
prototype: Object.create(HTMLButtonElement.prototype),
extends: 'button'
});
تُعرف العناصر المخصّصة التي تكتسب سمات من العناصر المدمجة باسم العناصر المخصّصة لإضافة أنواع.
وترث هذه الأجهزة من إصدار متخصّص من HTMLElement
كطريقة لقول "العنصر X هو Y".
مثال:
<button is="mega-button">
توسيع عنصر مخصص
لإنشاء عنصر <x-foo-extended>
يتوسّع في العنصر المخصّص <x-foo>
، ما عليك سوى اكتساب النموذج الأولي له
وتحديد العلامة التي ستكتسِبها:
var XFooProto = Object.create(HTMLElement.prototype);
...
var XFooExtended = document.registerElement('x-foo-extended', {
prototype: XFooProto,
extends: 'x-foo'
});
راجع إضافة خصائص JavaScript وطرقها أدناه لمزيد من المعلومات حول إنشاء نماذج أولية للعناصر.
كيفية ترقية العناصر
هل تساءلت يومًا عن سبب عدم اعتراض منظِّم HTML على العلامات غير العادية؟
على سبيل المثال، سيشعر بالسعادة إذا أعلنّا عن <randomtag>
في الصفحة. وفقًا لمواصفات HTML:
عذرًا <randomtag>
. أنت غير عادي وترث من HTMLUnknownElement
.
ولا ينطبق ذلك على العناصر المخصّصة. يتم اكتساب العناصر التي تحمل أسماء عناصر مخصّصة صالحة من HTMLElement
. يمكنك التحقّق من هذه الحقيقة من خلال تشغيل وحدة التحكّم: Ctrl + Shift + J
(أو Cmd + Opt + J
على جهاز Mac)، ولصق أسطر الرمز البرمجي التالية التي ستؤدي إلى عرض true
:
// "tabs" is not a valid custom element name
document.createElement('tabs').__proto__ === HTMLUnknownElement.prototype
// "x-tabs" is a valid custom element name
document.createElement('x-tabs').__proto__ == HTMLElement.prototype
العناصر التي لم يتم حلها
بما أنّه يتم تسجيل العناصر المخصّصة من خلال نص برمجي باستخدام document.registerElement()
، يمكن تعريفها أو إنشاؤها قبل تسجيل تعريفها من خلال المتصفّح. على سبيل المثال، يمكنك الإفصاح عن <x-tabs>
على الصفحة ولكن ينتهي بك الأمر باستدعاء document.registerElement('x-tabs')
في وقت لاحق.
قبل ترقية العناصر إلى تعريفها، تُعرف باسم العناصر غير المحسَّنة. هذه هي عناصر HTML التي تحتوي على اسم عنصر مخصّص صالح ولكن لم يتم تسجيلها.
قد يساعد هذا الجدول في إبقاء الأمور في خط مستقيم:
الاسم | اكتساب من | أمثلة |
---|---|---|
عنصر لم يتم حلّه | HTMLElement |
<x-tabs> ، <my-element> |
عنصر غير معروف | HTMLUnknownElement |
<tabs> ، <foo_bar> |
إنشاء مثيل للعناصر
لا تزال الأساليب الشائعة لإنشاء العناصر سارية على العناصر المخصّصة. كما هو الحال مع أي عنصر عادي، يمكن الإعلان عن هذه العناصر في HTML أو إنشاؤها في DOM باستخدام JavaScript.
إنشاء مثيل للعلامات المخصّصة
أوضحها:
<x-foo></x-foo>
إنشاء DOM في JavaScript:
var xFoo = document.createElement('x-foo');
xFoo.addEventListener('click', function(e) {
alert('Thanks!');
});
استخدِم عامل التشغيل new
:
var xFoo = new XFoo();
document.body.appendChild(xFoo);
إنشاء عناصر إضافة النوع
إن إنشاء مثيل العناصر المخصصة بنمط إضافة النوع قريب جدًا من العلامات المخصصة.
الإفصاح عنها:
<!-- <button> "is a" mega button -->
<button is="mega-button">
إنشاء DOM في JS:
var megaButton = document.createElement('button', 'mega-button');
// megaButton instanceof MegaButton === true
كما ترى، هناك الآن إصدار تم تحميله بشكل زائد من document.createElement()
يتعامل مع السمة is=""
كمعلمة ثانية.
استخدِم عامل التشغيل new
:
var megaButton = new MegaButton();
document.body.appendChild(megaButton);
لقد تعلمنا حتى الآن كيفية استخدام document.registerElement()
لإعلام المتصفّح بعلامة جديدة، ولكن
لا يؤدي ذلك إلى الكثير. لنضيف الخصائص والطرق.
إضافة سمات JS وطُرقها
إنّ الميزة الفعّالة في العناصر المخصّصة هي أنّه يمكنك تجميع وظائف مخصّصة مع العنصر من خلال تحديد السمات والطُرق في تعريف العنصر. يمكنك اعتبار ذلك طريقة لإنشاء واجهة برمجة تطبيقات عامة للعنصر.
في ما يلي مثال كامل:
var XFooProto = Object.create(HTMLElement.prototype);
// 1. Give x-foo a foo() method.
XFooProto.foo = function() {
alert('foo() called');
};
// 2. Define a property read-only "bar".
Object.defineProperty(XFooProto, "bar", {value: 5});
// 3. Register x-foo's definition.
var XFoo = document.registerElement('x-foo', {prototype: XFooProto});
// 4. Instantiate an x-foo.
var xfoo = document.createElement('x-foo');
// 5. Add it to the page.
document.body.appendChild(xfoo);
بالطبع، هناك آلاف الطرق لإنشاء prototype
. إذا لم تكن
من محبّي إنشاء نماذج أولية بهذه الطريقة، إليك نسخة أكثر تكثيفًا من الإجراء نفسه:
var XFoo = document.registerElement('x-foo', {
prototype: Object.create(HTMLElement.prototype, {
bar: {
get: function () {
return 5;
}
},
foo: {
value: function () {
alert('foo() called');
}
}
})
});
يسمح التنسيق الأول باستخدام ES5 Object.defineProperty
. ويسمح الحقل الثاني باستخدام get/set.
طرق معاودة الاتصال في مراحل النشاط
يمكن للعناصر تحديد طرق خاصة للاستفادة من الأوقات المناسبة لظهورها. وتُسمّى هذه الطرق بشكل مناسب وظائف الاستدعاء المتعلّقة بدورة الحياة. ولكلٍّ من هذه الخيارات اسم وغرض محدَّد:
اسم معاودة الاتصال | يتم استدعاؤه عندما |
---|---|
createdCallback | يتم إنشاء مثيل للعنصر |
attachedCallback | تم إدراج مثيل في المستند |
detachedCallback | تمّت إزالة مثيل من المستند |
attributeChangedCallback(attrName, oldVal, newVal) | تمت إضافة سمة أو إزالتها أو تعديلها |
مثال: تحديد createdCallback()
وattachedCallback()
على <x-foo>
:
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {...};
proto.attachedCallback = function() {...};
var XFoo = document.registerElement('x-foo', {prototype: proto});
تكون جميع استدعاءات مراحل النشاط اختيارية، ولكن عليك تحديدها إذا كان ذلك مناسبًا.
على سبيل المثال، لنفترض أنّ العنصر معقد بما فيه الكفاية ويفتح اتصالاً بقاعدة البيانات المفهرسة
في createdCallback()
. قبل إزالته من DOM، عليك تنفيذ عملية التنظيف اللازمة في detachedCallback()
. ملاحظة: يجب عدم الاعتماد على ذلك، مثلاً إذا أغلق المستخدم علامة التبويب، ولكن يجب اعتباره وسيلة محتملّة للتحسين.
هناك استدعاءات أخرى لمراحل استخدام حالة الاستخدام وهي إعداد أدوات معالجة الأحداث التلقائية على العنصر:
proto.createdCallback = function() {
this.addEventListener('click', function(e) {
alert('Thanks!');
});
};
إضافة الترميز
لقد أنشأنا <x-foo>
، وأضفنا إليها واجهة برمجة تطبيقات JavaScript، ولكنّها فارغة. هل سنقدم بعض ترميز HTML لعرضه؟
تكون عمليات الاستدعاء في إحدى مراحل النشاط مفيدة في هذه الحالة. على وجه الخصوص، يمكننا استخدام
createdCallback()
لمنح عنصر بعض علامات HTML التلقائية:
var XFooProto = Object.create(HTMLElement.prototype);
XFooProto.createdCallback = function() {
this.innerHTML = "**I'm an x-foo-with-markup!**";
};
var XFoo = document.registerElement('x-foo-with-markup', {prototype: XFooProto});
من المفترض أن يؤدي إنشاء مثيل لهذه العلامة وفحصها في أدوات مطوّري البرامج (النقر بزر الماوس الأيمن واختيار "فحص العنصر") إلى عرض ما يلي:
▾<x-foo-with-markup>
**I'm an x-foo-with-markup!**
</x-foo-with-markup>
تجميع العناصر الداخلية في Shadow DOM
تشكّل Shadow DOM بحد ذاتها أداة فعّالة لتغليف المحتوى. استخدمه إلى جانب العناصر المخصصة واحصل على أشياء رائعة!
يقدّم Shadow DOM عناصر مخصّصة:
- طريقة لإخفاء أمعائها، وبالتالي حماية المستخدمين من تفاصيل التنفيذ المروعة
- تضمين الأنماط…مجانًا
يشبه إنشاء عنصر من Shadow DOM إنشاء عنصر
يعرض الترميز الأساسي. الفرق هو createdCallback()
:
var XFooProto = Object.create(HTMLElement.prototype);
XFooProto.createdCallback = function() {
// 1. Attach a shadow root on the element.
var shadow = this.createShadowRoot();
// 2. Fill it with markup goodness.
shadow.innerHTML = "**I'm in the element's Shadow DOM!**";
};
var XFoo = document.registerElement('x-foo-shadowdom', {prototype: XFooProto});
بدلاً من ضبط .innerHTML
للعنصر، أنشأت
Shadow Root لـ <x-foo-shadowdom>
ثم ملأته بالترميز.
عند تفعيل الإعداد "عرض Shadow DOM" في "أدوات مطوّري البرامج"، سيظهر لك رمز
#shadow-root
يمكن توسيعه:
▾<x-foo-shadowdom>
▾#shadow-root
**I'm in the element's Shadow DOM!**
</x-foo-shadowdom>
هذا هو جذر الظلال
إنشاء عناصر من نموذج
نماذج HTML هي عنصر أساسي آخر جديد لواجهة برمجة التطبيقات يتناسب بشكل جيد مع عالم العناصر المخصصة.
مثال: تسجيل عنصر تم إنشاؤه من <template>
وShadow DOM:
<template id="sdtemplate">
<style>
p { color: orange; }
</style>
<p>I'm in Shadow DOM. My markup was stamped from a <template>.
</template>
<script>
var proto = Object.create(HTMLElement.prototype, {
createdCallback: {
value: function() {
var t = document.querySelector('#sdtemplate');
var clone = document.importNode(t.content, true);
this.createShadowRoot().appendChild(clone);
}
}
});
document.registerElement('x-foo-from-template', {prototype: proto});
</script>
<template id="sdtemplate">
<style>:host p { color: orange; }</style>
<p>I'm in Shadow DOM. My markup was stamped from a <template>.
</template>
<div class="demoarea">
<x-foo-from-template></x-foo-from-template>
</div>
تقدّم هذه السطور القليلة من الرموز الكثير من التجارب. لنفهم كل ما يحدث:
- لقد سجّلنا عنصرًا جديدًا في HTML:
<x-foo-from-template>
- تم إنشاء عنصر DOM من
<template>
- يتم إخفاء التفاصيل المخيفة للعنصر باستخدام Shadow DOM
- يوفّر Shadow DOM تغليف نمط العنصر (على سبيل المثال، لا يحوِّل
p {color: orange;}
الصفحة بأكملها إلى اللون البرتقالي)
جيد جدًا!
تصميم العناصر المخصّصة
كما هو الحال مع أي علامة HTML، يمكن لمستخدمي علامتك المخصّصة اختيار نمطها باستخدام أدوات الاختيار:
<style>
app-panel {
display: flex;
}
[is="x-item"] {
transition: opacity 400ms ease-in-out;
opacity: 0.3;
flex: 1;
text-align: center;
border-radius: 50%;
}
[is="x-item"]:hover {
opacity: 1.0;
background: rgb(255, 0, 255);
color: white;
}
app-panel > [is="x-item"] {
padding: 5px;
list-style: none;
margin: 0 7px;
}
</style>
<app-panel>
<li is="x-item">Do</li>
<li is="x-item">Re</li>
<li is="x-item">Mi</li>
</app-panel>
تغيير نمط العناصر التي تستخدم Shadow DOM
عند إضافة Shadow DOM، يمكنك خوض تجربة أكثر تشويقًا. تستفيد العناصر المخصّصة التي تستخدم Shadow DOM من مزاياه الرائعة.
يدمج Shadow DOM عنصرًا مع تغليف النمط. لا تخرج الأنماط المحددة في جذر الظل من المضيف ولا تخرج من الصفحة. في حال استخدام عنصر مخصّص، يكون العنصر نفسه هو المضيف. تسمح خصائص تغليف الأنماط أيضًا للعناصر المخصصة بتحديد الأنماط الافتراضية لأنفسها.
يُعدّ تصميم Shadow DOM موضوعًا كبيرًا. إذا أردت الاطّلاع على مزيد من المعلومات حول هذا الموضوع، أنصحك بقراءة بعض مقالاتي الأخرى:
- "دليل عناصر التصميم" في مستندات البوليمر.
- "Shadow DOM 201: CSS & Styling" هنا.
منع عمليات التزوير باستخدام :unresolved
للحدّ من FOUC، تحدّد العناصر المخصّصة
صفًا صوريًا جديدًا في CSS، وهو :unresolved
. ويمكنك استخدامه لاستهداف العناصر التي لم يتم حلها، مباشرة إلى أن يستدعي المتصفّح createdCallback()
(يمكنك الاطّلاع على طرق مراحل النشاط).
وبعد ذلك، لن يعود العنصر غير محدّد المصدر. تكتمل عملية الترقية
وتحول العنصر إلى تعريفه.
مثال: عرض علامات "x-foo" بشكل تدريجي عند تسجيلها:
<style>
x-foo {
opacity: 1;
transition: opacity 300ms;
}
x-foo:unresolved {
opacity: 0;
}
</style>
يُرجى العِلم أنّ :unresolved
لا ينطبق إلا على العناصر غير المحسَّنة،
وليس على العناصر التي تكتسِب سمات من HTMLUnknownElement
(راجِع كيفية ترقية العناصر).
<style>
/* apply a dashed border to all unresolved elements */
:unresolved {
border: 1px dashed red;
display: inline-block;
}
/* x-panel's that are unresolved are red */
x-panel:unresolved {
color: red;
}
/* once the definition of x-panel is registered, it becomes green */
x-panel {
color: green;
display: block;
padding: 5px;
display: block;
}
</style>
<panel>
I'm black because :unresolved doesn't apply to "panel".
It's not a valid custom element name.
</panel>
<x-panel>I'm red because I match x-panel:unresolved.</x-panel>
التوافق مع السجلّ والمتصفّح
رصد الميزات
يتم التحقق من وجود document.registerElement()
عند اكتشاف الميزات:
function supportsCustomElements() {
return 'registerElement' in document;
}
if (supportsCustomElements()) {
// Good to go!
} else {
// Use other libraries to create components.
}
دعم المتصفح
بدأ ظهور document.registerElement()
لأول مرة في الإصدار 27 من Chrome والإصدار 23 تقريبًا من Firefox. ومع ذلك، تطوّرت المواصفات كثيرًا منذ ذلك الحين. الإصدار 31 من Chrome هو أول إصدار يقدّم
دعمًا حقيقيًا للمواصفات المعدَّلة.
إلى أن يصبح توافق المتصفّحات ممتازًا، هناك polyfill يستخدمه Polymer من Google وX-Tag من Mozilla.
ماذا حدث لـ HTMLElementElement؟
بالنسبة إلى المستخدمين الذين تابعوا عملنا على التنسيق، سيعرفون أنّه كان هناك <element>
.
لقد كان رائعًا. يمكنك استخدامها لتسجيل عناصر جديدة بشكل تعريفي:
<element name="my-element">
...
</element>
للأسف، حدثت الكثير من المشاكل في التوقيت في عملية الترقية،
وحالات استثنائية، وسيناريوهات شبيهة بيوم القيامة، ما أدى إلى عدم حلّ جميع المشاكل. كان يجب وضع "<element>
" على الأرفف. في آب (أغسطس) 2013، نشر ديميتري غلازكوف في تطبيقات الويب العامة أعلن فيه عن إزالته، في الوقت الحالي على الأقل.
تجدر الإشارة إلى أنّ البوليمر ينفِّذ شكلاً تعريفيًا لتسجيل العناصر باستخدام <polymer-element>
. الطريقة وهو يستخدم document.registerElement('polymer-element')
والتقنيات التي وصفتها في إنشاء عناصر من قالب.
الخاتمة
تمنحنا العناصر المخصّصة أداة لتوسيع مفردات HTML وتعليمها حيلًا جديدة،
والانتقال عبر ثقوب دودية في منصة الويب. يمكنك دمج هذه العناصر مع العناصر الأساسية الأخرى
للمنصة مثل Shadow DOM و<template>
، وسنبدأ في إدراك
صورة مكونات الويب. يمكن أن تصبح ميزة "الترميز" جذابة مرة أخرى.
إذا كنت مهتمًا بالبدء باستخدام مكوّنات الويب، أنصحك بالاطّلاع على Polymer. إنها تتجاوز ما يكفي لبدء الاستخدام.