96- آموزش جاوااسکریپت | پروژه عملی: قسمت 9 از 12- اصلاح نامگذاری تابع و پیادهسازی بهروزرسانی آیتمها در حالت ویرایش
در این بخش، نام تابع مربوط به کلیک روی آیکن ویرایش اصلاح میشود و سپس فرایند کامل بهروزرسانی آیتم، غیرفعالکردن ارسال فرم با Enter، بهروزرسانی رابط کاربری و محاسبه دوباره مجموع کالریها پیادهسازی میشود.
در این بخش ابتدا یک اشتباه کوچک در نامگذاری تابع ویرایش اصلاح میشود. تابعی که هنگام کلیک روی آیکن ویرایش اجرا میشود، نباید itemUpdateSubmit نام داشته باشد، چون هنوز عملیات ثبت بهروزرسانی انجام نشده است. نام درست این تابع itemEditClick است.
بعد از این اصلاح، منطق کامل بهروزرسانی آیتمها پیادهسازی میشود؛ یعنی پس از ورود به حالت ویرایش و تغییر دادهها، با کلیک روی دکمه Update، اطلاعات آیتم هم در ساختار داده و هم در رابط کاربری بهروزرسانی خواهد شد.
اصلاح نام تابع کلیک روی ویرایش
تابعی که با کلیک روی آیکن ویرایش اجرا میشود، باید نامی داشته باشد که دقیقاً رفتار آن را نشان دهد. چون این تابع فقط با کلیک روی آیکن ویرایش درگیر است، بهتر است از نام itemEditClick استفاده کنیم.
کد
document
.querySelector(UISelectors.itemList)
.addEventListener('click', itemEditClick);
const itemEditClick = function (e) {
e.preventDefault();
if (e.target.classList.contains('edit-item')) {
const listId = e.target.parentNode.parentNode.id;
const listIdArr = listId.split('-');
const id = parseInt(listIdArr[1]);
const itemToEdit = ItemController.getItemById(id);
ItemController.setCurrentItem(itemToEdit);
UIController.addItemToForm();
}
};
ثبت رویداد دکمه Update
بعد از ورود آیتم به حالت ویرایش، باید دکمه Update بتواند عملیات بهروزرسانی را اجرا کند. برای این کار، یک event listener جدید روی دکمه Update اضافه میکنیم.
کد
document
.querySelector(UISelectors.updateBtn)
.addEventListener('click', itemUpdateSubmit);
ایجاد تابع itemUpdateSubmit
این تابع مسئول دریافت دادههای جدید فرم، ارسال آنها به کنترلر آیتمها، بهروزرسانی رابط کاربری، محاسبه مجدد مجموع کالریها و خروج از حالت ویرایش است.
کد اولیه
const itemUpdateSubmit = function (e) {
e.preventDefault();
const input = UIController.getItemInput();
const updatedItem = ItemController.updateItem(input.name, input.calories);
UIController.updateListItem(updatedItem);
const totalCalories = ItemController.getTotalCalories();
UIController.showTotalCalories(totalCalories);
UIController.clearEditState();
};
غیرفعالکردن ارسال فرم با کلید Enter
در حالت ویرایش چند دکمه مختلف روی فرم وجود دارد؛ مانند Update، Delete و Back. به همین دلیل اگر کاربر Enter را فشار دهد، نباید فرم بهصورت خودکار ارسال شود. بهترین راه این است که ارسال فرم با Enter را غیرفعال کنیم.
کد
document.addEventListener('keypress', function (e) {
if (e.keyCode === 13 || e.which === 13) {
e.preventDefault();
return false;
}
});
دریافت دادههای فرم
برای خواندن اطلاعات ورودی فرم، از متدی که از قبل در UIController وجود دارد استفاده میکنیم.
کد
getItemInput: function () {
return {
name: document.querySelector(UISelectors.itemNameInput).value,
calories: document.querySelector(UISelectors.itemCaloriesInput).value
};
}
پیادهسازی متد updateItem در ItemController
در این مرحله باید آیتم انتخابشده را در ساختار داده پیدا کنیم و مقدارهای جدید را روی آن اعمال کنیم.
منطق این متد به این صورت است:
- کالری ورودی از رشته به عدد تبدیل میشود.
- در آرایه
itemsپیمایش انجام میشود. - آیتمی که شناسه آن با
currentItem.idبرابر است پیدا میشود. - مقدار
nameوcaloriesآن بهروزرسانی میشود. - در نهایت آیتم بهروزرسانیشده بازگردانده میشود.
کد
updateItem: function (name, calories) {
calories = parseInt(calories);
let found = null;
data.items.forEach(function (item) {
if (item.id === data.currentItem.id) {
item.name = name;
item.calories = calories;
found = item;
}
});
return found;
}
بهروزرسانی آیتم در رابط کاربری
بعد از بهروزرسانی داده در ItemController، باید همان تغییر را در لیست نمایشدادهشده در صفحه نیز اعمال کنیم.
برای این کار:
- همه آیتمهای لیست از DOM گرفته میشوند.
NodeListبه آرایه تبدیل میشود.- روی هر آیتم پیمایش انجام میشود.
- اگر شناسه DOM با شناسه آیتم بهروزرسانیشده برابر بود، محتوای HTML آن جایگزین میشود.
کد
updateListItem: function (item) {
let listItems = document.querySelectorAll(UISelectors.listItems);
listItems = Array.from(listItems);
listItems.forEach(function (listItem) {
const itemId = listItem.getAttribute('id');
if (itemId === `item-${item.id}`) {
document.querySelector(`#${itemId}`).innerHTML = `
<strong>${item.name}: </strong> <em>${item.calories} Calories</em>
<a href="#" class="secondary-content">
<i class="edit-item fa fa-pencil"></i>
</a>
`;
}
});
}
افزودن سلکتور listItems
برای انتخاب همه آیتمهای لیست در متد updateListItem، باید یک selector جدید تعریف کنیم.
کد
const UISelectors = {
itemList: '#item-list',
listItems: '#item-list li',
addBtn: '.add-btn',
updateBtn: '.update-btn',
deleteBtn: '.delete-btn',
backBtn: '.back-btn',
itemNameInput: '#item-name',
itemCaloriesInput: '#item-calories'
};
محاسبه مجدد مجموع کالریها
پس از بهروزرسانی یک آیتم، باید مجموع کالریها دوباره محاسبه و در رابط کاربری نمایش داده شود.
کد
const totalCalories = ItemController.getTotalCalories();
UIController.showTotalCalories(totalCalories);
خروج از حالت ویرایش پس از Update
بعد از پایان عملیات بهروزرسانی، بهتر است فرم دوباره به حالت عادی برگردد. با فراخوانی clearEditState این کار انجام میشود:
- ورودیها پاک میشوند.
- دکمههای مخصوص ویرایش مخفی میشوند.
- دکمه Add دوباره نمایش داده میشود.
کد
UIController.clearEditState();
نسخه نهایی تابع itemUpdateSubmit
در نهایت تابع ثبت بهروزرسانی به این شکل خواهد بود:
کد
const itemUpdateSubmit = function (e) {
e.preventDefault();
const input = UIController.getItemInput();
const updatedItem = ItemController.updateItem(input.name, input.calories);
UIController.updateListItem(updatedItem);
const totalCalories = ItemController.getTotalCalories();
UIController.showTotalCalories(totalCalories);
UIController.clearEditState();
};
جمعبندی
در این بخش چند مرحله مهم انجام شد:
- نام تابع مربوط به کلیک روی آیکن ویرایش اصلاح شد.
- رویداد دکمه Update اضافه شد.
- ارسال فرم با کلید Enter غیرفعال شد.
- منطق بهروزرسانی آیتم در دادهها پیادهسازی شد.
- آیتم ویرایششده در رابط کاربری نیز بهروزرسانی شد.
- مجموع کالریها دوباره محاسبه شد.
- فرم پس از ثبت تغییرات از حالت ویرایش خارج شد.
این تغییرات باعث میشود فرایند ویرایش آیتمها کاملتر و رفتار فرم برای کاربر شفافتر شود.
کد نهایی این بخش
const ItemController = (function () {
const data = {
items: [],
currentItem: null,
totalCalories: 0
};
return {
getItemById: function (id) {
let found = null;
data.items.forEach(function (item) {
if (item.id === id) {
found = item;
}
});
return found;
},
setCurrentItem: function (item) {
data.currentItem = item;
},
getCurrentItem: function () {
return data.currentItem;
},
updateItem: function (name, calories) {
calories = parseInt(calories);
let found = null;
data.items.forEach(function (item) {
if (item.id === data.currentItem.id) {
item.name = name;
item.calories = calories;
found = item;
}
});
return found;
},
getTotalCalories: function () {
let total = 0;
data.items.forEach(function (item) {
total += item.calories;
});
data.totalCalories = total;
return data.totalCalories;
}
};
})();
const UIController = (function () {
const UISelectors = {
itemList: '#item-list',
listItems: '#item-list li',
addBtn: '.add-btn',
updateBtn: '.update-btn',
deleteBtn: '.delete-btn',
backBtn: '.back-btn',
itemNameInput: '#item-name',
itemCaloriesInput: '#item-calories',
totalCalories: '.total-calories'
};
return {
getItemInput: function () {
return {
name: document.querySelector(UISelectors.itemNameInput).value,
calories: document.querySelector(UISelectors.itemCaloriesInput).value
};
},
showTotalCalories: function (totalCalories) {
document.querySelector(UISelectors.totalCalories).textContent = totalCalories;
},
clearInput: function () {
document.querySelector(UISelectors.itemNameInput).value = '';
document.querySelector(UISelectors.itemCaloriesInput).value = '';
},
clearEditState: function () {
UIController.clearInput();
document.querySelector(UISelectors.updateBtn).style.display = 'none';
document.querySelector(UISelectors.deleteBtn).style.display = 'none';
document.querySelector(UISelectors.backBtn).style.display = 'none';
document.querySelector(UISelectors.addBtn).style.display = 'inline';
},
showEditState: function () {
document.querySelector(UISelectors.updateBtn).style.display = 'inline';
document.querySelector(UISelectors.deleteBtn).style.display = 'inline';
document.querySelector(UISelectors.backBtn).style.display = 'inline';
document.querySelector(UISelectors.addBtn).style.display = 'none';
},
addItemToForm: function () {
document.querySelector(UISelectors.itemNameInput).value =
ItemController.getCurrentItem().name;
document.querySelector(UISelectors.itemCaloriesInput).value =
ItemController.getCurrentItem().calories;
UIController.showEditState();
},
updateListItem: function (item) {
let listItems = document.querySelectorAll(UISelectors.listItems);
listItems = Array.from(listItems);
listItems.forEach(function (listItem) {
const itemId = listItem.getAttribute('id');
if (itemId === `item-${item.id}`) {
document.querySelector(`#${itemId}`).innerHTML = `
<strong>${item.name}: </strong> <em>${item.calories} Calories</em>
<a href="#" class="secondary-content">
<i class="edit-item fa fa-pencil"></i>
</a>
`;
}
});
},
getSelectors: function () {
return UISelectors;
}
};
})();
const App = (function (ItemController, UIController) {
const UISelectors = UIController.getSelectors();
const loadEventListeners = function () {
document
.querySelector(UISelectors.itemList)
.addEventListener('click', itemEditClick);
document
.querySelector(UISelectors.updateBtn)
.addEventListener('click', itemUpdateSubmit);
document.addEventListener('keypress', function (e) {
if (e.keyCode === 13 || e.which === 13) {
e.preventDefault();
return false;
}
});
};
const itemEditClick = function (e) {
e.preventDefault();
if (e.target.classList.contains('edit-item')) {
const listId = e.target.parentNode.parentNode.id;
const listIdArr = listId.split('-');
const id = parseInt(listIdArr[1]);
const itemToEdit = ItemController.getItemById(id);
ItemController.setCurrentItem(itemToEdit);
UIController.addItemToForm();
}
};
const itemUpdateSubmit = function (e) {
e.preventDefault();
const input = UIController.getItemInput();
const updatedItem = ItemController.updateItem(input.name, input.calories);
UIController.updateListItem(updatedItem);
const totalCalories = ItemController.getTotalCalories();
UIController.showTotalCalories(totalCalories);
UIController.clearEditState();
};
return {
init: function () {
UIController.clearEditState();
loadEventListeners();
}
};
})(ItemController, UIController);
App.init();