list.js
list.js copied to clipboard
Feature Request: pass in name of IIFE method to invoke for data
The issue is on this StackOverflow question, but to reiterate, there's some business requirements that my company has that has me using an IIFE to resolve. We have menus that have items and categories, and categories can have items. On the edit menu page, each category in the menu has its own set of items, and these sets are allowed to have non-null intersection (for example, the item "Cheese Pizza" can appear on both a "Pizza" category and a "Lunch special" category).
I came up with the following IIFE to fulfill those requirements (an adding/removal of items/categories then calls the methods of the IIFE, which handle the data manipulation accordingly):
/* all the categories, items, and modifiers that power this page */
const menuState = (function() {
class MenuData {
constructor(attached = [], available = []) {
// attached,available MUST be arrays!
if ((!Array.isArray(attached)) || (!Array.isArray(available))) {
throw TypeError("passed data MUST be in the form of Arrays!")
}
this.attached = attached;
this.available = available;
}
add(entities) {
// entities MUST be Array
if (!Array.isArray(entities)) throw ReferenceError("entities must be array")
// from here, we simply move from this.available to this.attached, the entities
// but first, let's check if they're even available
if (hasEntities(entities, this.available)) {
// attach them
this.attached = this.attached.concat(entities)
// they are no longer available
this.available= this.available
.filter(excluderFactory(entities))
}
// if they're not attached, that is an error
if (!hasEntities(entities, this.attached)) {
throw Error('The entities you were trying to add were neither attached nor available')
}
}
remove(entities) {
// entities MUST be Array
if (!Array.isArray(entities)) throw ReferenceError("entities must be array")
// from here, we simply move from this.attached to this.available, the entities
// but first, let's check if they're even attached
if (hasEntities(entities, this.attached)) {
// make them available
this.available = this.available.concat(entities)
// detach them
this.attached = this.attached
.filter(excluderFactory(entities))
}
// if they're not available, that is an error
if (!hasEntities(entities, this.available)) {
throw Error('The entities you were trying to remove were neither attached nor available')
}
}
};
let _categories = new MenuData(),
_items = new MenuData()
let _itemPool = [],
_categoryItems = {}
/**
* Determines if an array has entities with an Id.
* @param {Object[]} entities
* @param {Object[]} arrayToCheck
* @returns {boolean} whether arrayToCheck has objects with entities' Ids.
*/
function hasEntities(entities, arrayToCheck) {
for (let idx in entities) {
if (arrayToCheck.find((element) => element.Id === entities[idx].Id)) {
if (idx == entities.length - 1) {
return true;
}
continue;
}
return false;
}
}
/**
* Returns a callback for the purpose of excluding entities
* @param {Object[]} entities the entities to exclude
*/
function excluderFactory(entities) {
return function(model) {
return !entities.find((entity) => entity.Id === model.Id)
}
}
return {
getAllCategories : () => _categories.attached.concat(_categories.available),
getAttachedCategories : () => _categories.attached.slice(),
getAvailableCategories : () => _categories.available.slice(),
addCategories : (categories) => {
_categories.add(categories)
},
removeCategories : (categories) => {
_categories.remove(categories)
},
/**
* Updates the _items to the category.
* @param {Object} category the category object to update to. MUST HAVE ID!
*/
changeCurrentCategory : function(category) {
if (category.Id === undefined) {
throw ReferenceError("Category MUST have Id!")
}
_items = _categoryItems[category.Id]
},
// because _items can be reset to reference other states, we MUST
// directly reference it in these public methods
getAllItems : () => _items.attached.concat(_items.available).slice(),
getAttachedItems : () => _items.attached.slice(),
getAvailableItems : () => _items.available.slice(),
addItems : function(entities) {
return _items.add(entities)
},
removeItems : function(entities) {
return _items.remove(entities)
},
/**
* initializes the item pool of the menu state.
* All category-item states are constructed from the item pool.
*
* @param {Object[]} items
*/
initItemPool : (items) => {
if (!Array.isArray(items)) {
throw TypeError("item pool can only be initialized with an Array.")
}
// if _itemPool already has data, we're done here
if (_itemPool.length) {
throw Error("item pool is already initialized!")
}
_itemPool = items
},
getItemPool : () => _itemPool.slice(),
initCategories : (categories) => {
if (!Array.isArray(categories)) {
throw TypeError("categories can only be initialized with an Array.")
}
// if _categories already has data, we're done here
if ((_categories.attached.length) || (_categories.available.length)) {
throw Error("categories is already initialized!")
}
_categories = new MenuData([], categories)
},
/**
* Creates an entry in the category items object
* @param {number} categoryId : the ID of the category containing these items
* @param {Object[]} categoryItems : the category items. These are assumed attached to a category
*/
addCategoryItems : function(categoryId, categoryItems) {
categoryId = parseInt(categoryId) || 0
// if there is entry for categoryId already
if (_categoryItems[categoryId]) {
// throw an error
throw Error(`There already exists data for category with ID ${categoryId} !`)
}
// make the "complement" of categoryItems, using _itemPool
let availableItems = _itemPool.filter(excluderFactory(categoryItems))
// write categoryItems,availableItems to key categoryId of _categoryItems
_categoryItems[categoryId] = new MenuData(categoryItems,
availableItems)
},
getCategoryItems : () => _categoryItems
}
})()
I require the methods getAvailableItems
and getAvailableCategories
of this IIFE to be invoked when item modal, category modal is popped (respectively). I search the results on CodePen and in the API specifications on the site, but see no good way to do so.