I have a todo list built with vanilla JavaScript.
As you'd expect, when you click the add button new todos appear on the DOM.
The problem is that the todo list gets pushed upwards each time you add a new todo and eventually it overwrites the navbar and leaves the viewport.
I've tried adding all the possible CSS position properties to the title but the list still keeps moving regardless
For the code and visuals - https://codepen.io/greevesh/pen/gOxNEPy
This is the element I want to prevent from moving -
<div class="d-flex justify-content-center mb-3" style="position: sticky;">
<img class="logo" src="/img/planning.svg" alt="tasktracker-logo">
</div>
CodePudding user response:
You could allow the list to grow but stop moving up when reaching the top by adding margin: auto
to your sub-container. See this answer for more details
.sub-container {
margin: auto;
display: block;
}
CodePudding user response:
Your .container
is a flex with fixed height. When todo-lists overflow, the flexbox tried to keep them all centered within the container.
An easy fix would be changing to min-height: 100vh
. This way the flex can grow when the lists are added.
.container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
CodePudding user response:
That is because you make the .container
align-items: center;
. Please remove that and add padding-top
or margin-top
as you want. Putting align-items: center;
is a bad practice for element that has dynamic height.
const toDoList = document.getElementById("toDoList");
const title = document.getElementById("title");
const toDoContainer = document.getElementById("toDoContainer");
const allCheckboxes = toDoContainer.getElementsByClassName("checkbox");
const allXBtns = toDoContainer.getElementsByClassName("X");
const saveBtn = document.getElementById("save");
const clearBtn = document.getElementById("clear");
const saveMsg = document.getElementById("saveMsg");
const saveTitle = () => {
localStorage.setItem(activeEmail.innerHTML " Title", JSON.stringify(title.value));
};
function createToDo() {
const createdToDoContainer = document.createElement("div");
createdToDoContainer.id = 'toDo' new Date().getTime(); // unique ID
const createdCheckbox = document.createElement("INPUT");
createdCheckbox.setAttribute("type", "checkbox");
const createdToDo = document.createElement("INPUT");
const createdXBtn = document.createElement("SPAN");
createdToDoContainer.appendChild(createdCheckbox);
createdToDoContainer.appendChild(createdToDo);
createdToDoContainer.appendChild(createdXBtn);
createdToDoContainer.classList.add("toDoInnerContainer");
createdCheckbox.classList.add("checkbox");
createdToDo.classList.add("input");
createdXBtn.classList.add("X");
createdXBtn.innerHTML = "X";
toDoContainer.appendChild(createdToDoContainer);
}
let checkedToDos = [];
// delete button functionality
toDoContainer.addEventListener("click", (e) => {
const tgt = e.target;
if (tgt.classList.contains("X")) {
const parent = tgt.parentElement;
parent.remove();
const toDoValue = parent.querySelector(".input").value;
if (parent.querySelector(".checkbox").checked) {
if (checkedToDos.includes(toDoValue)) {
checkedToDos = checkedToDos.filter(val => val !== toDoValue);
}
}
}
});
const saveToDos = () => {
// change the global todo object (and make it an array)
toDos = [...document.querySelectorAll(".input")].map(toDo => {
const checked = toDo.parentNode.querySelector(".checkbox").checked;
const id = toDo.closest("div").id;
const val = toDo.value;
if (toDo.parentNode.querySelector(".checkbox").checked == true) {
checkedToDos.push(val);
}
return { id, val, checked }
});
localStorage.setItem((activeEmail.innerHTML), JSON.stringify(toDos));
};
// changes todo styling depending on checkbox state (checked or not checked)
toDoContainer.addEventListener("change", (e) => {
const tgt = e.target;
const chk = tgt.checked;
const toDo = tgt.parentNode.querySelector('.input');
toDo.style.textDecoration = chk ? "line-through" : "none";
toDo.style.opacity = chk && toDo.value !== "" ? "50%" : "100%";
});
document.getElementById("add").addEventListener("click", createToDo);
saveBtn.addEventListener("click", () => {
saveTitle();
saveToDos();
saveMsg.innerHTML = "Your tasks have been saved.";
});
// makes sure save message disappears once user clicks elsewhere
window.addEventListener("click", (e) => {
const tgt = e.target;
if (saveMsg.innerHTML !== "" && saveBtn !== tgt) {
saveMsg.innerHTML = "";
}
})
const allToDos = toDoContainer.getElementsByClassName("input");
const clearToDosAndTitle = () => {
title.value = "";
checkedToDos.splice(0, checkedToDos.length);
[...document.getElementsByClassName("toDoInnerContainer")].map(toDo => {
// remove all todos but leave at least one empty one on the DOM
// the length of the checkbox collection matters most because it's the first part of the todo to be loaded onto the DOM
while (toDo.lastChild && allCheckboxes.length > 1) {
toDo.lastChild.remove();
}
// empties the only todo that was left on the DOM after clearance
if (allToDos[0].value !== "") {
allToDos[0].value = "";
}
});
}
clearBtn.addEventListener("click", () => {
clearToDosAndTitle();
});
const loadEmptyToDoInputs = () => {
const user = JSON.parse(localStorage.getItem(activeEmail.innerHTML));
if (user && user.length > 1) {
// using a while loop instead of a foreach in this case prevents an unrequested duplicate todo being added to the DOM
while (allToDos.length != user.length) {
createToDo();
}
}
}
const loadToDos = () => {
loadEmptyToDoInputs();
loadToDoTextValues();
loadCheckedToDoStyling();
loadDefaultEmptyToDo();
}
const loadCheckedToDoStyling = () => {
const user = JSON.parse(localStorage.getItem(activeEmail.innerHTML));
user.forEach(value => {
let checkedValues = [];
if (value.checked && toDoTextValues.includes(value.val)) {
checkedValues.push(value.val);
}
toDos = [...document.getElementsByClassName("input")].map(toDo => {
if (checkedValues.includes(toDo.value)) {
box = toDo.parentElement.firstChild;
box.checked = true;
toDo.style.textDecoration = "line-through";
toDo.style.opacity = "50%";
}
}
)});
}
const loadTitle = () => {
if (localStorage.getItem(activeEmail.innerHTML " Title")) {
title.value = JSON.parse(localStorage.getItem(activeEmail.innerHTML " Title"));
}
}
const loadDefaultEmptyToDo = () => {
// if there are no checkboxes, there are no todos, so load an empty one by default
if (allCheckboxes.length == 0) {
createToDo();
}
}
let toDoTextValues = [];
const loadToDoTextValues = () => {
const user = JSON.parse(localStorage.getItem(activeEmail.innerHTML));
if (user) {
user.forEach(value => {
toDoTextValues.push(value.val);
});
toDos = [...document.getElementsByClassName("input")].map(toDo => {
for (let value = 0; value < toDoTextValues.length - user.length 1; value ) {
toDo.value = toDoTextValues[value];
}
toDoTextValues.length ;
});
}}
#title {
border: none;
font-size: 45px;
text-align: center;
margin-top: 20px;
}
#title::placeholder {
text-align: center;
}
#title:focus {
text-align: center;
outline: none;
}
#title:focus::placeholder {
visibility: hidden;
}
input[type="checkbox"] {
height: 20px;
width: 20px;;
}
.input {
border-top: none;
border-right: none;
border-left: none;
margin: 0 0 25px 30px;
font-size: 30px;
display: block;
}
.input:focus {
outline: none;
}
.X {
background-color: #E60E0E;
color: white;
border-radius: 50%;
height: 30px;
width: 30px;
font-size: 25px;
font-family: 'Helvetica', 'Arial', sans-serif;
display: flex;
justify-content: center;
align-items: center;
margin: -85px 100px 0 0;
float: right;
}
.X:hover {
cursor: pointer;
background-color: #d81313;
}
#toDoBtnContainer {
margin-top: 35px;
}
#add {
color: #fff;
font-weight: 400;
background-color: #27a348;
}
#add:hover {
background-color: #21af47;
}
#save {
color: #fff;
font-weight: 400;
background-color: #04992b;
}
#save:hover {
background-color: #099b30;
}
#clear {
color: #fff;
font-weight: 400;
background-color: #cc2121;
}
#clear:hover {
background-color: #d10f0f;
}
#saveMsg {
margin-top: 15px;
color: #04992b;
font-weight: 600;
}
.container {
display: flex;
justify-content: center;
height: 100vh;
padding-top: 120px;
}
.sub-container {
display: block;
}
.logo {
height: 75px;
width: 75px;
}
.headings {
text-align: center;
margin-bottom: 30px;
}
.headings h4 {
font-weight: 300;
}
button {
height: 35px;
font-size: 20px;
}
nav {
display: flex;
padding: 20px;
background-color: blue;
}
#signOut {
margin: 0 20px 0 auto;
color: #fff;
font-weight: 400;
background-color: #E22929;
}
#signOut:hover {
background-color: #db2626;
}
#activeEmail {
margin-right: 100px;
font-weight: 500;
}
<nav class="hide">
<button type="button" class="btn btn-lg pt-0 hide" id="signOut">Sign out</button>
<p class="mt-1 hide" id="activeEmail"></p>
</nav>
<div class="container">
<div class="sub-container">
<div class="d-flex justify-content-center mb-3" style="position: sticky;">
<img class="logo" src="/img/planning.svg" alt="tasktracker-logo">
</div>
<div class="headings">
<h1>TaskTracker</h1>
<h4>Leave no task untracked.</h4>
</div>
<div class="hide" id="toDoList">
<h2><input id="title" type="text" placeholder="Add Title"></h2>
<div id="toDoContainer">
</div>
<div id="toDoBtnContainer">
<button type="button" class="btn btn-lg pt-0" id="add">Add</button>
<button type="button" class="btn btn-lg pt-0" id="save">Save</button>
<button type="button" class="btn btn-lg pt-0" id="clear">Clear everything</button>
</div>
<p id="saveMsg"></p>
</div>
</div>
</div>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>