برنامه مدیریت کالری غذا: وارد کردن آیتم‌ها از سمت کاربر

Tracalorie Project: Importing Items from User

21 مرداد 1399
برنامه ی مدیریت کالری غذا: وارد کردن آیتم ها از سمت کاربر

در این قسمت از این سری آموزشی باید کاری کنیم که کاربر بتواند خودش آیتم های غذایی را در برنامه ثبت کند. در حال حاضر چنین قابلیتی وجود ندارد و فقط همان آیتم های خودمان برایمان بارگذاری می شود. ثبت داده های کاربر همیشه از طریق event-listener ها می باشد بنابراین من تمام event-listener های خودم را درون یک تابع در کنترلر اصلی (App) قرار می دهم. این تابع را در ابتدای کنترلر خود بگذارید:

// App Controller
const App = (function(ItemCtrl, UICtrl){
  // Load event listeners
  const loadEventListeners = function(){

  }

// بقیه کدها //

برای دریافت selector های خودمان مانند جلسه قبل از شیء UISelectors استفاده می کنیم اما از آنجا که این شیء خصوصی است باید متدی تعریف کنیم که آن را به ما برگرداند. کد زیر نحوه انجام این کار در کنترلر UI را به شما نشان می دهد:

// بقیه کدها //
  // Public methods
  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>`;
      });

      // Insert list items
      document.querySelector(UISelectors.itemList).innerHTML = html;
    },
    getSelectors: function(){
      return UISelectors;
    }
  }
})();

حالا می توانیم به کنترلر App برگشته و event-listener خود را تکمیل کنیم:

// App Controller
const App = (function(ItemCtrl, UICtrl){
  // Load event listeners
  const loadEventListeners = function(){
    // Get UI selectors
    const UISelectors = UICtrl.getSelectors();

    // Add item event
    document.querySelector(UISelectors.addBtn).addEventListener('click', itemAddSubmit);
  }
// بقیه کدها //

همانطور که می بینید منطق این کد بسیار ساده است. با استفاده از getSelectors سلکتورهای خودمان را پیدا می کنیم. از طرفی در هنگام کلیک شدن روی دکمه Add Meal (کلاس add-btn) باید متدی به نام itemAddSubmit اجرا شود که مسئول اضافه کردن آیتم به UI ما است. ما هنوز نه این متد را تعریف کرده ایم و نه کلاس add-btn را بنابراین ابتدا به کنترلر UI و شیء UISelectors می رویم تا سلکتورهای مورد نظر را تعریف کنیم:

// UI Controller
const UICtrl = (function(){
  const UISelectors = {
    itemList: '#item-list',
    addBtn: '.add-btn',
    itemNameInput: '#item-name',
    itemCaloriesInput: '#item-calories'
  }
// بقیه کدها //

همانطور که مشاهده می کنید من addBtn را اضافه کرده ام که همان دکمه ثبت فرم است. به علاوه آن input های موجود برای کاربر (نام غذا یا همان Meal و تعداد کالری آن یا همان Calories) را نیز تعریف کرده ام تا بعدا دوباره به این قسمت برنگردیم. این id ها و کلاس ها همگی از فایل index.html برداشته شده اند و به سلیقه شما بستگی دارند.

حالا یک بار دیگر به کنترلر App نگاه کنید:

// App Controller
const App = (function(ItemCtrl, UICtrl){
  // Load event listeners
  const loadEventListeners = function(){
    // Get UI selectors
    const UISelectors = UICtrl.getSelectors();

    // Add item event
    document.querySelector(UISelectors.addBtn).addEventListener('click', itemAddSubmit);
  }
// بقیه کدها //

تا این قسمت از کار addBtn را اضافه کرده ایم اما هنوز متد itemAddSubmit تعریف نشده است. من این متد را در همین قسمت (پایین تر از  loadEventListeners) تعریف می کنم:

// App Controller
const App = (function(ItemCtrl, UICtrl){
  // Load event listeners
  const loadEventListeners = function(){
    // Get UI selectors
    const UISelectors = UICtrl.getSelectors();

    // Add item event
    document.querySelector(UISelectors.addBtn).addEventListener('click', itemAddSubmit);
  }

  // Add item submit
  const itemAddSubmit = function(e){

    e.preventDefault();
  }

تنها چیزی که درون این تابع نوشته ایم preventDefault است تا از ثبت شدن فرم جلوگیری کنیم. به نظر شما منطق این متد چطور تعریف شود؟ درست است! در اولین مرحله باید ورودی های کاربر را دریافت کنیم تا در مراحل بعد آن ها را ثبت کنیم. برای دریافت ورودی های کاربر من UICtrl برگردم و متد جدیدی را برای این کار تعریف کنم:

// بقیه کدها //
      // Insert list items
      document.querySelector(UISelectors.itemList).innerHTML = html;
    },
    getItemInput: function(){
      return {
        name:document.querySelector(UISelectors.itemNameInput).value,
        calories:document.querySelector(UISelectors.itemCaloriesInput).value
      }
    },
    getSelectors: function(){
      return UISelectors;
    }
  }
})();

من نام این تابع را getItemInput گذاشته ام و کار آن استفاده از querySelector برای دریافت ورودی های کاربر است. توجه داشته باشید که مقادیر UISelectors.itemNameInput و UISelectors.itemCaloriesInput همان سلکتورهایی هستند که در شیء UISelectors تعریف کردیم و اضافه کردن value مقدار درون آن ها را به ما می دهد. حالا که چنین تابعی داریم می توانیم به راحتی کدهای تابع itemAddSubmit را بنویسیم:

  // Add item submit
  const itemAddSubmit = function(e){
    // Get form input from UI Controller
    const input = UICtrl.getItemInput();

    // Check for name and calorie input
    if(input.name !== '' && input.calories !== ''){
      // Add item
      const newItem = ItemCtrl.addItem(input.name, input.calories);
    }

    e.preventDefault();
  }

در کد بالا ابتدا getItemInput مقدار تایپ شده توسط کاربر را به ما می دهد که آن را در متغیری به نام input قرار می دهیم. سپس با یک شرط ساده if چک می کنیم که name و calories (فیلدهای ورودی) هیچ کدام خالی نباشند. اگر خالی نبودند تابعی به نام addItem مسئولیت اضافه کردن آیتم جدید را به عهده خواهد داشت. طبیعتا ما هنوز چنین تابعی را تعریف نکرده ایم بنابراین قدم بعد تعریف addItem است اما قبل از آن باید کاری را در init انجام دهیم. آیا می توانید حدس بزنید؟ بله! ما event-listener خود را درون یک تابع قرار دادیم اما این تابع خودش درون یک متغیر است بنابراین تا زمانی که آن متغیر را صدا نزنیم Event-listener ما اصلا اجرا نمی شود. من این کار را درون تابع init می کنم چرا که همزمان با بارگذاری برنامه باید event-listener خودمان را اجرا کنیم:

  // Public methods
  return {
    init: function(){
      // Fetch items from data structure
      const items = ItemCtrl.getItems();

      // Populate list with items
      UICtrl.populateItemList(items);

      // Load event listeners
      loadEventListeners();
    }
  }

حالا به تعریف متد addItem برمی گردیم. من این کار را در کنترلر آیتم ها انجام می دهم:

  // Public methods
  return {
    getItems: function(){
      return data.items;
    },
    addItem: function(name, calories){
      let ID;
      // Create ID
      if(data.items.length > 0){
        ID = data.items[data.items.length - 1].id + 1;
      } else {
        ID = 0;
      }

      // Calories to number
      calories = parseInt(calories);

      // Create new item
      newItem = new Item(ID, name, calories);

      // Add to items array
      data.items.push(newItem);

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

قبلا getItem را نوشته بودیم و با آن کاری نداریم بنابراین با جدا کردن آن با یک ویرگول به تعریف addItem می پردازیم. خوب به شرط if درون تابع نگاه کنید چرا که این شرط وظیفه تعریف id برای آیتم های غذایی را بر عهده دارد. اگر data.items.length از صفر بزرگتر باشد یعنی در حال حاضر درون آرایه items یک یا چند آیتم غذایی داریم (کاربر قبلا چیزی را ثبت کرده است). از طرفی برای اینکه id ها تکراری نشوند و به خطا برنخوریم باید آن ها را به صورت ترتیبی تعریف کنیم یعنی از صفر به بالا بروند (1 و 2 و 3 و 4 و...). حالا باید به یک تفاوت مهم توجه کنیم!

Length نشان دهنده طول است بنابراین از عدد یک شروع می شود. مثلا آرایه items ما در حال حاضر 3 آیتم دارد بنابراین length آن برابر 3 است. از طرفی index همیشه از عدد صفر شروع می شود بنابراین items دارای ایندکس های صفر و یک و دو است. با این حساب برای به دست آوردن index مربوط به آخرین آیتم غذایی باید طول را منهای 1 کنیم:

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

با این حساب قسمت زیر id آخرین آیتم غذایی را به ما می دهد:

ID = data.items[data.items.length - 1].id

سپس با اضافه کردن یک واحد به آن می توانیم id جدید را برای عنصر اضافه شده محاسبه کنیم. یک واحد اضافه می کنیم تا id جدید با id آخرین آیتم غذایی موجود در لیست تداخل نداشته باشد:

        ID = data.items[data.items.length - 1].id + 1;

البته اگر تعداد اعضای داخل items بزرگتر از صفر نبود (یعنی هیچ آیتمی وجود نداشت) کاربر از قبل چیزی را وارد نکرده است و به همین دلیل id برابر با صفر خواهد بود تا از اول شروع کنیم.

دوباره به کدهای تابع addItem نگاه کنید. می دانید که داده های ورودی کاربر همیشه به صورت رشته خواهند بود، بنابراین برای انجام محاسبات احتمالی آن را به صورت عدد در آورده ایم:

      calories = parseInt(calories);

مرحله بعد ساخت خود آیتم ها بود. من برای انجام این کار از constructor ای که در جلسات قبل تعریف کرده بودیم استفاده کرده ام:

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

بنابراین درون addItem گفته ایم:

      // Create new item
      newItem = new Item(ID, name, calories);

      // Add to items array
      data.items.push(newItem);

      return newItem;

یعنی ابتدا با آن constructor شیء جدید را که یک آیتم غذایی است ساخته و سپس آن را به آرایه items فرستاده ایم (با متد push). در نهایت هم این آیتم جدید را برگردانده ایم. اگر الان کدها را در مرورگر تست کنید شاهد هیچ اتفاقی نخواهید بود چرا که فعلا کدی برای نمایش items در UI ننوشته ایم. در قسمت بعد این کار را انجام خواهیم داد.

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

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