// Import functions to initialize and display recipes
import {
init,
displayFilteredRecipes,
FilterByMealType,
FilterByCuisine,
FilterByTime,
FilterByFavorite,
resetFilters
} from '../../display.js';
/**
* Main exported function to render mobile-friendly filters and actions
*/
export default function () {
requestAnimationFrame(() => {
init();
});
// Add event listener to the logo
document.querySelector('.logo-section').addEventListener('click', (e) => {
e.preventDefault();
resetFilters();
});
if (!document.querySelector('.mobile-search-actions')) {
// Mobile search and icon row
const headerSearch = document.querySelector('.top-nav .search-bar');
const searchClone = headerSearch.cloneNode(true);
const mobileSearchActions = document.createElement('div');
mobileSearchActions.className = 'mobile-search-actions';
mobileSearchActions.appendChild(searchClone);
// small icons
const mobileActions = document.createElement('div');
mobileActions.className = 'mobile-actions';
const shuffleIcon = document.createElement('button');
shuffleIcon.id = 'shuffle-icon';
shuffleIcon.textContent = '🔀';
const addIcon = document.createElement('button');
addIcon.id = 'add-icon';
addIcon.textContent = 'âž•';
mobileActions.append(addIcon);
mobileSearchActions.appendChild(mobileActions);
const siteHeader = document.querySelector('.site-header');
siteHeader.insertAdjacentElement('afterend', mobileSearchActions);
// mobile icons have same functionality as "normal" action buttons
shuffleIcon.addEventListener('click', () => document.querySelector('.btn-shuffle').click());
addIcon.addEventListener('click', () => document.querySelector('.btn-add').click());
}
// Create filter navigation section
const section = document.createElement('section');
section.className = 'sub-nav';
const filters = document.createElement('ul');
filters.className = 'filters';
// Add filter dropdowns
const mealFilter = createFilter('filter1', 'Meal', [
'Breakfast',
'Lunch',
'Dinner',
'Dessert',
'Snack',
'Beverage',
]);
const selectedMeal = mealFilter.querySelector('select');
selectedMeal.addEventListener('change', (e) => {
const currMeal = e.target.value;
if (currMeal == '') {
init();
} else {
FilterByMealType(currMeal);
}
});
const cuisineFilter = createFilter('filter2', 'Cuisine', [
'African',
'Asian',
'European',
'Latin American',
'Middle Eastern',
'North American',
]);
const selectedCuisine = cuisineFilter.querySelector('select');
selectedCuisine.addEventListener('change', (e) => {
const currCuisine = e.target.value;
if (currCuisine == '') {
init();
} else {
FilterByCuisine(currCuisine);
}
});
const timeFilter = createFilter('filter3', 'Estimated Time', [
'Under 30 minutes',
'Under 1 Hour',
'Over 1 Hour',
]);
const time = timeFilter.querySelector('select');
time.addEventListener('change', (e) => {
const currTime = e.target.value;
if (currTime == '') {
init();
} else {
FilterByTime(currTime);
}
});
const ingredientsFilter = createIngredientFilter('filter4', 'Ingredients');
document.querySelector('.btn-favorite').addEventListener('click', () => {
FilterByFavorite();
});
filters.append(mealFilter, cuisineFilter, timeFilter, ingredientsFilter);
section.append(filters);
// Main content area for recipe cards
const main = document.createElement('main');
section.appendChild(main);
const actions = document.createElement('div');
actions.className = 'actions';
const addBtn = document.createElement('button');
addBtn.className = 'btn-add';
addBtn.textContent = 'Add Recipe Card';
addBtn.addEventListener('click', () => {
location.hash = '#/create';
});
actions.append(addBtn);
section.append(actions);
// mobile icons have same functionality as "normal" action buttons
// addIcon.addEventListener('click', () => addBtn.click());
return section;
}
/**
* Creates a dropdown filter based on an attribute of a recipe
* @param {string} name - Name of the filter
* @param {string} label - Type of filter
* @param {Array} options - Options in filter
* @returns {HTMLElement} - returns an HTMLElement containing a select tag for a filter
*/
function createFilter(name, label, options) {
const li = document.createElement('li');
const select = document.createElement('select');
select.className = 'btn-filter';
select.name = name;
const defaultOption = document.createElement('option');
defaultOption.disabled = false;
defaultOption.selected = true;
defaultOption.textContent = label;
defaultOption.value = '';
select.appendChild(defaultOption);
for (const opt of options) {
const option = document.createElement('option');
option.textContent = opt;
option.value = opt;
select.appendChild(option);
}
li.appendChild(select);
return li;
}
/**
* Creates the ingredient dropdown filter with search bar
* @param {string} name - Filter name
* @param {string} label - Label of filter
* @returns {HTMLElement} - returns an HTMLElement containing the ingredient filter with a search bar
*/
function createIngredientFilter(name, label) {
const all = document.createElement('div');
const ingredientFilterButton = document.createElement('button');
ingredientFilterButton.className = 'btn-filter';
ingredientFilterButton.textContent = label;
const dropDown = document.createElement('div');
dropDown.className = 'ingredient-dropdown';
dropDown.style.display = 'none';
const dropContent = document.createElement('div');
dropContent.className = 'ingredient-dropdown-content';
dropDown.appendChild(dropContent);
const leftSide = document.createElement('div');
leftSide.className = 'ingredient-dropdown-left';
const searchBar = document.createElement('input');
searchBar.type = 'text';
searchBar.placeholder = 'Search Ingredients';
searchBar.className = 'ingredient-dropdown-search-bar';
//list of ingredients
const resultsContainer = document.createElement('ul');
resultsContainer.className = 'ingredient-results-container';
resultsContainer.style.listStyle = 'none';
leftSide.appendChild(searchBar);
leftSide.appendChild(resultsContainer);
const rightSide = document.createElement('div');
rightSide.className = 'ingredient-dropdown-right hidden';
// container for selected tags
const selectedTagsContainer = document.createElement('div');
selectedTagsContainer.className = 'ingredient-tags-container';
rightSide.appendChild(selectedTagsContainer);
dropContent.appendChild(leftSide);
dropContent.appendChild(rightSide);
dropDown.appendChild(dropContent);
// prevent clicks inside dropdown from closing it
dropDown.addEventListener('click', (e) => {
e.stopPropagation();
});
const selectedIngredients = new Set(); // track the selected ingredient (max 3)
ingredientFilterButton.addEventListener('click', async function (event) {
event.stopPropagation();
if (dropDown.style.display === 'none') {
dropDown.style.display = 'block';
const fromStorage = await getIngredients();
resultsContainer.innerHTML = '';
for (const opt of fromStorage) {
const option = document.createElement('li');
option.textContent = opt;
option.dataset.value = opt; // this behaves like unique id. it sets the data-value attribute to the <li> tag
if (selectedIngredients.has(opt)) {
// mark as selected if already selected
option.classList.add('selected');
}
option.addEventListener('click', () => {
if (selectedIngredients.has(opt)) {
// if already selected and gets clicked again
selectedIngredients.delete(opt);
option.classList.remove('selected');
removeTag(opt);
} else if (selectedIngredients.size < 3) {
selectedIngredients.add(opt);
option.classList.add('selected');
addTag(opt);
}
});
resultsContainer.appendChild(option);
}
} else {
dropDown.style.display = 'none';
}
});
function addTag(opt) {
const tag = document.createElement('div');
tag.className = 'ingredient-tag';
tag.dataset.value = opt;
const tagText = document.createElement('span');
tagText.textContent = opt;
// This is for the making the tag as symbol, handles the click to remove event as well
const removeButton = document.createElement('button');
// 'x' symbol / html entities
removeButton.innerHTML = '×';
removeButton.addEventListener('click', function (event) {
event.stopPropagation();
selectedIngredients.delete(opt);
const itemList = resultsContainer.querySelector(
`li[data-value="${opt}"]`
);
if (itemList) {
itemList.classList.remove('selected');
}
tag.remove();
if (selectedIngredients.size === 0) {
rightSide.classList.add('hidden');
}
displayFilteredRecipes(Array.from(selectedIngredients));
});
tag.appendChild(tagText);
tag.appendChild(removeButton);
selectedTagsContainer.appendChild(tag);
rightSide.classList.remove('hidden');
displayFilteredRecipes(Array.from(selectedIngredients));
}
function removeTag(opt) {
const tag = selectedTagsContainer.querySelector(
// this is the format of how data-value is being assigned
`.ingredient-tag[data-value="${opt}"]`
);
if (tag) {
selectedIngredients.delete(opt);
tag.remove();
if (selectedIngredients.size === 0) {
rightSide.classList.add('hidden');
}
displayFilteredRecipes(Array.from(selectedIngredients));
}
}
// render the ingredient results based on typing
searchBar.addEventListener('input', function (event) {
renderResults(searchBar.value);
});
function renderResults(searchTarget = '') {
const searchValue = searchTarget.toLowerCase().trim();
const items = resultsContainer.querySelectorAll('li');
items.forEach((item) => {
const text = item.textContent.toLowerCase();
if (text.includes(searchValue)) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
}
// render the ingredients based on search enter
searchBar.addEventListener('keypress', function (event) {
if (event.key === 'Enter') {
event.preventDefault();
const searchValue = searchBar.value.toLowerCase().trim();
if (searchValue) {
displayFilteredRecipes(searchValue);
dropDown.style.display = 'none';
}
}
});
/**
* This is an asynchronous function which grabs all the recipes from localStorage to make a list of all unique ingredients
* @returns {Array} - An Array of unique ingredients from all recipes in Local storage
*/
async function getIngredients() {
const recipeMetadata = localStorage.getItem('recipe_metadata');
if (!recipeMetadata) {
return [];
}
try {
const recipes = JSON.parse(recipeMetadata);
const uniqueIngredients = new Set();
recipes.forEach((recipe) => {
if (
recipe.recipeIngredient &&
Array.isArray(
recipe.recipeIngredient && recipe.recipeIngredient
)
) {
recipe.recipeIngredient.forEach((ingredient) => {
if (
ingredient &&
ingredient.name &&
typeof ingredient.name === 'string' &&
ingredient.name.trim() !== ''
) {
uniqueIngredients.add(
ingredient.name.trim().toLowerCase()
);
} // right now it will store as lowercases
});
}
});
return Array.from(uniqueIngredients).sort();
} catch (error) {
console.error(
'Error parsing recipe metadata for Ingredients filter',
error
);
return [];
}
}
// close dropdown on clicking anything outside of the box container
document.addEventListener('click', () => {
dropDown.style.display = 'none';
});
all.appendChild(ingredientFilterButton);
all.appendChild(dropDown);
return all;
}