This code originally took input.value and added it to the page. I added local storage to this project and the code is already written but I’m having a hard time displaying the input to the page from localStorage. The input is stored in local storage as objects in an array. I wrote a for loop to loop through those values and pass them to functions that builds the element and appends it to the li and later appends to the ul. It’s not displaying on the page and I’m not getting any errors in the console. I'm not sure where to turn so heres my code:
function fetchInvite() {
const rsvpInvite = JSON.parse(localStorage.getItem("Invitees"));
const rsvpList = document.getElementById('invitedList');
for(var i = 0; i < rsvpInvite.length; i++) {
const name = rsvpInvite[i].name;
const confirm = rsvpInvite[i].confirmed;
createLI(name, confirm);
function createLI(name, confirm) {
const li = document.createElement('li');
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;
}
appendToLI('span', 'textContent', name);
appendToLI('label', 'textContent', confirm)
.appendChild(createElement('input', 'type', 'checkbox'));
appendToLI('button', 'textContent', 'edit');
appendToLI('button', 'textContent', 'remove');
return li;
}
}
}
The full project is available here: https://github.com/tianniNakiMyers/RSVP_TeamTreeHouse/blob/master/app.js
The problem with your code is that you probably never called fetchInvite.
Apart from that, here is a refactoring of your code:
function elt(parent, tag, text) { // create element of tagname tag and append it to a parent (if provided) then set its textContent to text and return it
var el = document.createElement(tag);
if(parent) {
parent.appendChild(el);
}
el.textContent = text;
return el;
}
function fetchInvite() {
const rsvpInvite = JSON.parse(localStorage.getItem("Invitees"));
const rsvpList = document.getElementById('invitedList');
for(var i = 0; i < rsvpInvite.length; i++) {
const name = rsvpInvite[i].name;
const confirm = rsvpInvite[i].confirmed;
const li = elt(rsvpList, 'li', ''); // create an li element with empty text and append it to rsvpList
elt(li, 'span', name); // create a span whose text is name and append it to li
elt(elt(li, 'label', confirm), 'input', '').type = 'checkbox'; // create an input append it to a label element that contain the text confirm and that is appended to li, then set type of input to 'checkbox'
elt(li, 'button', 'edit'); // ...
elt(li, 'button', 'remove');
}
}
fetchInvite(); // don't forget to call it
Related
The parameter of my function is a function. It should create an element but I should still be able to add attributes by using the parameter details.
E.g.:
const addElement = (details) => {
const element = document.createElement('div');
}
addElement(function() {
element.id = 'my-div'; // Not working since element is not defined
});
Well, I have tried to store the element in an object to be able to use it outside of that function.
let element = {};
const displayVideo = (type, details) => {
element = document.createElement(type);
element.width = 200;
element.height = 200;
element.classList.add('my-class'); // <--- THE PROBLEM!
if (details) {
details();
}
document.querySelector('#layer').insertBefore(element, document.querySelector('#el'));
};
displayVideo('VIDEO', function () {
element.controls = true;
});
My element can not be created because of element.classList.add('my-class'); and I don't even get an error message. If I remove that line, it works but I would still like to be able to add a class to that object. How can I do this?
Just pass element into the function. Since you're just editing properties on the object, this won't cause reference vs value errors.
const addElement = (details) => {
const element = document.createElement('div');
if (details) details(element);
return element;
}
const ele = addElement(function(element) {
element.id = 'my-div';
});
console.log(ele);
In this case details could be something like classname.
function element(type, classname) {
var element = document.createElement(type);
if (classname !== undefined) {
element.classList.add(classname);
}
return element;
};
element("div","my-class"); //<div class="my-class"></div>
Of course instead of classname you could use an array or an object and loop through in order to set multiple attributes.
Or you could store the return value of your function in a variable and then add all the attributes:
var myelement = element("div");
myelement.classList.add("my-new-class");
myelement //<div class="my-new-class"></div>
In javascript I have a list of objects of the same class.
Every object has the same variables and methods.
For each object that is added to the list, an input field is added in the html code.
Now, when the value of an input field is changed (<input oninput=...>)
I want to call the inputModified()method of the exact object in the list that was added to the list along with the input field.
How would you achieve this?
Also, if an object is removed from the list, than the html code that was added for that object is removed as well.
Is an incremental id the only way?
Also, please note that I can not use jquery or anything similar. Thanks!
The best way is not to use <input oninput=...> at all. Use modern event handling. Then you either attach a handler that closes over the entry in the list, or you include the position in the list on the element and use a delegated input handler on the container these inputs are in to figure out which entry on the list to update.
Here's an example of that first option, closing over the entry in the list:
function addToDOM(entry) {
const input = document.createElement("input");
input.type = "text";
input.value = entry.value;
// The event handler closes over `input` and `entry`
input.addEventListener("input", function() {
entry.value = input.value;
});
return input;
}
const list = [];
const container = document.getElementById("container");
for (let n = 0; n < 10; ++n) {
const entry = {
value: `Value ${n + 1}`
};
container.appendChild(addToDOM(entry));
list.push(entry);
}
// Adding another later
setTimeout(() => {
const entry = {
value: "Added later"
};
container.appendChild(addToDOM(entry));
list.push(entry);
}, 800);
document.getElementById("btnShowAll").addEventListener("click", function() {
list.forEach(({value}, index) => {
console.log(`[${index}].value = ${value}`);
});
});
<input type="button" id="btnShowAll" value="Show All">
<div id="container"></div>
Here's an example of the second option with a single delegated handler. Note, though, that if you modify the list, the index we're storing as a data-* attribute gets out of date:
function addToDOM(entry, index) {
const input = document.createElement("input");
input.type = "text";
input.value = entry.value;
input.setAttribute("data-index", index);
return input;
}
const list = [];
const container = document.getElementById("container");
container.addEventListener("input", function(e) {
const input = e.target;
const index = +input.getAttribute("data-index");
list[index].value = input.value;
});
for (let n = 0; n < 10; ++n) {
const entry = {
value: `Value ${n + 1}`
};
container.appendChild(addToDOM(entry, list.length));
list.push(entry);
}
// Adding another later
setTimeout(() => {
const entry = {
value: "Added later"
};
container.appendChild(addToDOM(entry, list.length));
list.push(entry);
}, 800);
document.getElementById("btnShowAll").addEventListener("click", function() {
list.forEach(({value}, index) => {
console.log(`[${index}].value = ${value}`);
});
});
<input type="button" id="btnShowAll" value="Show All">
<div id="container"></div>
If I want to remove/add element on DOM I just use ng-if and the code under it does not compile into to DOM, can I do the same using pure js? I don't want the HTML code inside my js code.
Hiding it using CSS:
<div id = "infoPage" style="display: none;">
Will still insert the element to the DOM.
EDIT
The condition for showing or not is based on a flag like:
var show = false; //or true
You can try something like this:
Idea:
Create an object that holds reference of currentElement and its parent (so you know where to add).
Create a clone of current element as you want to add same element after its removed.
Create a property using Object.defineProperty. This way you can have your own setter and you can observe changes over it.
To toggle element, check
If value is true, you have to add element. But check if same element is already available or not to avoid duplication.
If false, remove element.
var CustomNGIf = function(element, callback, propertyName) {
var _value = null;
// Create copies of elements do that you can store/use it in future
this.parent = element.parentNode;
this.element = element;
this.clone = null;
// Create a property that is supposed to be watched
Object.defineProperty(this, propertyName, {
get: function() {
return _value;
},
set: function(value) {
// If same value is passed, do nothing.
if (_value === value) return;
_value = !!value;
this.handleChange(_value);
}
});
this.handleChange = function(value) {
this.clone = this.element.cloneNode(true);
if (_value) {
var index = Array.from(this.parent.children).indexOf(this.element);
// Check if element is already existing or not.
// This can happen if some code breaks before deleting node.
if (index >= 0) return;
this.element = this.clone.cloneNode(true);
this.parent.appendChild(this.element);
} else {
this.element.remove();
}
// For any special handling
callback && callback();
}
}
var div = document.getElementById('infoPage');
const propName = 'value';
var obj = new CustomNGIf(div, function() {
console.log("change")
}, propName);
var count = 0;
var interval = setInterval(function() {
obj[propName] = count++ % 2;
if (count >= 10) {
window.clearInterval(interval);
}
}, 2000)
<div class='content'>
<div id="infoPage"> test </div>
</div>
Quick question... what is the reason that I need to return the 'element' variable when using the appendToLi? If I remove the line, the code will not add anything to the list. It's confusing to me because the appendToLi function calls are not returning the element to anything.
For example, I would understand if it looked like this...
let element = appendToLi(property, value, text);
But that is not the case. I have...
appendToLi(property, value, text);
document.addEventListener('DOMContentLoaded', () => {
//Targets the unordered list element
const list = document.getElementById("myUL");
//Targets the children of the unordered list
const li = list.children;
//Targets the form element.
const form = document.getElementById("registrar");
//Function declaration (Begins process of creating list item)
function createListItem(text){
//Creates the list item
const li = document.createElement('li');
//Function delcaration (creates an element and returns element)
function createElement(elementName, property, value) {
const element = document.createElement(elementName);
element[property] = value;
return element;
}
//Function declaration (Adds the created element to the list)
function appendToLi(elementName, property, value){
const element = createElement(elementName, property, value);
li.appendChild(element);
return element;
}
//Appends all children to the list item.
appendToLi('span', 'textContent', text);
appendToLi('label', 'textContent', 'Completed')
.appendChild(createElement('input', 'type', 'checkbox'));
appendToLi('button', 'textContent', 'remove');
appendToLi('button', 'textContent', 'edit');
/*Returns the list item and it's children to what has called the
createListItem function*/
return li;
}
//Event listener (listens for click on submit button/enter press)
form.addEventListener('submit', (e) => {
e.preventDefault();
//Targets the input element.
const input = document.querySelector('input');
//If the user has not entered any text in the input field, alerts.
if(input.value === '') {
alert('Please enter an item!');
//Otherise begins the process of creating the list item.
} else {
//Holds the user text input.
const text = input.value;
/*Calls the createListItem function which will begin the process
through various other functions.*/
const listItem = createListItem(text);
list.appendChild(listItem);
input.value = '';
}
});
This:
appendToLi('label', 'textContent', 'Completed')
.appendChild(createElement('input', 'type', 'checkbox'));
... throws an error and interrupts script execution if appendToLi() doesn't return the element.
The above lines of code are equivalent to this:
var element = appendToLi('label', 'textContent', 'Completed');
element.appendChild(createElement('input', 'type', 'checkbox'));
Here, it's easier to see what happens when appendToLi() doesn't return anything: element becomes undefined and you then try to invoke a method called appendChild on it which throws an error since undefined doesn't have any methods or properties.
I understood that you are creating elements when form is submitted. There is guess what I can make as I don't know why you are doing what. In the code section below:
//Function declaration (Adds the created element to the list)
function appendToLi(elementName, property, value){
const element = createElement(elementName, property, value);
li.appendChild(element);
return element;
}
//Creates the list item
const li = document.createElement('li');
//Appends all children to the list item.
appendToLi('span', 'textContent', text);
Here you created the li after you have declared the function appendToLi function try to declare before the function declaration. That might help.
I have a link on which I am generate input, label and text on click event and I would like to delete it at the next click event on the same link:
It doesn't work, here 's my new code :
var addAnswer = (function() {
var label;
var text;
return function (array_output) {
label.parentNode.removeChild(label);
label.removeChild(text);
text = null;
label = null;
label = document.createElement('label');
text = document.createTextNode(array_output[i]);
document.body.appendChild(label)
label.appendChild(text);
};
}());
var tab = ['one', 'two','three','four','five']
var label = document.createElement('label');
var i = 0;
window.onclick = function () {
addAnswer(tab);
i++;
}
I would like to see, at click event, "one" then onother click : 'two', then click again : 'three'...
EDIT: OK i finally found out :
var addAnswer = (function() {
var label;
var text;
return function (array_output) {
if(label) {
label.parentNode.removeChild(label);
label.removeChild(text);
text = null;
label = null;
label = document.createElement('label');
text = document.createTextNode(array_output[i++]);
document.body.appendChild(label)
label.appendChild(text);
}else{
label = document.createElement('label');
text = document.createTextNode(array_output[i]);
document.body.appendChild(label)
label.appendChild(text);
}
};
}());
var tab = ['one', 'two','three','four','five']
var label = document.createElement('label');
var i = 0;
window.onclick = function () {
addAnswer(tab);
}
Each time you call the function, a new execution context is created. So when you do:
if(label) {
label.parentNode.removeChild(label)
}
then label will always be undefined since it is not assigned a value until later. You likely need to store a reference to the created element so you can delete it later. A closure may suit:
var addAnswer = (function() {
// Declare label here in "outer" execution context
var label;
return function (array_output) {
// If label is truthy, assume references an element so remove it
// and then remove reference so it's available for garbage collection
if(label) {
label.parentNode.removeChild(label);
label = null;
// If label was falsey, create a new one and keep a reference
} else {
// do stuff
// don't declare label here so reference is kept using
// the variable "label" in the outer scope
label = document.createElement('label');
// do more stuff
}
};
}());
The above will add a label on the first click, then remove it on the next. But it will only work for one label at a time. Is that what you want, or do you want to be able to add lots of elements? If so, you can store references in an array, then iterate over the array to remove them.