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

البحث في DOM عبر *getElement و *querySelector في جافاسكربت


محمد أمين بوقرة

تُفيد خصائص التنقّل في DOM كثيرا عندما تكون العناصر قريبة من بعضها البعض. لكن ماذا لو لم تكن كذلك؟ كيف يمكن تحصيل عنصرٍ ما على الصفحة؟

هناك المزيد من توابع البحث لهذا الغرض.

document.getElementById أو فقط id

إذا كان للعنصر سمة id، فيمكننا تحصيله باستخدام التابع (document.getElementById(id، أينما وُجد.

على سبيل المثال:

<div id="elem">
  <div id="elem-content">Element</div>
</div>

<script>
  // تحصيل العنصر
  let elem = document.getElementById('elem');

  // تلوين خلفيّته باﻷحمر
  elem.style.background = 'red';
</script>

بالإضافة إلى هذا، يمكن الإشارة إلى العنصر بواسطة متغيّر عامّ (global variable) اسمه نفس قيمة الـ id.

<div id="elem">
  <div id="elem-content">Element</div>
</div>

<script>
  // id="elem" الذي سمته DOM إلى عنصر elem يشير المتغيّر
  elem.style.background = 'red';

  // واصلة في وسطه، لذا فلا يمكن أن يكون اسمًا لمتغير id="elem-content"لدى
  // window['elem-content'] لكن يمكن الوصول إليه بواسطة اﻷقواس المربعة...
</script>

… هذا إذا لم نصرّح بمتغيّر جافاسكربت له نفس الاسم، فإنّ اﻷولويّة حينها تكون له:

<div id="elem"></div>

<script>
  let elem = 5; //  <div id="elem"> هو 5، وليس إشارة إلى elem يكون الآن

  alert(elem); // 5
</script>


يُرجى عدم استخدام المتغيّرات العامّة المسمّات على قيم الـ id للوصول إلى العناصر

هذا السلوك مُبيّن في المواصفة، مما يجعله وفق المعايير نوعا ما. لكنّه مدعوم في الغالب بغرض التوافق (compatibility).

يحاول المتصفّح مساعدتنا بالمزج بين مجالات اﻷسماء (namespaces) في جافاسكربت و DOM. لا بأس بذلك في النصوص البرمجيّة البسيطة المضّمنة في HTML، لكن لا يُنصح به في العموم. فقد يؤدي ذلك إلى تناقضات في التسمية. كما أنّه عند قراءة شفرة جافاسكربت دون النظر إلى HTML، قد لا يتضّح من أين يأتي المتغيّر.

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

في التطبيقات الواقعيّة، تُعدّ document.getElementById هي الطريقة المفضّلة.



يجب أن يكون الـ id فريدًا

يجب أن يكون الـ id فريدًا. لا يمكن أن يحمل أكثر من عنصر في المستند نفس الـ id.

إذا كانت هناك عدّة عناصر لها نفس الـ id ، فإنّه لا يمكن التنبؤ بسلوك التوابع التي تستخدمها. فقد تُرجع مثلا document.getElementById أيًّا من العناصر عشوائيًّا. لذا يُرجى الالتزام بالقاعدة وإبقاء الـ id فريدا.



document.getElementById فقط، وليس anyElem.getElementById

يمكن استدعاء التابع getElementById على الكائن document فقط. يبحث هذا التابع عن الـ id المراد في المستند بأكمله.


querySelectorAll

يُعدّ (css)elem.querySelectorAll أكثر التوابع تنوّعا في الاستخدام على الإطلاق. يعيد هذا التابع جميع العناصر التي ينطبق عليها محدّد selector) CSS) معيّن.

نبحث في اﻷسفل مثلا عن جميع العناصر <li> التي هي آخر اﻷبناء:

<ul>
  <li>The</li>
  <li>test</li>
</ul>
<ul>
  <li>has</li>
  <li>passed</li>
</ul>
<script>
  let elements = document.querySelectorAll('ul > li:last-child');

  for (let elem of elements) {
    alert(elem.innerHTML); // "test", "passed"
  }
</script>

هذا التابع قويّ بالفعل، ﻷنّه يمكن معه استخدام أيّ محدّد CSS.


يمكن استخدام أشباه اﻷصناف كذلك

يدعم محدّد CSS أيضا أشباه اﻷصناف (pseudo-class) مثل hover: و active:. على سبيل المثال، يعيد ('document.querySelectorAll(':hover مجموعة العناصر التي يوجد عليها المؤشّر حاليّا (حسب ترتيب تفرعها: بداية من <html> إلى آخر فرع منها).


querySelector

يعيد استدعاء (elem.querySelector(css أوّل العناصر التي ينطبق عليها محدّد CSS.

بعبارة أخرى، هي نفس نتيجة [elem.querySelectorAll(css)[0 ، لكنّ هذه اﻷخيرة تبحث عن جميع العناصر وتختار بعد ذلك أوّلها، بينما تبحث elem.querySelector عن عنصر واحد فقط. فهي بذلك أسرع في البحث وأقصر في الكتابة.

matches

تقوم التوابع السابقة بالبحث في DOM.

لا يقوم (elem.matches(css بالبحث عن أيّ شيء، بل يتحقّق فقط من أنّ العنصر elem يطابق محدّد CSS المراد، ويعيد إمّا true أو false.

يُفيد هذا التابع عند المرور على عدد من العناصر (في مصفوفة أو شيء من هذا القبيل) ونريد ترشيح العناصر التي تهمّنا فقط.

على سبيل المثال:

<a href="http://example.com/file.zip">...</a>
<a href="http://ya.ru">...</a>

<script>
  // document.body.children يمكن تطبيقها على أيّة مجموعة مكان 
  for (let elem of document.body.children) {
    if (elem.matches('a[href$="zip"]')) {
      alert("The archive reference: " + elem.href );
    }
  }
</script>

closest

يتمثّل أسلاف (ancestors) عنصر ما في أبيه، وأب أبيه، وهكذا. يشكّل جميع اﻷسلاف معًا سلسلة الآباء التي تبتدئ من العنصر وتنتهي عند القمّة.

يبحث تابع (elem.closest(css عن أقرب سلفٍ ينطبق عليه محدّد CSS. يشمل البحثُ العنصرَ elem نفسَه.

بعبارة أخرى، ينطلق التابع closest صعودًا من العنصر المراد ويفحص كلّا من الآباء. فإذا طابق أحد اﻷسلاف المُحدِّد يتوقّف البحث، ويعيدُ السلفَ المطابق.

على سبيل المثال:

<h1>Contents</h1>

<div class="contents">
  <ul class="book">
    <li class="chapter">Chapter 1</li>
    <li class="chapter">Chapter 1</li>
  </ul>
</div>

<script>
  let chapter = document.querySelector('.chapter'); // LI

  alert(chapter.closest('.book')); // UL
  alert(chapter.closest('.contents')); // DIV

  alert(chapter.closest('h1')); // (ليس من اﻷسلاف h1 ﻷن) null
</script>

*getElementsBy

هناك أيضا توابع أخرى للبحث عن العقد حسب الوسم (tag) والصنف (class) وغيرها.

تُعدّ هذه التوابع غالبًا من الماضي، إذ أنّ querySelector أقوى وأقصر في الكتابة.

إذًا سنذكرها هنا من باب التمام، كما أنّه لا يزال من الممكن إيجادها في النصوص البرمجيّة القديمة.

  • يبحث (elem.getElementsByTagName(tag عن العناصر التي لها نفس الوسم المُراد ويعيدهم جميعا على شكل مجموعة. يمكن للمعامل tag أن يكون نجمة أيضا "*" ليشمل "جميع الوسوم".
  • يعيد (elem.getElementsByClassName(className العناصر التي لها نفس صنف CSS المراد.
  • يعيد (document.getElementsByName(name كل العناصر التي في المستند التي لها نفس السمة name. يندر استعمال هذا التابع.

على سبيل المثال:

// التي في المستند div حصّل جميع وسوم
let divs = document.getElementsByTagName('div');

لنجد جميع وسوم input التي في المستند:

<table id="table">
  <tr>
    <td>Your age:</td>

    <td>
      <label>
        <input type="radio" name="age" value="young" checked> less than 18
      </label>
      <label>
        <input type="radio" name="age" value="mature"> from 18 to 50
      </label>
      <label>
        <input type="radio" name="age" value="senior"> more than 60
      </label>
    </td>
  </tr>
</table>

<script>
  let inputs = table.getElementsByTagName('input');

  for (let input of inputs) {
    alert( input.value + ': ' + input.checked );
  }
</script>


لا تنسَ حرف "s" للجمع!

ينسى المطوّرون المبتدئون أحيانا الحرف "s"، فيعمدون إلى مناداة getElementByTagName بدل getElement<b>s</b>ByTagName.

يخلو getElementByTagName من الحرف "s" ﻷنّه يعيد عنصرا وحيدا. لكنّ getElement<b>s</b>ByTagName يعيد مجموعة من العناصر، لهذا فإنّ بداخله "s".



يعيد التابع مجموعة، وليس عنصرًا!

من الأخطاء الشائعة لدى المبتدئين أيضا هو كتابة:

// لا تعمل
document.getElementsByTagName('input').value = 5;

لن يعمل ذلك، ﻷنّه يحاول إسناد القيمة إلى مجموعةٍ من المُدخلات بدل إسنادها إلى العناصر التي تحتويها.

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

// (ًيُفترض أن تعمل (إذا كانت هناك مُدخلات
document.getElementsByTagName('input')[0].value = 5;

للبحث عن العناصر ذات الصنف article.:

<form name="my-form">
  <div class="article">Article</div>
  <div class="long article">Long article</div>
</form>

<script>
  // name أوجد حسب السمة
  let form = document.getElementsByName('my-form')[0];

  // أوجد حسب الصنف داخل النموذج
  let articles = form.getElementsByClassName('article');
  alert(articles.length); // 2 - "article" وجدنا عنصران لهما الصنف
</script>

المجموعات الحية

تُعيد جميع التوابع "*getElementsBy" مجموعة حيّة (live). تعكس هذه المجموعات الوضع الحاليّ الذي عليه المستند و "تُحدَّث تلقائيًّا" كلّما تغيّر.

في المثال أدناه نصّان برمجيّان:

  1. يُنشئ اﻷوّل مرجعًا إلى مجموعة العناصر <div>؛ طول المجموعة عندها هو 1.
  2. يُنفَّذ النصّ الثاني بعدما يقابل المتصفّح عنصر <div> آخر، ويكون طول المجموعة حينها هو 2.
<div>First div</div>

<script>
  let divs = document.getElementsByTagName('div');
  alert(divs.length); // 1
</script>

<div>Second div</div>

<script>
  alert(divs.length); // 2
</script>

في المقابل، يعيد querySelectorAll مجموعة ساكنة (static)، كأنّها مصفوفة ثابتة من العناصر.

إذا طبقناه في المثال أعلاه، فسيُخرج كلا النصّان 1:

<div>First div</div>

<script>
  let divs = document.querySelectorAll('div');
  alert(divs.length); // 1
</script>

<div>Second div</div>

<script>
  alert(divs.length); // 1
</script>

يمكننا الآن رؤية الفرق بوضوح. لم تزدد المجموعة الساكنة بظهور عنصر <div> جديد في المستند.

الملخص

هناك 6 توابع رئيسيّة للبحث عن العقد في DOM:

التابع يبحث بواسطة ... يمكن مناداته على عنصر؟ حيّة؟
querySelector محدّد CSS -
querySelectorAll محدّد CSS -
getElementById id - -
getElementsByName name -
getElementsByTagName الوسم أو '*'
getElementsByClassName الصنف

تُعدّ querySelector و querySelectorAll أكثرها استخداما على الإطلاق، لكن قد تُفيد *getElementBy أحيانا أو قد توجد في النصوص البرمجيّة القديمة.

عدا ذلك:

  • هناك (elem.matches(css للتحقّق من أنّ العنصر elem مطابق لمحدّد CSS المراد.
  • هناك (elem.closest(css للبحث عن أقرب سلفٍ مطابق لمحدّد CSS المراد. يشمل البحثُ العنصرَ نفسه.

ولنضف هنا تابعا آخر للتحقّق من العلاقة ابن-أب، إذ قد تفيد أحيانا:

  • يعيد (elemA.contains(elemB القيمة true إذا كان elemB داخلا تحت elemA (أي عنصرا سليلا لـ elemA) أو إذا كان elemA==elemB.

التمارين

البحث عن العناصر

اﻷهميّة: 4

إليك مستندًا يحتوي على جدول ونموذج.

كيف يمكن إيجاد؟ …

  1. الجدول الذي له "id="age-table.
  2. جميع العناصر label بداخل ذلك الجدول (من المفترض أن تكون هناك 3 منها).
  3. أوّل td في ذلك الجدول (التي فيها الكلمة "Age").
  4. النموذج form الذي له "name="search.
  5. أوّل input في ذلك النموذج.
  6. آخر input في ذلك النموذج

افتح table.html في نافذة مستقلة واستعمل أدوات المتصفّح لذلك.

الحل

هناك عدّة طرق لفعل ذلك.

هذه إحداها:

// id="age-table" الجدول الذي له
let table = document.getElementById('age-table')

// بداخل ذلك الجدول label جميع العناصر
table.getElementsByTagName('label')
// أو
document.querySelectorAll('#age-table label')

//  ("Age" في ذلك الجدول (التي فيها الكلمة td أوّل 
table.rows[0].cells[0]
// أو
table.getElementsByTagName('td')[0]
// أو
table.querySelector('td')

// name="search" النموذج الذي له
//  name="search" على افتراض أنّ هناك عنصرا واحدا في المستند له
let form = document.getElementsByName('search')[0]
// خصّيصا form ،أو
document.querySelector('form[name="search"]')

// في ذلك النموذج input أوّل
form.getElementsByTagName('input')[0]
// أو
form.querySelector('input')


// في ذلك النموذج input آخر
let inputs = form.querySelectorAll('input') // inputs أوجد جميع الـ 
inputs[inputs.length-1] // اختر آخرها

ترجمة وبتصرف لمقال *Searching: getElement*, querySelector من كتاب The Modern JavaScript Tutorial


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

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

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



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

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

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

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   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.


×
×
  • أضف...