I have an input form field that outputs text on submit to another created input, essentially an editable todo list. I have tried to make the input text value auto grow, but cannot figure out how to do it. Right now the user has to scroll over to see the rest of the text on each list item. This should not be.
What I tried:
I have tried creating a span and attaching editableContent but that makes my input text disappear.
I have tried setting an attribute on max-length on the created input but cannot get it to work. What is the best way to accomplish auto growing the text input value?
Here is the full codepen
const createTodoText = (todo) => {
const itemText = document.createElement("INPUT");
// const itemText = document.createElement("span");
// itemText.contentEditable
// itemText.contentEditable = 'true'
itemText.classList.add("todoText");
itemText.value = todo.name;
itemText.addEventListener("click", (e) => {
e.currentTarget.classList.add("active");
});
// update todo item when user clicks away
itemText.addEventListener("blur", (e) => {
todo.name = e.currentTarget.value;
renderTodos();
});
return itemText;
};
There you go: -
// select DOM elements
const todoForm = document.querySelector(".todo-form");
const addButton = document.querySelector(".add-button");
const input = document.querySelector(".todo-input");
const ul = document.getElementById("todoList");
let todos = [];
todoForm.addEventListener("submit", function (e) {
e.preventDefault();
addTodo(input.value);
});
const addTodo = (input) => {
if (input !== "") {
const todo = {
id: Date.now(),
name: input,
completed: false
};
todos.push(todo);
renderTodos();
todoForm.reset();
}
};
const renderTodos = (todo) => {
ul.innerHTML = "";
todos.forEach((item) => {
let li = document.createElement("LI");
// li.classList.add('item');
li.setAttribute("class", "item");
li.setAttribute("data-key", item.id);
const itemText = createTodoText(item);
const cb = buildCheckbox(item);
const db = buildDeleteButton(item);
// if (item.completed === true) {
// li.classList.add('checked');
// }
li.append(cb);
li.append(db);
li.append(itemText);
ul.append(li);
});
};
const createTodoText = (todo) => {
const itemText = document.createElement("span");
itemText.setAttribute('role','textbox');
itemText.setAttribute('contenteditable',"true");
itemText.classList.add("todoText");
itemText.innerHTML = todo.name;
itemText.addEventListener("click", (e) => {
e.currentTarget.classList.add("active");
});
// update todo item when user clicks away
itemText.addEventListener("blur", (e) => {
todo.name = e.target.textContent;
renderTodos();
});
return itemText;
};
const buildCheckbox = (todo) => {
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.name = 'checkbox';
cb.classList.add('checkbox');
cb.checked = todo.completed;
// checkbox not staying on current state ??
cb.addEventListener('click', function (e) {
if (e.target.type === 'checkbox') {
// todo.completed = e.target.value;
todo.completed = e.currentTarget.checked
e.target.parentElement.classList.toggle('checked');
}
});
return cb;
};
const buildDeleteButton = (todo) => {
const deleteButton = document.createElement("button");
deleteButton.className = "delete-button";
deleteButton.innerText = "x";
deleteButton.addEventListener("click", function (e) {
// duplicates children sometimes ??
const div = this.parentElement;
div.style.display = "none";
todos = todos.filter((item) => item.id !== todo.id);
});
return deleteButton;
};
// //------ Local Storage ------
function addToLocalStorage(todos) {}
function getFromLocalStorage() {}
// getFromLocalStorage();
This is the Javscript code part. In createTodoText, you can see the changes i've made. It's working according to what you want. What i've done is simple used 'span' instead of 'input'.
How about trying something like
if (todo.name.length) {itemText.size = todo.name.length;}
Related
I have a todo list with CRUD. I just implemented a task counter to show the number of tasks left to complete. The count increments/decrements with checking/unchecking items and decrements on deleting items. Except there is one problem. For some reason on the last item deleted, the count stays at 1 instead of going to 0. Full Codepen
Here is what I tried:
function renderTaskCount() {
const incompleteTaskCount = todos.filter((item) => !item.completed).length;
const taskString = incompleteTaskCount === 1 ? 'task' : 'tasks';
listCountElement.innerText = `${incompleteTaskCount} ${taskString} remaining`;
return incompleteTaskCount;
}
const renderTodos = () => {
ul.innerHTML = '';
todos.forEach((item) => {
let li = document.createElement('LI');
li.setAttribute('class', 'item');
li.setAttribute('data-key', item.id);
const itemText = createTodoText(item);
const cb = buildCheckbox(item);
const db = buildDeleteButton(item);
li.append(cb);
li.append(db);
li.append(itemText);
ul.append(li);
//update height of textarea
updateHeight(itemText);
renderTaskCount();
});
};
You have
const renderTodos = () => {
ul.innerHTML = '';
todos.forEach((item) => {
let li = document.createElement('LI');
li.setAttribute('class', 'item');
li.setAttribute('data-key', item.id);
const itemText = createTodoText(item);
const cb = buildCheckbox(item);
const db = buildDeleteButton(item);
li.append(cb);
li.append(db);
li.append(itemText);
ul.append(li);
//update height of textarea
updateHeight(itemText);
renderTaskCount();
});
};
where renderTaskCount updates the count - which is done at the end of each iteration. So if the todos array is empty, the count will never be updated. You need
const renderTodos = () => {
ul.innerHTML = '';
todos.forEach((item) => {
// ...
});
renderTaskCount();
};
so that whether renderTaskCount executes whether or not the todos array is populated.
I have a problem with the local storage it seems the items are getting saved to local storage but I cannot make it work to load at start.
Any tips and advice much appreciated.
I am posting the code below.
const input = document.getElementById('input');
const list = document.getElementById('list');
const addButton = document.getElementById('addButton');
const completed = document.getElementById("completed");
let LIST;
let id;
let loadSTORAGE = localStorage.getItem("STORAGE");
if (loadSTORAGE) {
LIST = JSON.parse(loadSTORAGE);
id = LIST.length;
loadList(LIST);
} else {
LIST = [];
id = 0;
}
function loadList() {
LIST.forEach(function() {
addTask();
});
}
addButton.addEventListener("click", addTask);
input.addEventListener("keyup", function(event) {
(event.keyCode === 13 ? addTask() : null)
})
function addTask() {
const newTask = document.createElement("li");
const delBtn = document.createElement("button");
const checkBtn = document.createElement("button");
delBtn.innerHTML = "<button>Reset</button>"
checkBtn.innerHTML = "<button>Done</button>"
if (input.value !== "") {
newTask.textContent = input.value;
list.appendChild(newTask);
newTask.appendChild(checkBtn);
newTask.appendChild(delBtn);
LIST.push({
name: input.value,
id: id,
});
id++
input.value = "";
console.log(LIST);
localStorage.setItem("STORAGE", JSON.stringify(LIST));
}
checkBtn.addEventListener("click", function() {
const parent = this.parentNode
parent.remove();
completed.appendChild(parent);
});
delBtn.addEventListener("click", function() {
const parent = this.parentNode
parent.remove();
});
}
You need to break out the logic of building the item and getting the value. Something like the following where the addTask just makes sure there is input and calls a method that builds an item. Now with the localstorage call, you can call just the code that builds the item.
const input = document.getElementById('input');
const list = document.getElementById('list');
const addButton = document.getElementById('addButton');
const completed = document.getElementById("completed");
const loadSTORAGE = localStorage.getItem("STORAGE");
const LIST = loadSTORAGE ? JSON.parse(loadSTORAGE) : [];
let id = LIST.length;
loadList(LIST);
function loadList() {
LIST.forEach(function(data) {
addTaskElement(data);
});
}
function addTask() {
if (input.value !== "") {
cons newItem = {
name: input.value,
id: id,
};
LIST.push(newItem);
id++;
localStorage.setItem("STORAGE", JSON.stringify(LIST));
input.value = "";
addTaskElement(newItem);
}
}
function addTaskElement(data) {
const newTask = document.createElement("li");
const delBtn = document.createElement("button");
const checkBtn = document.createElement("button");
delBtn.textContent = "Reset"
checkBtn.textContent = "Done"
newTask.textContent = data.name;
newTask.appendChild(checkBtn);
newTask.appendChild(delBtn);
list.appendChild(newTask);
}
Whenever I add a todo to my array, it's not being refreshed in html, what do I need to sort this out? Also, how can I connect the Delete button which is being created in loop to a function?
const form = document.querySelector('form')
const input = document.querySelector('input')
const btnAdd = document.querySelector('button')
let ul = document.querySelector('ul')
let todos = ["one", "two", "three"];
pushTodos = (e) => {
e.preventDefault();
todos.push(input.value);
input.value = '';
console.log(todos);
}
for(let i of todos) {
createList = document.createElement("li");
createList.innerHTML+=i + '<button>Delete</delete>';
console.log(todos);
ul.appendChild(createList);
}
form.addEventListener('submit', pushTodos)
The problem you have is that you only update DOM only once on document load. You don't do anything after updating the list. To fix this, create a sync function to synchronize your memory with the DOM and call it every time you update the list:
let todos = ["one", "two", "three"];
const syncTodosWithDOM = () => {
ul.innerHTML = "";
for (let i of todos) {
createList = document.createElement("li");
createList.innerHTML+=i + '<button>Delete</delete>';
console.log(todos);
ul.appendChild(createList);
}
};
pushTodos = (e) => {
e.preventDefault();
todos.push(input.value);
input.value = '';
console.log(todos);
syncTodosWithDOM();
}
syncTodosWithDOM();
form.addEventListener('submit', pushTodos)
Iterate todos inside pushTodos function. Create a separate function createDomElem which will create dom element and call this function again when the element is pushed in the array
pushTodos = (e) => {
e.preventDefault();
todos.push(input.value);
input.value = '';
console.log(todos);
createDomElem()
}
createDomElem = () => {
for (let i of todos) {
createList = document.createElement("li");
createList.innerHTML += i + '<button>Delete</delete>';
console.log(todos);
ul.innerHTML = ''; // remove previous list
ul.appendChild(createList);
}
}
createDomElem() // will create initial list of todos
In pushTodos you also need to refresh HTML.
pushTodos = (e) => {
e.preventDefault();
todos.push(input.value);
input.value = '';
createList = document.createElement("li");
createList.innerHTML+=input.value + '<button>Delete</delete>';
ul.appendChild(createList);
}
This is the js code
let form = document.getElementById('todoForm');
let input = document.getElementById('todoInput');
let btn = document.getElementById('btn');
let todos = [];
const loadTodos = () => {
let parent = document.getElementById('todoList');
todos.forEach(todo => {
let newLi = document.createElement('li');
newLi.innerHTML = `<li>${todo.text}</li>`
parent.appendChild(newLi);
})
}
btn.addEventListener('click', (e) => {
e.preventDefault();
let text = input.value;
let todo = {
id: todos.length + 1,
text: text,
complete: false,
}
todos.push(todo);
loadTodos();
})
window.onload = () => {
loadTodos();
}
When I add a todo for the first time its ok, but the seconed time will print the first todo again include the seconed.
example:
first todo
2.first todo
3.seconed todo
You should make another function to handle single todo added, below is your updated code
let form = document.getElementById('todoForm');
let input = document.getElementById('todoInput');
let btn = document.getElementById('btn');
let todos = [];
const loadTodos = () => {
let parent = document.getElementById('todoList');
todos.forEach(todo => {
let newLi = document.createElement('li');
newLi.innerHTML = `<li>${todo.text}</li>`
parent.appendChild(newLi);
})
}
const renderNewToDo = (todo) => {
let parent = document.getElementById('todoList');
let newLi = document.createElement('li');
newLi.innerHTML = `<li>${todo.text}</li>`
parent.appendChild(newLi);
}
btn.addEventListener('click', (e) => {
e.preventDefault();
let text = input.value;
let todo = {
id: todos.length + 1,
text: text,
complete: false,
}
todos.push(todo);
renderNewToDo(todo);
})
window.onload = () => {
loadTodos();
}
I am building a mock-up RSVP app, and I can't get the localStorage data to come up when the page refreshes. I am aiming to be able to insert a name and have the name get appended to the invitation list. Then, the user can either repeat those steps for multiple names or edit the names in the list. I have this part down, but if I were to refresh the page, the invitees are no longer in the list below the input bar. I need it to where it will keep the names in the list, and the buttons on the list items (edit, remove) will still work.
With the 'main code' below, the item is added to the localStorage and set as 'rsvp', but the visible list is not updated until I refresh the page. I need it to update every time I hit the submit button. I have tried adding
if (rsvp != null) {
ul.outerHTML = rsvp;
}
right below
console.log(rsvp);
but when I click submit, the list is not updated and in the console you see the data that was loaded the previous time you used the app.
For example, if I type in 'Test', click submit, type in 'Test2', click submit then type in 'Test3' and click submit again - the list is not visibly updated, you get an error in the console saying 'Uncaught DOMException: Failed to set the 'outerHTML' property on 'Element': This element has no parent node.', and the list is never updated until you refresh the page, type in another name and click submit. Again, if you do this, the list is not updated until you repeat the same process.
Main code (without the rsvp 'if' statement in the handler)
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('registrar');
const input = form.querySelector('input');
const mainDiv = document.querySelector('.main');
const ul = document.getElementById('invitedList');
const div = document.createElement('div');
const filterLabel = document.createElement('label');
const filterCheckbox = document.createElement('input');
filterLabel.textContent = "Hide those who haven't responded";
filterCheckbox.type = 'checkbox';
div.appendChild(filterLabel);
div.appendChild(filterCheckbox);
mainDiv.insertBefore(div, ul);
// Creates the list item for the RSVP list
function createLI(text) {
function createElement(elementName, property, value) {
const element = document.createElement(elementName);
element[property] = value;
return element;
}
function appendToLI(elementName, property, value) {
const element = createElement(elementName, property, value);
li.appendChild(element);
return element;
}
const li = document.createElement('li');
appendToLI('span', 'textContent', text);
appendToLI('label','textContent', 'Confirm')
.appendChild(createElement('input', 'type', 'checkbox'));
appendToLI('button', 'textContent', 'edit');
appendToLI('button', 'textContent', 'remove');
return li;
}
form.addEventListener('submit', (e) => {
e.preventDefault();
const text = input.value;
input.value = '';
// Checks for empty string in the input area
if (text === '') {
alert("You have not entered a name, please try again.");
return;
}
// Checks for duplicate names
for (i = 0; i < ul.children.length; i++) {
if (text === ul.children[i].children[0].textContent) {
alert("This name has already been entered. Please enter a different name.");
return;
}
}
const li = createLI(text);
ul.appendChild(li);
localStorage.setItem('rsvp', JSON.stringify(ul.outerHTML));
});
const rsvp = JSON.parse(localStorage.getItem('rsvp'));
if (rsvp != null) {
ul.outerHTML = rsvp;
}
// Changes list item from confirm to confirmed
ul.addEventListener('change', (e) => {
const checkbox = event.target;
const checked = checkbox.checked;
const label = checkbox.parentNode;
const listItem = checkbox.parentNode.parentNode;
if (checked) {
listItem.className = 'responded';
label.childNodes[0].textContent = 'Confirmed';
} else {
listItem.className = '';
label.childNodes[0].textContent = 'Confirm';
}
});
ul.addEventListener('click', (e) => {
if (e.target.tagName === 'BUTTON') {
const button = e.target;
const li = button.parentNode;
const ul = li.parentNode;
const action = button.textContent;
const nameActions = {
remove: () => {
ul.removeChild(li);
},
edit: () => {
const span = li.firstElementChild;
const input = document.createElement('input');
input.type = 'text';
input.value = span.textContent;
li.insertBefore(input, span);
li.removeChild(span);
button.textContent = 'save';
},
save: () => {
const input = li.firstElementChild;
const span = document.createElement('span');
span.textContent = input.value;
li.insertBefore(span, input);
li.removeChild(input);
button.textContent = 'edit';
}
};
// select and run action in button's name
nameActions[action]();
}
});
// Filters out those who have not yet responded
filterCheckbox.addEventListener('change', (e) => {
const isChecked = e.target.checked;
const lis = ul.children;
if (isChecked) {
for (let i = 0; i < lis.length; i++) {
let li = lis[i];
if (li.className === 'responded') {
li.style.display = '';
} else {
li.style.display = 'none';
}
}
} else {
for (let i = 0; i < lis.length; i++) {
let li = lis[i];
li.style.display = '';
}
}
});
});
const rsvp = JSON.parse(localStorage.getItem('rsvp'))
is called within DOMContentLoaded event handler
if (rsvp != null) {
ul.outerHTML = rsvp;
}
is called immediately following .addEventListener(); also the code at Question does not indicate where localStorage.getItem('rsvp') is set before const rsvp = JSON.parse(localStorage.getItem('rsvp')) is called.
You can check is localStorage has the property key "rsvp" before defining rsvp, and use if condition and statement with DOMContentLoaded event handler.