در این آموزش، ما قصد داریم از وانیلی جاوا اسکریپت برای پیاده سازی یک ویژگی چند فیلتری استفاده کنیم، که به کاربر اجازه می دهد چندین نوع فیلتر مختلف را انتخاب و ترکیب کند.

فیلتر کردن یک ویژگی متداول در اکثر وب سایت های کاربر محور مانند سایت های تجارت الکترونیک یا صفحات وبلاگ است. این به کاربر اجازه می دهد با استفاده از شرایط خاص، محتوا را محدود کند.

در آموزش قبلی به نحوه فیلتر کردن داده ها از یک لیست با استفاده از یک پارامتر واحد پرداختیم.

این روش به عنوان مقدمه خوبی در مورد نحوه فیلتر کردن داده ها در یک لیست عمل کرد، اما اکثر موارد واقعی نیاز به روش پیچیده تری برای فیلتر کردن دارند.

از وب سایتی استفاده کنید که لباس می فروشد. کاربران به توانایی فیلتر بر اساس رنگ، اندازه، نوع محصول و غیره نیاز دارند. همچنین ممکن است بخواهید فیلترهای مختلفی را در یک دسته انتخاب کنید. به عنوان مثال، در نسخه ی نمایشی بالا که یک صفحه مقاله را دوباره ایجاد می کند، ممکن است کاربر بخواهد همه مقالات برچسب گذاری شده با “جاوا اسکریپت” و “CSS” را در مقابل فقط یک برچسب ببیند.

1. چیدمان و استایل

ما از همان طرح‌بندی و استایل آموزشی قبلی استفاده می‌کنیم و HTML خود را به‌روزرسانی می‌کنیم تا شامل یک دسته بندی ها و مرحله ظرف ما همچنین یک عنصر تعداد مقاله بعد از عنوان برای مقالات خود و یک محفظه برای نمایش پیام “نتیجه ای یافت نشد” اضافه می کنیم. این HTML به روز شده ما است.

1
<main class="container"https://webdesign.tutsplus.com/tutorials/>
2
  <div class="container__title"https://webdesign.tutsplus.com/tutorials/>Categories</div>
3
  <div id="post-categories" class="filter-container"https://webdesign.tutsplus.com/tutorials/></div>
4
  <div class="container__title" >Level</div>
5
  <div id="post-level" class="filter-container"https://webdesign.tutsplus.com/tutorials/></div>
6
  <div class="container__title"https://webdesign.tutsplus.com/tutorials/>
7
    Tutorials (<span id="post-count"https://webdesign.tutsplus.com/tutorials/></span>)
8
  </div>
9
  <div id="posts-container"https://webdesign.tutsplus.com/tutorials/></div>
10
  <p id="no-posts"https://webdesign.tutsplus.com/tutorials/></p>
11
</main>

2. نمایش داده ها با جاوا اسکریپت

در یک آموزش قبلی، ما در مورد خراش دادن داده ها از صفحه نویسنده Tuts+ برای ایجاد یک شی داده ساختگی برای نسخه نمایشی خود بحث کردیم. ما از واکشی API برای بازیابی داده های خراشیده شده ذخیره شده در a خلاصه Github.

این چیزی است که شی داده ما به نظر می رسد:

1
[
2
  {
3
    "https://webdesign.tutsplus.com/tutorials/title"https://webdesign.tutsplus.com/tutorials/: ""https://webdesign.tutsplus.com/tutorials/,
4
    "https://webdesign.tutsplus.com/tutorials/link"https://webdesign.tutsplus.com/tutorials/: ""https://webdesign.tutsplus.com/tutorials/,
5
    "https://webdesign.tutsplus.com/tutorials/image"https://webdesign.tutsplus.com/tutorials/: ""https://webdesign.tutsplus.com/tutorials/,
6
    "https://webdesign.tutsplus.com/tutorials/categories"https://webdesign.tutsplus.com/tutorials/: [""https://webdesign.tutsplus.com/tutorials/
7
    "https://webdesign.tutsplus.com/tutorials/level"https://webdesign.tutsplus.com/tutorials/: ""
8
  },
9
  ...
10
 ]

این اسکریپت برای واکشی داده ها از اسکریپت است:

1
fetch("https://webdesign.tutsplus.com/tutorials/https://gist.githubusercontent.com/jemimaabu/564beec0a30dbd7d63a90a153d2bc80b/raw/12741cce73c71c179381cc9e3b5f79988ea76a45/tutorial-levels"
2
).then(async (response) => {
3
  // handle response data
4
});

هنگامی که داده‌های واکشی شده خود را دریافت کردیم، می‌توانیم داده‌ها را دستکاری کرده و در صفحه اضافه کنیم.

3. افزودن داده ها به صفحه وب

برای هر شی در پاسخ واکشی شده، یک post div ایجاد می کنیم که داده ها را در صفحه نمایش می دهد. ابتدا بیایید متغیرهای جهانی خود را تعریف کنیم.

ما یک را ایجاد خواهیم کرد postsData متغیر برای ذخیره داده های واکشی شده ما.

1
let postsData = ""https://webdesign.tutsplus.com/tutorials/;

از آنجایی که این آموزش از چندین فیلتر استفاده می کند، ما نیز به یک نیاز داریم currentFilters متغیر برای ذخیره فیلترهایی که در حال حاضر انتخاب شده اند:

1
let currentFilters = {
2
  categories: [],
3
  level: []
4
};

اکنون متغیرهایی را برای عناصر موجود خود ایجاد می کنیم:

1
const postsContainer = document.querySelector("https://webdesign.tutsplus.com/tutorials/#posts-container"https://webdesign.tutsplus.com/tutorials/);
2
const categoriesContainer = document.querySelector("https://webdesign.tutsplus.com/tutorials/#post-categories"https://webdesign.tutsplus.com/tutorials/);
3
const levelsContainer = document.querySelector("https://webdesign.tutsplus.com/tutorials/#post-levels"https://webdesign.tutsplus.com/tutorials/);
4
const postCount = document.querySelector("https://webdesign.tutsplus.com/tutorials/#post-count"https://webdesign.tutsplus.com/tutorials/);
5
const noResults = document.querySelector("https://webdesign.tutsplus.com/tutorials/#no-results"https://webdesign.tutsplus.com/tutorials/);

سپس یک تابع ایجاد می کنیم createPost() که با اضافه کردن یک div جدید به postsContainer عنصر در این تابع، یک عنصر div جدید با نام کلاس “post” ایجاد می کنیم و innerHTML را به عنوان داده ای که می خواهیم نمایش دهیم تنظیم می کنیم.

1
const createPost = (postData) => {
2
  const { title, link, image, categories, level } = postData;
3
  const post = document.createElement("https://webdesign.tutsplus.com/tutorials/div"https://webdesign.tutsplus.com/tutorials/);
4
  post.className = "https://webdesign.tutsplus.com/tutorials/post"https://webdesign.tutsplus.com/tutorials/;
5
  post.innerHTML = `
6
      <a class="post-preview" href="https://webdesign.tutsplus.com/tutorials/${link}" target="_blank">
7
        <img class="post-image" src="https://webdesign.tutsplus.com/tutorials/${image}">
8
      </a>
9
      <div class="post-content">
10
        <p class="post-title">${title}</p>
11
        <div class="post-tags">
12
          ${categories
13
            .map((category) => {
14
              return "https://webdesign.tutsplus.com/tutorials/<span class="post-tag">' + category + "https://webdesign.tutsplus.com/tutorials/</span>"https://webdesign.tutsplus.com/tutorials/;
15
            })
16
            .join(""https://webdesign.tutsplus.com/tutorials/)}
17
        </div>
18
        <div class="post-footer">
19
          <span class="post-level">${level}</span>
20
        </div>
21
      </div>
22
  `;
23
24
  postsContainer.append(post);
25
};

در داخل پست innerHTML ما از عبارت استفاده می کنیم join("") روش بر روی ما categories.map() برای حذف نماد ‘،’ که در هر آرایه گنجانده شده است.

اکنون می توانیم تابع پاسخ خود را برای فراخوانی به روز کنیم createPost() زمانی که داده ها واکشی شدند، عمل کنید و ما را نیز به روز کنید postCount عنصر:

1
fetch("https://webdesign.tutsplus.com/tutorials/https://gist.githubusercontent.com/jemimaabu/b89339c1b7e5f81f8737fb66a858b6fc/raw/cdded4a10dbc98858481b5aedbcce3f3026dc271/tutorials"
2
).then(async (response) => {
3
  postsData = await response.json();
4
  postsData.map((post) => createPost(post));
5
  postCount.innerText = postsData.length;
6
});

4. پارامترهای فیلتر را از Response دریافت کنید

از آنجایی که ما از جاوا اسکریپت استفاده می کنیم، می توانیم از طریق پاسخ خود نقشه برداری کنیم تا یک لیست پویا از پارامترهای فیلتر ایجاد کنیم.

ما یک اسکریپت خواهیم نوشت که از طریق آن مرتب می شود categories و level در هر شی پاسخ کلید می خورد و یک لیست منحصر به فرد را برمی گرداند. می‌توانیم شی پاسخ خود را به‌روزرسانی کنیم تا بتوانیم فهرستی منحصر به فرد از پارامترهای فیلتر را دریافت کنیم.

یک نکته قابل توجه این است که categories و level کلیدها از انواع داده‌های متفاوتی هستند، بنابراین باید به گونه‌ای متفاوت با آنها کار کرد. دسته ها یک آرایه و سطح یک رشته است.

1
fetch(
2
  "https://webdesign.tutsplus.com/tutorials/https://gist.githubusercontent.com/jemimaabu/564beec0a30dbd7d63a90a153d2bc80b/raw/12741cce73c71c179381cc9e3b5f79988ea76a45/tutorial-levels"
3
).then(async (response) => {
4
  postsData = await response.json();
5
  postsData.map((post) => createPost(post));
6
  postCount.innerText = postsData.length;
7
8
  دسته بندی داده ها = [
9
    ...new Set(
10
      postsData
11
        .map((post) => post.categories)
12
        .reduce((acc, curVal) => acc.concat(curVal), [])
13
    )
14
  ];
15
16
  levelData = [...new Set(postsData.map((post) => post.level))];
17
});

شکستن کد دسته بندی داده های ما:

  • ما استفاده می کنیم [... new Set] برای ایجاد یک آرایه از مقادیر منحصر به فرد. تنظیم یک شی از مقادیر منحصر به فرد و نحو گسترش را برمی گرداند […] شی را به آرایه تبدیل می کند.
  • ما از طریق postsData نگاشت می کنیم تا آرایه دسته بندی هر شیء پست را در داخل پاسخ داده بدست آوریم.
  • ما استفاده می کنیم .reduce() روشی برای ترکیب آرایه های دسته بندی برای هر شیء پست در یک آرایه.

از آنجایی که سطح یک رشته است، نیازی نیست آن را به یک آرایه کاهش دهیم تا بتوانیم یک آرایه جدید با استفاده از Set شی و map روش.

هنگامی که مجموعه مقادیر فیلتر منحصر به فرد خود را از نتایج پست دریافت کردیم، می‌توانیم یک تابع برای اضافه کردن هر فیلتر به صفحه ایجاد کنیم.

ما یک عنصر دکمه جدید ایجاد می کنیم و innerText را با توجه به مقدار فیلتر تنظیم می کنیم. ما همچنین باید نوع فیلتر آن را در نظر بگیریم (فیلتر دسته یا فیلتر سطح).

هر دکمه فیلتر دارای یک شنونده رویداد کلیکی است که روی آن تنظیم شده است handleButtonClick تابعی که مسئول مدیریت منطق فیلترینگ خواهد بود. ما همچنین یک ویژگی “وضعیت داده” را برای تغییر وضعیت دکمه هنگام کلیک کردن تنظیم می کنیم.

1
const createFilter = (key, param, container) => {
2
  const filterButton = document.createElement("https://webdesign.tutsplus.com/tutorials/button"https://webdesign.tutsplus.com/tutorials/);
3
  filterButton.className = "https://webdesign.tutsplus.com/tutorials/filter-button"https://webdesign.tutsplus.com/tutorials/;
4
  filterButton.innerText = param;
5
  filterButton.setAttribute("https://webdesign.tutsplus.com/tutorials/data-state"https://webdesign.tutsplus.com/tutorials/, "https://webdesign.tutsplus.com/tutorials/inactive"https://webdesign.tutsplus.com/tutorials/);
6
  filterButton.addEventListener("https://webdesign.tutsplus.com/tutorials/click"https://webdesign.tutsplus.com/tutorials/, (e) =>
7
    handleButtonClick(e, key, param, container)
8
  );
9
10
  container.append(filterButton);
11
};

createFilter پارامترهای زیر را می گیرد و به آن پاس می دهد handleButtonClick عملکرد:

  • key: کلید فیلتر شی پاسخ (دسته یا سطح)
  • param: مقدار متناظر کلید (به عنوان مثال پست[0].level=”مبتدی”)
  • container: عنصر ظرفی که فیلتر به آن اضافه می شود.

تابع پاسخ اکنون می تواند برای فراخوانی به روز شود createFilter() عملکرد:

1
fetch(
2
  "https://webdesign.tutsplus.com/tutorials/https://gist.githubusercontent.com/jemimaabu/564beec0a30dbd7d63a90a153d2bc80b/raw/12741cce73c71c179381cc9e3b5f79988ea76a45/tutorial-levels"
3
).then(async (response) => {
4
  postsData = await response.json();
5
  postsData.map((post) => createPost(post));
6
  postCount.innerText = postsData.length;
7
8
  دسته بندی داده ها = [
9
    ...new Set(
10
      postsData
11
        .map((post) => post.categories)
12
        .reduce((acc, curVal) => acc.concat(curVal), [])
13
    )
14
  ];
15
  categoriesData.map((category) =>
16
    createFilter("https://webdesign.tutsplus.com/tutorials/categories"https://webdesign.tutsplus.com/tutorials/, category, categoriesContainer)
17
  );
18
19
  levelData = [...new Set(postsData.map((post) => post.level))];
20
  levelData.map((level) => createFilter("https://webdesign.tutsplus.com/tutorials/level"https://webdesign.tutsplus.com/tutorials/, level, levelsContainer));
21
});

5. دکمه Handle کلیک کنید

اکنون دکمه‌های فیلتر و داده‌های اولیه را دریافت کرده‌ایم، می‌توانیم عملکردی را برای فیلتر کردن داده‌ها با کلیک روی یک دکمه تعریف کنیم. این جایی است که بخش عمده فیلترینگ ما به کار گرفته می شود.

ما می خواهیم تشخیص دهیم که چه زمانی روی یک دکمه کلیک شده است و وضعیت دکمه را به روز کنیم. در این آموزش، دکمه‌ها را تغییر می‌دهیم تا اگر یک بار کلیک کنید، روی دکمه تنظیم می‌شود فعال و اگر دوباره کلیک کنید، دکمه روی تنظیم می شود غیر فعال. بیایید نحوه برخورد با کلیک دکمه را بررسی کنیم:

  1. اگر یک دکمه غیرفعال کلیک شود، پارامتر فعلی را به کلید مناسب در خود اضافه می کنیم currentFilters هدف – شی. به عنوان مثال، اگر دکمه فیلتر انیمیشن در زیر Categories کلیک شده است، شی فعلی ما فیلترها باید شبیه باشد { categories: ['Animation'], level: [] }; ما همچنین سبک دکمه را به حالت فعال آن به روز خواهیم کرد.
  2. اگر دکمه از قبل فعال باشد، آن را روی غیرفعال تنظیم می کنیم و پارامتر را از کلید مربوطه حذف می کنیم currentFilters. ما می توانیم این کار را با فیلتر کردن کلید currentFilters انجام دهیم.
  3. وقتی وضعیت دکمه را به‌روزرسانی کردیم، پست‌ها را بر اساس مقادیر موجود در آن فیلتر می‌کنیم currentFilters هدف – شی.

اکنون می توانیم خودمان را تعریف کنیم handleButtonClick عملکرد:

1
const handleButtonClick = (e, key, param, container) => {
2
  const button = e.target;
3
  const buttonState = button.getAttribute("https://webdesign.tutsplus.com/tutorials/data-state"https://webdesign.tutsplus.com/tutorials/);
4
  if (buttonState == "https://webdesign.tutsplus.com/tutorials/inactive"https://webdesign.tutsplus.com/tutorials/) {
5
    button.classList.add("https://webdesign.tutsplus.com/tutorials/is-active"https://webdesign.tutsplus.com/tutorials/);
6
    button.setAttribute("https://webdesign.tutsplus.com/tutorials/data-state"https://webdesign.tutsplus.com/tutorials/, "https://webdesign.tutsplus.com/tutorials/active"https://webdesign.tutsplus.com/tutorials/);
7
    currentFilters[key].push(param);
8
    handleFilterPosts(currentFilters);
9
  } else {
10
    button.classList.remove("https://webdesign.tutsplus.com/tutorials/is-active"https://webdesign.tutsplus.com/tutorials/);
11
    button.setAttribute("https://webdesign.tutsplus.com/tutorials/data-state"https://webdesign.tutsplus.com/tutorials/, "https://webdesign.tutsplus.com/tutorials/inactive"https://webdesign.tutsplus.com/tutorials/);
12
    currentFilters[key] = currentFilters[key].filter((item) => item !== param);
13
    handleFilterPosts(currentFilters);
14
  }
15
};

بیایید نگاهی به نحوه به روز رسانی فعلی فیلترها بر اساس وضعیت دکمه بیندازیم:

6. تعریف چند فیلتر

یکی از مواردی که هنگام دستکاری داده ها باید در نظر داشته باشید، اجتناب از آن است جهش. ما می خواهیم اطمینان حاصل کنیم که آرایه اصلی ما هنگام فیلتر کردن تغییر نمی کند.

ابتدا یک نمونه جدید از متغیر postsData خود به نام ایجاد می کنیم filteredPosts برای جلوگیری از جهش آرایه اصلی

1
let filteredPosts = [...postsData];

حالا می توانیم روی منطق فیلترینگ کار کنیم. به خاطر داشته باشید که postsData ما دو نوع داده مختلف را برای فیلترها برمی گرداند: دسته ها یک آرایه و سطح یک رشته است.

ما می خواهیم مطمئن شویم که جزء فیلتر ما برای این دو نوع داده مختلف کار می کند.

برای آرایه دسته ها در currentFilter، ابتدا باید بررسی کنیم که آیا آرایه خالی نیست با استفاده از filters.categories.length > 0

اکنون دسته‌های پست را بر اساس دسته‌های فعلی فیلترها فیلتر می‌کنیم. ما می خواهیم هر پستی را که در آن هر یک از مقادیر موجود در پست باشد، برگردانیم[categories] در CurrentFilters نیز وجود دارند[categories].

ما می توانیم این کار را با استفاده از .filter() و .some روش.

راsome()متد آزمایش می کند که آیا حداقل یک عنصر در آرایه از آزمون پیاده سازی شده توسط تابع ارائه شده عبور می کند یا خیر. – MDN

ما آرایه پست را فیلتر می کنیم تا هر پستی را که برخی از مقادیر در آن وجود دارد، برگرداند post.categories در ما گنجانده شده اند currentFilters.categories. در اینجا کد به نظر می رسد:

1
 if (filters.categories.length > 0) {
2
    filteredPosts = filteredPosts.filter((post) =>
3
      post.categories.some((category) => {
4
        return filters.categories.includes(category);
5
      })
6
    );
7
  }

سپس فیلترedPosts را نیز به فیلتر سطح خود منتقل می کنیم. اینجاست که منطق چند فیلتر وارد می شود زیرا ما فقط منطق فیلتر را روی مقدار فیلتر شده قبلی انجام می دهیم.

هنگام فیلتر کردن با یک رشته، فقط باید بررسی کنیم که آیا post.level ارزش در ما گنجانده شده است currentFilters.level آرایه

1
  if (filters.level.length > 0) {
2
    filteredPosts = filteredPosts.filter((post) =>
3
      filters.level.includes(post.level)
4
    );
5
  }

برای پیاده‌سازی فیلتر پیشرفته‌تر، می‌توانیم توابع فیلتر دسته‌بندی و فیلتر سطح را در یکی فشرده کنیم.

1
  let filterKeys = Object.keys(filters);
2
3
  filterKeys.forEach((key) => {
4
    let currentKey = filters[key]
5
    if (currentKey.length <= 0) {
6
      return;
7
    }
8
9
    filteredPosts = filteredPosts.filter((post) => {
10
      let currentValue = post[key]
11
      return Array.isArray(currentValue)
12
        ? currentValue.some((val) => currentKey.includes(val))
13
        : currentKey.includes(currentValue);
14
    });
15
  });

در تابع بالا، ما:

  • مقادیر کلیدی را در ما دریافت کنید currentFilters هدف – شی. این یک آرایه را برمی گرداند ["categories", "level"]
  • نقشه از طریق ما filterKeys آرایه و انجام یک بازگشت زودهنگام اگر currentKey یک آرایه خالی است
  • عملکرد فیلتر را روی ما انجام دهید filteredPosts آرایه بسته به اینکه آیا مقدار فیلتر فعلی یک آرایه است یا نه.

در نهایت می توانیم منطق چند فیلتری خود را تعریف کنیم:

  • یک تابع ایجاد کنید handleFilterPosts() که یک پارامتر فیلتر را می پذیرد
  • تنظیم filteredPosts با تمام مقادیر موجود در آرایه فعلی فیلترهای ما.
  • ما نیز خود را به روز خواهیم کرد postCount و noResults عناصر بر اساس داده های موجود در filteredPosts
  • در نهایت، همه عناصر موجود در posts-container را پاک می‌کنیم و filteredData جدید را به ظرف اضافه می‌کنیم.
1
const handleFilterPosts = (filters) => {
2
  let filteredPosts = [...postsData];
3
  let filterKeys = Object.keys(filters);
4
5
  filterKeys.forEach((key) => {
6
    let currentKey = filters[key]
7
    if (currentKey.length <= 0) {
8
      return;
9
    }
10
11
    filteredPosts = filteredPosts.filter((post) => {
12
      let currentValue = post[key]
13
      return Array.isArray(currentValue)
14
        ? currentValue.some((val) => currentKey.includes(val))
15
        : currentKey.includes(currentValue);
16
    });
17
  });
18
19
  postCount.innerText = filteredPosts.length;
20
21
  if (filteredPosts.length == 0) {
22
    noResults.innerText = "https://webdesign.tutsplus.com/tutorials/Sorry, we couldn't find any results."https://webdesign.tutsplus.com/tutorials/;
23
  } else {
24
    noResults.innerText = ""https://webdesign.tutsplus.com/tutorials/;
25
  }
26
27
  postsContainer.innerHTML = ""https://webdesign.tutsplus.com/tutorials/;
28
  filteredPosts.map((post) => createPost(post));
29
};

نتیجه

و با این کار، پیاده سازی چند فیلتری خود را راه اندازی کرده ایم!