يُعدّ التعديل على نموذج تمثيل المستند ككائن (DOM) مفتاح الحصول على صفحات "حيّة" (ديناميكية). سوف تتعرف فيما يلي على كيفية إنشاء عناصر جديدة على الصفحات في لمح البصر (بطريقة آنية دون الحاجة إلى إعادة تحميل الصفحات).
مثال: إظهار رسالة
فلنعرض ذلك على شكل مثالٍ تطبيقيٍ، حيث نُضيف رسالةً للصفحة تكون أكثر تنسيقًا من رسالة alert
كالآتي:
<style> .alert { padding: 15px; border: 1px solid #d6e9c6; border-radius: 4px; color: #3c763d; background-color: #dff0d8; } </style> <div class="alert"> <strong>Hi there!</strong> You've read an important message. </div>
استعملنا في هذا المثال لغة HTML. فلنُنشئ نفس الحاوية div
باستخدام جافاسكربت (على افتراض أن شيفرة HTML/CSS تتضمّن الأنماط التنسيقية).
إنشاء عنصر ما
توجد طريقتان لإنشاء عناصر (DOM):
-
الدالّة
(document.createElement(tag
: تُستخدم لإنشاء عقدة عناصرية جديدة باستعمال الوسم الذي يُمرَّر لها كالآتي:
let div = document.createElement('div');
-
الدالّة
(document.createTextNode(text
: تُستخدم لإنشاء عقدة نصية جديدة باستعمال النص الذي يُمرَّر لها كالآتي:
let textNode = document.createTextNode('Here I am');
غالبا ما نحتاج إلى إنشاء عقدٍ عناصريةٍ، مثل الحاوية div
، من أجل إظهار الرسالة.
إنشاء الرسالة
يتطلّب إنشاء حاوية الرسالة div
ثلاث مراحل:
// 1. إنشاء العنصر <div> let div = document.createElement('div'); // 2. إسناد القيمة "alert" لصنف الحاوية div.className = "alert"; // 3. وضع المحتوى داخل الحاوية div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";
صحيحٌ أنّنا أنشأنا العنصر ولكنه لحد الآن غير ظاهرٍ في الصفحة لأنّنا أنشأناه داخل المتغيّر div
ولم نُضفه بعد للصفحة.
دوالّ إضافة العناصر للصفحة
من أجل إظهار الحاوية div
، نحتاج إلى إضافتها في موضع ما من الصفحة، داخل العنصر <body>
مثلا والمشار إليه بالكائن document.body
. توجد دالّة خاصة تسمّى append تساعد على ذلك كما هو موضّح في المثال الآتي (انظر السطر الموسوم بـ (*)):
<style> .alert { padding: 15px; border: 1px solid #d6e9c6; border-radius: 4px; color: #3c763d; background-color: #dff0d8; } </style> <script> let div = document.createElement('div'); div.className = "alert"; div.innerHTML = "<strong>Hi there!</strong> You've read an important message."; document.body.append(div);//(*) </script>
نادينا، في هذا المثال، الدالّة append
وطبقناها على الكائن document.body
. كان يمكن أن نُطبقها على أيّ عنصرٍ كان من أجل إضافة عنصرٍ آخر له، حيث يمكننا على سبيل المثال إضافة عنصر ما للعنصر <div>
باستعمال الدالّة div.append(anotherElement)
. وهذه قائمة دوالّ غيرها تُستعمل لإضافة عنصرٍ ما لعنصرٍ آخرٍ في موضعٍ معيّنٍ من الصفحة:
-
node.append(...nodes or strings)
: إضافة عقد أو سلاسل نصية في نهاية العقدةnode
. -
node.prepend(...nodes or strings)
: إضافة عقد أو سلاسل نصية في بداية العقدةnode
. -
node.before(...nodes or strings)
: إضافة عقد أو سلاسل نصية قبل العقدةnode
. -
node.after(...nodes or strings)
: إضافة عقد أو سلاسل نصية بعد العقدةnode
. -
node.replaceWith(...nodes or strings)
: وضع عقد أو سلاسل نصية مكان العقدةnode
.
وتكون وسائط هذه الدوالّ عبارة عن قائمة عشوائية من عقد DOM أو سلاسل نصية (التي تصبح آليا عقدًا نصية). دعنا نرى ذلك عبر المثال الحيّ التالي، حيث تُستخدم فيه هذه الدوالّ لإضافة عناصر li
لقائمة ما ونصين، أحدهما قبلها والآخر بعدها.
<ol id="ol"> <li>0</li> <li>1</li> <li>2</li> </ol> <script> ol.before('before'); // إضافة السلسلة "before" قبل العنصر <ol> ol.after('after'); // إضافة السلسلة "after" بعد العنصر <ol> let liFirst = document.createElement('li'); liFirst.innerHTML = 'prepend'; ol.prepend(liFirst); // إضافة العنصر liFirst في بداية القائمة <ol> let liLast = document.createElement('li'); liLast.innerHTML = 'append'; ol.append(liLast); // إضافة العنصر liLast في نهاية القائمة <ol> </script>
وفيما يلي صورةٌ توضيحيةٌ لما تقوم به هذه الدوالّ:
وبالتالي تصبح القائمة في شكلها النهائي كما يلي:
before <ol id="ol"> <li>prepend</li> <li>0</li> <li>1</li> <li>2</li> <li>append</li> </ol> after
وكما سبق أن ذكرنا، يُمكننا إضافة العديد من العقد والسلاسل النصية بمناداة الدالّة مرّةً واحدةً فقط. في المثال التالي، أُضيفت كلٌّ من السلسلة النصية والعنصر دفعةً واحدةً:
<div id="div"></div> <script> div.before('<p>Hello</p>', document.createElement('hr')); </script>
لاحظ هنا أن النص أُضيف بمثابة "نصٍ" وليس بمثابة شيفرة HTML، حيث تظهر التسلسلات التهريبية مثل <
و>
. وبالتالي تصبح شيفرة HTML النهائية كالآتي:
<p>Hello</p> <hr> <div id="div"></div>
وبعبارة أخرى، تُضاف السلاسل النصية بطريقة آمنة كأنها أُضيفت باستعمال الخاصية textContent
.
تُستخدم هذه الدوالّ إذا فقط لإضافة عقد DOM أو مقاطع نصية. ولكن ماذا لو أردنا إضافة سلسلةٍ نصيةٍ تحوي شيفرة HTML بما فيها الوسوم وغيرها، مثل ما هو معمول به مع الخاصية innerHTML
؟
الدوالّ insertAdjacentHTML/Text/Element
والجوابٍ على السؤال السابق، هو استخدام دالّةٍ أخرى متعدّدة الاستعمالات، إنها الدالّة elem.insertAdjacentHTML(where, html)
. الوسيط الأوّل where
للدالّة عبارة عن كلمة رمز (code word) تُحدِّد في أيّ موضع بالضبط من العنصر سيُضاف الوسيط الثاني، وتأخذ هذه الكلمة الرمزواحدةً من القيم التالية:
-
"beforebegin"
: إضافة شيفرة HTML مباشرةً قبل العنصرelem
. -
"afterbegin"
: إضافة شيفرة HTML في بداية العنصرelem
. -
"beforeend"
: إضافة شيفرة HTML في نهاية العنصرelem
. -
"afterend"
: إضافة شيفرة HTML مباشرةً بعد العنصرelem
.
ويكون الوسيط الثاني html
للدالّة عبارة عن سلسلة نصية تُضاف على شكل شيفرة HTML. لاحظ المثال التالي:
<div id="div"></div> <script> div.insertAdjacentHTML('beforebegin', '<p>Hello</p>'); div.insertAdjacentHTML('afterend', '<p>Bye</p>'); </script>
الذي يولّد الشيفرة التالية:
<p>Hello</p> <div id="div"></div> <p>Bye</p>
كانت هذه هي الطريقة التي تُمكّننا من إضافة شيفرة HTML، أيًّا كانت، للصفحة. وفيما يلي صورة توضيحية لطرائق الإضافة في مختلف المواضع:
ويمكننا بسهولة ملاحظة أوجه الشبه بين هذه الصورة والصورة التي قبلها، حيث أن مواضع الإضافة هي نفسها لكن هذه الدالّة تُضيف شيفرة HTML في حين تُضيف السابقة عقدًا أو سلاسل نصيةً. ولهذه الدالّة دالّتين "شقيقتين":
-
elem.insertAdjacentText(where, text)
: لها نفس البنية ولكن السلسلة النصية تُضاف هنا كنص وليس كشيفرة HTML. -
elem.insertAdjacentElement(where, elem)
: لها أيضًا نفس البنية ولكنها تُستعمل لإضافة عنصرٍ ما.
وُجدت هاتين الدالّتين فقط لضرورة توحيد البنية، ولكن تُستخدم فقط الدالّة elem.insertAdjacentHTML
على أرض الواقع، طالما وُجدت الدوالّ append/prepend/before/after
التي تُستخدم لإضافة كلٍّ من العقد والنصوص بالإضافة إلى أنها أقصر من حيث عدد الأحرف.
وفيما يلي طريقةٌ بديلةٌ لإظهار رسالةٍ ما:
<style> .alert { padding: 15px; border: 1px solid #d6e9c6; border-radius: 4px; color: #3c763d; background-color: #dff0d8; } </style> <script> document.body.insertAdjacentHTML("afterbegin", `<div class="alert"> <strong>Hi there!</strong> You've read an important message. </div>`); </script>
إزالة/حذف عقدة ما
اُستحدثت الدالّة node.remove()
لإزالة/حذف عقدةٍ ما. فلنجعل الرسالة التي أظهرناها في المثال السابق تختفي بعد ثانية واحدة من إظهارها:
<style> .alert { padding: 15px; border: 1px solid #d6e9c6; border-radius: 4px; color: #3c763d; background-color: #dff0d8; } </style> <script> let div = document.createElement('div'); div.className = "alert"; div.innerHTML = "<strong>Hi there!</strong> You've read an important message."; document.body.append(div); setTimeout(() => div.remove(), 1000); </script>
لاحظ هنا أنه لا داعيَ لتحويل عنصرٍ ما من الموضع الذي كان فيه إذا أردت وضعه في مكان آخر، هذا لأنّ كافة دوالّ الإضافة تُحوِّل آليا العنصر من مكانه السابق. فلنُحوّل من خلال المثال الآتي موضعي العنصرين التاليين:
<div id="first">First</div> <div id="second">Second</div> <script> // لا داعيَ لمناداة الدالّة remove second.after(first); //تُضيف العنصر second بعد العنصر first </script>
نسخ العُقد باستعمال الدالة cloneNode
كيف يمكننا إضافة رسالة مطابقة للرسالة السابقة؟ نستطيع إنشاء دالّة ووضع الشيفرة بداخلها، ولكن هناك طريقة بديلة تنسخ العقدة div
وتعدّل النص المتضمّن بداخلها (إن أردنا ذلك). وتسمح لنا هذه الطريقة باختصار الوقت والجهد سيما لو كانت شيفرة العنصر محلّ النسخ طويلةً بعض الشيء.
تُنشِئ الدالّة elem.cloneNode(true)
عند مناداتها نسخةً كاملةً للعنصر، بما فيه من سمات وتوابع (العناصر الوليدة). أما إذا نادينا الدالّة elem.cloneNode(false)
- مع تغيير الوسيط من true
إلى false
- تُنشِئ الدالّة العنصر وحده دون العناصر التابعة له (العناصر الوليدة).
وهذا مثال عن نسخ الرسالة:
<style> .alert { padding: 15px; border: 1px solid #d6e9c6; border-radius: 4px; color: #3c763d; background-color: #dff0d8; } </style> <div class="alert" id="div"> <strong>Hi there!</strong> You've read an important message. </div> <script> let div2 = div.cloneNode(true); // نسخ الرسالة div2.querySelector('strong').innerHTML = 'Bye there!'; // تغيير محتوى الرسالة المنسوخة div.after(div2); // إظهار النسخة بعد الحاوية الأولى </script>
العقدة الخاصة DocumentFragment
تُعدّ DocumentFragment
عقدة DOM خاصة تُستعمل كمُغلِّف (wrapper) لتمرير قوائم تحوي عددًا من العقد. ويمكننا إضافة عقدٍ لها باستعمال الدالّة append، ولكن عند إضافتها هي (أي العقدة الخاصة DocumentFragment
) في موضعٍ ما، يكون محتواها هو ما يُضاف في هذا الموضع بدلا عنها.
تُنشِئ الدالّة getListContent
في هذا المثال مقطعا يتضمّن عناصر <li>
تُضاف لاحقا إلى العنصر <ul>
:
<ul id="ul"></ul> <script> function getListContent() { let fragment = new DocumentFragment(); for(let i=1; i<=3; i++) { let li = document.createElement('li'); li.append(i); fragment.append(li); } return fragment; } ul.append(getListContent()); // (*) </script>
لاحظ هنا في السطر الأخير الموسوم بالرمز (*) أنّنا أضفنا العقدة الخاصة DocumentFragment
باستعمال الدالّة append
(التي تضيف عنصرًا في نهاية عنصرٍ آخر) لكنها تخلّلت الشيفرة فحصلنا على البنية التالية:
<ul> <li>1</li> <li>2</li> <li>3</li> </ul>
ونادرًا ما تُستخدم عقدة DOM الخاصية DocumentFragment
، ذلك لأنه لا يوجد سببٌ يحملنا على إضافة عنصرٍ أو عناصرَ إلى عقدة من نوعٍ خاصٍ في حين يمكننا وضعه/وضعها في مصفوفة من العقد كالآتي:
<ul id="ul"></ul> <script> function getListContent() { let result = []; for(let i=1; i<=3; i++) { let li = document.createElement('li'); li.append(i); result.push(li); } return result; } ul.append(...getListContent()); </script>
أتينا على ذكر العقدة الخاصة DocumentFragment
لأنها تندرج ضمن بعض المفاهيم، كالعنصر template (أي القالب) مثلا، التي سنتطرّق إليها لاحقا.
دوال الإضافة والحذف على الطريقة القديمة
ملاحظة: تساعد هذه المعلومة على فهم الشيفرات القديمة ولكنك لن تحتاج إليها أثناء التطوير باستعمال الطرائق العصرية. تَضمّ لغة جافاسكربت أيضًا دوالًا قديمةً تُطبّق على كائنات DOM والتي مازالت موجودة لضرورة التأريخ. تعود هذه الدوالّ إلى زمنٍ بعيدٍ جدًا ولا نحتاج لاستعمالها في الوقت الحالي طالما وَضعت لغة جافاسكربت بين أيدينا دوالًا أكثر مرونة مثل الدوالّ:
-
append
-
prepend
- before
-
after
-
remove
-
replaceWith
السبب الوحيد الذي جعلنا نذكر لك هنا هذه الدوالّ هو أنك يمكن أن تصادفها في الكثير من السكربتات القديمة.
الدالة appendChild
تضيف الدالّة parentElem.appendChild(node)
العقدة node
كآخر عنصرٍ وليدٍ للعنصر parentElem. سنضيف في المثال التالي عنصرًا جديدًا <li>
في نهاية العنصر الوالد <ol>
:
<ol id="list"> <li>0</li> <li>1</li> <li>2</li> </ol> <script> let newLi = document.createElement('li'); newLi.innerHTML = 'Hello, world!'; list.appendChild(newLi); </script>
الدالة insertBefore
وتضيف الدالّة parentElem.insertBefore(node, nextSibling)
العقدة node
قبل العقدة nextSibling
ضمن العقدة parentElem
.
تُضيف الشيفرة التالية عنصرًا جديدًا للقائمة <ol>
مباشرة قبل العنصر<li>
الثاني في القائمة:
<ol id="list"> <li>0</li> <li>1</li> <li>2</li> </ol> <script> let newLi = document.createElement('li'); newLi.innerHTML = 'Hello, world!'; list.insertBefore(newLi, list.children[1]); </script>
يمكننا إضافة العقدة newLi
في المرتبة الأولى كالآتي:
list.insertBefore(newLi, list.firstChild);
الدالة replaceWith
تَستبدلُ هذه الدالّة العقدة node
بالعقدة oldChild
التي تُعدّ وليدة العقدة parentElem
.
الدالة removeChild
تحذِف هذه الدالّة العقدة node
من العقدة parentElem
(حيث نفترض هنا أن العقدة node
وليدة العقدة parentElem
).
يُحذَف العنصر الأول <li>
من القائمة <ol>
في المثال الموالي:
<ol id="list"> <li>0</li> <li>1</li> <li>2</li> </ol> <script> let li = list.firstElementChild; list.removeChild(li); </script>
تكون مخرجات (output) هذه الدوالّ عبارة عن عُقد (العقد التي حُذفت/التي أُضيفت)، حيث تُوّلِد الدالّة parentElement.appendChild(node)
عقدةً ولكن غالبًا ما لا تُستعمل القيمة التي توّلدت من مناداة هذه الدالّة، نحتاج فقط إلى تطبيق الدالّة/تنفيذها.
نبذة عن الدالة "document.write"
هناك أيضا الدالّة document.write
، وهي دالّة قديمة جدًا تُستخدم لإضافة جزءٍ ما لصفحة الويب، وبنيتها كالآتي:
<p>Somewhere in the page...</p> <script> document.write('<b>Hello from JS</b>'); </script> <p>The end</p>
تؤدي مناداة الدالّة document.write(html)
إلى كتابة الشيفرة html
في الصفحة 'آنذاك وفي هذا الموضع بالضبط'. وبذلك تُولَّد السلسلة النصية html
بطريقة ديناميكية (آنية) مرنة. يمكن إذًا استعمال لغة جافاسكربت لإنشاء وكتابة صفحة ويب كاملة.
اِستُحدثت هذه الدالّة في وقت لم تكن فيه كائنات DOM موجودة ولم تكن هناك معايير أيضًا….إلخ، أي منذ زمنٍ بعيدٍ جدًا، ومازالت 'حيّة' ما دام البعض يستعملها لحد الآن في سكربتاته، ولكن نادرًا ما نصادفها في السكربتات العصرية الدارجة حاليا، نظرًا لمحدوديتها في الجوانب التالية:
تُنفَّذ مناداة الدالّة document.write
فقط أثناء تحميل الصفحة وفي حال نُوديَت بعد ذلك، يُحذف محتوى الصفحة كما في المثال التالي:
<p>After one second the contents of this page will be replaced...</p> <script> // مناداة الدالّة document.write بعد مرور ثانية واحدة // أي بعد تحميل الصفحة، فتَحذِف هذه الدالّة المحتوى الأصلي للصفحة setTimeout(() => document.write('<b>...By this.</b>'), 1000); </script>
لا ينبغي إذًا استعمال هذه الدالّة بعد تحميل الصفحة، على عكس غيرها من دوالّ DOM التي تحدثنا عنها سابقا.
تحدثنا عن الجانب السلبي لهذه الدالّة، لكنها تملك جانبا إيجابيًا أيضًا؛ فمن وجهة نظر تقنية، وفي حال ما نوديت الدالّة document.write
أثناء تحليل/قراءة المتصفّح لشيفرة HTML تدريجيا، وكتبت الدالّة بعض الأسطر، يَعتبر المتصفّح أن هذه الأسطر كانت موجودةً منذ البداية في شيفرة HTML، يُسرِّع ذلك كثيرًا من عملية القراءة حيث لا تشمل هذه العملية أيّ تعديلات على DOM، فتكتب الدالّة مباشرةً على الصفحة قبل أن يُنشَأ نموذج تمثيل المستند ككائن.
فإذا أردنا مثلا إضافة سلسلةٍ كبيرةٍ تحوي شيفرة HTML بطريقة ديناميكية في مرحلة تحميل الصفحة، وكان عامل السرعة يصنع الفارق بالنسبة لنا، يمكن أن تكون هذه الطريقة فعالة جدًا. ولكننا نادرًا ما نصادف هذه السيناريوهات على أرض الواقع. بل غالبا ما نصادف هذه الدالّة في بعض السكربتات القديمة (سكربتات كُتبت في وقت لم تكن تُستعمل هذه الدالّة لأغراضٍ كهذه).
الخلاصة
-
الدوال التي تُستخدم في إنشاء عقدٍ جديدةٍ هي:
-
document.createElement(tag)
: تُنشِئ عنصرًا بناءً على الوسمtag
. -
document.createTextNode(value)
: تُنشِئ عقدةً نصيةً (نادرًا ما تُستخدم). -
elem.cloneNode(deep)
: تنسخ العنصر، مع كل توابعه (أي العناصر الوليدة) إذا كانت قيمة المعاملdeep
تُساويtrue
.
-
-
الدوالّ التي تستخدم للإضافة والحذف هي:
-
node.append(...nodes or strings)
: تُضيف عقدة أو سلسلة نصية في نهاية العقدةnode
. -node.prepend(...nodes or strings)
: تُضيف عقدة أو سلسلة نصية في بداية العقدةnode
. -node.before(...nodes or strings)
: تُضيف عقدة أو سلسلة نصية مباشرة قبل العقدةnode
. -node.after(...nodes or strings)
: تُضيف عقدة أو سلسلة نصية مباشرة بعد العقدةnode
. -node.replaceWith(...nodes or strings)
: تستبدل بالعقدةnode
ما مُرِّر إليها من عقدة/عقد أو محتوى نصي. -node.remove()
: تحذف العقدةnode
.
-
وهي تُضيف السلاسل النصية كنص وليس كشيفرة HTML.
-
الدوالّ القديمة هي:
-
rent.appendChild(node)
-
parent.insertBefore(node, nextSibling)
-
parent.removeChild(node)
-
parent.replaceChild(newElem, node)
-
وتكون مُخرجات (output) هذه الدوالّ عبارة عن عُقد.
-
تضيف الدالّة
elem.insertAdjacentHTML(where, html)
شيفرة HTML، التي تُمرّر لها عبر الوسيطhtml
، في المواضع التالية بناءً على قيمة الوسيطwhere
: -
"beforebegin"
: تُضاف الشيفرةhtml
مباشرة قبل العنصرelem
. -
"afterbegin"
: تُضاف الشيفرةhtml
في بداية العنصرelem
. -
"beforeend"
: تُضاف الشيفرةhtml
في نهاية العنصرelem
. -
"afterend"
: تُضاف الشيفرةhtml
مباشرة بعد العنصرelem
. -
هناك أيضًا دوالٌّ مماثلةٌ لها وهي
elem.insertAdjacentText
وelem.insertAdjacentElement
ولكن نادرًا ما تٌستخدم. -
إذا أردت إضافة شيفرة HTML إلى الصفحة قبل نهاية عملية تحميلها اِستعمل الدالّة
document.write(html)
.
تؤدي مناداة هذه الدالّة بعد انتهاء عملية تحميل الصفحة إلى مسح محتوى الصفحة، ونُصادفها خاصة في السكربتات القديمة.
تمارين
createTextNode/innerHTML/textContent
درجة الأهمية: 5 لدينا عنصر DOM فارغ يسمّى elem
وسلسلة نصية text
. حدِّد، من بين التعليمات الثلاث التالية، تلك التي لها نفس الوظيفة.
-
elem.append(document.createTextNode(text))
-
elem.innerHTML = text
-
elem.textContent = text
الحل
التعليمتان 1 و3 لهما نفس الوظيفة، حيث تضيف كلتا التعليمتين السلسلة النصية text
للعنصر elem
كنص وليس كشيفرة HTML. وفيما يلي مثال على ذلك:
<div id="elem1"></div> <div id="elem2"></div> <div id="elem3"></div> <script> let text = '<b>text</b>'; elem1.append(document.createTextNode(text)); elem2.innerHTML = text; elem3.textContent = text; </script>
مسح عنصر ما
درجة الأهمية: 5
أنشئ الدالّة clear(elem)
التي تحذف محتوى العنصر elem
بالكامل.
<ol id="elem"> <li>Hello</li> <li>World</li> </ol> <script> function clear(elem) { /*اكتب الشيفرة هنا */ } clear(elem); // clears the list </script>
الحل
فلنبدأ بالحل الذي لا ينبغي أن تقترحه، لأنه ببساطة حلٌ خاطئ.
function clear(elem) { for (let i=0; i < elem.childNodes.length; i++) { elem.childNodes[i].remove(); } }
طريقة الحل هذه لا تنفع، لأن مناداة الدالّة remove()
تؤدي إلى تغيير مواضع العُقد ضِمن المجموعة elem.childNodes
، فبعد حذف العقدة الأولى مثلا (التي كانت في الموضع 0 من المجموعة) تُسحب المجموعة كاملةً للخلف لتصبح العقدة ذات الموضع 1 مكان العقدة ذات الموضع 0، لكن المؤشر i
في الدورة الثانية من الحلقة يأخذ القيمة 1 وبالتالي تُحذف العقدة ذات الموضع 1 وتبقى العقدة ذات الموضع 0 موجودة، وهكذا دواليك. لن تُحذف إذا بعض العناصر من المجموعة. ويحدث نفس الشيء عند استعمال الحلقة for...of
.
الحل الصحيح يكون مثلا كالآتي:
function clear(elem) { while (elem.firstChild) { elem.firstChild.remove(); } }
أو بطريقة أبسط:
function clear(elem) { elem.innerHTML = ''; }
لماذا لا تختفي السلسلة 'aaa'؟
درجة الأهمية: 1
عند مناداة الدالّة table.remove()
، في المثال التالي، يُحذف الجدول من الصفحة ولكن السلسلة 'aaa' تبقى ظاهرة. فلماذا يحدث ذلك؟
<table id="table"> aaa <tr> <td>Test</td> </tr> </table> <script> alert(table); // الجدول موجود table.remove(); // لماذا مازالت السلسلة 'aaa' ؟ ظاهرة </script>
الحل
يعود السبب في حدوث ذلك إلى خطأ في شيفرة HTML لهذا التمرين.
اِعلم أن من مهام المتصفّح تصحيح الأخطاء آليًا. وبما أن المواصفة التقنية تنصّ على أن الجداول لا تضمّ أيّ سلاسل نصية، بل فقط الوسوم الخاصة بالجداول، يُضيف المتصفح السلسلة "aaa"
قبل الجدول table
مباشرة. وعندما يُحذف الجدول، تبقى السلسلة "aaa"
ظاهرة.
للإجابة على سؤال هذا التمرين، يمكنك الإستعانة بعرض نموذج تمثيل المستند ككائن DOM باستعمال المتصفح، وستظهر لك السلسلة "aaa"
قبل الجدول table
.
تُحدِّد معايير HTML، وبالتفصيل، كيف يُعالِج المتصفح الشيفرة التي تحتوي على أخطاء، ويُعدّ تعامل المتصفح بهذه الطريقة مع الخطأ المتضمَّن في شيفرة HTML لهذا التمرين صحيحا.
إنشاء قائمة
درجة الأهمية: 4 اكتب شيفرة تُنشِئ واجهةً تسمح للمستخدم بإدخال محتوى ما على مراحل وتجمع هذا المحتوى في شكل قائمة. ولكل عنصر من القائمة عليك:
-
مطالبة المستخدِم بإدخال المحتوى باستعمال الدالّة
prompt
. -
إنشاء العنصر
<li>
بناءً على المحتوى الذي أدخله المستخدِم. -
مواصلة العملية إلى غاية فراغ المستخدِم من إدخال المعطيات (بالضغط على المفتاح
esc
على لوحة المفاتيح، أو الضغط على زر الالغاء الظاهر على النافذة).
يجب أن تُنشَئ العناصر ديناميكيًا وإذا أدخل المستخدِم وسوم HTML، تُعالَج على أنها نصوص، لا شيفرة HTML.
الحل
ملاحظة: لاحظ استعمال الخاصية textContent
لإسناد محتوى للعنصر <li>
.
إنشاء شجرة اعتمادًا على كائن
درجة الأهمية: 5
اكتب الدالّة createTree()
التي تُنشئ قائمةً متفرعةً من العناصر ul/li
بناءً على كائن متفرع:
let data = { "Fish": { "trout": {}, "salmon": {} }, "Tree": { "Huge": { "sequoia": {}, "oak": {} }, "Flowering": { "apple tree": {}, "magnolia": {} } } };
وتكون بنيتها كالآتي:
let container = document.getElementById('container'); createTree(container, data); // تُنشِئ الشجرة بداخل الحاوية ‘container’
على أن تكون النتيجة المتحصل عليها كالآتي:
اِختر واحدةً من بين الطريقتين التاليتين لحل التمرين:
-
أنشئ الشجرة في شكل شيفرة HTML ثم أسندها للخاصية
Container.innerHTML
أو - أنشئ عُقد الشجرة وأضفها باستعمال دوالّ DOM.
ويُستحسن أن تَحُل التمرين بالطريقين.
ملاحظة: لا ينبغي أن تَضمّ الشجرة في أوراقها عناصر إضافية فارغة <ul></ul>
.
الحل
ملاحظة: أسهل طريقة لتصفح الكائن (المرور بعناصره الواحد تلوى الآخر) تكون باستعمال التعاود (recursion).
إظهار العناصر الوليدة لشجرة ما
درجة الأهمية: 5
لدينا هذه الشجرة وهي مبنية على شكل عناصر ul/li
متفرعة. اكتب شيفرة تُضيف من خلالها لكل عنصر <li>
عدد عناصره الوليدة مع إهمال الأوراق (العُقد التي ليس لها عناصر وليدة)، والنتيجة تكون على الشكل التالي:
الحل
لإضافة نصٍ لكلّ عنصر من العناصر <li>
يمكن التعديل على العُقدة النصية data
.
إنشاء يومية
الأهمية:4
اكتب الدالّة createCalendar(elem, year, month)
التي تُنشئ يومية الشهر month
من السنة year
، وتسندها للعنصر elem
. تكون اليومية على شكل جدول تُمثِّل فيه العناصر <tr>
الأسابيع والعناصر <td>
الأيام، وتكون خانات السطر الأول عبارة عن عناصر <th>
تحوي أسماء أيام الأسبوع مُرتّبةً من الإثنين إلى الأحد. فعلى سبيل المثال، تؤدي مناداة الدالّة createCalendar(cal, 2012, 9)
إلى إنشاء اليومية التالية وإسنادها للعنصر cal
.
ملاحظة: يكفي في هذه المرحلة إظهار اليومية، لا نحتاج أن تكون تفاعلية.
يمكنك الإطلاع على شيفرة التمرين من هنا
الحل
نُنشئ الجدول على شكل سلسلة نصية "<table>....</table>"
ثم نسندها إلى الخاصية innerHTML
، باتباع خطوات الخوارزمية التالية:
-
إنشاء خانات السطر الأول من الجدول (table header) باستعمال عناصر
<th>
تحوي أسماء أيام الأسبوع. -
إنشاء الكائن
date
باستعمال الدالّة البانيةd=newDate(year, month-1)
والذي يُمثِّل أول يوم في الشهرmonth
(مع الأخذ في الحسبان أن ترتيب الأشهر في جافاسكربت يبدأ من 0 وليس من 1). -
ملء الخانات التي تسبق خانة اليوم الأول من الشهر (أي اليوم
d.getDay()
)، والتي تكون فارغة في اليومية، بإضافة عناصر<td></td>
فارغة للجدول. -
الانتقال تصاعديا عبر الأيام باستعمال الدالّة
d.setDate(d.getDate()+1)
وإضافة الخانات<td>
لليومية مادامت قيمةd.getMonth()
تشير إلى الشهر نفسه (لم نصل بعد للشهر الموالي). وإذا كان اليوم يوم أحدٍ نضيف سطرًا جديدًا<tr></tr>
. -
إذا وصلنا إلى نهاية الشهر، ومازالت في السطر خانات فارغة، نملؤها بعناصر
<td></td>
فارغة لنجعل الجدول مستطيلا.
إنشاء ساعة ملونة باستعمال الدالّة setInterval
الأهمية: 4
أنشئ ساعة ملونة تشبه الساعة في الشكل التالي:
استعمل HTML/CSS للتنسيق، وجافاسكربت فقط لتحديث القيم الزمنية للعناصر.
الحل
فلنكتب أولا شيفرة التنسيق باستعمال HTML/CSS. نضع كلَّ مكون زمني في حاوية خاصة به (الساعات في حاوية، الدقائق في حاوية أخرى والثواني أيضا في حاوية) ونلونها باستعمال CSS.
<div id="clock"> <span class="hour">hh</span>:<span class="min">mm</span>:<span class="sec">ss</span> </div>
تحدِّث الدالّة update()
الوقت، وتنادي الدالّة setInterval
الدالّة update()
كل ثانية لتحيين الوقت.
function update() { let clock = document.getElementById('clock'); let date = new Date(); // (*) let hours = date.getHours(); if (hours < 10) hours = '0' + hours; clock.children[0].innerHTML = hours; let minutes = date.getMinutes(); if (minutes < 10) minutes = '0' + minutes; clock.children[1].innerHTML = minutes; let seconds = date.getSeconds(); if (seconds < 10) seconds = '0' + seconds; clock.children[2].innerHTML = seconds; }
نتحقّق في السطر الموسوم بالرمز (*) من الوقت آنيا لأن القيم التي تَنتُج من مناداة الدالّة setInterval
غير موثوقة، حيث يمكن أن يكون فيها بعض التأخير. فيما يلي الدوالّ التي تُستعمل لإدارة الساعة:
let timerId; function clockStart() { // run the clock timerId = setInterval(update, 1000); update(); // (*) } function clockStop() { clearInterval(timerId); timerId = null; }
لاحظ أن الدالّة update()
، بالإضافة لكونها مُبرمَجةً لتُنفَّذ دوريًا كلّ ثانية داخل الدالّة clockStart()
، فهي تُنفَّذُ في السطر الموسوم بالرمز (*) في الحين ولا تنتظر حتى تنادي الدالّة setInterval
الدالّة update()
، وذلك لتفادي بقاء الساعة فارغةً طول مدّة تنفيذ الدالّةsetInterval
(أي مدّة ثانية واحدة) عند مناداتها أول مرة.
إضافة شيفرة HTML إلى قائمة
_الأهمية: 5 _
اكتب الشيفرة التي تُضيف المقطع <li>2</li><li>3</li>
بين العنصرين <li>
فيما يلي:
<ul id="ul"> <li id="one">1</li> <li id="two">4</li> </ul>
الحل
تُعدّ الدالّة insertAdjacentHTML
أحسن اختيار حين يتعلق الأمر بإضافة شيفرة HTML في موضع ما من الصفحة.
one.insertAdjacentHTML('afterend', '<li>2</li><li>3</li>');
فرز جدول ما
الأهمية: 5
لدينا الجدول التالي (قد يحوي عددًا أكبر من السطور):
<table> <thead> <tr> <th>Name</th><th>Surname</th><th>Age</th> </tr> </thead> <tbody> <tr> <td>John</td><td>Smith</td><td>10</td> </tr> <tr> <td>Pete</td><td>Brown</td><td>15</td> </tr> <tr> <td>Ann</td><td>Lee</td><td>5</td> </tr> <tr> <td>...</td><td>...</td><td>...</td> </tr> </tbody> </table>
اكتب الشيفرة التي تعيدُ ترتيبه ترتيبًا أبجديًا حسب محتوى العمود name
.
الحل
الحل عبارة عن بضعة سطور فقط، ولكننا نحتاج إلى شرح الحيل التي اُستعمِلت فيه. ويكون باتباع خطوات الخوارزمية التالية:
-
استخراج سطور الجدول من العنصر
tbody
. -
ترتيبها حسب محتوى العمود الأول
name
ترتيبًا أبجديًا. -
إضافة العُقد حسب الترتيب الجديد باستعمال
append(sortedRows)
.
لستَ مضطرًا لحذف العناصر<tr>
، عليك فقط إعادة إضافتها حيث تنتقل آليًا من مواضعها الأصلية إلى المواضع الجديدة بعد ترتيبها.
ملاحظة: العنصر tbody
من الجدول مذكورٌ صراحةً في شيفرة HTML ولكن حتى وإن لم يُذكر صراحةً، فهو دائمًا موجود في بنية DOM.
ترجمة -وبتصرف- للفصل Modifying the document من كتاب Browser: Document, Events, Interfaces
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.