اذهب إلى المحتوى

التفريق بين السمات (attributes) في HTML والخاصيات (properties) في جافاسكربت


عائشة ميرا

عندما يُحمِّل المتصفّح الصّفحة، فهو يقرأ/يحلّل شيفرة HTML ويُنشئ من خلالها كائنات DOM. وحين يتعلّق الأمر بالعُقد العناصرية (element nodes)، فإن غالبية سمات HTML المعيارية تصبح خاصيات للكائنات DOM.

فعلى سبيل المثال، إذا كانت السمة كالتالي: <"body id="page>، يكتسب كائن DOM الخاصية "body.id="page. لكن عملية الربط بين السمات والخاصيات لا تتم تقابليا (كلّ سمة لا يقابلها بالضرورة خاصية). فيما يلي ستتعرف على كيفية التمييز بين الْمفهومَيْن، لتتعلم طريقة استخدامهما حين يتطابقان وحين يختلفان.

خاصيات DOM

سبق وأن تعرفت على خاصيات نموذج تمثيل المستند ككائن (أي خاصيات DOM) المبنيّة مسبقًا. هناك العديد منها ولكن ذلك لا يمنع المبرمج من إضافة/إنشاء خاصيات أخرى إذا لم تكن الخاصيات المبنيّة مسبقًا كافية لتلبية حاجاته. تُعدّ عُقد DOM كائنات جافاسكربت عادية يمكنك التعديل عليها.

فلننشئ على سبيل المثال خاصيّة جديدة للكائن document.body:

document.body.myData = {
  name: 'Caesar',
  title: 'Imperator'
};
alert(document.body.myData.title); // Imperator
كما يمكن إضافة دالّة أيضًا:
document.body.sayTagName = function() {
  alert(this.tagName);
};
document.body.sayTagName(); // BODY 
(قيمة this في الدالة هي document.body)

يمكن أيضًا تعديل النماذج المبنيّة مسبقًا مثل Element.prototype وإضافة دوالّ جديدة تُطبّق على كلّ العناصر.

Element.prototype.sayHi = function() {
  alert(`Hello, I'm ${this.tagName}`);
};
document.documentElement.sayHi(); // Hello, I'm HTML
document.body.sayHi(); // Hello, I'm BODY

تسلُك خاصيات ودوالّ DOM إذا سلوكًا مماثلًا لسلوك كائنات جافاسكربت العاديّة ويمكنها استقبال أيّ قيمةٍ كانت. فهي تخضع لشرط التمييز بين الحروف الكبيرة والحروف الصغيرة (فنكتب مثلا elem.nodeType، ولا نكتب elem.NodeType).

سمات HTML

يمكن أن تملك وسوم HTML سمات، ويتعرف المتصفّح على السمات المعيارية عندما يقرأ الوسوم ويحلّلها لإنشاء خاصيات لكائنات DOM المقابلة لها.

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

انظر المثال التالي:

<body id="test" something="non-standard">
  <script>
    alert(document.body.id); // test
    // السمة غير المعيارية لا تولّد خاصية مقابلة لها
    alert(document.body.something); // undefined
  </script>
</body>

لاحظ أن سمةً ما قد تكون معياريةً لعناصر دون غيرها وغير مُعرّفةٍ إذا تعلّق الأمر بعناصر أخرى. فمثلًا تُعدّ السمة "type" سمةً معياريةً للعنصر <input>‏ (HTMLInputElement) وغير معيارية للعنصر <body>‏ (HTMLBodyElement). وستجد وصفًا للسمات المعيارية في مواصفات صنف كل عنصر.

والمثال التالي يوضّح ذلك:

<body id="body" type="...">
  <input id="input" type="text">
  <script>
    alert(input.type); // text
    alert(body.type); // undefined
لم تٌنشأ خاصية DOM  لأنها غير معيارية //
  </script>
</body>

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

  • (elem.hasAttribute(name: للتحقّق من وجود السمة.
  • (elem.getAttribute(name: للحصول على قيمة السمة.
  • (elem.setAttribute(name, value: إسناد القيمة value للسمة name.
  • (elem.removeAttribute(name: حذف السمة.

وتُستخدم هذه الدوالّ مع السمات غير المعيارية كما هي مكتوبة بالضبط في شيفرة HTML. يمكن أيضًا قراءة كافة السمات باستعمال elem.attributes؛ وهو مجموعة من الكائنات التي تنتمي إلى صنف Attr المبني مسبقًا، تملك خاصيتي الاسم name والقيمة value.

فيما يلي عرض لكيفية قراءة سمة غير معيارية:

<body something="non-standard">
  <script>
    alert(document.body.getAttribute('something')); // non-standard
  </script>
</body>

تملك سمات HTML الميزات التالية:

  • لا تخضع لشرط التمييز بين الحروف الصغيرة والحروف الكبيرة (id هو نفسه ID).
  • تكون قيم هذه السمات دائما من نوع سلاسل نصّية (أي من النوع string).

فيما يلي عرضٌ موسّعٌ يوضّح طريقة التعامل مع السمات:

<body>
  <div id="elem" about="Elephant"></div>
  <script>
    alert( elem.getAttribute('About') ); // (1) 'Elephant', جلب
    elem.setAttribute('Test', 123); // (2), ضبط
    alert( elem.outerHTML ); // (3), التحقق من وجود السمة في شيفرة HTML (نعم)
    for (let attr of elem.attributes) { // (4) عرض القائمة كاملة
      alert( `${attr.name} = ${attr.value}` );
    }
  </script>
</body>

لاحظ ما يلي: (1).('getAttribute('About: الحرف الأول من كلمة About هو حرف كبير مع أنه صغير في شيفرة HTML. هذا لا يُسبّب أيّ مشكلةٍ طالما أن أسماء السمات لا تخضع لشرط التمييز بين الحروف الكبيرة والحروف الصغيرة. (2). يمكن إسناد قيمة من أيّ نوعٍ كان للسمة، ولكن هذه القيمة تصبح سلسلةً نصّيةً. قيمة السمة Test اذًا هنا هي "123". (3). تظهر كافة السمات بما فيها تلك التي أضفناها (أي غير المعيارية مثل تلك التي كانت موجودة في الشيفرة كالسمة 'About'، وتلك التي أضفناها كالسمة 'Test') في outerHTML. (4). تُعدّ مجموعة السمات attributes قابلة للتمرير ضمن حلقة تكرار، وتضمّ كافة سمات العنصر، المعيارية منها وغير المعيارية، في شكل كائنات تملك خاصيتي الاسم name والقيمة value.

المزامنة (synchronization) بين الخاصية والسمة

حين تتغيّر سمة معيارية ما، يجري تحديث الخاصية الموافقة لها آليًا والعكس كذلك صحيح (ما عدا في بعض الحالات الاستثنائية). عُدّلت في المثال الموالي السمة "id"، فلاحظ أن ذلك أدّى الى تعديل الخاصية أيضًا. وحدث الشّيء نفسه في المثال العكسي الذي جاء بعده، حيث عُدّلت الخاصية وهو ما أدّى إلى تعديل السمة آليًا.

<input>
<script>
  let input = document.querySelector('input');
  // سمة => خاصّية
  input.setAttribute('id', 'id');
  alert(input.id); // id (تحديث)
  // خاصية => سمة 
  input.id = 'newId';
  alert(input.getAttribute('id')); // newId (تحديث)
</script>

غير أن هناك بعض الاستثناءات، فعلى سبيل المثال، لا يمكن مزامنة input.value سوى في اتجاه واحد، من السمة إلى الخاصية، وليس العكس.

<input>
<script>
  let input = document.querySelector('input');
  // سمة ⇐ خاصية
  input.setAttribute('value', 'text');
  alert(input.value); // text
  //خاصية ⇍ سمة
  input.value = 'newValue';
  alert(input.getAttribute('value')); // text (لم يتم التحديث)
</script>

في المثال أعلاه:

  • يُؤدّي تعديل قيمة السمة value إلى تعديل الخاصية آليًا.
  • ولكن التعديل على الخاصية مباشرةً لا يُؤدّي إلى أيّ أثر على السمة.

هذه "الميزة" يمكن أن تكون فعلا مفيدة، حيث يمكن للمستخدم أن يغيّر القيمة value، وبعدها، إذا أردت استعادة القيمة الأولية من HTML، تجدها في السمة.

خاصيات DOM لها أنواع

لا تكون خاصيات DOM دائمًا عبارة عن سلاسل نصّية (أي من النوع "string")، فمثلا خاصية input.checked (في مربعات الاختيار (checkbox)) تكون عبارة عن قيمة بوليانية/منطقية.

<input id="input" type="checkbox" checked> checkbox
<script>
  alert(input.getAttribute('checked')); // قيمة السمة من نوع سلسلة نصّية فارغة
  alert(input.checked); // قيمة الخاصية هي “true”
</script>

وهناك أمثلة أخرى؛ تكون السمة style عبارة عن سلسلة نصّية ولكن الخاصية style هي كائن.

<div id="div" style="color:red;font-size:120%">Hello</div>
<script>
  // سلسلة نصّية
  alert(div.getAttribute('style')); // color:red;font-size:120%
  // كائن 
  alert(div.style); // [object CSSStyleDeclaration]
  alert(div.style.color); // red
</script>

غير أن غالبية الخاصيات تكون من نوع سلاسل نصّية.

وفي حالات نادرة جدًا، حتى وإن كانت خاصية DOM من نوع سلسلة نصّية، يمكن أن تختلف عن السمة. فمثلا، خاصية href في DOM تكون دائما عبارة عن عنوان URL مطلق كامل، حتى وإن تضمّنت السمة عنوان URL نسبي أو إشارةً إلى أحد العناصر في الصفحة عبر الصياغة hash#. وفيما يلي مثالٌ على ذلك.

<a id="a" href="#hello">link</a>
<script>
  // attribute
  alert(a.getAttribute('href')); // #hello

  // property
  alert(a.href ); // ‫ URL  كامل على شكل http://site.com/page#hello 
</script>

إذا ما أردت الوصول إلى قيمة href، أو أيّ سمةٍ غيرها، كما هي مدّونة بالضبط في شيفرة HTML، استعمل الخاصية getAttribute.

السمات غير المعيارية والخاصية dataset

نستخدم الكثير من السمات المعيارية عند الكتابة بلغة جافاسكربت ولكن ماذا عن السمات غير المعيارية وهل هي مفيدة؟ فلنرى أولا إن كانت مفيدة أم لا وما هو دورها.

تُستخدم أحيانا السمات غير المعيارية في تمرير البيانات من شيفرة HTML إلى جافاسكربت أو لـ : "وسم" عناصر HTML حتى تتعرّف عليها جافاسكربت.

<!-- وسم العنصر div لإظهار الاسم هنا-->
<div show-info="name"></div>
<!-- والعمر هنا -->
<div show-info="age"></div>
<script>
  // تجد الشيفرة العنصر الموسوم وتقوم بإظهار المطلوب
  let user = {
    name: "Pete",
    age: 25
  };
  for(let div of document.querySelectorAll('[show-info]')) {
    // ملئ الحقل بالمعلومات الموافقة له
    let field = div.getAttribute('show-info');
    div.innerHTML = user[field]; //(*) 
  }
</script>

(*): إظهارالقيمة "Pete" داخل العنصر div الموسوم بـ name وإظهار القيمة 25 داخل العنصر div الموسوم بـage. كما يمكن استعمالها لتنسيق عنصر ما. فمثلا، تُستخدم هنا السمة order-state للتعبير عن حالة الطلب (إن كان جديدًا، معلّقًا أو ملغى).

<style>
  /* تعتمد الأنماط على السمة غير المعيارية 'order-state' */
  .order[order-state="new"] {
    color: green;
  }
  .order[order-state="pending"] {
    color: blue;
  }
  .order[order-state="canceled"] {
    color: red;
  }
</style>
<div class="order" order-state="new">
  A new order.
</div>
<div class="order" order-state="pending">
  A pending order.
</div>
<div class="order" order-state="canceled">
  A canceled order.
</div>

لماذا يُستحسن استخدام السمة بدل استخدام الأصناف order-state-canceled. وorder-state-pending. و order-state-new.؟ ذلك لأنه يَسهُل استعمال الخاصيات موازنةً مع الأصناف حيث يمكن تعديل الحالة بمنتهى السهولة كما في المثال التالي:

// أسهل من حذف/إضافة صنف جديد
div.setAttribute('order-state', 'canceled');

ولكن السمات غير المعيارية قد تتسبّب في المشكل التالي: ماذا لو استُخدمت سمةٌ غير معياريةٍ لغرضٍ ما وبعدها اِستُحدِثت كسمةٍ معياريةٍ تقوم بوظيفةٍ ما؟ تُعد لغة HTML حيّةً، ومتطورةً، حيث تَستحدِث باستمرار سماتٍ جديدةٍ تلبّي حاجات المطوّرين. قد ينجرّ عن هذا الأمر انعكاساتٍ غير متوقعة. ولتفادي حصول مثل هذا التضارب وُجدت السمات *-data.

كلّ السمات التي تبدأ بـ "-data" هي سمات محجوزة يستعملها المطوّرون فقط. ويمكن الوصول إليها عبر الخاصية dataset. فمثلا، إذا كانت السمة المسمّاة "data-about" تابعة للعنصر elem ، يمكن الوصول إليها باستعمال elem.dataset.about كالآتي:

<body data-about="Elephants">
<script>
  alert(document.body.dataset.about); // Elephants
</script>

تُصبح أسماء السمات التي تتكوّن من عدّة كلمات مثل data-order-state على الشكل: dataset.orderState، حيث تُكتب -عند استعمالها كخاصيات- بنمط سنام الجمل أي بجعل أول حرف من كلّ كلمة كبيرًا عدا الكلمة الأولى. نعيد فيما يلي كتابة المثال السابق (المثال الخاص بـ "حالة الطلب") بطريقة أخرى.

<style>
  .order[data-order-state="new"] {
    color: green;
  }
  .order[data-order-state="pending"] {
    color: blue;
  }
  .order[data-order-state="canceled"] {
    color: red;
  }
</style>
<div id="order" class="order" data-order-state="new">
  A new order.
</div>
<script>
  // قراءة 
  alert(order.dataset.orderState); // new
  // تعديل 
  order.dataset.orderState = "pending"; // (*)
</script>

يُعدّ استعمال السمات التي تبدأ بـ *-data طريقةً فعالةً وآمنةً لتمرير البيانات غير المعيارية. لاحظ هنا أنه لا يمكنك قراءة السمات *-data فحسب، بل حتى تعديلها، ومن ثمّ تُطبَّق تنسيقات CSS بناءً عليها. في المثال أعلاه، غيّرت تعليمة السطر الأخير (الموسوم بالرمز (*)) اللّون ليصبح أزرقًا.

الخلاصة

  • السمة هي ما يدوّن على شيفرة HTML
  • الخاصية هي ما يندرج ضمن الكائن DOM

موازنة بسيطة

  الخاصيات السمات
النوع أيّ قيمةٍ كانت، للخاصيات أنواعٌ موضّحةٌ في المواصفة التقنية سلسلة نصّية
الاسم يخضع لشرط التمييز بين الحروف الكبيرة والحروف الصغيرة لا يخضع لشرط التمييز بين الحروف الكبيرة والحروف الصغيرة

دوالّ العمليات على السمات هي:

  • (elem.hasAttribute(name: للتحقق من وجود السمة.
  • (elem.getAttribute(name: للحصول على قيمة السمة.
  • (elem.setAttribute(name, value: إسناد القيمة value للسمة name.
  • (elem.removeAttribute(name: حذف السمة.
  • elem.attributes: يمثّل مجموعة السمات الخاصة بالعنصر المحدد.

ويُستحسن استعمال خاصيات DOM في أغلب الحالات، وعليك اللجوء إلى استعمال السمات فقط حين تحتاج إلى ذلك وإذا كان استعمال خاصيات DOM لا يفي بالغرض. مثلا: *تحتاج إلى سمةٍ غير معيارية، ولكن إن كانت تبدأ بـ -data عليك استعمالdataset. *تُريد قراءة القيمة كما هي مكتوبة بالضبط في شيفرة HTML. قد تكون قيمة الخاصية DOM مختلفةً عنها. فمثلا، تكون الخاصية href دائما عبارة عن عنوان URL مطلق كامل، وقد تحتاج أنت الوصول إلى القيمة الأصلية/الأولى.

تمارين

الوصول إلى سمةٍ ما

درجة الأهمية: 5
اُكتب الشيفرة التي تسمح باختيار العنصر من المستند باستعمال السمة data-widget-name وقراءة قيمتها.

<!DOCTYPE html>
<html>
<body>
  <div data-widget-name="menu">Choose the genre</div>
  <script>
    /*أكتب الشيفرة هنا */
  </script>
</body>
</html>

الحل

<!DOCTYPE html>
<html>
<body>
  <div data-widget-name="menu">Choose the genre</div>
  <script>
    // الحصول عليه
    let elem = document.querySelector('[data-widget-name]');
    // قراءة القيمة 
    alert(elem.dataset.widgetName);
    // أو
    alert(elem.getAttribute('data-widget-name'));
  </script>
</body>
</html>

تغيير لون الروابط الخارجية إلى البرتقالي

درجة الأهمية: 3
غيّر لون جميع الروابط الخارجية إلى البرتقالي من خلال التعديل على خاصية style . يُعدّ الرابط خارجيًا إذا:

  • كانت السمة href الخاصة به تضمّ السلسلة النصّية '//:'
  • لم تكن تبدأ بـالسلسلة النصّية 'http://internal.com'

مثال:

<a name="list">the list</a>
<ul>
  <li><a href="http://google.com">http://google.com</a></li>
  <li><a href="/tutorial">/tutorial.html</a></li>
  <li><a href="local/path">local/path</a></li>
  <li><a href="ftp://ftp.com/my.zip">ftp://ftp.com/my.zip</a></li>
  <li><a href="http://nodejs.org">http://nodejs.org</a></li>
  <li><a href="http://internal.com/test">http://internal.com/test</a></li>
</ul>
<script>
  // تنسيق رابط واحد
  let link = document.querySelector('a');
  link.style.color = 'orange';
</script>

ينبغي أن تكون النتيجة كالآتي:

001orangeExternalLinks.png

الحل

عليك أولا إيجاد كلّ الروابط الخارجية. وهناك طريقتان: الأولى من خلال إيجاد كافة الروابط باستعمال ('document.querySelectorAll('a وبعدها أخذ ما تحتاج إليه فقط.

let links = document.querySelectorAll('a');
for (let link of links) {
  let href = link.getAttribute('href');
  if (!href) continue; // لا وجود للسمة 
  if (!href.includes('://')) continue; // لا وجود للسلسلة النصية '://' 
  if (href.startsWith('http://internal.com')) continue; // رابط داخلي 
  link.style.color = 'orange';
}

لاحظ هنا أنّنا نستعمل ('link.getAttribute('href وليس link.href لأنّنا نريد استخراج القيمة من شيفرة HTML . والطريقة الثانية، أبسط من الأولى، تكون بإضافة عمليات التحقّق لمحدّد CSS:

 البحث في href عن كلّ الروابط التي تضمّ السلسلة النصّية '//:' //  
//  ولا تبدأ بـالسلسلة النصّية 'http://internal.com'
let selector = 'a[href*="://"]:not([href^="http://internal.com"])';
let links = document.querySelectorAll(selector);
links.forEach(link => link.style.color = 'orange');

ترجمة -وبتصرف- للفصل Attributes and properties من كتاب Browser: Document, Events, Interfaces


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...