آموزش جاوا اسکریپت – جلسه ۲۲ – درخت DOM

در مقاله قبل (بخش 21 آموزش جاوا اسکریپت)، مباحث مربوط به تعریف کلاس و نحوه پیاده سازی مفاهیم شی گرایی توسط CLASS ها را آموزش دادیم. در این مقاله درخت html DOM را بررسی می کنیم و نحوه دسترسی به عنصر موردنظر در DOM را آموزش می دهیم.
درخت DOM در HTML چیست؟
تگ ها (Tags) ستون فقرات HTML هستند. بر اساس Document Object Model (DOM) هر یک از تگ های html یک آبجکت می باشند. تگ های تودرتو یا nested فرزندهای تگ های والد هستند. متن داخل المان ها نیز به همین صورت (فرزند المان هستند)
دسترسی به المان های DOM توسط js:
تمام آبجکت های داخل DOM توسط جاوا اسکریپت قابل دسترسی و تغییر هستند. بعنوان مثال عبارت document.body بیانگر تگ <body> در یک صفحه html می باشد.
با اجرای کد زیر، رنگ بک گراند body صفحه وب برای مدت 3 ثانیه قرمز خواهد شد:
document.body.style.background = 'red'; // make the background red setTimeout(() => document.body.style.background = '', 3000); // return back
در این مثال از style.background برای تغییر رنگ بک گراند المان استفاده کردیم اما می توان از property های بسیاری روی المان های html استفاده کنیم. مانند: innerHtml (برای گرفتن html داخلی المان) یا offsetWidth (طول المان) و…
بزودی درباره نحوه دستکاری و تغییر المان های DOM صحبت خواهیم کرد اما ابتدا باید ساختار DOM را بفهمیم.
مثالی ساده از درخت DOM:
در ابتدا بیایید با یک داکیومنت ساده شروع کنیم:
<!DOCTYPE HTML> <html> <head> <title>About elk</title> </head> <body> The truth about elk. </body> </html>
در واقع DOM بیانگر ساختار درختی تگ های HTML بصورت زیر می باشد:
تگ ها معرف المان های DOM می باشند که ساختار درختی DOM را تشکیل می دهند. تگ <html> المان والد است و تگ های <head> و <body> فرزندهای آن هستند. در تصویر بالا متن داخل المان ها را با #text مشخص کرده ایم که یک داده از نوع رشته است. بعنوان مثال تگ title شامل متن About elk است.
لطفا در نظر داشته باشید که:
- کاراکتر ویژه ↵ بیانگر خط جدید (new line) است. مانند \n در جاوا اسکریپت
- کاراکتر ویژه ␣ نمایش دهنده فاصله (space) است.
کاراکترهای فوق (خط جدید و فاصله) بطور کلی معتبر و مجاز هستند. مانند کاراکترهای معمولی و اعداد. آنها بعنوان یک text node در DOM شناخته شده هستند.
نکته 1: کاراکترهای فاصله (␣) و خط جدید (↵) در صورتیکه قبل از head باشند نادیده گرفته می شوند.
نکته 2: اگر کاراکتری بعد از تگ بسته </body> تعریف شود بطور خودکار به داخل تگ body درج می شوند.
مثالی از Text Node در DOM:
در کد زیر هیچ فاصله ای وجود ندارد و فقط text node داریم:
<!DOCTYPE HTML> <html><head><title>About elk</title></head><body>The truth about elk.</body></html>
نکته: در المان جدول یا table بطور پیش فرض تگ <tbody> تولید می شود. بدین صورت که اگر حتی شما این تگ را حین تعریف جدول ننویسید، مرورگر آن را به شکل زیر تعریف خواهد کرد:
<table id="table"><tr><td>1</td></tr></table>
انواع node ها در HTML DOM:
علاوه بر element و text node انواع دیگری node در DOM وجود دارند مانند Comment:
<!DOCTYPE HTML> <html> <body> The truth about elk. <ol> <li>An elk is a smart</li> <!-- comment --> <li>...and cunning animal!</li> </ol> </body> </html>
هر چیزی در html حتی کامنت ها هم بخشی از DOM می باشند. حتی عبارت <!DOCTYPE…> در بالای صفحات html نیز یک node از DOM می باشد.
بطور کلی 12 نوع DOM Node داریم که در اینجا می توانید لیست کامل DOM Node ها را مشاهده کنید.
ما معمولا با 4 نوع از آنها کار می کنیم:
document: نقطه شروع کار با DOM است.
element nodes: شامل تگ های HTML است که درخت DOMرا تشکیل می دهند.
text nodes: شامل متون المان ها می باشد.
comments: برای درج اطلاعات توضیحی بکار می روند که نمایش داده نمی شوند.
مشاهده DOM در مرورگر:
می توانید برای مشاهده ساختار DOM از ابزار Live DOM Viewer استفاده کنید. روش دیگر برای مشاهده DOM یک صفحه HTML استفاده از ابزار توسعه مرورگر یا browser developer tools می باشد.
برای تست کردن یک مثال، فایل elk.html را در مرورگر باز کنید و به تب elements مراجعه کنید. تصویری شبیه زیر را خواهید دید:
برای نمایش المان خاصی در این بخش باید با ماوس روی المان موردنظر راست کلیک کرده و گزینه inspect را بزنیم.
در بخش سمت راست سه تب زیر وجود دارد:
- Styles: برای مشاهده CSS های اعمال شده روی المان موردنظر. شامل استایل های پیش فرض المان در مرورگر (built-in styles) و استایل های سفارشی المان. تمام استایل ها را می توان در این بخش مشاهده و تست کرد. مانند margin-padding-font-size-width و…
- Computed: برای مشاهده لیست تمام استایل های اعمال شده روی المان
- Event Listener: برای مشاهده لیست event listener های اعمال شده روی DOM
کار با تب Console:
وقتی می خواهیم با المان های DOM کار کنیم احتمالا گاهی اوقات می خواهیم اسکریپت های JS روی آن اعمال کنیم. برای تست جاوا اسکریپت روی المان مورد نظر دو مرحله زیر را داریم:
- روی المان موردنظر راست کلیک کرده و inspect را بزنید.
- دکمه ESC را بزنید تا بخش Console باز شود.
در بخش بعد نحوه اجرای کدهای جاوا اسکریپت را بر روی المان های DOM به منظور دسترسی به المان و تغییر آن آموزش می دهیم.
پیمایش DOM:
DOM اجازه می دهد تا با المان های آن کار کنید و آنها را تغییر دهید اما ابتدا باید بتوانیم به این المان ها دسترسی پیدا کنیم. دسترسی به node های DOM در ابتدا با document آغاز می شود. در واقع از طریق document می توان به تمام المان های یک صفحه وب در جاوا اسکریپت دسترسی پیدا کرد.
در تصویر زیر نحوه دسترسی به DOM node ها را نمایش دادیم:
المان documentElement و body:
Dom node مربوط به تگ html برابر documentElement می باشد.
<html> = document.documentElement
Dom node مربوط به body برابر document.body می باشد.
<body> = document.body
دسترسی به تگ Head:
برای دسترسی به تگ head در dom نیز داریم.
<head> = document.head
المان های فرزند در DOM (childNodes, firstChild, lastChild):
دو عبارت را باید برای کار با المان های فرزند یاد بگیرید:
- Child node یا Children: بیانگر المان هایی است که فرزند مستقیم یک المان می باشند. مثلا المان های head و body فرزندان المان html هستند.
- Descendants: شامل تمام المان های تو در توی آن المان. فرزندان و…
در مثال زیر تگ body دو فرزند مستقیم دارد به نام های ul و div.
<html> <body> <div>Begin</div> <ul> <li> <b>Information</b> </li> </ul> </body> </html>
اما descendant های تگ body فقط ul و div نمی باشند بلکه تمام فرزندان آنها را نیز شامل می شود (مثلا li و b)
مجموعه childNodes شامل تمام node های فرزند یک المان می باشد (شامل نودهای متنی و…)
<html> <body> <div>Begin</div> <ul> <li>Information</li> </ul> <div>End</div> <script> for (let i = 0; i < document.body.childNodes.length; i++) { alert( document.body.childNodes[i] ); // Text, DIV, Text, UL, ..., SCRIPT } </script> ...more stuff... </body> </html>
در این مثال تمام نودهای فرزند المان body را نمایش دادیم.
Property های firstChild و lastChild امکان دسترسی سریع به اولین و آخرین فرزند المان را به ما می دهد.
اگر نود فرزند یا child node وجود داشته باشد عبارات زیر همواره true خواهند بود:
elem.childNodes[0] === elem.firstChild elem.childNodes[elem.childNodes.length - 1] === elem.lastChild
DOM collections:
همانطور که مشاهده می شود childNode ظاهرا شبیه آرایه است اما در واقع اینگونه نیست و یک collection می باشد.
برای پیمایش آن می توان از ساختار for..of استفاده کرد:
for (let node of document.body.childNodes) { alert(node); // shows all nodes from the collection }
بنابراین متدهای مخصوص آرایه برای childNodes جواب نمی دهد. مثلا کد زیر خطا دارد:
alert(document.body.childNodes.filter); // undefined (there's no filter method!)
المان های sibling و المان parent:
Siblingها نودهایی هستند که فرزند یک والد مشترک باشند. بعنوان مثال تگ های head و body هم خانواده یا sibling می باشند.
<html> <head>...</head><body>...</body> </html>
- تگ body بعنوان هم خانواده راست یا بعدی (right/ next siblings) معرفی می شود (nextSibling)
- تگ head بعنوان هم خانواده چپ یا قبلی (left/ previous siblings) معرفی می شود (previousSibling)
*نود والد یا parent node نیز با parentNode شناخته می شود.
بعنوان مثال:
// parent of <body> is <html> alert( document.body.parentNode === document.documentElement ); // true // after <head> goes <body> alert( document.head.nextSibling ); // HTMLBodyElement // before <body> goes <head> alert( document.body.previousSibling ); // HTMLHeadElement
Element-only navigation:
در childNodes به تمام نودهای فرزند یک نود دسترسی داریم. در واقع می توان به text node و element node و comment node دسترسی داشت.
اما در بسیاری از تسک ها ما نیازی به دسترسی به text node و comment node نداریم و فقط element node را بصورت ساختار درختی می خواهیم.
بعنوان مثال:
مشاهده می شود که برای دسترسی به element node کافیست کلمه element را در میان property های قبلی تعریف کنیم. مثلا lastChild به lastElementChild تبدیل خواهد شد.
- children : معرف فرزندهایی است که نودهای المان هستند.
- firstElementChild, lastElementChild : معرفی اولین و آخرین المان های فرزند می باشد.
- previousElementSibling, nextElementSibling : معرف المان های هم خانواده
- parentElement : معرف المان والد
در مثال زیر می خواهیم المان های فرزند تگ body را بیابیم:
<html> <body> <div>Begin</div> <ul> <li>Information</li> </ul> <div>End</div> <script> for (let elem of document.body.children) { alert(elem); // DIV, UL, DIV, SCRIPT } </script> ... </body> </html>
نودهای تشکیل دهنده جدول (Table):
المان <table> شامل property های زیر می باشد:
- rows : مجموعه المان های <tr> یک جدول
- caption/tHead/tFoot : معرف المان های caption>, <thead>, <tfoot>>
- tBodies: مجموعه ای از المان های داخل <tbody>
- rows: مجموعه <tr> های داخل جدول
- cells: مجموعه td و th های داخل یک tr در جدول
- sectionRowIndex: ایندکس یا موقعیت المان tr در جدول
- rowIndex: معرف تعداد tr های داخل جدول
- cellIndex: بیانگر تعداد سلول های داخل tr
مثال:
<table id="table"> <tr> <td>one</td><td>two</td> </tr> <tr> <td>three</td><td>four</td> </tr> </table> <script> // get td with "two" (first row, second column) let td = table.rows[0].cells[1]; td.style.backgroundColor = "red"; // highlight it </script>
در این مثال یک جدول داریم که شامل دو سطر یا tr است و هر کدام از این سطرها دو ستون یا td دارند. برای رسیدن به ستون دوم از سطر اول جدول، دستور row[0].cells[1] را اجرا کرده ایم.
جستجوی المان های DOM:
برای اینکه بتوانیم یک المان را در DOM جستجو کنیم چه باید کرد؟ خوشبختانه متدهایی در جاوا اسکریپت وجود دارد که می توان بوسیله آنها به آیتم مورد نظر در DOM دسترسی پیدا کنیم.
دسترسی به المان توسط getElementById:
اگر اتریبیوت id برای المان موردنظر تعریف شده باشد می توانیم با استفاده از متد getElementById(id) به آن دسترسی داشته باشیم. کد زیر را در نظر بگیرید:
<div id="elem"> <div id="elem-content">Element</div> </div> <script> // get the element let elem = document.getElementById('elem'); // make its background red elem.style.background = 'red'; </script>
همچنین می توانیم توسط خود id به آیتم موردنظر در dom برسیم:
<div id="elem"> <div id="elem-content">Element</div> </div> <script> // elem is a reference to DOM-element with id="elem" elem.style.background = 'red'; // id="elem-content" has a hyphen inside, so it can't be a variable name // ...but we can access it using square brackets: window['elem-content'] </script>
بنابراین توسط آیدی المان می توان بطور مستقیم به آن دست یافت. مگر اینکه در کدهای جاوا اسکریپتی خود (مانند زیر) یک متغیر بنام آن id تعریف کنیم:
<div id="elem"></div> <script> let elem = 5; // now elem is 5, not a reference to <div id="elem"> alert(elem); // 5 </script>
در این مثال دیگر elem به المان با آیدی elem اشاره نمی کند بلکه به متغیر elem اشاره می کند.
نکته: نام آیدی تعریف شده باید در DOM منحصربفرد باشد. یعنی فقط یک المان با آیدی مشخص وجود داشته باشد. در صورتی که چندین المان یک آیدی واحد داشته باشند، رفتار getElementById غیر قابل پیش بینی خواهد بود و یکی از المان های مذکور بصورت رندوم انتخاب خواهد شد.
متد querySelectorAll:
متد elem.querySelectorAll(css) تمام المان هایی را که با css ورودی همخوانی دارند را بر می گرداند.
بعنوان مثال در کد زیر می خواهیم li را برگردانیم که آخرین فرزند ul است:
<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>
نکته: امکان استفاده از کلاس های سودو (pseudo-classes) در این متد وجود دارد. مانند :hover یا :active
بعنوان مثال document.querySelectorAll(‘:hover’) تمام المان هایی را بر می گرداند که عمل hover روی آنها در حال اجرا است.
متد querySelector:
متد elem.querySelector(css) اولین المانی را به خروجی می دهد که با css ورودی متد تطابق داشته باشد.
خروجی این متد مانند elem.querySelectorAll(css)[0] می باشد با این تفاوت که querySelectorAll بدنبال تمام المان هایی می گردد که در شرط css صدق کنند و اولین آیتم را بر می گرداند اما querySelector پس از اینکه به اولین المانی که شرط css را داشته باشد برسد آنرا به خروجی می دهد. پس سرعت اجرای بیشتر و دستور خلاصه تری دارد.
متد matches:
متدهایی که تا اینجا دیدیم برای جستجوی المان در DOM بکار می روند اما متد elem.matches(css) خروجی بولین یعنی true یا false دارد. بدین شکل که اگر المان elem در شرط css صدق کند، خروجی true خواهد بود وگرنه false می باشد.
متد matches برای زمانی مفیدتر واقع می شود که در حال پیمایش یک آرایه یا لوپ روی آن هستید و می خواهید المان هایی را که شرط css را دارند را برگردانید.
<a href="http://example.com/file.zip">...</a> <a href="http://ya.ru">...</a> <script> // can be any collection instead of document.body.children for (let elem of document.body.children) { if (elem.matches('a[href$="zip"]')) { alert("The archive reference: " + elem.href ); } } </script>
متد closest:
متد elem.closest(css) بدنبال نزدیکترین والدی می گردد که شرط css را داشته باشد. به بیان دیگر متد closest از اولین والد یا parent المان شروع به سرچ می کند و به سمت بالا می رود. هر جا که شرط css برقرار باشد عملیات جستجو متوقف شده و والد یافت شده به خروجی می رود.
بعنوان مثال:
<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')); // null (because h1 is not an ancestor) </script>
متد getElementsBy*:
برای دسترسی به المان دلخواه در DOM متدهایی دیگری نیز وجود دارد. در این بخش به معرفی آنها می پردازیم:
- getElementsByTagName(tag) : این متد بدنبال تمام المان هایی می گردد که نام تگ آنها با مقدار ورودی متد یکسان باشد. اگر بجای tag از * استفاده شود تمام تگ های DOM را بر می گرداند.
- getElementsByClassName(className) : المان هایی را پیدا می کند که کلاس CSS آنها برابر className باشد.
- getElementsByName(name) : تمام المان هایی را بر می گرداند که اتریبیوت name آنها در شرط صدق کند.
بعنوان مثال:
// get all divs in the document 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>
نکته 1: مراقب تعریف حرف “s” در متد getElementsByTagName باشید. در صورتی که s داشته باشد مجموعه از المان های موردنظر را بر می گرداند و اگر بصورت مفرد یعنی getElementByTagName باشد خروجی تنها یک المان خواهد بود.
نکته 2: برای مقداردهی یک آیتم از لیست خروجی getElementsByTagName نمی توان بصورت زیر عمل کرد:
// doesn't work document.getElementsByTagName('input').value = 5;
مشخص است که با خطا مواجه می شویم. زیرا خروجی متد فوق کالکشنی از المان ها می باشد و ما باید برای تغییر یکی از المان ها، ایندکس آن را مشخص کنیم. مانند زیر:
// should work (if there's an input) 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> // find by name attribute let form = document.getElementsByName('my-form')[0]; // find by class inside the form let articles = form.getElementsByClassName('article'); alert(articles.length); // 2, found two elements with class "article" </script>
اجرای زنده دستورات:
در کد زیر دو خروجی متفاوت داریم:
- تعداد المان هایی که تگ div دارند در مرحله اول یک است.
- در مرحله دوم تعداد المان های div دو عدد خواهد بود.
<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) خواهد بود:
<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 های مرحله دوم نیز برابر با یک شده است. در صورتی که ما دو عدد div در DOM داریم.
جمع بندی:
در جاوا اسکریپت برای سرچ المان در DOM شش متد اصلی داریم:
در این مقاله (بخش 22) درخت DOM و نحوه پیمایش آن را توسط JS آموزش دادیم و توسط متدهایی مانند getelementbyId، querySelector، childNodes، closest و… به المنت موردنظر دسترسی پیدا کردیم. در مقاله بعد (بخش 23 از آموزش js) نحوه تغییر عناصر دام (DOM Manipulation) را آموزش خواهیم داد. مانند درج یک عنصر در DOM، حذف عناصر موجود، تولید یک عنصر جدید در DOM و…
دیدگاهتان را بنویسید