list.js icon indicating copy to clipboard operation
list.js copied to clipboard

Feature Request: pass in name of IIFE method to invoke for data

Open MikeWarren2014 opened this issue 6 years ago • 0 comments

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.

MikeWarren2014 avatar Dec 26 '18 06:12 MikeWarren2014