پروژه ی ایجاد To-Do List: جستجوی task ها و ذخیره در حافظه ی محلی

15 آبان 1398
پروژه ی ایجاد To-Do List: جست و جوی task ها و ذخیره در حافظه ی محلی

فیلترکردن (جستجوی) task ها

تا این قسمت توانسته ایم هر task دلخواهی را به لیست task ها اضافه کنیم و در صورت نیاز نیز آن را حذف کنیم اما یکی از فیلدهای برنامه (Filter Tasks) هنوز بی استفاده بوده و کدی برایش نوشته نشده است. کار این فیلد جستجو بین task های ثبت شده است. بنابراین باید یک event-listener نیز به آن اضافه کنیم:

// 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);

    // Filter tasks event
    filter.addEventListener('keyup', filterTasks);
}

این event از نوع keyup است؛ یعنی با هر بار برداشتن دست از روی کلیدهای کیبورد اجرا می شود. ممکن است از خودتان بپرسید چرا چنین event ای را انتخاب کرده ایم. باید بگویم دلیل آن بسیار ساده است، ما می خواهیم جستجوی خود را با هر کلمه ای که در فیلد فیلتر تایپ می شود، ارتقاء ببخشیم و تابع آن را دوباره اجرا کنیم. به طور مثال اگر از click استفاده کنیم اصلا کار ما معنا نخواهد داشت. از آنجایی که این event یک تابع به نام filterTasks را اجرا می کند باید کدهای آن را هم بنویسیم. در ضمن توجه داشته باشید که شما می توانید تمام این نام ها را تغییر دهید، چرا که کاملا بستگی به سلیقه ی شما دارند و در روند اجرایی برنامه هیچ تاثیری نخواهند داشت.

برای تعریف تابع filterTasks اولین قدم، دریافت مقدار تایپ شده توسط کاربر است:

// Filter Tasks
function filterTasks(e) {
    const text = e.target.value.toLowerCase();

}

برای آنکه جستجوی ما دقیق باشد، از تابع toLowerCase استفاده کرده ام که مخصوص خود زبان جاوا اسکریپت است و حروف یک رشته را به حروف کوچک انگلیسی تبدیل می کند. اگر تمام حروف کوچک باشند دیگر به مشکل حروف کوچک و بزرگ و جستجوی case-sensitive یا case-insensitive برنخواهیم خورد. حالا باید تمام آیتم های لیست مان (تگ های <li>) را بگیریم و روی آن ها یک حلقه را برای جستجو اجرا کنیم:

function filterTasks(e) {
    const text = e.target.value.toLowerCase();

    document.querySelectorAll('.collection-item').forEach();
}

مشخصا برای دریافت <li> ها از کلاسشان (یعنی collection-item) استفاده کرده ایم. همچنین توجه داشته باشید که دستور querySelector با querySelectorAll تفاوتی اساسی دارد؛ querySelector تنها نتیجه ی اول را برمی گرداند (چه یک نتیجه داشته باشیم چه هزار نتیجه) اما querySelectorAll تمام نتایج ممکن را برمی گرداند (چه یکی باشد چه هزار تا) و به همین دلیل است که می توانیم از حلقه ی forEach روی آن استفاده کنیم. در واقع querySelectorAll یک node list برمی گرداند در حالی که دستورات دیگر مشابه آن یک HTML collection برمی گردانند که نمی توان روی آن ها از forEach استفاده کرد مگر آنکه ابتدا تبدیل به آرایه شوند.

حالا می توانیم فرآیند جستجو را تکمیل کنیم:

function filterTasks(e) {
    const text = e.target.value.toLowerCase();

    document.querySelectorAll('.collection-item').forEach(function (task) {
        const item = task.firstChild.textContent;

        if (item.toLowerCase().indexOf(text) != -1) {
            task.style.display = 'block';
        } else {
            task.style.display = 'none';
        }
    });
}

در این کد به حلقه ی forEach یک تابع پاس داده ایم؛ این تابع یک پارامتر به نام task می گیرد که در واقع نماینده ی هر یک از <li> های ما است. سپس اولین فرزند (firstChild) هر کدام از این <li> ها را دریافت کرده ایم و آن را درون متغیر item گذاشته ایم. توجه کنید که firstChild هر تگ <li> برابر با متن داخلش است:

firstChild تگ های li برابر متن داخلشان هستند
firstChild تگ های li برابر متن داخلشان هستند

سپس در یک شرط if گفته ایم تابع indexOf را روی مقدار item با حروف کوچک صدا بزن و به دنبال Text باش (همان مقدار تایپ شده توسط کاربر). اگر تابع indexOf چیزی پیدا نکند مقدار 1- را برمی گرداند. بنابراین ما شرط را برعکس کرده ایم تا اگر چیزی پیدا کرد، آن <li> دارای display = block شده و در غیر این صورت display = none بگیرد.

ذخیره ی task ها در حافظه ی محلی

در حال حاضر اگر صفحه را refresh کنیم تمام task ها از بین می روند و برنامه از نو شروع به کار می کند. وقتی می گویم حافظه ی محلی (Local Storage) باید بدانیم که خودش جز جاوا اسکریپت است و نیازی به استفاده از هیچ پکیجی نیست. برای شروع باید وارد تابع addTask شده و پس از اضافه کردن task به DOM (خط بعد از taskList.appendChild(li)) تابع خود را اجرا می کنیم. من نام storeTaskInLocalStorage را برایش انتخاب کرده ام. پس از اضافه کردن دستور اجرای این تابع، به تابع addTask چنین کدی را خواهیم داشت:

// Add Task
function addTask(e) {
    if (taskInput.value === '') {
        alert('Add a task');
    }

    // Create li element
    const li = document.createElement('li');
    // Add class
    li.className = 'collection-item';
    // Create text node and append to li
    li.appendChild(document.createTextNode(taskInput.value));
    // Create new link element
    const link = document.createElement('a');
    // Add class
    link.className = 'delete-item secondary-content';
    // Add icon html
    link.innerHTML = '<i class="fa fa-remove"></i>';
    // Append the link to li
    li.appendChild(link);

    // Append li to ul
    taskList.appendChild(li);

    // Store in LS
    storeTaskInLocalStorage(taskInput.value);

    // Clear input
    taskInput.value = '';

    e.preventDefault();
}

یعنی هر چیزی که کاربر در فیلد New Task تایپ کند در حافظه ذخیره خواهد شد. البته هنوز کد تابع storeTaskInLocalStorage را ننوشته ایم بنابراین خارج از تابع addTask می گوییم:

// Store Task
function storeTaskInLocalStorage(task) {
    let tasks;

    if (localStorage.getItem('tasks') === null) {
        tasks = [];
    } else {
        tasks = JSON.parse(localStorage.getItem('tasks'));
    }
}

در ابتدا یک متغیر به نام tasks را تعریف کرده ایم اما به آن هیچ مقداری نداده ایم. سپس در یک شرط if گفته ایم اگر آیتمی به نام tasks درون localStorage وجود نداشت (برابر با null بود)، مقدار tasks را یک آرایه ی خالی قرار بده، در غیر این صورت tasks را برابر هر مقداری قرار بده که در localStorage باشد. مشکل آنجاست که localStorage تنها رشته ها را قبول می کند بنابراین باید آن را به JSON تبدیل کنیم تا مشکلی پیش نیاید. حالا کد را تکمیل می کنیم:

// Store Task
function storeTaskInLocalStorage(task) {
    let tasks;

    if (localStorage.getItem('tasks') === null) {
        tasks = [];
    } else {
        tasks = JSON.parse(localStorage.getItem('tasks'));
    }

    tasks.push(task);

    localStorage.setItem('tasks', JSON.stringify(tasks));
}

یعنی task جدید اضافه شده توسط کاربر را نیز به همین آرایه ی tasks اضافه کن و سپس آن را (متغیر tasks را که task جدید را نیز گرفته است) در localStorage ثبت کن. مقدار key را همان tasks گذاشته ایم (همان مقداری که در شرط if چک کرده ایم). همانطور که گفتم localStorage تنها رشته ها را قبول می کند. بنابراین با استفاده از تابع JSON.stringify مقدار task جدید را به صورت یک رشته به آن ارسال کرده ایم.

اگر الان به مرورگر برویم و مقداری را به عنوان یک Task نوشته و روی دکمه ی Add Task کلیک کنیم، مثل همیشه مانند لیستی اضافه خواهد شد اما با refresh کردن مرورگر باز هم از بین می‌رود. اگر به قسمت Application از dev tools مرورگر بروید و به local storage نگاه کنید، چنین چیزی را می بینید:

task ثبت شده توسط من درون local storage ذخیره شده است.
task ثبت شده توسط من درون local storage ذخیره شده است.

بله، مقدار تایپ شده ی من درون local storage است. در قسمت بعدی می خواهیم این مقادیر را از local storage گرفته و درون برنامه به کاربر نمایش دهیم.

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

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