پروژه ی ایجاد To-Do List: حذف task ها

06 آبان 1398
پروژه ی ایجاد To-Do List: حذف task ها

مروری بر Event Delegation

در قسمت قبل کاری کردیم که با کلیک روی Add Task یک task جدید در لیست ما نمایش داده شود و در این قسمت می خواهیم کدها را تکمیل کنیم تا با کلیک روی علامت های ضربدر، task مورد نظر حذف شود:

نمونه ای از Task های ایجاد شده
نمونه ای از Task های ایجاد شده

همانطور که در مثال بالا می بینید، ما دو task داریم (بیشتر از 1 عدد) و از طرفی قرار است آن ها را به صورت پویا حذف یا اضافه کنیم به همین خاطر باید از Event Delegation استفاده کنیم. بنابراین بهتر است ابتدا Event Delegation را برایتان توضیح دهم. فرض کنید کد HTML ما بدین شکل باشد:

<ul id="parent-list">
	<li id="post-1">Item 1</li>
	<li id="post-2">Item 2</li>
	<li id="post-3">Item 3</li>
	<li id="post-4">Item 4</li>
	<li id="post-5">Item 5</li>
	<li id="post-6">Item 6</li>
</ul>

حالا فرض کنید که قرار است با کلیک روی هر کدام از این li ها اتفاق خاصی بیفتد (مهم نیست چه اتفاقی). یکی از روش های انجام این کار اضافه کردن event-listener به تک تک li ها می باشد اما اگر بعدا یک li به این لیست اضافه کنیم، چطور؟ آن گاه باید برگردیم و سورس کد را تغییر دهیم تا برای li جدید نیز یک event-listener داشته باشیم! اگر بخواهیم این کار را در جای جای برنامه مان انجام بدهیم، کار عملا غیرممکنی می شود چرا که باید هر روز سورس کد برنامه را تغییر دهیم! روش بهتر این است که event-listener را به عنصر پدر (یعنی UL ) بدهیم.

سوال: جاوا اسکریپت چطور می فهمد روی کدام عنصر کلیک شده است؟

پاسخ: این کار بر عهده ی شما است. شما با استفاده از target عنصر می توانید آن را تشخیص دهید. به طور مثال اگر برای کد HTML بالا بخواهیم چیزی بنویسیم، می گوییم:

// Get the element, add a click listener...
document.getElementById("parent-list").addEventListener("click", function(e) {
	// e.target is the clicked element!
	// If it was a list item
	if(e.target && e.target.nodeName == "LI") {
		// List item found!  Output the ID!
		console.log("List item ", e.target.id.replace("post-", ""), " was clicked!");
	}
});

یعنی ابتدا یک event-listener به عنصر UL داده ایم، سپس چک کرده ایم که روی عنصر کلیک شده باشد و عنصر کلیک شده یک LI باشد (چرا که ممکن است روی خود UL یا عنصر دیگری کلیک شود). در صورتی که شرط ها بر قرار بود کار خاصی را انجام می دهیم (مثلا در این قسمت متنی را console.log کرده ایم.

به این مفهوم Event Delegation می گویند.

حذف Task ها

در پروژه ی خودمان نیز باید از event delegation استفاده کنیم چرا که قرار است Task های جدیدی به برنامه اضافه شوند. بنابراین به درون تابع loadEventListeners میرویم و این event جدید را به UL اضافه می کنیم:

function loadEventListeners() {
    // Add task event
    form.addEventListener('submit', addTask);

    // Remove task event
    taskList.addEventListener('click', removeTask);
}

این event قرار است تابع removeTask را اجرا کند که باید آن را کدنویسی کنیم. به پایین تر از تابع addTask میرویم و این تابع جدید را تعریف می کنیم:

function removeTask(e) {
    console.log(e.target);
}

اگر الان روی علامت ضربدر کلیک کنیم در قسمت console مرورگر چه چیزی به ما برگردانده می شود؟ بله تگ i که برای آیکون ما بود برگردانده می شود:

تگ <i> برگردانده شده در console مرورگر
تگ برگردانده شده در console مرورگر

در حالی که ما تگ a را می خواهیم که پدر تگ i است. حتما کدهای HTML پروژه را به یاد دارید:

تگ <a> پدر تگ <i> می باشد.
تگ پدر تگ می باشد.

بنابراین باید شرطی را تعیین کنیم که اگر عنصر کلیک شده پدری با کلاس delete-item داشت آن گاه اتفاق خاصی بیفتد:

// Remove Task
function removeTask(e) {
    if (e.target.parentElement.classList.contains('delete-item')) {
        console.log(e.target);
    }
}

البته اینجا فعلا console.log کرده ایم اما باید کاری کنیم که با کلیک روی علامت آیکون (تگ <i>) تمام تگ <li> حذف شود که یعنی پدرِ پدرِ تگ <i> حذف شود (پدر اول تگ <i>، تگ <a> و پدر دوم آن خود <li> است). بنابراین به جای console.log کردن، باید بگوییم:

function removeTask(e) {
    if (e.target.parentElement.classList.contains('delete-item')) {
        e.target.parentElement.parentElement.remove();
    }
}

حالا اگر کد را در مرورگر امتحان کنید، بدون هیچ مشکلی اجرا خواهد شد اما بهتر است کد را درون یک confirmation بگذاریم تا کاربر به اشتباه چیزی را حذف نکند:

// Remove Task
function removeTask(e) {
    if (e.target.parentElement.classList.contains('delete-item')) {
        if (confirm('Are You Sure?')) {
            e.target.parentElement.parentElement.remove();
        }
    }
}

حالا نوبت به کدنویسی دکمه ی Clear Tasks میرسد. کار این دکمه این است که تمام Task ها را یکجا حذف کند. برای این دکمه نیز یک event-listener در تابع loadEventListeners اضافه می کنیم:

// Load all event listeners
function loadEventListeners() {
    // Add task event
    form.addEventListener('submit', addTask);

    // Remove task event
    taskList.addEventListener('click', removeTask);

    // Clear task event
    clearBtn.addEventListener('click', clearTasks);
}

قرار است این event یک تابع به نام clearTasks را ایجاد کند که باید آن را نیز کدنویسی کنیم. شما می توانید این تابع را با روش های مختلفی اجرا کنید؛ در روش اول از innerHTML استفاده می کنیم:

// Clear Tasks
function clearTasks() {
    taskList.innerHTML = '';
}

اگر دقت کنید <li> ها innerHTML عنصر TaskList حساب می شوند بنابراین با خالی کردن innerHTML آن میتوانیم همه ی آن را حذف کنیم.  در روش دوم از یک حلقه ی while استفاده می کنیم و می گوییم تا زمانی که taskList دارای فرزند است (تگ های <li> آن فرزند را حذف کن. من روش اول را کامنت کرده و روش دوم را اضافه می کنم:

// Clear Tasks
function clearTasks() {
    // taskList.innerHTML = '';
  
    // Faster
    while(taskList.firstChild) {
      taskList.removeChild(taskList.firstChild);
    }
  
  }

بنابر برخی تست های انجام شده ظاهرا روش دوم سریع تر از روش اول است اما در عمل آنچنان تفاوتی ندارند. با این حال من برایتان کامنت کرده ام که روش دوم Faster (سریع تر) است.

مقایسه ی سرعت دو دستور firstChild و innerHTML (هر چه سهم بیشتری در نمودار داشته باشند، بهتر و سریع تر است)
مقایسه ی سرعت دو دستور firstChild و innerHTML (هر چه سهم بیشتری در نمودار داشته باشند، بهتر و سریع تر است)

برای اطلاعات بیشتر به وبسایت https://jsperf.com/innerhtml-vs-removechild/47 مراجعه کنید. در قسمت بعدی می خواهیم کاری کنیم که بتوانیم task ها را فیلتر کرده یا به عبارتی دیگر بین آن ها جست و جو انجام دهیم.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری پروژه‌های مدرن جاوا اسکریپت توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما (1 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

علیرضا ترابی
01 مهر 1399
خیلی عالیه این پروژه . هر چی پیش میرم بیش تر لذت میبرم و کاربردی با کد ها اشنا میشم

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.