MATIN MOLKARA
SERIES: JavaScript — PART 93 OF 108

93- آموزش جاوااسکریپت | پروژه عملی: قسمت 6 از 12- افزودن آیتم جدید به رابط کاربری و مدیریت نمایش لیست در Calorie Tracker

در این بخش، آیتم جدید را بعد از ثبت فرم مستقیماً به رابط کاربری اضافه می‌کنیم، فیلدهای ورودی را پاک‌سازی می‌کنیم، نوع ورودی کالری را به number تغییر می‌دهیم و نمایش یا مخفی‌سازی لیست را بر اساس وجود آیتم‌ها مدیریت می‌کنیم.

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

در این بخش، قدم بعدی را برمی‌داریم:
باید آیتم تازه‌ساخته‌شده را بلافاصله در رابط کاربری نمایش دهیم. علاوه بر این، فیلدهای فرم را بعد از ثبت پاک می‌کنیم و وضعیت نمایش لیست را نیز بر اساس خالی یا پر بودن آن کنترل خواهیم کرد.


افزودن آیتم جدید به رابط کاربری

بعد از اینکه آیتم جدید در Item Controller ساخته و به داده‌ها اضافه شد، باید همان آیتم را به UI Controller ارسال کنیم تا در لیست نمایش داده شود.

به‌روزرسانی تابع itemAddSubmit

در App Controller، بعد از ساخت newItem، متد جدیدی از UI Controller را صدا می‌زنیم.

const itemAddSubmit = function (e) {
  const input = UICtrl.getItemInput();

  if (input.name !== '' && input.calories !== '') {
    const newItem = ItemCtrl.addItem(input.name, input.calories);

    // افزودن آیتم به رابط کاربری
    UICtrl.addListItem(newItem);
  }

  e.preventDefault();
};

ساخت متد addListItem در UI Controller

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

کد متد addListItem

addListItem: function (item) {
  // ساخت عنصر li
  const li = document.createElement('li');

  // افزودن class
  li.className = 'collection-item';

  // افزودن id داینامیک
  li.id = `item-${item.id}`;

  // افزودن محتوای داخلی
  li.innerHTML = `
    <strong>${item.name}: </strong>
    <em>${item.calories} Calories</em>
    <a href="#" class="secondary-content">
      <i class="edit-item fa fa-pencil"></i>
    </a>
  `;

  // افزودن آیتم به انتهای لیست
  document
    .querySelector(UISelectors.itemList)
    .insertAdjacentElement('beforeend', li);
}

توضیح عملکرد addListItem

در این متد چند مرحله مهم انجام می‌شود:

1. ساخت عنصر li

برای ساخت آیتم لیست از createElement استفاده می‌کنیم:

const li = document.createElement('li');

2. تنظیم کلاس

برای هماهنگی با استایل پروژه، کلاس collection-item به آن داده می‌شود:

li.className = 'collection-item';

3. تنظیم شناسه یکتا

شناسه هر آیتم به شکل item-{id} ساخته می‌شود:

li.id = `item-${item.id}`;

4. درج HTML داخلی

اطلاعات مربوط به نام و کالری آیتم به همراه آیکن ویرایش داخل innerHTML قرار می‌گیرند:

li.innerHTML = `
  <strong>${item.name}: </strong>
  <em>${item.calories} Calories</em>
  <a href="#" class="secondary-content">
    <i class="edit-item fa fa-pencil"></i>
  </a>
`;

5. افزودن به لیست

در نهایت آیتم به انتهای لیست اضافه می‌شود:

document
  .querySelector(UISelectors.itemList)
  .insertAdjacentElement('beforeend', li);

پاک‌سازی فیلدهای فرم بعد از ثبت آیتم

بعد از اضافه شدن آیتم، بهتر است ورودی‌های فرم خالی شوند تا کاربر بتواند آیتم بعدی را راحت‌تر ثبت کند.

فراخوانی متد پاک‌سازی در itemAddSubmit

const itemAddSubmit = function (e) {
  const input = UICtrl.getItemInput();

  if (input.name !== '' && input.calories !== '') {
    const newItem = ItemCtrl.addItem(input.name, input.calories);

    UICtrl.addListItem(newItem);

    // پاک‌سازی ورودی‌ها
    UICtrl.clearInput();
  }

  e.preventDefault();
};

ساخت متد clearInput

در UI Controller یک متد جدید تعریف می‌کنیم تا مقدار فیلدها را خالی کند.

clearInput: function () {
  document.querySelector(UISelectors.itemNameInput).value = '';
  document.querySelector(UISelectors.itemCaloriesInput).value = '';
}

توضیح

این متد مقدار هر دو ورودی را برابر رشته خالی قرار می‌دهد:

  • ورودی نام آیتم
  • ورودی کالری

تغییر نوع ورودی کالری به number

در این مرحله بهتر است در فایل index.html، نوع فیلد مربوط به کالری را از text به number تغییر دهیم تا فقط ورودی عددی پذیرفته شود.

کد HTML

<input type="number" id="item-calories" placeholder="Add Calories">

مزیت این تغییر

با این کار:

  • کاربر نمی‌تواند مقدار غیرعددی وارد کند
  • داده ورودی معتبرتر می‌شود
  • تجربه کاربری فرم بهتر خواهد شد

حذف داده‌های اولیه hard-coded

تا اینجا برای تست، چند آیتم اولیه را به‌صورت دستی داخل data.items قرار داده بودیم.
حالا که امکان افزودن آیتم از طریق فرم فراهم شده، می‌توانیم این داده‌های اولیه را حذف یا کامنت کنیم.

قبل

const data = {
  items: [
    { id: 0, name: 'Steak Dinner', calories: 1200 },
    { id: 1, name: 'Cookie', calories: 400 },
    { id: 2, name: 'Eggs', calories: 300 }
  ],
  currentItem: null,
  totalCalories: 0
};

بعد

const data = {
  items: [],
  currentItem: null,
  totalCalories: 0
};

نکته

در این حالت، آیتم‌ها فقط در حافظه موقت برنامه ذخیره می‌شوند و با رفرش صفحه از بین می‌روند.
در بخش‌های بعدی، این داده‌ها را در Local Storage نگه‌داری خواهیم کرد تا بعد از بارگذاری مجدد صفحه نیز باقی بمانند.


مخفی کردن لیست وقتی آیتمی وجود ندارد

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

ساخت متد hideList

در UI Controller متدی برای مخفی‌کردن لیست ایجاد می‌کنیم:

hideList: function () {
  document.querySelector(UISelectors.itemList).style.display = 'none';
}

بررسی وجود آیتم‌ها در زمان init

در App Controller، هنگام شروع برنامه بررسی می‌کنیم که آیا آیتمی وجود دارد یا نه.

init: function () {
  const items = ItemCtrl.getItems();

  // بررسی وجود آیتم
  if (items.length === 0) {
    UICtrl.hideList();
  } else {
    UICtrl.populateItemList(items);
  }

  loadEventListeners();
}

توضیح

اگر آرایه items خالی باشد:

  • لیست پنهان می‌شود

در غیر این صورت:

  • آیتم‌ها در رابط کاربری نمایش داده می‌شوند

نمایش دوباره لیست بعد از افزودن آیتم

وقتی لیست در ابتدا مخفی شده باشد و بعد کاربر آیتم جدیدی اضافه کند، باید دوباره آن را نمایش دهیم.
بهترین محل برای این کار داخل متد addListItem است.

نسخه به‌روزشده addListItem

addListItem: function (item) {
  // نمایش لیست
  document.querySelector(UISelectors.itemList).style.display = 'block';

  // ساخت عنصر li
  const li = document.createElement('li');

  // افزودن class
  li.className = 'collection-item';

  // افزودن id داینامیک
  li.id = `item-${item.id}`;

  // افزودن محتوای داخلی
  li.innerHTML = `
    <strong>${item.name}: </strong>
    <em>${item.calories} Calories</em>
    <a href="#" class="secondary-content">
      <i class="edit-item fa fa-pencil"></i>
    </a>
  `;

  // افزودن آیتم به انتهای لیست
  document
    .querySelector(UISelectors.itemList)
    .insertAdjacentElement('beforeend', li);
}

نتیجه

با این تغییر:

  • اگر لیست خالی باشد، در ابتدا مخفی می‌شود
  • با اضافه شدن اولین آیتم، لیست دوباره نمایش داده می‌شود

نسخه کامل کد این بخش

app.js

// Storage Controller

// Item Controller
const ItemCtrl = (function () {
  const Item = function (id, name, calories) {
    this.id = id;
    this.name = name;
    this.calories = calories;
  };

  const data = {
    items: [],
    currentItem: null,
    totalCalories: 0
  };

  return {
    getItems: function () {
      return data.items;
    },

    addItem: function (name, calories) {
      let id;

      if (data.items.length > 0) {
        id = data.items[data.items.length - 1].id + 1;
      } else {
        id = 0;
      }

      calories = parseInt(calories);

      const newItem = new Item(id, name, calories);

      data.items.push(newItem);

      return newItem;
    },

    logData: function () {
      return data;
    }
  };
})();

// UI Controller
const UICtrl = (function () {
  const UISelectors = {
    itemList: '#item-list',
    addBtn: '.add-btn',
    itemNameInput: '#item-name',
    itemCaloriesInput: '#item-calories'
  };

  return {
    populateItemList: function (items) {
      let html = '';

      items.forEach(function (item) {
        html += `
          <li class="collection-item" id="item-${item.id}">
            <strong>${item.name}: </strong>
            <em>${item.calories} Calories</em>
            <a href="#" class="secondary-content">
              <i class="edit-item fa fa-pencil"></i>
            </a>
          </li>
        `;
      });

      document.querySelector(UISelectors.itemList).innerHTML = html;
    },

    getItemInput: function () {
      return {
        name: document.querySelector(UISelectors.itemNameInput).value,
        calories: document.querySelector(UISelectors.itemCaloriesInput).value
      };
    },

    addListItem: function (item) {
      document.querySelector(UISelectors.itemList).style.display = 'block';

      const li = document.createElement('li');

      li.className = 'collection-item';
      li.id = `item-${item.id}`;

      li.innerHTML = `
        <strong>${item.name}: </strong>
        <em>${item.calories} Calories</em>
        <a href="#" class="secondary-content">
          <i class="edit-item fa fa-pencil"></i>
        </a>
      `;

      document
        .querySelector(UISelectors.itemList)
        .insertAdjacentElement('beforeend', li);
    },

    clearInput: function () {
      document.querySelector(UISelectors.itemNameInput).value = '';
      document.querySelector(UISelectors.itemCaloriesInput).value = '';
    },

    hideList: function () {
      document.querySelector(UISelectors.itemList).style.display = 'none';
    },

    getSelectors: function () {
      return UISelectors;
    }
  };
})();

// App Controller
const App = (function (ItemCtrl, UICtrl) {
  const loadEventListeners = function () {
    const UISelectors = UICtrl.getSelectors();

    document
      .querySelector(UISelectors.addBtn)
      .addEventListener('click', itemAddSubmit);
  };

  const itemAddSubmit = function (e) {
    const input = UICtrl.getItemInput();

    if (input.name !== '' && input.calories !== '') {
      const newItem = ItemCtrl.addItem(input.name, input.calories);

      UICtrl.addListItem(newItem);
      UICtrl.clearInput();
    }

    e.preventDefault();
  };

  return {
    init: function () {
      const items = ItemCtrl.getItems();

      if (items.length === 0) {
        UICtrl.hideList();
      } else {
        UICtrl.populateItemList(items);
      }

      loadEventListeners();
    }
  };
})(ItemCtrl, UICtrl);

App.init();

به‌روزرسانی index.html

فقط بخش مربوط به فیلد کالری:

<input type="number" id="item-calories" placeholder="Add Calories">

جمع‌بندی

در این بخش، فرایند افزودن آیتم را از نظر رابط کاربری کامل کردیم.

در این مرحله:

  • آیتم جدید را بلافاصله به لیست رابط کاربری اضافه کردیم
  • متد addListItem را در UI Controller ساختیم
  • ورودی‌های فرم را بعد از ثبت پاک کردیم
  • نوع ورودی کالری را به number تغییر دادیم
  • داده‌های اولیه hard-coded را حذف کردیم
  • در صورت خالی بودن لیست، آن را مخفی کردیم
  • با اضافه شدن آیتم جدید، لیست را دوباره نمایش دادیم

در بخش بعدی، سراغ محاسبه و نمایش مجموع کالری‌ها می‌رویم تا با هر آیتم جدید، مقدار total calories نیز به‌روزرسانی شود.

// 0 comments
#JavaScript#Module Pattern#UI Controller#Item Controller#DOM Manipulation#Event Listener#Form Handling#Calorie Tracker#آموزش جاوااسکریپت#پروژه جاوااسکریپت

نظرات (0)

نظر خود را بنویسید