کار با عناصر صفحه (DOM Manipulation) - بخش اول
ایجاد گرههای جدید
یکی از کارهای مرسوم در رابطه با DOM، ایجاد گرههای جدید و افزودن آنها به درخت DOM است. این گرهها میتوانند از هر نوعی باشند. اما در این بخش صرفاً به نحوهی ایجاد گرههای نوع Element و نوع Text میپردازیم که پرکاربردترین موارد هستند.
با استفاده از متد createElement از شئ document میتوان یک گرهی جدید از نوع Element ایجاد کرد. آرگومان ورودی این متد نوع تگ مورد نظر را مشخص میکند. مثلاً برای ایجاد یک عنصر از نوع <div> میتوان از دستور زیر استفاده کرد.
const div = document.createElement('div');
برای ایجاد گرههای متنی نیز میتوان از متد createTextNode استفاده کرد.
const text = document.createTextNode('This is a sample text');
حال برای قرار دادن گرهی متنی در یک عنصر، میتوان از متد appendChild استفاده کرد. این متد یک گره را به انتهای لیست فرزندان یک عنصر اضافه میکند. مثلاً با دستور زیر میتوان گرهی متنی فوق را به عنصر <div> قبلی اضافه کرد.
div.appendChild(text);
نکتهی بسیار مهمی که باید به آن توجه شود این است که گرههایی که توسط جاوا اسکریپت ایجاد میشوند، تا زمانی که به درخت DOM اضافه نشوند در صفحهی وب نمایش داده نمیشوند. یعنی گرههایی که با دستورات فوق ایجاد شدهاند صرفاً در حافظه ذخیره شدهاند و در صفحهی وب نمایش داده نمیشوند. برای نمایش این گرهها در صفحه، باید آنها را به بخش مورد نظر از صفحه اضافه کرد. به عنوان مثال دستور زیر، گرهی ذخیره شده در متغیر div را به انتهای عنصر <body> اضافه میکند.
document.body.appendChild(div);
با اجرای مجموعهی دستورات فوق به صورت متوالی، عبارت "This is a sample text" در صفحهی وب نمایش داده میشود. یعنی با استفاده از جاوا اسکریپت میتوان محتوای جدیدی را به صفحهی وب اضافه کرد. این مثال را میتوانید اینجا در CodePen اجرا کنید.
اگر احساس میکنید که ایجاد گرهها با دستورات فوق، کاری بسیار طولانی و خسته کننده است، حق با شماست. روشهای سادهتری برای ایجاد عناصر و متن داخل عناصر وجود دارد که در ادامه به بررسی آنها میپردازیم.
خاصیت textContent
گرههای از نوع Element خاصیتی به نام textContent دارند که متن داخلشان را نگهداری میکند. از این خاصیت هم میتوان برای خواندن متن داخل عناصر استفاده کرد و هم میتوان با این خاصیت متن داخل عناصر را تغییر داد. مثلاً دو گرهی قبلی را با استفاده از این خاصیت به شکل سادهتری میتوان ایجاد کرد.
const div = document.createElement('div');
div.textContent = 'This is a sample text';
در صورتی که یک عنصر علاوه بر متن، شامل عناصر دیگری نیز باشد. خاصیت textContent فقط بخش متنی را بازمیگرداند. به عنوان مثال کدهای HTML زیر را در نظر بگیرید که در آن عنصر <p> دارای فرزندانی از نوع Text و Element است.
<p id="p1">This is a paragraph and <a href="#">this is a link</a> in the paragraph.</p>
قطعه کد زیر مقدار خاصیت textContent را در کنسول نمایش میدهد.
const p = document.getElementById('p1');
console.log(p.textContent);
← "This is a paragraph and this is a link in the paragraph."
مشاهده میکنید که محتوای متنی عنصر <a> نیز در خاصیت textContent عنصر <p> وجود دارد. اما تگهای شروع و پایان عنصر <a> حذف شدهاند. همچنین در صورتی که مقدار جدیدی که به خاصیت textContent داده میشود، حاوی تگهای HTML باشد. این تگها به صورت متنی تفسیر میشوند. یعنی کاراکترهای خاص HTML به کدهای معادل تبدیل میشوند. مثلاً کاراکتر ">" به کد ";lt&" تبدیل میشود.
استفاده از یک تابع برای ایجاد عناصر
برای سادهتر شدن ایجاد عناصر و محتوای متنی آنها، میتوان یک تابع به صورت زیر تعریف کرد. این تابع نام یک تگ HTML را به همراه یک رشته دریافت میکند و یک عنصر از نوع مشخص شده ایجاد کرده و رشتهی ارسال شده را نیز در خاصیت textContent آن قرار میدهد و نتیجه را بازمیگرداند.
function createElement(tag , text){
const elm = document.createElement(tag);
elm.textContent = text;
return elm;
}
حال با استفاده از تابع فوق میتوان به راحتی با یک دستور، هر نوع عنصری را با محتوای متنی دلخواه ایجاد کرد. مثلاً دستور زیر یک عنصر از نوع <h1> را با محتوای مشخص شده ایجاد میکند.
const heading = createElement('h1' , 'Test Heading');
به عنوان یک مثال عملیتر، فرض کنید قصد داریم کدهای HTML زیر را به صورت پویا با استفاده از جاوا اسکریپت ایجاد کرده و در تگ <body> از سند HTML قرار دهیم.
<h1>List of fruits</h1>
<ul>
<li>Apple</li>
<li>Orange</li>
<li>Banana</li>
<li>Cherry</li>
</ul>
با استفاده از برنامهی زیر میتوان عناصر لازم برای ایجاد کدهای HTML فوق را ایجاد کرده و به انتهای عنصر <body> اضافه کرد.
const heading = createElement('h1' , 'List of fruits');
const ul = createElement('ul' , '');
const li1 = createElement('li' , 'Apple');
const li2 = createElement('li' , 'Orange');
const li3 = createElement('li' , 'Banana');
const li4 = createElement('li' , 'Cherry');
ul.appendChild(li1);
ul.appendChild(li2);
ul.appendChild(li3);
ul.appendChild(li4);
document.body.appendChild(heading);
document.body.appendChild(ul);
نتیجهی اجرای دستورات فوق، صفحهای مانند شکل زیر خواهد بود. توجه کنید که اگر فقط دو خط آخر را از کد فوق حذف کنیم. هیچ محتوایی در صفحهی وب نمایش داده نخواهد شد. زیرا تا زمانی که عناصر ایجاد شده به درخت DOM اضافه نشوند، در صفحهی وب نمایش داده نمیشوند. این مثال را میتوانید اینجا اجرا کنید.
نکته : کدهایی که با برنامهی فوق به سند HTML اضافه میشوند، کمی با کدهای HTML نشان داده شده متفاوت هستند. با توجه به این که کدهای HTML فوق به صورت دستی تایپ شدهاند، بین تگهای HTML از فضاهای خالی و تو رفتگی استفاده شده است. اما کدهایی که توسط جاوا اسکریپت ایجاد میشوند هیچ نوع فضای خالی یا تو رفتگی ایجاد نمیکنند. هرچند نتیجهای که در صفحهی وب مشاهده میشود در هر دو حالت یکسان است. اما درخت DOM در این دو حالت یکسان نیست و حالت اول دارای تعدادی گرهی اضافی از نوع متنی است که در حالت دوم وجود ندارد. البته توسط جاوا اسکریپت میتوان این فضاهای خالی را نیز به درخت DOM اضافه کرد. ولی در اکثر مواقع نیازی به انجام این کار نیست.
نکتهی مهم : در صورتی که برنامهی جاوا اسکریپت فوق را در بخش <head> از یک سند HTML قرار دهید. برنامه به درستی عمل نخواهد کرد و هیچ محتوایی در صفحهی وب نمایش داده نخواهد شد. به طور کلی دستوراتی که برای دسترسی به عناصر DOM به کار میروند (مانند دو دستور آخر که با عنصر <body> در ارتباط هستند)، باید بعد از بارگذاری عنصر مورد نظر اجرا شوند. با توجه به این که صفحات وب از بالا به پایین تفسیر میشوند، در صورتی که دستورات فوق در بخش <head> قرار داده شوند، در زمان اجرا شدن دستورات، هنوز عنصر <body> توسط مرورگر بارگذاری نشده است. بنابراین برنامه با خطا مواجه خواهد شد. برای جلوگیری از بروز چنین مشکلی دو راه حل وجود دارد.
راه حل اول این است که کدهای جاوا اسکریپت را بعد از تگ شروع عنصر مورد نظر قرار دهیم. در این صورت در زمان اجرای کدهای جاوا اسکریپت، قطعاً عنصر مورد نظر بارگذاری شده است. به همین دلیل معمولاً تگهای <script> در انتهای تگ <body> قرار داده میشوند. زیرا با رسیدن به انتهای تگ <body> تمام عناصر صفحهی وب بارگذاری شدهاند. (البته این کار سرعت بارگذاری کدهای HTML را نیز افزایش میدهد که موجب نمایش سریعتر صفحهی وب به کاربر میشود)
راه حل دوم این است که تمام کدهای جاوا اسکریپت را بعد از بارگذاری کامل صفحهی وب اجرا کنیم. برای این کار تمام کدها را داخل یک تابع قرار میدهیم و تابع مورد نظر را پس بارگذاری کامل صفحه فراخوانی میکنیم. در این صورت دیگر اهمیتی ندارد که تگهای <script> را در چه بخشی از سند HTML قرار دهیم. زیرا تا زمانی که کل عناصر صفحه بارگذاری نشوند این کدها اجرا نخواهند شد. برای استفاده از این روش باید از رویدادها در جاوا اسکریپت استفاده کنیم. در فصل بعدی با رویدادها آشنا خواهید شد و نحوهی استفاده از این روش را خواهید دید.
متد insertBefore
مثال قبلی را دوباره در نظر بگیرید. فرض کنید بعد از اجرای تمام دستورات، تصمیم میگیریم یک پاراگراف جدید بعد از عنصر <h1> و قبل از عنصر <ul> اضافه کنیم. در این حالت نمیتوان از متد appendChild استفاده کرد. زیرا این متد یک گرهی جدید را به عنوان آخرین فرزند به یک عنصر اضافه میکند. اما در این حالت قصد داریم یک گرهی جدید را بین فرزندان یک عنصر اضافه کنیم.
برای حل این مسئله میتوان از متد insertBefore استفاده کرد. این متد یک گرهی جدید را قبل از یک گرهی موجود درج میکند. در این مثال باید پاراگراف جدید را قبل از عنصر <ul> درج کنیم. توجه کنید که این متد را باید بر روی عنصر والد اجرا کنیم. یعنی در این مثال خاص که عنصر <body> والد عنصر <ul> است، باید متد insertBefore را بر روی عنصر <body> اجرا کنیم.
این متد دو ورودی دریافت میکند که ورودی اول گرهی جدید برای درج است و ورودی دوم گرهای است که گرهی جدید باید قبل از آن درج شود. همچنین این متد گرهی جدید درج شده را به عنوان خروجی نیز بازمیگرداند. قطعه کد زیر روش انجام این کار را نشان میدهد. توجه کنید که در این مثال فرض شده است که کدهای HTML در سند HTML موجود هستند و با جاوا اسکریپت ایجاد نشدهاند.
const ul = document.querySelector('ul');
const p = createElement('p' , 'This is a test paragraph');
document.body.insertBefore(p , ul);
این مثال را میتوانید اینجا اجرا کنید.
در این مثال با توجه به این که فقط یک عنصر <ul> در سند HTML وجود دارد، انتخاب آن با متد querySelector کار بسیار سادهای است. اما در اسناد HTML واقعی معمولاً انتخاب عناصر به وسیلهی نام تگها کار سادهای نیست. زیرا معمولاً عناصر زیادی از یک نوع خاص در یک سند HTML وجود دارد. در چنین شرایطی میتوان از متد getElementById و صفت id برای انتخاب عنصر مورد نظر استفاده کرد. اما در برخی شرایط ممکن است برای عنصر مورد نظر صفت id تعیین نشده باشد. در این صورت میتوان از خاصیتهایی که برای حرکت در DOM تعریف شدهاند استفاده کرده و از یک عنصر شروع کرده و به عنصر مورد نظر رسید.
به عنوان مثال فرض کنید در کدهای HTML همین مثال عنصر <h1> دارای صفت id با مقدار "heading" باشد. حال برای انتخاب عنصر <ul> میتوان ابتدا عنصر <h1> را انتخاب کرد. سپس با خاصیت nextElementSibing میتوان عنصر <ul> را نیز انتخاب کرد. قطعه کد زیر نحوهی انجام این کار را نشان میدهد.
const h1 = document.getElementById('heading');
const ul = h1.nextElementSibling;
const p = createElement('p' , 'This is a test paragraph');
document.body.insertBefore(p , ul);
نکته : بر خلاف تصور احتمالی شما، متدی به نام insertAfter برای درج یک گره، بعد از گرهای دیگر وجود ندارد.