DOM التعديل هو مفتاح إنشاء صفحات “حية”.
سنرى هنا كيفية إنشاء عناصر جديدة “بسرعة” وتعديل محتوى الصفحة الحالية.
دعونا نتظاهر باستخدام مثال. سنضيف رسالة على الصفحة تبدو أجمل من “تنبيه”.
إليك كيف ستبدو:
<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 باستخدام JavaScript (على افتراض أن الأنماط موجودة في HTML أو ملف CSS خارجي).
لإنشاء عقد DOM ، هناك طريقتان:
document.createElement(tag)Creates a new element node with the given tag:
let div = document.createElement('div');
document.createTextNode(text)Creates a new text node with the given text:
let textNode = document.createTextNode('Here I am');
في حالتنا، تكون الرسالة div مع فئة` تنبيه 'و HTML فيها:
في حالتنا، إنشاء div الرسالة ينطلب 3 مراحل:
// 1. Create <div> element
let div = document.createElement('div');
// 2. Set its class to "alert"
div.className = "alert";
// 3. Fill it with the content
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";
لقد أنشأنا العنصر ، ولكن حتى الآن إنه متغير فقط. لا يمكننا رؤية العنصر على الصفحة ، لأنه ليس جزءًا من المستند حتى الآن
لعرض “div” ، نحتاج إلى إدراجه في مكان ما في “المستند”. على سبيل المثال ، في document.body. هناك طريقة خاصة append لذلك:document.body.append (div).
توفر هذه المجموعة من الطرق المزيد من الطرق لإدراج:
node.append (... nodes or strings) – إلحاق عقد أو سلاسل في نهاية node ،node.prepend (... العقد أو السلاسل) – إدراج العقد أو السلاسل في بداية العقدة ،node.before (... nodes or strings) –- أدخل العقد أو السلاسل قبل node ،node.after(...nodes or strings) – إدراج العقد أو السلاسل بعد العقدة ،node.replaceWith (... العقد أو السلاسل) – - يستبدل العقدة بالعقد أو السلاسل المعطاة.فيما يلي مثال على استخدام هذه الأساليب لإضافة عناصر إلى قائمة والنص قبلها / بعدها:
<ol id="ol">
<li>0</li>
<li>1</li>
<li>2</li>
</ol>
<script>
ol.before('before'); // insert string "before" before <ol>
ol.after('after'); // insert string "after" after <ol>
let liFirst = document.createElement('li');
liFirst.innerHTML = 'prepend';
ol.prepend(liFirst); // insert liFirst at the beginning of <ol>
let liLast = document.createElement('li');
liLast.innerHTML = 'append';
ol.append(liLast); // insert liLast at the end of <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 النهائي هو:
<p>Hello</p>
<hr>
<div id="div"></div>
بمعنى آخر ، يتم إدخال السلاسل بطريقة آمنة ، مثل `` elem.textContent` يفعل ذلك.
لذلك ، لا يمكن استخدام هذه الطرق إلا لإدراج عقد DOM أو أجزاء نصية.
ولكن ماذا لو أردنا إدراج HTML “كـ html” ، مع عمل جميع العلامات والأشياء ، مثل elem.innerHTML?
لذلك يمكننا استخدام طريقة أخرى متعددة الاستخدامات: elem.insertAdjacentHTML (حيث ، 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 التعسفي بالصفحة.
إليك صورة متغيرات الإدراج:
! [] (insert-adjacent.svg)!
يمكننا أن نلاحظ بسهولة أوجه التشابه بين هذا والصورة السابقة. نقاط الإدراج هي نفسها في الواقع ، ولكن هذه الطريقة تدخل HTML.
الطريقة لديها شقيقان:
elem.insertAdjacentText (حيث ، نص) – نفس البنية ، ولكن يتم إدراج سلسلة “نص” “كنص” بدلاً من HTML ،elem.insertAdjacentElement (أين ، elem) – نفس البنية ، ولكن إدراج عنصر.وهي موجودة بشكل أساسي لجعل بناء الجملة “موحدًا”. عمليًا ، يتم استخدام insertAdjacentHTML فقط معظم الوقت. لأن العناصر والنصوص ، لدينا طرق “إلحاق / قبل / قبل / بعد” – فهي أقصر في الكتابة ويمكنها إدراج العقد / أجزاء النص.
لذا إليك متغير بديل لعرض رسالة:
<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>
// no need to call remove
second.after(first); // take #second and after it insert #first
</script>
كيفية إدراج رسالة أخرى مماثلة؟
يمكننا عمل دالة ووضع الكود هناك. لكن الطريقة البديلة ستكون * استنساخ * “div” الموجود وتعديل النص الموجود بداخله (إذا لزم الأمر).
في بعض الأحيان عندما يكون لدينا عنصر كبير ، قد يكون ذلك أسرع وأبسط.
elem.cloneNode (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); // clone the message
div2.querySelector('strong').innerHTML = 'Bye there!'; // change the clone
div.after(div2); // show the clone after the existing div
</script>
DocumentFragment عبارة عن عقدة DOM خاصة تعمل كغلاف لتمرير قوائم العقد.
يمكننا إلحاق العقد الأخرى بها ، ولكن عندما ندرجها في مكان ما ، يتم إدراج محتواها بدلاً من ذلك.
على سبيل المثال ، يُنشئ 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 ، ولكنه" يمتزج "، وبالتالي فإن البنية الناتجة ستكون:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
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()); // append + "..." operator = friends!
</script>
نذكر DocumentFragment بشكل أساسي نظرًا لوجود بعض المفاهيم فوقه ، مثل عنصر [template] (info: template-element) ، الذي سنغطيه لاحقًا.
[قديم]
هناك أيضًا طرق معالجة DOM “المدرسة القديمة” ، موجودة لأسباب تاريخية.
تأتي هذه الأساليب من العصور القديمة حقًا. في الوقت الحاضر ، لا يوجد سبب لاستخدامها ، حيث أن الأساليب الحديثة ، مثل “إلحاق” ، “قبل” ، “قبل” ، “بعد” ، “إزالة” ، “استبدال” ، تكون أكثر مرونة.
السبب الوحيد لإدراج هذه الطرق هنا هو أنه يمكنك العثور عليها في العديد من النصوص القديمة:
الوالدان. appendChild (العقدة)إلحاق العقدة بآخر طفل لـ" الوالدين ".
يضيف المثال التالي <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>
parentElem.insertBefore(node, nextSibling)
:إدراج “العقدة” قبل “nextSibling” في “motherElem”.
يدرج الكود التالي عنصر قائمة جديد قبل الثانية `<li>`:
```html run height=100
<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` كعنصر أول ، يمكننا القيام بذلك على النحو التالي:
```js
list.insertBefore(newLi, list.firstChild);
```
parentElem.replaceChild(node, oldChild)
:يستبدل oldChild بـ" عقدة “بين أطفال” الوالدين ".
الوالدلمحذف الطفل (عقدة)يزيل العقدة منالوالدين (بافتراض أن العقدة هي تابعها).
أولاً من `:
<ol id="list">
<li>0</li>
<li>1</li>
<li>2</li>
</ol>
<script>
let li = list.firstElementChild;
list.removeChild(li);
</script>
كل هذه الأساليب ترجع العقدة المدرجة / المُزالة. بمعنى آخر ، تُرجع parents 'appendChild (العقدة) العقدة`. ولكن عادة لا يتم استخدام القيمة التي تم إرجاعها ، نقوم فقط بتشغيل الطريقة.
هناك طريقة أخرى قديمة جدًا لإضافة شيء ما إلى صفحة الويب: 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` ديناميكيًا ، لذا فهي مرنة نوعًا ما. يمكننا استخدام JavaScript لإنشاء صفحة ويب كاملة وكتابتها.
تأتي الطريقة من الأوقات التي لم يكن فيها DOM ، ولا معايير … الأوقات القديمة حقًا. إنها لا تزال حية ، لأن هناك سكربتات تستخدمها.
في النصوص الحديثة نادرًا ما نراها بسبب القيود المهمة التالية:
** لا يعمل الاتصال بـ document.write إلا أثناء تحميل الصفحة. **
إذا نسميها بعد ذلك ، فسيتم مسح محتوى المستند الحالي.
على سبيل المثال:
<p>After one second the contents of this page will be replaced...</p>
<script>
// document.write after 1 second
// that's after the page loaded, so it erases the existing content
setTimeout(() => document.write('<b>...By this.</b>'), 1000);
</script>
لذا فهو غير قابل للاستخدام في مرحلة “بعد التحميل” ، على عكس طرق DOM الأخرى التي تناولناها أعلاه.
هذا هو الجانب السلبي.
هناك جانب صاعد أيضا. من الناحية الفنية ، عندما يتم استدعاء document.write أثناء قراءة المستعرض لـ HTML (" تحليل ") ، ويكتب شيئًا ، يستهلكه المستعرض تمامًا كما لو كان موجودًا في البداية ، في نص HTML.
لذلك يعمل بسرعة فائقة ، لأنه لا يوجد * تعديل DOM * المعنية. يكتب مباشرة في نص الصفحة ، بينما لم يتم بناء DOM بعد.
لذا إذا احتجنا إلى إضافة الكثير من النص إلى HTML ديناميكيًا ، ونحن في مرحلة تحميل الصفحة ، وكانت السرعة مهمة ، فقد يساعد ذلك. لكن في الواقع نادرا ما تجتمع هذه المتطلبات. وعادة ما يمكننا رؤية هذه الطريقة في البرامج النصية لمجرد أنها قديمة.
طرق إنشاء العقد الجديدة:
document.createElement (علامة) – ينشئ عنصرًا بالعلامة المحددة ،document.createTextNode (القيمة) – إنشاء عقدة نصية (نادرًا ما تستخدم) ،elem.cloneNode (deep) – استنساخ العنصر ، إذا كان deep == true ثم مع جميع الأحفاد.الإدخال والفك:
node.append (... nodes or strings) – أدخل في node ، في النهاية ،node.prepend (... العقد أو السلاسل) – أدخل في العقدة ، في البداية ،node.before (... nodes or strings) –- أدخل مباشرة قبل node ،node.after (... nodes or strings) –- أدخل مباشرة بعد node ،node.replaceWith (... العقد أو السلاسل) –- استبدال العقدة.node.remove () –- قم بإزالة العقدة.يتم إدراج السلاسل النصية “كنص”.
هناك أيضًا طرق “المدرسة القديمة”:
الوالد. appendChild (العقدة)mother.insertBefore (عقدة ، nextSibling)mother.removeChild (node)الأصل. إعادة مكان الطفل (newElem ، عقدة)جميع هذه الطرق تُرجع العقدة.
بالنظر إلى بعض HTML في html ،elem.insertAdjacentHTML (حيث ، html)يُدخلها بناءً على قيمةأين:
هناك أيضًا طرق مشابهة ، elem.insertAdjacentText وelem.insertAdjacentElement ، والتي تُدرج سلاسل نصية وعناصر ، ولكن نادرًا ما يتم استخدامها.
لإلحاق HTML بالصفحة قبل أن ينتهي التحميل:
document.write (html)بعد تحميل الصفحة تقوم هذه المكالمة بمسح المستند. غالبا ما ينظر إليها في النصوص القديمة.
We have an empty DOM element elem and a string text.
Which of these 3 commands will do exactly the same?
elem.append(document.createTextNode(text))elem.innerHTML = textelem.textContent = textAnswer: 1 and 3.
Both commands result in adding the text “as text” into the elem.
Here’s an example:
<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>
Create a function clear(elem) that removes everything from the element.
<ol id="elem">
<li>Hello</li>
<li>World</li>
</ol>
<script>
function clear(elem) { /* your code */ }
clear(elem); // clears the list
</script>
First, let’s see how not to do it:
function clear(elem) {
for (let i=0; i < elem.childNodes.length; i++) {
elem.childNodes[i].remove();
}
}
That won’t work, because the call to remove() shifts the collection elem.childNodes, so elements start from the index 0 every time. But i increases, and some elements will be skipped.
The for..of loop also does the same.
The right variant could be:
function clear(elem) {
while (elem.firstChild) {
elem.firstChild.remove();
}
}
And also there’s a simpler way to do the same:
function clear(elem) {
elem.innerHTML = '';
}
In the example below, the call table.remove() removes the table from the document.
But if you run it, you can see that the text "aaa" is still visible.
Why does that happen?
<table id="table">
aaa
<tr>
<td>Test</td>
</tr>
</table>
<script>
alert(table); // the table, as it should be
table.remove();
// why there's still aaa in the document?
</script>
The HTML in the task is incorrect. That’s the reason of the odd thing.
The browser has to fix it automatically. But there may be no text inside the <table>: according to the spec only table-specific tags are allowed. So the browser adds "aaa" before the <table>.
Now it’s obvious that when we remove the table, it remains.
The question can be easily answered by exploring the DOM using the browser tools. It shows "aaa" before the <table>.
The HTML standard specifies in detail how to process bad HTML, and such behavior of the browser is correct.
Write an interface to create a list from user input.
For every list item:
prompt.<li> with it and add it to <ul>.All elements should be created dynamically.
If a user types HTML-tags, they should be treated like a text.
Please note the usage of textContent to assign the <li> content.
Write a function createTree that creates a nested ul/li list from the nested object.
For instance:
let data = {
"Fish": {
"trout": {},
"salmon": {}
},
"Tree": {
"Huge": {
"sequoia": {},
"oak": {}
},
"Flowering": {
"apple tree": {},
"magnolia": {}
}
}
};
The syntax:
let container = document.getElementById('container');
createTree(container, data); // creates the tree in the container
The result (tree) should look like this:
Choose one of two ways of solving this task:
container.innerHTML.Would be great if you could do both.
P.S. The tree should not have “extra” elements like empty <ul></ul> for the leaves.
The easiest way to walk the object is to use recursion.
There’s a tree organized as nested ul/li.
Write the code that adds to each <li> the number of its descendants. Skip leaves (nodes without children).
The result:
To append text to each <li> we can alter the text node data.
Write a function createCalendar(elem, year, month).
The call should create a calendar for the given year/month and put it inside elem.
The calendar should be a table, where a week is <tr>, and a day is <td>. The table top should be <th> with weekday names: the first day should be Monday, and so on till Sunday.
For instance, createCalendar(cal, 2012, 9) should generate in element cal the following calendar:
P.S. For this task it’s enough to generate the calendar, should not yet be clickable.
We’ll create the table as a string: "<table>...</table>", and then assign it to innerHTML.
The algorithm:
<th> and weekday names.d = new Date(year, month-1). That’s the first day of month (taking into account that months in JavaScript start from 0, not 1).d.getDay() may be empty. Let’s fill them in with <td></td>.d: d.setDate(d.getDate()+1). If d.getMonth() is not yet the next month, then add the new cell <td> to the calendar. If that’s a Sunday, then add a newline “</tr><tr>”.<td> into it, to make it square.Create a colored clock like here:
Use HTML/CSS for the styling, JavaScript only updates time in elements.
First, let’s make HTML/CSS.
Each component of the time would look great in its own <span>:
<div id="clock">
<span class="hour">hh</span>:<span class="min">mm</span>:<span class="sec">ss</span>
</div>
Also we’ll need CSS to color them.
The update function will refresh the clock, to be called by setInterval every second:
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;
}
In the line (*) we every time check the current date. The calls to setInterval are not reliable: they may happen with delays.
The clock-managing functions:
let timerId;
function clockStart() { // run the clock
if (!timerId) { // only set a new interval if the clock is not running
timerId = setInterval(update, 1000);
}
update(); // (*)
}
function clockStop() {
clearInterval(timerId);
timerId = null; // (**)
}
Please note that the call to update() is not only scheduled in clockStart(), but immediately run in the line (*). Otherwise the visitor would have to wait till the first execution of setInterval. And the clock would be empty till then.
Also it is important to set a new interval in clockStart() only when the clock is not running. Otherways clicking the start button several times would set multiple concurrent intervals. Even worse – we would only keep the timerID of the last interval, losing references to all others. Then we wouldn’t be able to stop the clock ever again! Note that we need to clear the timerID when the clock is stopped in the line (**), so that it can be started again by running clockStart().
Write the code to insert <li>2</li><li>3</li> between two <li> here:
<ul id="ul">
<li id="one">1</li>
<li id="two">4</li>
</ul>
When we need to insert a piece of HTML somewhere, insertAdjacentHTML is the best fit.
The solution:
one.insertAdjacentHTML('afterend', '<li>2</li><li>3</li>');
There’s a table:
<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>
There may be more rows in it.
Write the code to sort it by the "name" column.
The solution is short, yet may look a bit tricky, so here I provide it with extensive comments:
let sortedRows = Array.from(table.tBodies[0].rows) // 1
.sort((rowA, rowB) => rowA.cells[0].innerHTML.localeCompare(rowB.cells[0].innerHTML));
table.tBodies[0].append(...sortedRows); // (3)
The step-by-step algorthm:
<tr>, from <tbody>.<td> (the name field)..append(...sortedRows).We don’t have to remove row elements, just “re-insert”, they leave the old place automatically.
P.S. In our case, there’s an explicit <tbody> in the table, but even if HTML table doesn’t have <tbody>, the DOM structure always has it.
التعليقات
<code>، وللكثير من السطور استخدم<pre>، ولأكثر من 10 سطور استخدم (plnkr, JSBin, codepen…)