I'm trying to create a todo list then use drag and drop to arrange it as I want. I have a dummy data (list items) which I can arrange as I want. the problem I am having is any new todo I add to the list item can not be dragged or rearrange.
here is my code below
var form = document.getElementById('addForm');
var itemList = document.getElementById('items');
form.addEventListener('submit', addTodo);
itemList.addEventListener('click', removeItem)
function addTodo(e) {
e.preventDefault()
// get input value
var newTodo = document.getElementById('todo');
// create new li element
var li = document.createElement('li')
//add class draggable property
li.className = 'draggable';
li.draggable = true
// add textnode with input value
li.appendChild(document.createTextNode(newTodo.value))
// delete button
var delBtn = document.createElement('button')
delBtn.className = 'btn btn-danger btn-sm float-right del';
delBtn.appendChild(document.createTextNode('X'))
li.appendChild(delBtn)
itemList.appendChild(li)
newTodo.value = ""
}
function removeItem(e) {
if (e.target.classList.contains('del')) {
if (confirm('Are you sure?')) {
var li = e.target.parentElement
itemList.removeChild(li)
}
}
}
const draggables = document.querySelectorAll('.draggable')
draggables.forEach(draggable => {
draggable.addEventListener('dragstart', () => {
draggable.classList.add('dragging')
})
draggable.addEventListener('dragend', () => {
draggable.classList.remove('dragging')
})
})
itemList.addEventListener('dragover', (e) => {
e.preventDefault()
const draggable = document.querySelector('.dragging')
const afterElement = getDragAfterElement(draggable, e.clientY)
console.log(afterElement);
if (afterElement == null) {
itemList.appendChild(draggable)
} else {
itemList.insertBefore(draggable, afterElement)
}
})
function getDragAfterElement(draggables, y) {
const draggableElements = [...document.querySelectorAll('.draggable:not(.dragging)')]
console.log('Dragable', draggableElements);
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect()
const offset = y - box.top - box.height / 2
if (offset < 0 && offset > closest.offset) {
return {
offset: offset,
element: child
}
} else {
return closest
}
}, {
offset: Number.NEGATIVE_INFINITY
}).element
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo task</title>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css">
<!-- Google Fonts -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
<!-- Bootstrap core CSS -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet">
<!-- Material Design Bootstrap -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.19.1/css/mdb.min.css" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container mt-2">
<div class="row justify-content-center">
<div class="col-md-8">
<h1>Todo Task</h1>
<div class="card">
<div class="card-header bg-grey">
<h4>What Todo</h4>
</div>
<div class="card-body">
<form action="" id="addForm">
<div class="form-group mt-2 pl-5">
<label for="">Title of Task</label>
<input type="text" id="todo" placeholder="Enter todo..." class="form-control">
</div>
<button type="submit" id="submit" class="btn btn-primary btn-sm">Submit</button>
</form>
</div>
</div>
<div class="jumb mt-4">
<h2>Todo Lists</h2>
<ul class="list-group" id="items">
<li class="draggable" draggable="true">Item 1 <button class="btn btn-danger btn-sm float-right del">X</button></li>
<li class="draggable" draggable="true">Item 2 <button class="btn btn-danger btn-sm float-right del">X</button></li>
<li class="draggable" draggable="true">Item 3 <button class="btn btn-danger btn-sm float-right del">X</button></li>
</ul>
</div>
</div>
</div>
</div>
<!-- JQuery -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<!-- Bootstrap tooltips -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.4/umd/popper.min.js"></script>
<!-- Bootstrap core JavaScript -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/js/bootstrap.min.js"></script>
<!-- MDB core JavaScript -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.19.1/js/mdb.min.js"></script>
<script src="app.js"></script>
</body>
</html>
If I understand it correctly you are attaching some events to every item on the list to allow the drag&drop functionality.
The new elements don't have those events attached to them, though. You need to watch for new elements and initialize the drag&drop functionality for them too.
NOTICE: Heretic Monkey has linked to a more comprehensive answer.
You need to warp this piece of into function
const draggables = document.querySelectorAll('.draggable')
draggables.forEach(draggable => {
draggable.addEventListener('dragstart', () => {
draggable.classList.add('dragging')
})
draggable.addEventListener('dragend', () => {
draggable.classList.remove('dragging')
})
})
To
function enableDragDrop() {
const draggables = document.querySelectorAll('.draggable')
draggables.forEach(draggable => {
draggable.addEventListener('dragstart', () => {
draggable.classList.add('dragging')
})
draggable.addEventListener('dragend', () => {
draggable.classList.remove('dragging')
})
})
}
and call the function in addTodo(e) that is called event binding
function addTodo(e) {
....
enableDragDrop();
}
also, call the function on page load show it should bind the drag events by default
....
var form = document.getElementById('addForm');
var itemList = document.getElementById('items');
form.addEventListener('submit', addTodo);
itemList.addEventListener('click', removeItem)
enableDragDrop();
Complete JS.
var form = document.getElementById('addForm');
var itemList = document.getElementById('items');
form.addEventListener('submit', addTodo);
itemList.addEventListener('click', removeItem)
enableDragDrop();
function addTodo(e) {
e.preventDefault()
// get input value
var newTodo = document.getElementById('todo');
// create new li element
var li = document.createElement('li')
//add class draggable property
li.className = 'draggable';
li.draggable = true
// add textnode with input value
li.appendChild(document.createTextNode(newTodo.value))
// delete button
var delBtn = document.createElement('button')
delBtn.className = 'btn btn-danger btn-sm float-right del';
delBtn.appendChild(document.createTextNode('X'))
li.appendChild(delBtn)
itemList.appendChild(li)
newTodo.value = "";
enableDragDrop();
}
function removeItem(e) {
if (e.target.classList.contains('del')) {
if (confirm('Are you sure?')) {
var li = e.target.parentElement
itemList.removeChild(li)
}
}
}
function enableDragDrop() {
const draggables = document.querySelectorAll('.draggable')
draggables.forEach(draggable => {
draggable.addEventListener('dragstart', () => {
draggable.classList.add('dragging')
})
draggable.addEventListener('dragend', () => {
draggable.classList.remove('dragging')
})
})
}
itemList.addEventListener('dragover', (e) => {
e.preventDefault()
const draggable = document.querySelector('.dragging')
const afterElement = getDragAfterElement(draggable, e.clientY)
console.log(afterElement);
if (afterElement == null) {
itemList.appendChild(draggable)
} else {
itemList.insertBefore(draggable, afterElement)
}
})
function getDragAfterElement(draggables, y) {
const draggableElements = [...document.querySelectorAll('.draggable:not(.dragging)')]
console.log('Dragable', draggableElements);
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect()
const offset = y - box.top - box.height / 2
if (offset < 0 && offset > closest.offset) {
return {offset: offset, element: child}
} else {
return closest
}
}, {offset: Number.NEGATIVE_INFINITY}).element
}
Related
so i'm currently trying to build search icon on the header clickable where you're able to search for, in this case, pokemon names, however i keep getting a console error
searchQuery.value undefined
Not sure why because the code looks valid.
Also, when you click on the search icon the icon goes above it and once you hit submit on the input it duplicates. I'm sure it's all connected the bug.
No sure why this is happening and would greatly appreciate any advice to fix this.
let pokemonRepository = (function() {
let pokemonList = [];
// API
let apiUrl = "https://pokeapi.co/api/v2/pokemon/?limit=150";
let searchIcon = $(".btn-outline-secondary");
let modalContainer = $(".modal");
let modalDialog = $(".modal-dialog");
let modalContent = $(".modal-content");
let modalBody = $(".modal-body");
let modalTitle = $(".modal-title");
let modalHeader = $(".modal-header");
let modalClose = $(".btn-close");
let listItemArray = $("li");
function add(pokemon) {
if (
typeof pokemon === "object" &&
"name" in pokemon &&
"detailsUrl" in pokemon
) {
pokemonList.push(pokemon);
} else {
console.error("pokemon is not correct");
}
}
function getAll() {
return pokemonList;
}
// filters through pokemon names
function search(pokemonName) {
return pokemonList.filter((pokemon) => pokemon.name === pokemonName);
}
// Function adds a list of pokemon
function addListItem(pokemon) {
let pokemonDisplay = $(".list-group-horizontal");
// Creates li element
let listItem = $("<li>");
listItem.addClass(
"list-group-item text-center col-sm-6 col-md-4 border border-secondary bg-image img-fluid"
);
// Creates h1 for Pokemon Name
let listTitle = $("<h1>");
listTitle.html(`${pokemon.name}`);
listTitle.addClass("display-6");
// Creates div which holds sprites
let listImg = $("<div>");
loadDetails(pokemon).then(function() {
listImg.append(
`<img src=${pokemon.imageUrlFront} alt="${pokemon.name} sprite"/>`
);
});
let listButton = $("<button>");
listButton.text("show More");
// Added Bootstrap Utility Class
listButton.addClass("mp-2 btn btn-secondary");
listButton.attr("type", "button");
listButton.attr("data-bs-toggle", "modal");
listButton.attr("data-bs-toggle", "#pokemonModal");
listItem.append(listTitle);
listItem.append(listImg);
listItem.append(listButton);
pokemonDisplay.append(listItem);
buttonEvent(listButton, pokemon);
}
function buttonEvent(listButton, pokemon) {
listButton.on("click", () => {
showDetails(pokemon);
});
}
function showDetails(pokemon) {
loadDetails(pokemon).then(() => {
// Clears existing content
modalContainer.empty();
modalTitle.addClass("modal-title h5 col-sml-3");
let pokemonType = {
fire: "text-danger",
grass: "text-success",
water: "text-primary",
electric: "text-warning",
flying: "text-info",
poison: "text-secondary",
};
pokemon.types.forEach((type) =>
modalTitle.addClass(pokemonType[type.type.name])
);
modalTitle.html(`${pokemon.name}`);
modalBody.html(`
Entry: ${pokemon.id}<br>
Height: ${pokemon.height}<br>
Weight: ${pokemon.weight}<br>
Types: ${pokemon.types[0].type.name}`);
if (pokemon.types.length === 2) {
modalBody.innerHTML += `, ${pokemon.types[1].type.name}`;
}
modalBody.innerHTML += `<br>Abilities: ${pokemon.abilities[0]}.ability.name}`;
if (pokemon.abilities.length === 2) {
modalBody.innerHTML += `, ${pokemon.abilities[1]}.ability.name}`;
}
modalBody.append(`<br>
<img src=${pokemon.imageUrlFront} alt="${pokemon.name} front sprite">
<img src=${pokemon.imageUrlBack} alt="${pokemon.name} back sprite">
<br>
`);
modalDialog.append(modalContent);
modalContent.append(modalHeader);
modalHeader.append(modalTitle);
modalHeader.append(modalClose);
modalContent.append(modalBody);
modalContainer.append(modalDialog);
});
modalContainer.modal("show");
}
modalContainer.on("shown.bs.modal", () => {
// Jquery eventlistener
modalClose.on("click", () => {
modalContainer.removeClass("fade");
modalContainer.modal("hide");
listItemArray[0].children().click();
});
});
searchIcon.on("click", () => {
// fetching .d-flex class in form
let bodyHeader = $(".d-flex");
// returns the number of child elements
if (bodyHeader.length === 1) {
//creates input element
let searchQuery = $("<input>");
searchQuery.attr("placeholder", "Pokemon Name");
searchQuery.attr("type", "search");
searchQuery.attr("aria-label", "search Pokemon Name");
searchQuery.addClass("form-control my-3 ps-2 col-sm");
searchIcon.blur();
searchQuery.focus();
bodyHeader.append(searchQuery);
searchQuery.on("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
searchQuery.value =
searchQuery.value.charAt(0).toUpperCase() +
searchQuery.value.slice(1);
for (let i = 0; i < listItemArray.length; i++) {
if (
902 >
listItemArray[i].children().last().getBoundingClientRect()[
"top"
] &&
listItemArray[i].children().last().getBoundingClientRect()[
"top"
] > 42
) {
listItemArray[i].children().last().click();
}
}
for (let i = 0; i < listItemArray.length; i++) {
if (listItemArray[i].text().split("\n")[0] === searchQuery.value) {
setTimeout(function() {
listItemArray[i].children().last().click();
}, 5);
}
}
}
});
}
});
// Fetches data from API
function loadList() {
return fetch(apiUrl)
.then(function(response) {
return response.json();
})
.then(function(json) {
json.results.forEach((item) => {
let pokemon = {
name: item.name.charAt(0).toUpperCase() + item.name.slice(1),
detailsUrl: item.url,
};
add(pokemon);
});
})
.catch(function(error) {
console.error(error);
});
}
function loadDetails(item) {
let url = item.detailsUrl;
return fetch(url)
.then(function(response) {
return response.json();
})
.then(function(details) {
item.imageUrlFront = details.sprites.front_default;
item.imageUrlBack = details.sprites.back_default;
item.id = details.id;
item.height = details.height;
item.weight = details.weight;
item.types = details.types;
item.abilities = details.abilities;
})
.catch(function(error) {
console.error(error);
});
}
return {
add: add,
getAll: getAll,
addListItem: addListItem,
search: search,
showDetails: showDetails,
loadList: loadList,
loadDetails: loadDetails,
buttonEvent: buttonEvent,
};
})();
pokemonRepository.loadList().then(function() {
pokemonRepository.getAll().forEach(function(pokemon) {
pokemonRepository.addListItem(pokemon);
});
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="The Pokédex is a simple encyclopedia of Pokémon and their characteristics." />
<link rel="shortcut icon" href="img/favicon.png" type="image/x-icon" />
<title>Pokédex App</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons#1.8.3/font/bootstrap-icons.css" />
<link rel="stylesheet" href="/dist/style.production.css" />
</head>
<body>
<nav class="navbar navbar-expand-lg sticky-top navbar-light bg-light">
<div class="container-fluid">
<a href="#home" class="navbar-brand">
<img src="img/ball.png" width="30" height="24" alt="" class="d-inline-block align-text-top" /><span class="text-uppercase font-weight-bold text-secondary">Pokèdex</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarTogglerDemo01" aria-controls="navbarTogglerDemo01" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#home">Home</a
>
</li>
<li class="nav-item">
<a class="nav-link" href="#about">About</a>
</li>
</ul>
</li>
</ul>
<form class="d-flex row" role="search">
<!-- <input
class="form-control me-2"
placeholder="Pokemon Name"
aria-label="Search"
/> -->
<button class="btn btn-outline-secondary" type="submit">
<i class="bi bi-search"></i>
</button>
</form>
</div>
</div>
</nav>
<p class="fw-bold position-absolute top-10 start-50 text-center text-danger"></p>
<!-- Pokemon Display -->
<div class="container">
<ul class="list-group list-group-horizontal flex-fill row mt-4"></ul>
</div>
<!-- Display Ends Here -->
<div class="modal fade" id="pokemonModal" tabindex="-1" role="dialog" aria-labelledby="pokemonModalLabel" aria-hidden="true">
<div class="modal-dialog pt-5 text-center" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title col-sm-3" id="pokemonModalLabel"></h5>
<button type="button" class="btn-close me-3" data-dismiss="modal" aria-label="Close" aria-hidden="true"></button>
</div>
<!-- Content is dynamically created using jquery -->
<div class="modal-body"></div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/#popperjs/core#2.11.5/dist/umd/popper.min.js" integrity="sha384-Xe+8cL9oJa6tN/veChSP7q+mnSPaj5Bcu9mPX5F5xIGE0DVittaqT5lorf0EI7Vk" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/js/bootstrap.min.js" integrity="sha384-ODmDIVzN+pFdexxHEHFBQH3/9/vQ9uori45z4JjnFsRydbmQbmL5t1tQ0culUzyK" crossorigin="anonymous"></script>
<script src="/src/js/scripts.js"></script>
<script src="/src/js/promise-polyfill.js"></script>
<script src="/src/js/fetch-pollyfill.js"></script>
</body>
</html>
Since searchQuery is a jQuery object, you need to use its .val() method to set and get the value. So change.
searchQuery.value =
searchQuery.value.charAt(0).toUpperCase() +
searchQuery.value.slice(1);
to
searchQuery.val((i, value) => value[0].toUpperCase() + value.slice(1));
whenever I click my "show more" button my modal pops up however when I click my X button on the left corner it doesn't close like I expect it to. It does respond to clicking outside of the box and the "esc" key.
I believe the issue is happening on modalClose.on() but everything looks fine to me.
Any suggestions as to why this might be happening?
let pokemonRepository = (function() {
let pokemonList = [];
// API
let apiUrl = "https://pokeapi.co/api/v2/pokemon/?limit=150";
let modalContainer = $(".modal");
let modalDialog = $(".modal-dialog");
let modalContent = $(".modal-content");
let modalBody = $(".modal-body");
let modalTitle = $(".modal-title");
let modalHeader = $(".modal-header");
let modalClose = $(".btn-close");
let searchIcon = $(".search-icon");
let listItemArray = $("li");
function add(pokemon) {
if (
typeof pokemon === "object" &&
"name" in pokemon &&
"detailsUrl" in pokemon
) {
pokemonList.push(pokemon);
} else {
console.error("pokemon is not correct");
}
}
function getAll() {
return pokemonList;
}
// filters through pokemon names
function search(pokemonName) {
return pokemonList.filter((pokemon) => pokemon.name === pokemonName);
}
// Function adds a list of pokemon
function addListItem(pokemon) {
let pokemonDisplay = $(".list-group-horizontal");
// Creates li element
let listItem = $("<li>");
listItem.addClass(
"list-group-item text-center col-sm-6 col-md-4 border border-secondary bg-image img-fluid"
);
// Creates h1 for Pokemon Name
let listTitle = $("<h1>");
listTitle.html(`${pokemon.name}`);
listTitle.addClass("display-6");
// Creates div which holds sprites
let listImg = $("<div>");
loadDetails(pokemon).then(function() {
listImg.append(
`<img src=${pokemon.imageUrlFront} alt="${pokemon.name} sprite"/>`
);
});
let listButton = $("<button>");
listButton.text("show More");
// Added Bootstrap Utility Class
listButton.addClass("mp-2 btn btn-secondary");
listButton.attr("type", "button");
listButton.attr("data-bs-toggle", "modal");
listButton.attr("data-bs-toggle", "#pokemonModal");
listItem.append(listTitle);
listItem.append(listImg);
listItem.append(listButton);
pokemonDisplay.append(listItem);
buttonEvent(listButton, pokemon);
}
function buttonEvent(listButton, pokemon) {
listButton.on("click", () => {
showDetails(pokemon);
});
}
function showDetails(pokemon) {
loadDetails(pokemon).then(() => {
// Clears existing content
modalContainer.empty();
modalTitle.addClass("modal-title h5 col-sml-3");
let pokemonType = {
fire: "text-danger",
grass: "text-success",
water: "text-primary",
electric: "text-warning",
flying: "text-info",
poison: "text-secondary",
};
pokemon.types.forEach((type) =>
modalTitle.addClass(pokemonType[type.type.name])
);
modalTitle.html(`${pokemon.name}`);
modalBody.html(`
Entry: ${pokemon.id}<br>
Height: ${pokemon.height}<br>
Weight: ${pokemon.weight}<br>
Types: ${pokemon.types[0].type.name}`);
if (pokemon.types.length === 2) {
modalBody.innerHTML += `, ${pokemon.types[1].type.name}`;
}
modalBody.innerHTML += `<br>Abilities: ${pokemon.abilities[0]}.ability.name}`;
if (pokemon.abilities.length === 2) {
modalBody.innerHTML += `, ${pokemon.abilities[1]}.ability.name}`;
}
modalBody.append(`<br>
<img src=${pokemon.imageUrlFront} alt="${pokemon.name} front sprite">
<img src=${pokemon.imageUrlBack} alt="${pokemon.name} back sprite">
<br>
`);
modalDialog.append(modalContent);
modalContent.append(modalHeader);
modalHeader.append(modalTitle);
modalHeader.append(modalClose);
modalContent.append(modalBody);
modalContainer.append(modalDialog);
});
modalContainer.modal("show");
}
// Jquery eventlistener
modalClose.on("click", () => {
modalContainer.removeClass("fade");
modalContainer.show();
listItemArray[0].lastChild.click();
});
searchIcon.on("click", () => {
// fetching .d-flex class in form
let bodyHeader = $(".d-flex");
// returns the number of child elements
if (bodyHeader.lastChild.length === 1) {
//creates input element
let searchQuery = $("<input>");
searchQuery.attr("placeholder", "Pokemon Name");
searchQuery.attr("type", "search");
searchQuery.attr("aria-label", "search Pokemon Name");
searchQuery.addClass("form-control my-3 ps-2 col-sm");
searchIcon.blur();
searchQuery.focus();
bodyHeader.append(searchQuery);
searchQuery.on("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
searchQuery.value =
searchQuery.value.charAt(0).toUpperCase() +
searchQuery.value.slice(1);
for (let i = 0; i < listItemArray.length; i++) {
if (
902 > listItemArray[i].lastChild.getBoundingClientRect()["top"] &&
listItemArray[i].lastChild.getBoundingClientRect()["top"] > 42
) {
listItemArray[i].lastChild.click();
}
}
for (let i = 0; i < listItemArray.length; i++) {
if (
listItemArray[i].innerText.split("\n")[0] === searchQuery.value
) {
setTimeout(function() {
listItemArray[i].lastChild.click();
}, 5);
}
}
}
});
}
});
// Fetches data from API
function loadList() {
return fetch(apiUrl)
.then(function(response) {
return response.json();
})
.then(function(json) {
json.results.forEach((item) => {
let pokemon = {
name: item.name.charAt(0).toUpperCase() + item.name.slice(1),
detailsUrl: item.url,
};
add(pokemon);
});
})
.catch(function(error) {
console.error(error);
});
}
function loadDetails(item) {
let url = item.detailsUrl;
return fetch(url)
.then(function(response) {
return response.json();
})
.then(function(details) {
item.imageUrlFront = details.sprites.front_default;
item.imageUrlBack = details.sprites.back_default;
item.id = details.id;
item.height = details.height;
item.weight = details.weight;
item.types = details.types;
item.abilities = details.abilities;
})
.catch(function(error) {
console.error(error);
});
}
return {
add: add,
getAll: getAll,
addListItem: addListItem,
search: search,
showDetails: showDetails,
loadList: loadList,
loadDetails: loadDetails,
buttonEvent: buttonEvent,
};
})();
pokemonRepository.loadList().then(function() {
pokemonRepository.getAll().forEach(function(pokemon) {
pokemonRepository.addListItem(pokemon);
});
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="The Pokédex is a simple encyclopedia of Pokémon and their characteristics." />
<link rel="shortcut icon" href="img/favicon.png" type="image/x-icon" />
<title>Pokédex App</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons#1.8.3/font/bootstrap-icons.css" />
<link rel="stylesheet" href="/dist/style.production.css" />
</head>
<body>
<nav class="navbar navbar-expand-lg sticky-top navbar-light bg-light">
<div class="container-fluid">
<a href="#home" class="navbar-brand">
<img src="img/ball.png" width="30" height="24" alt="" class="d-inline-block align-text-top" /><span class="text-uppercase font-weight-bold text-secondary">Pokèdex</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarTogglerDemo01" aria-controls="navbarTogglerDemo01" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#home">Home</a
>
</li>
<li class="nav-item">
<a class="nav-link" href="#about">About</a>
</li>
</ul>
</li>
</ul>
<form class="d-flex" role="search">
<input class="form-control me-2" placeholder="Pokemon Name" aria-label="Search" />
<button class="btn btn-outline-secondary" type="submit">
Search
</button>
</form>
</div>
</div>
</nav>
<p class="fw-bold position-absolute top-10 start-50 text-center text-danger"></p>
<!-- Pokemon Display -->
<div class="container">
<ul class="list-group list-group-horizontal flex-fill row mt-4"></ul>
</div>
<!-- Display Ends Here -->
<div class="modal fade" id="pokemonModal" tabindex="-1" role="dialog" aria-labelledby="pokemonModalLabel" aria-hidden="true">
<div class="modal-dialog pt-5 text-center" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title col-sm-3" id="pokemonModalLabel"></h5>
<button type="button" class="btn-close me-3" data-dismiss="modal" aria-label="Close" aria-hidden="true"></button>
</div>
<!-- Content is dynamically created using jquery -->
<div class="modal-body"></div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/#popperjs/core#2.11.5/dist/umd/popper.min.js" integrity="sha384-Xe+8cL9oJa6tN/veChSP7q+mnSPaj5Bcu9mPX5F5xIGE0DVittaqT5lorf0EI7Vk" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/js/bootstrap.min.js" integrity="sha384-ODmDIVzN+pFdexxHEHFBQH3/9/vQ9uori45z4JjnFsRydbmQbmL5t1tQ0culUzyK" crossorigin="anonymous"></script>
<script src="/src/js/scripts.js"></script>
<script src="/src/js/promise-polyfill.js"></script>
<script src="/src/js/fetch-pollyfill.js"></script>
</body>
</html>
Because for some reason the modal is being reconstructed I assume all the event handlers bootstrap configs for it are gone. So you want to catch click on close, you need to do that after it is shown. This can be done using the event bootstrap provides.
modalContainer.on('shown.bs.modal', event => {
// Jquery eventlistener
modalClose.on("click", () => {
modalContainer.modal("hide");
});
})
let pokemonRepository = (function() {
let pokemonList = [];
// API
let apiUrl = "https://pokeapi.co/api/v2/pokemon/?limit=150";
let modalContainer = $(".modal");
let modalDialog = $(".modal-dialog");
let modalContent = $(".modal-content");
let modalBody = $(".modal-body");
let modalTitle = $(".modal-title");
let modalHeader = $(".modal-header");
let modalClose = $(".btn-close");
let searchIcon = $(".search-icon");
let listItemArray = $("li");
function add(pokemon) {
if (
typeof pokemon === "object" &&
"name" in pokemon &&
"detailsUrl" in pokemon
) {
pokemonList.push(pokemon);
} else {
console.error("pokemon is not correct");
}
}
function getAll() {
return pokemonList;
}
// filters through pokemon names
function search(pokemonName) {
return pokemonList.filter((pokemon) => pokemon.name === pokemonName);
}
// Function adds a list of pokemon
function addListItem(pokemon) {
let pokemonDisplay = $(".list-group-horizontal");
// Creates li element
let listItem = $("<li>");
listItem.addClass(
"list-group-item text-center col-sm-6 col-md-4 border border-secondary bg-image img-fluid"
);
// Creates h1 for Pokemon Name
let listTitle = $("<h1>");
listTitle.html(`${pokemon.name}`);
listTitle.addClass("display-6");
// Creates div which holds sprites
let listImg = $("<div>");
loadDetails(pokemon).then(function() {
listImg.append(
`<img src=${pokemon.imageUrlFront} alt="${pokemon.name} sprite"/>`
);
});
let listButton = $("<button>");
listButton.text("show More");
// Added Bootstrap Utility Class
listButton.addClass("mp-2 btn btn-secondary");
listButton.attr("type", "button");
listButton.attr("data-bs-toggle", "modal");
listButton.attr("data-bs-toggle", "#pokemonModal");
listItem.append(listTitle);
listItem.append(listImg);
listItem.append(listButton);
pokemonDisplay.append(listItem);
buttonEvent(listButton, pokemon);
}
function buttonEvent(listButton, pokemon) {
listButton.on("click", () => {
showDetails(pokemon);
});
}
function showDetails(pokemon) {
loadDetails(pokemon).then(() => {
// Clears existing content
modalContainer.empty();
modalTitle.addClass("modal-title h5 col-sml-3");
let pokemonType = {
fire: "text-danger",
grass: "text-success",
water: "text-primary",
electric: "text-warning",
flying: "text-info",
poison: "text-secondary",
};
pokemon.types.forEach((type) =>
modalTitle.addClass(pokemonType[type.type.name])
);
modalTitle.html(`${pokemon.name}`);
modalBody.html(`
Entry: ${pokemon.id}<br>
Height: ${pokemon.height}<br>
Weight: ${pokemon.weight}<br>
Types: ${pokemon.types[0].type.name}`);
if (pokemon.types.length === 2) {
modalBody.innerHTML += `, ${pokemon.types[1].type.name}`;
}
modalBody.innerHTML += `<br>Abilities: ${pokemon.abilities[0]}.ability.name}`;
if (pokemon.abilities.length === 2) {
modalBody.innerHTML += `, ${pokemon.abilities[1]}.ability.name}`;
}
modalBody.append(`<br>
<img src=${pokemon.imageUrlFront} alt="${pokemon.name} front sprite">
<img src=${pokemon.imageUrlBack} alt="${pokemon.name} back sprite">
<br>
`);
modalDialog.append(modalContent);
modalContent.append(modalHeader);
modalHeader.append(modalTitle);
modalHeader.append(modalClose);
modalContent.append(modalBody);
modalContainer.append(modalDialog);
});
modalContainer.on('shown.bs.modal', event => {
// Jquery eventlistener
modalClose.on("click", () => {
modalContainer.modal("hide");
});
})
modalContainer.modal("show");
}
searchIcon.on("click", () => {
// fetching .d-flex class in form
let bodyHeader = $(".d-flex");
// returns the number of child elements
if (bodyHeader.lastChild.length === 1) {
//creates input element
let searchQuery = $("<input>");
searchQuery.attr("placeholder", "Pokemon Name");
searchQuery.attr("type", "search");
searchQuery.attr("aria-label", "search Pokemon Name");
searchQuery.addClass("form-control my-3 ps-2 col-sm");
searchIcon.blur();
searchQuery.focus();
bodyHeader.append(searchQuery);
searchQuery.on("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
searchQuery.value =
searchQuery.value.charAt(0).toUpperCase() +
searchQuery.value.slice(1);
for (let i = 0; i < listItemArray.length; i++) {
if (
902 > listItemArray[i].lastChild.getBoundingClientRect()["top"] &&
listItemArray[i].lastChild.getBoundingClientRect()["top"] > 42
) {
listItemArray[i].lastChild.click();
}
}
for (let i = 0; i < listItemArray.length; i++) {
if (
listItemArray[i].innerText.split("\n")[0] === searchQuery.value
) {
setTimeout(function() {
listItemArray[i].lastChild.click();
}, 5);
}
}
}
});
}
});
// Fetches data from API
function loadList() {
return fetch(apiUrl)
.then(function(response) {
return response.json();
})
.then(function(json) {
json.results.forEach((item) => {
let pokemon = {
name: item.name.charAt(0).toUpperCase() + item.name.slice(1),
detailsUrl: item.url,
};
add(pokemon);
});
})
.catch(function(error) {
console.error(error);
});
}
function loadDetails(item) {
let url = item.detailsUrl;
return fetch(url)
.then(function(response) {
return response.json();
})
.then(function(details) {
item.imageUrlFront = details.sprites.front_default;
item.imageUrlBack = details.sprites.back_default;
item.id = details.id;
item.height = details.height;
item.weight = details.weight;
item.types = details.types;
item.abilities = details.abilities;
})
.catch(function(error) {
console.error(error);
});
}
return {
add: add,
getAll: getAll,
addListItem: addListItem,
search: search,
showDetails: showDetails,
loadList: loadList,
loadDetails: loadDetails,
buttonEvent: buttonEvent,
};
})();
pokemonRepository.loadList().then(function() {
pokemonRepository.getAll().forEach(function(pokemon) {
pokemonRepository.addListItem(pokemon);
});
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="The Pokédex is a simple encyclopedia of Pokémon and their characteristics." />
<link rel="shortcut icon" href="img/favicon.png" type="image/x-icon" />
<title>Pokédex App</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons#1.8.3/font/bootstrap-icons.css" />
<link rel="stylesheet" href="/dist/style.production.css" />
</head>
<body>
<nav class="navbar navbar-expand-lg sticky-top navbar-light bg-light">
<div class="container-fluid">
<a href="#home" class="navbar-brand">
<img src="img/ball.png" width="30" height="24" alt="" class="d-inline-block align-text-top" /><span class="text-uppercase font-weight-bold text-secondary">Pokèdex</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarTogglerDemo01" aria-controls="navbarTogglerDemo01" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#home">Home</a
>
</li>
<li class="nav-item">
<a class="nav-link" href="#about">About</a>
</li>
</ul>
</li>
</ul>
<form class="d-flex" role="search">
<input class="form-control me-2" placeholder="Pokemon Name" aria-label="Search" />
<button class="btn btn-outline-secondary" type="submit">
Search
</button>
</form>
</div>
</div>
</nav>
<p class="fw-bold position-absolute top-10 start-50 text-center text-danger"></p>
<!-- Pokemon Display -->
<div class="container">
<ul class="list-group list-group-horizontal flex-fill row mt-4"></ul>
</div>
<!-- Display Ends Here -->
<div class="modal fade" id="pokemonModal" tabindex="-1" role="dialog" aria-labelledby="pokemonModalLabel" aria-hidden="true">
<div class="modal-dialog pt-5 text-center" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title col-sm-3" id="pokemonModalLabel"></h5>
<button type="button" class="btn-close me-3" data-dismiss="modal" aria-label="Close" aria-hidden="true"></button>
</div>
<!-- Content is dynamically created using jquery -->
<div class="modal-body"></div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/#popperjs/core#2.11.5/dist/umd/popper.min.js" integrity="sha384-Xe+8cL9oJa6tN/veChSP7q+mnSPaj5Bcu9mPX5F5xIGE0DVittaqT5lorf0EI7Vk" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/js/bootstrap.min.js" integrity="sha384-ODmDIVzN+pFdexxHEHFBQH3/9/vQ9uori45z4JjnFsRydbmQbmL5t1tQ0culUzyK" crossorigin="anonymous"></script>
<script src="/src/js/scripts.js"></script>
<script src="/src/js/promise-polyfill.js"></script>
<script src="/src/js/fetch-pollyfill.js"></script>
</body>
</html>
I got multiple divs with the class tab <div class="tab">i am div 1</div><div class="tab">i am div 2 </div>. Inside i have some input field anon the bottom I got a next and back button
<div class="funnel-buttons text-right">
<button type="button" class="icon-btn" id="prevBtn">Back</button>
<button type="button" class="icon-btn" id="nextBtn" onclick="nextTab()">Next</button>
</div>
now after clicking next or back button I want the next or prev div to shown up so I made a JS function when tab[0] is displayed and clicking next I should dissappear.
const tab = document.getElementsByClassName('tab');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
tab[0].style.display = 'block';
tab[1].style.display = 'none';
function nextTab () {
if(tab[0].style.display == 'block') {
tab[1].style.display = 'block';
tab[0].style.display = 'none';
}
}
But when I do so all the tabs disappear and nothing is shown.
Where is the error?
you can solve like this
const tab = document.getElementsByClassName('tab');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
function nextTab() {
const currentTab = document.querySelector('.show');
const tabArray = Array.from(tab);
const currentIndex = tabArray.indexOf(currentTab);
console.log(currentIndex);
currentTab.classList.remove('show');
currentTab.classList.add('hide');
if (tabArray.length > currentIndex + 1) {
tabArray[currentIndex + 1].classList.remove('hide');
tabArray[currentIndex + 1].classList.add('show');
} else {
// to return first tab at the end
tabArray[0].classList.remove('hide');
tabArray[0].classList.add('show');
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.show {
display: block;
}
.hide {
display: none;
}
</style>
</head>
<body>
<div class="tab show">1</div>
<div class="tab hide">2</div>
<div class="tab hide">3</div>
<div class="funnel-buttons text-right">
<button type="button" class="icon-btn" id="prevBtn">Back</button>
<button type="button" class="icon-btn" id="nextBtn"
onclick="nextTab()">Next</button>
</div>
<script src="app.js"></script>
</body>
</html>
You can introduce a variable to capture which is the current tab - in my code currentVisible. Based on that you can manipulate which tab you want to see on the UI. Also you can use .forEach() to iterate through all the <div> elements which has tab class added. Finally you can change the visibility with ternary operator like i === currentVisible ? 'block' : 'none'.
Try as the following:
const tab = document.getElementsByClassName('tab');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
let currentVisible = 0;
const handleTab = n => {
if (currentVisible + n >= 0 && currentVisible + n < tab.length) {
currentVisible += n;
Array.prototype.forEach.call(tab, (e, i) => {
tab[i].style.display = i === currentVisible ? 'block' : 'none';
});
}
}
handleTab(0);
<div class="funnel-buttons text-right">
<button type="button" class="icon-btn" id="prevBtn" onclick="handleTab(-1)">Back</button>
<button type="button" class="icon-btn" id="nextBtn" onclick="handleTab(1)">Next</button>
</div>
<div class="tab">I am div 1</div>
<div class="tab">I am div 2 </div>
<div class="tab">I am div 3</div>
<div class="tab">I am div 4 </div>
I hope this helps!
Please help me with this.
What's wrong? Thanks!
I had the same problem and it fixed adding this js part which I extracted from here
$(document).ready(function() {
$('.stepper').activateStepper();
})
function validateStepOne() {
// Extract the checked checkboxes from the first step
if($('.step').first().find('input[type="checkbox"]:checked').length)
return true;
return false;
}
function validateStepThree() {
var validation = true;
if($('.step:nth-child(3) input[type="text"]').val().indexOf('materialize') === -1)
validation = false;
if($('.step:nth-child(3) input[type="checkbox"]:checked').length === 0)
validation = false;
return validation;
}
function nextStepThreeHandler() {
if(validateStepThree())
$('.stepper').nextStep();
else {
$('.stepper ').destroyFeedback(); $('.stepper').getStep($('.stepper').getActiveStep()).addClass('wrong');
}
}
/* Materializecss Stepper - By Kinark 2016
// https://github.com/Kinark/Materialize-stepper
// JS v2.1.3
*/
var validation = $.isFunction($.fn.valid) ? 1 : 0;
$.fn.isValid = function() {
if(validation){
return this.valid();
} else {
return true;
}
};
if (validation) {
$.validator.setDefaults({
errorClass: 'invalid',
validClass: "valid",
errorPlacement: function (error, element) {
if(element.is(':radio') || element.is(':checkbox')) {
error.insertBefore($(element).parent());
} else {
error.insertAfter(element); // default error placement.
// element.closest('label').data('error', error);
// element.next().attr('data-error', error);
}
},
success: function (element) {
if(!$(element).closest('li').find('label.invalid:not(:empty)').length){
$(element).closest('li').removeClass('wrong');
}
}
});
// When parallel stepper is defined we need to consider invisible and
// hidden fields
if($('.stepper.parallel').length) $.validator.setDefaults({ignore:''});
}
$.fn.getActiveStep = function() {
var active = this.find('.step.active');
return $(this.children('.step:visible')).index($(active))+1;
};
$.fn.activateStep = function(callback) {
if($(this).hasClass('step')) return;
var stepper = $(this).closest('ul.stepper');
stepper.find('>li').removeAttr("data-last");
if(window.innerWidth < 993 || !stepper.hasClass('horizontal')) {
$(this).addClass("step").stop().slideDown(400, function(){
$(this).css({'height':'auto', 'margin-bottom': '','display': 'inherit'});if(callback)callback();
stepper.find('>li.step').last().attr('data-last', 'true');
});
} else {
$(this).addClass("step").stop().css({'width':'0%','display': 'inherit'}).animate({width:'100%'}, 400, function(){
$(this).css({'height':'auto', 'margin-bottom': '','display': 'inherit'});if(callback)callback();
stepper.find('>li.step').last().attr('data-last', 'true');
});
}
};
$.fn.deactivateStep = function(callback) {
if(!$(this).hasClass('step')) return;
var stepper = $(this).closest('ul.stepper');
stepper.find('>li').removeAttr("data-last");
if(window.innerWidth < 993 || !stepper.hasClass('horizontal')) {
$(this).stop().css({'transition':'none', '-webkit-transition':'margin-bottom none'}).slideUp(400, function(){
$(this).removeClass("step").css({'height':'auto','margin-bottom':'','transition':'margin-bottom .4s','-webkit-transition':'margin-bottom .4s'});
if(callback)callback();
stepper.find('>li').removeAttr("data-last");
stepper.find('>li.step').last().attr('data-last', 'true');
});
} else {
$(this).stop().animate({width:'0%'}, 400, function(){
$(this).removeClass("step").hide().css({'height':'auto', 'margin-bottom': '','display': 'none', 'width': ''});
if(callback)callback();
stepper.find('>li.step').last().attr('data-last', 'true');
});
}
};
$.fn.showError = function(error) {
if(validation) {
var name = this.attr('name');
var form = this.closest('form');
var obj = {};
obj[name] = error;
form.validate().showErrors(obj);
this.closest('li').addClass('wrong');
} else {
this.removeClass('valid').addClass('invalid');
this.next().attr('data-error', error);
}
};
$.fn.activateFeedback = function() {
var active = this.find('.step.active:not(.feedbacking)').addClass('feedbacking').find('.step-content');
active.prepend('<div class="wait-feedback"> <div class="preloader-wrapper active"> <div class="spinner-layer spinner-blue"> <div class="circle-clipper left"> <div class="circle"></div></div><div class="gap-patch"> <div class="circle"></div></div><div class="circle-clipper right"> <div class="circle"></div></div></div><div class="spinner-layer spinner-red"> <div class="circle-clipper left"> <div class="circle"></div></div><div class="gap-patch"> <div class="circle"></div></div><div class="circle-clipper right"> <div class="circle"></div></div></div><div class="spinner-layer spinner-yellow"> <div class="circle-clipper left"> <div class="circle"></div></div><div class="gap-patch"> <div class="circle"></div></div><div class="circle-clipper right"> <div class="circle"></div></div></div><div class="spinner-layer spinner-green"> <div class="circle-clipper left"> <div class="circle"></div></div><div class="gap-patch"> <div class="circle"></div></div><div class="circle-clipper right"> <div class="circle"></div></div></div></div></div>');
};
$.fn.destroyFeedback = function() {
var active = this.find('.step.active.feedbacking');
if(active) {
active.removeClass('feedbacking');
active.find('.wait-feedback').remove();
}
return true;
};
$.fn.resetStepper = function(step) {
if(!step) step = 1;
var form = $(this).closest('form');
$(form)[0].reset();
Materialize.updateTextFields();
return $(this).openStep(step);
};
$.fn.submitStepper = function(step) {
var form = this.closest('form');
if(form.isValid()) {
form.submit();
}
};
$.fn.nextStep = function(callback, activefb, e) {
var stepper = this;
var settings = $(stepper).data('settings');
var form = this.closest('form');
var active = this.find('.step.active');
var next = $(this.children('.step:visible')).index($(active))+2;
var feedback = active.find('.next-step').length > 1 ? (e ? $(e.target).data("feedback") : undefined) : active.find('.next-step').data("feedback");
// If the stepper is parallel, we want to validate the input of the current active step. Not all elements.
if((settings.parallel && $(active).validateStep()) || (!settings.parallel && form.isValid())) {
if(feedback && activefb) {
if(settings.showFeedbackLoader) stepper.activateFeedback();
return window[feedback].call();
}
active.removeClass('wrong').addClass('done');
this.openStep(next, callback);
return this.trigger('nextstep');
} else {
return active.removeClass('done').addClass('wrong');
}
};
$.fn.prevStep = function(callback) {
var active = this.find('.step.active');
if(active.hasClass('feedbacking')) return;
var prev = $(this.children('.step:visible')).index($(active));
active.removeClass('wrong');
this.openStep(prev, callback);
return this.trigger('prevstep');
};
$.fn.openStep = function(step, callback) {
var settings = $(this).closest('ul.stepper').data('settings');
var $this = this;
var step_num = step - 1;
step = this.find('.step:visible:eq('+step_num+')');
if(step.hasClass('active')) return;
var active = this.find('.step.active');
var next;
var prev_active = next = $(this.children('.step:visible')).index($(active));
var order = step_num > prev_active ? 1 : 0;
if(active.hasClass('feedbacking')) $this.destroyFeedback();
active.closeAction(order);
step.openAction(order, function(){
if(settings.autoFocusInput) step.find('input:enabled:visible:first').focus();
$this.trigger('stepchange').trigger('step'+(step_num+1));
if(step.data('event')) $this.trigger(step.data('event'));
if(callback)callback();
});
};
$.fn.closeAction = function(order, callback) {
var closable = this.removeClass('active').find('.step-content');
if(window.innerWidth < 993 || !this.closest('ul').hasClass('horizontal')) {
closable.stop().slideUp(300,"easeOutQuad", callback);
} else {
if(order==1) {
closable.animate({left: '-100%'},function(){closable.css({display: 'none', left: '0%'}, callback);});
} else {
closable.animate({left: '100%'},function(){closable.css({display: 'none', left: '0%'}, callback);});
}
}
};
$.fn.openAction = function(order, callback) {
var openable = this.removeClass('done').addClass('active').find('.step-content');
if(window.innerWidth < 993 || !this.closest('ul').hasClass('horizontal')) {
openable.slideDown(300,"easeOutQuad", callback);
} else {
if(order==1) {
openable.css({left: '100%', display: 'block'}).animate({left: '0%'}, callback);
} else {
openable.css({left: '-100%', display: 'block'}).animate({left: '0%'}, callback);
}
}
};
$.fn.activateStepper = function(options) {
var settings = $.extend({
linearStepsNavigation: true,
autoFocusInput: true,
showFeedbackLoader: true,
autoFormCreation: true,
parallel: false // By default we don't assume the stepper is parallel
}, options);
$(document).on('click', function(e){
if(!$(e.target).parents(".stepper").length){
$('.stepper.focused').removeClass('focused');
}
});
$(this).each(function(){
var $stepper = $(this);
if(!$stepper.parents("form").length && settings.autoFormCreation) {
var method = $stepper.data('method');
var action = $stepper.data('action');
var method = (method ? method : "GET");
action = (action ? action : "?");
$stepper.wrap( '<form action="'+action+'" method="'+method+'"></form>' );
}
$stepper.data('settings', {linearStepsNavigation: settings.linearStepsNavigation,autoFocusInput: settings.autoFocusInput,showFeedbackLoader:settings.showFeedbackLoader, parallel:$stepper.hasClass('parallel')});
$stepper.find('li.step.active').openAction(1);
$stepper.find('>li').removeAttr("data-last");
$stepper.find('>li.step').last().attr('data-last', 'true');
$stepper.on("click", '.step:not(.active)', function () {
var object = $($stepper.children('.step:visible')).index($(this));
if($stepper.data('settings').parallel && validation) { // Invoke parallel stepper behaviour
$(this).addClass('temp-active');
$stepper.validatePreviousSteps()
$stepper.openStep(object + 1);
$(this).removeClass('temp-active');
} else if(!$stepper.hasClass('linear')) {
$stepper.openStep(object+1);
} else if(settings.linearStepsNavigation) {
var active = $stepper.find('.step.active');
if($($stepper.children('.step:visible')).index($(active))+1 == object) {
$stepper.nextStep(undefined, true, undefined);
} else if ($($stepper.children('.step:visible')).index($(active))-1 == object) {
$stepper.prevStep(undefined);
}
}
}).on("click", '.next-step', function(e) {
e.preventDefault();
$stepper.nextStep(undefined, true, e);
}).on("click", '.previous-step', function(e) {
e.preventDefault();
$stepper.prevStep(undefined);
}).on("click", "button:submit:not(.next-step, .previous-step)", function (e) {
e.preventDefault();
feedback = e ? $(e.target).data("feedback") : undefined;
var form = $stepper.closest('form');
if(form.isValid()) {
if(feedback) {
stepper.activateFeedback();
return window[feedback].call();
}
form.submit();
}
}).on("click", function () {
$('.stepper.focused').removeClass('focused');
$(this).addClass('focused');
});
});
};
/**
* Return the step element on given index.
*
* #param step, index of the step to be returned
* #returns {*}, the step requested
*/
$.fn.getStep = function(step) {
var settings = $(this).closest('ul.stepper').data('settings');
var $this = this;
var step_num = step - 1;
step = this.find('.step:visible:eq('+step_num+')');
return step;
};
/**
* Run validation over all previous steps from the steps this
* function is called on.
*/
$.fn.validatePreviousSteps = function() {
var active = $(this).find('.step.temp-active');
var index = $(this.children('.step')).index($(active));
// We assume that the validator is set to ignore nothing.
$(this.children('.step')).each(function(i) {
if (i >= index) {
$(this).removeClass('wrong done');
} else {
$(this).validateStep();
}
});
};
/**
* Validate the step that this function is called on.
*/
$.fn.validateStep = function() {
var stepper = this.closest('ul.stepper');
var form = this.closest('form');
var step = $(this);
// Retrieve the custom validator for that step if exists.
var validator = step.find('.next-step').data("validator");
if(this.validateStepInput()) { // If initial base validation succeeded go on
if(validator) { // If a custom validator is given also call that validator
if (window[validator].call()) {
step.removeClass('wrong').addClass('done');
return true;
}
else {
step.removeClass('done').addClass('wrong');
return false;
}
}
step.removeClass('wrong').addClass('done');
return true;
} else {
step.removeClass('done').addClass('wrong');
return false;
}
};
/**
* Uses the validation variable set by the stepper constructor
* to run standard validation on the current step.
* #returns {boolean}
*/
$.fn.validateStepInput = function() {
var valid = true;
if (validation) {
// Find all input fields dat need validation in current step.
$(this).find('input.validate').each(function() {
if (!$(this).valid()) {
valid = false;
return false;
}
});
}
return valid;
};
<head>
<!-- Materializecss compiled and minified CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.7/css/materialize.min.css">
<!--Import Google Icon Font-->
<link href="http://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/materialize-stepper#2.1.4/materialize-stepper.css" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<!-- Materializecss compiled and minified JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.7/js/materialize.min.js"></script>
<!-- jQueryValidation Plugin -->
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.15.0/jquery.validate.min.js"></script>
</head>
<body class="container">
<form>
<ul class="stepper parallel horizontal">
<li class="step active">
<div class="step-title waves-effect waves-dark">Step 1</div>
<div class="step-content">
<div class="row">
<div class='form-field col s12'>
<p>Step with custom validation</p>
<span>For this step we want to have a custom validator that checks if if at least one checkbox is checked</span>
<p>
<input name='checkbox1' type="checkbox" class="filled-in"
id="checkbox1" value='checkbox1'/>
<label for="checkbox1">Checkbox 1</label>
</p>
<p>
<input name='checkbox2' type="checkbox" class="filled-in"
id="checkbox2" value='checkbox2'/>
<label for="checkbox2">Checkbox 2</label>
</p>
</div>
</div>
<div class="step-actions">
<button class="waves-effect waves-dark btn next-step" data-validator="validateStepOne">CONTINUE</button>
<button class="waves-effect waves-dark btn-flat previous-step">BACK</button>
</div>
</div>
</li>
<li class="step">
<div class="step-title waves-effect waves-dark">Step 2</div>
<div class="step-content">
<div class="row">
<div class='form-field col s12'>
<p>Step with no custom validation</p>
<div class="input-field col s12">
<input type="text" name="textfield" class="required validate"/>
<label for="textfield">Random textfield</label>
</div>
</div>
</div>
<div class="step-actions">
<button class="waves-effect waves-dark btn next-step">CONTINUE</button>
<button class="waves-effect waves-dark btn-flat previous-step">BACK</button>
</div>
</div>
</li>
<li class="step">
<div class="step-title waves-effect waves-dark">Step 3</div>
<div class="step-content">
<div class="row">
<div class='form-field'>
<p>Step with feedback and custom validation</p>
<p>
<input name='checkbox3' type="checkbox" class="filled-in"
id="checkbox3" value='checkbox3'/>
<label for="checkbox3">Checkbox 3</label>
</p>
<p>
<input name='checkbox4' type="checkbox" class="filled-in"
id="checkbox4" value='checkbox4'/>
<label for="checkbox4">Checkbox 4</label>
</p>
</div>
<div class="input-field col s12">
<input type="text" id="textfield2" name="textfield2" class="required validate"/>
<label for="textfield2">This field should contain the word materialize</label>
</div>
</div>
<div class="step-actions">
<button class="waves-effect waves-dark btn next-step" data-feedback="nextStepThreeHandler" data-validator="validateStepThree">SUBMIT</button>
<button class="waves-effect waves-dark btn-flat previous-step">BACK</button>
</div>
</div>
</li>
<li class="step">
<div class="step-title waves-effect waves-dark">Step 4</div>
<div class="step-content">
<div class="row">
<div class='form-field'>
<p>Submit phase</p>
</div>
</div>
<div class="step-actions">
<input type="submit" class="waves-effect waves-dark btn next-step" value="SUBMIT"/>
<button class="waves-effect waves-dark btn-flat previous-step">BACK</button>
</div>
</div>
</li>
</ul>
</form>
</body>
Bulma dropdown doesn't seem to toggle on click. Below is the code snippet from the documentation:https://bulma.io/documentation/components/dropdown/
<link href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.0/css/bulma.min.css" rel="stylesheet"/>
<div class="dropdown is-active">
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu">
<span>Dropdown button</span>
<span class="icon is-small">
<i class="fa fa-angle-down" aria-hidden="true"></i>
</span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content">
<a href="#" class="dropdown-item">
Dropdown item
</a>
<a class="dropdown-item">
Other dropdown item
</a>
<a href="#" class="dropdown-item is-active">
Active dropdown item
</a>
<a href="#" class="dropdown-item">
Other dropdown item
</a>
<hr class="dropdown-divider">
<a href="#" class="dropdown-item">
With a divider
</a>
</div>
</div>
</div>
You need to toggle the class is-active using JavaScript. When .dropdown has .is-active it changes the display of .dropdown-menu from none to block.
Here is a basic way to implement it.
var dropdown = document.querySelector('.dropdown');
dropdown.addEventListener('click', function(event) {
event.stopPropagation();
dropdown.classList.toggle('is-active');
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.0/css/bulma.min.css" rel="stylesheet" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<div class="dropdown">
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu">
<span>Dropdown button</span>
<span class="icon is-small">
<!--fontawesome required for the icon-->
<i class="fa fa-angle-down" aria-hidden="true"></i>
</span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content">
<a href="#" class="dropdown-item">
Dropdown item
</a>
<a class="dropdown-item">
Other dropdown item
</a>
<a href="#" class="dropdown-item is-active">
Active dropdown item
</a>
<a href="#" class="dropdown-item">
Other dropdown item
</a>
<hr class="dropdown-divider">
<a href="#" class="dropdown-item">
With a divider
</a>
</div>
</div>
</div>
Here's a full solution that has worked well for me using vanilla JS and making sure dropdowns close when you click out of them or press the Esc key.
// Get all dropdowns on the page that aren't hoverable.
const dropdowns = document.querySelectorAll('.dropdown:not(.is-hoverable)');
if (dropdowns.length > 0) {
// For each dropdown, add event handler to open on click.
dropdowns.forEach(function(el) {
el.addEventListener('click', function(e) {
e.stopPropagation();
el.classList.toggle('is-active');
});
});
// If user clicks outside dropdown, close it.
document.addEventListener('click', function(e) {
closeDropdowns();
});
}
/*
* Close dropdowns by removing `is-active` class.
*/
function closeDropdowns() {
dropdowns.forEach(function(el) {
el.classList.remove('is-active');
});
}
// Close dropdowns if ESC pressed
document.addEventListener('keydown', function (event) {
let e = event || window.event;
if (e.key === 'Esc' || e.key === 'Escape') {
closeDropdowns();
}
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css" rel="stylesheet">
<body style="margin: 2rem">
<div class="dropdown">
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="dropdown-ui-actions">
<span>Actions</span>
<span class="icon is-small">
<i class="fas fa-angle-down" aria-hidden="true"></i>
</span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-ui-actions" role="menu">
<div class="dropdown-content">
<a href="#" class="dropdown-item">
Option 1
</a>
<a href="#" class="dropdown-item">
Option 2
</a>
<a href="#" class="dropdown-item">
Option 3
</a>
</div>
</div>
</div>
</body>
correction of te answer above:
I added the closeDropdowns() function, because when we click in a second menu, both menus stay opened.
// Get all dropdowns on the page that aren't hoverable.
const dropdowns = document.querySelectorAll('.dropdown:not(.is-hoverable)');
if (dropdowns.length > 0) {
// For each dropdown, add event handler to open on click.
dropdowns.forEach(function(el) {
el.addEventListener('click', function(e) {
closeDropdowns();
e.stopPropagation();
el.classList.toggle('is-active');
});
});
// If user clicks outside dropdown, close it.
document.addEventListener('click', function(e) {
closeDropdowns();
});
}
/*
* Close dropdowns by removing `is-active` class.
*/
function closeDropdowns() {
dropdowns.forEach(function(el) {
el.classList.remove('is-active');
});
}
// Close dropdowns if ESC pressed
document.addEventListener('keydown', function (event) {
let e = event || window.event;
if (e.key === 'Esc' || e.key === 'Escape') {
closeDropdowns();
}
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css" rel="stylesheet">
<body style="margin: 2rem">
<div class="dropdown">
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="dropdown-ui-actions">
<span>Actions</span>
<span class="icon is-small">
<i class="fas fa-angle-down" aria-hidden="true"></i>
</span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-ui-actions" role="menu">
<div class="dropdown-content">
<a href="#" class="dropdown-item">
Option 1
</a>
<a href="#" class="dropdown-item">
Option 2
</a>
<a href="#" class="dropdown-item">
Option 3
</a>
</div>
</div>
</div>
</body>
I sniffed the javascript source listener on the docs page (I don't know why they don't make this more obvious tbh) and here it is for anyone else who may benefit.
Please note below I commented out a cookies aspect which I did not have defined and was throwing an error and seemed unimportant for my purposes.
"use strict";
document.addEventListener("DOMContentLoaded", function () {
// Search
var setSearchToggle = function setSearchToggle() {
var icon = document.getElementById("searchIcon");
var search = document.getElementById("search");
var input = document.getElementById("algoliaSearch");
if (!icon) {
return;
}
icon.addEventListener("click", function (event) {
search.classList.toggle("bd-is-visible");
if (search.classList.contains("bd-is-visible")) {
algoliaSearch.focus();
}
});
window.addEventListener("keydown", function (event) {
if (event.key === "Escape") {
return search.classList.remove("bd-is-visible");
}
});
};
setSearchToggle();
// Navbar
var setNavbarVisibility = function setNavbarVisibility() {
var navbar = document.getElementById("navbar");
if (!navbar) {
return;
}
var cs = getComputedStyle(navbar);
var paddingX = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight);
var brand = navbar.querySelector(".navbar-brand");
var end = navbar.querySelector(".navbar-end");
var search = navbar.querySelector(".bd-search");
var original = document.getElementById("navbarStartOriginal");
var clone = document.getElementById("navbarStartClone");
var rest = navbar.clientWidth - paddingX - brand.clientWidth - end.clientWidth - search.clientWidth;
var space = original.clientWidth;
var all = document.querySelectorAll("#navbarStartClone > .bd-navbar-item");
var base = document.querySelectorAll("#navbarStartClone > .bd-navbar-item-base");
var more = document.querySelectorAll("#navbarStartOriginal > .bd-navbar-item-more");
var dropdown = document.querySelectorAll("#navbarStartOriginal .bd-navbar-dropdown > .navbar-item");
var count = 0;
var totalWidth = 0;
var showMore = function showMore() {};
var hideMore = function hideMore() {};
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = all[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var item = _step.value;
totalWidth += item.offsetWidth;
if (totalWidth > rest) {
break;
}
count++;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
var howManyMore = Math.max(0, count - base.length);
if (howManyMore > 0) {
for (var i = 0; i < howManyMore; i++) {
more[i].classList.add("bd-is-visible");
dropdown[i].classList.add("bd-is-hidden");
}
}
for (var j = howManyMore; j < more.length; j++) {
more[j].classList.remove("bd-is-visible");
}
for (var k = howManyMore; k < dropdown.length; k++) {
dropdown[k].classList.remove("bd-is-hidden");
}
};
setNavbarVisibility();
// Cookies
// var cookieBookModalName = "bulma_closed_book_modal";
// var cookieBookModal = Cookies.getJSON(cookieBookModalName) || false;
// Dropdowns
var $dropdowns = getAll(".dropdown:not(.is-hoverable)");
if ($dropdowns.length > 0) {
$dropdowns.forEach(function ($el) {
$el.addEventListener("click", function (event) {
event.stopPropagation();
$el.classList.toggle("is-active");
});
});
document.addEventListener("click", function (event) {
closeDropdowns();
});
}
function closeDropdowns() {
$dropdowns.forEach(function ($el) {
$el.classList.remove("is-active");
});
}
// Toggles
var $burgers = getAll(".navbar-burger");
if ($burgers.length > 0) {
$burgers.forEach(function ($el) {
if (!$el.dataset.target) {
return;
}
$el.addEventListener("click", function () {
var target = $el.dataset.target;
var $target = document.getElementById(target);
$el.classList.toggle("is-active");
$target.classList.toggle("is-active");
});
});
}
// Modals
var rootEl = document.documentElement;
var $modals = getAll(".modal");
var $modalButtons = getAll(".modal-button");
var $modalCloses = getAll(".modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button");
if ($modalButtons.length > 0) {
$modalButtons.forEach(function ($el) {
$el.addEventListener("click", function () {
var target = $el.dataset.target;
openModal(target);
});
});
}
if ($modalCloses.length > 0) {
$modalCloses.forEach(function ($el) {
$el.addEventListener("click", function () {
closeModals();
});
});
}
function openModal(target) {
var $target = document.getElementById(target);
rootEl.classList.add("is-clipped");
$target.classList.add("is-active");
}
function closeModals() {
rootEl.classList.remove("is-clipped");
$modals.forEach(function ($el) {
$el.classList.remove("is-active");
});
}
document.addEventListener("keydown", function (event) {
var e = event || window.event;
if (e.keyCode === 27) {
closeModals();
closeDropdowns();
}
});
// Clipboard
var $highlights = getAll(".highlight");
var itemsProcessed = 0;
if ($highlights.length > 0) {
$highlights.forEach(function ($el) {
var copyEl = '<button class="button bd-copy">Copy</button>';
var expandEl = '<button class="button is-small bd-expand">Expand</button>';
$el.insertAdjacentHTML("beforeend", copyEl);
var $parent = $el.parentNode;
if ($parent && $parent.classList.contains("bd-is-more")) {
var showEl = '<button class="button is-small bd-show"><span class="icon"><i class="fas fa-code"></i></span><strong>Show code</strong></button>';
$parent.insertAdjacentHTML("afterbegin", showEl);
} else if ($el.firstElementChild.scrollHeight > 480 && $el.firstElementChild.clientHeight <= 480) {
$el.insertAdjacentHTML("beforeend", expandEl);
}
itemsProcessed++;
if (itemsProcessed === $highlights.length) {
addHighlightControls();
}
});
}
function addHighlightControls() {
var $highlightButtons = getAll(".highlight .bd-copy, .highlight .bd-expand");
$highlightButtons.forEach(function ($el) {
$el.addEventListener("mouseenter", function () {
$el.parentNode.classList.add("bd-is-hovering");
$el.parentNode.parentNode.classList.add("bd-is-hovering");
});
$el.addEventListener("mouseleave", function () {
$el.parentNode.classList.remove("bd-is-hovering");
$el.parentNode.parentNode.classList.remove("bd-is-hovering");
});
});
var $highlightExpands = getAll(".bd-snippet .bd-expand");
$highlightExpands.forEach(function ($el) {
$el.addEventListener("click", function () {
$el.parentNode.firstElementChild.style.maxHeight = "none";
});
});
var $highlightShows = getAll(".bd-snippet .bd-show");
$highlightShows.forEach(function ($el) {
$el.addEventListener("click", function () {
var text = $el.querySelector("strong").textContent;
var newText = text === "Show code" ? "Hide code" : "Show code";
$el.querySelector("strong").textContent = newText;
$el.parentNode.classList.toggle("bd-is-more-clipped");
});
});
}
setTimeout(function () {
new Clipboard(".bd-copy", {
target: function target(trigger) {
return trigger.previousElementSibling.firstElementChild;
}
});
new Clipboard(".bd-clipboard");
}, 100);
// Events
var resizeTimer = void 0;
function handleResize() {
window.clearTimeout(resizeTimer);
resizeTimer = window.setTimeout(function () {
setNavbarVisibility();
}, 10);
}
window.addEventListener("resize", handleResize);
// Utils
function getAll(selector) {
var parent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document;
return Array.prototype.slice.call(parent.querySelectorAll(selector), 0);
}
});