Home > Enterprise >  CSS - Preventing element from being push upwards during DOM manipulation
CSS - Preventing element from being push upwards during DOM manipulation

Time:11-26

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>

  • Related