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.
Related
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
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>
I am using selectize.js and I have 2 select fields:
$('#field1').selectize();
$('#field2').selectize();
and I want to change automatically value of the second, when the first field has changed (or selected); and vice versa, change value of first when value of second has changed (or selected).
I was using change function, like this:
$("#field1").change(function () {
var d = 'value-field2'
var $select_field2 = $("#id_field2").selectize();
var selectize_field2 = $select_field2[0].selectize;
selectize_field2.setValue(d);
});
$("#field2").change(function () {
var d = 'value-field1'
var $select_field1 = $("#id_field1").selectize();
var selectize_field1 = $select_field1[0].selectize;
selectize_field1.setValue(d);
});
});
But here I go in an infinite call of these 2 functions, calling each other, because they change each other.
I tried using mouseover() or click() instead of change() but didn't succed to make it work.
Any ideas?
You need some kind of variable to identify when the JavaScript is changing the value instead of the user. You could use something like this:
var programmaticallyChanging = false;
$("#field1").change(function() {
if (!programmaticallyChanging) {
var d = 'value-field2'
var $select_field2 = $("#id_field2").selectize();
var selectize_field2 = $select_field2[0].selectize;
programmaticallyChanging = true;
selectize_field2.setValue(d);
programmaticallyChanging = false;
}
});
$("#field2").change(function() {
if (!programmaticallyChanging) {
var d = 'value-field1'
var $select_field1 = $("#id_field1").selectize();
var selectize_field1 = $select_field1[0].selectize;
programmaticallyChanging = true;
selectize_field1.setValue(d);
programmaticallyChanging = false
}
});
If possible, put the var programmaticallyChanging = false; into some function so that it's not exposed on the window.
I have a master object in my JS setup, i.e.:
var myGarage = {
cars: [
{
make: "Ford",
model: "Escape",
color: "Green",
inuse: false
},
{
make: "Dodge",
model: "Viper"
color: "Red",
inuse: true
},
{
make: "Toyota",
model: "Camry"
color: "Blue",
inuse: false
}
]
}
Now I loop over my cars and put them in a table. In the table I also have a button that lets me toggle the car as "in use" and "not in use".
How can I associate the DOM Element of every row with its corresponding car, so that if I toggle the "inuse" flag, I can update the master object?
You can actually attach an object directly to a node:
var n = document.getElementById('green-ford-escape');
n.carObject = myGarage.cars[0];
n.onclick = function() {
doSomethingWith(this.carObject);
}
For the same of removing ambiguity, in some cases, it's more clear write the above event handler to refer to n instead of this:
n.onclick = function() {
doSomethingWith(n.carObject);
}
You can also refer directly to the object from the attached event:
var n = document.getElementById('green-ford-escape');
n.onclick = function() {
doSomethingWith(myGarage.cars[0]);
}
In the latter case, myGarage does not have to be global. You can do this and expect it to work correctly:
(function(){
var myGarage = { /* ... etc ... */ };
var n = document.getElementById('green-ford-escape');
n.onclick = function() {
doSomethingWith(myGarage.cars[0]);
}
})();
The node's event function will "hold onto" the local variable correctly, effectively creating a private variable.
You can test this in your Chrome/FF/IE console:
var o = {a: 1};
var n = document.createElement('div');
n.innerHTML = "click me";
n.data = o;
n.onclick = function() { n.data.a++; console.log(n.data, o); }
document.body.appendChild(n);
You should see the console log two identical objects with each click, each with incrementing a values.
Beware that setting n.data to a primitive will not create a reference. It'll copy the value.
I'd suggest considering addEventListener, and a constructor that conforms your objects to the eventListener interface.
That way you can have a nice association between your object, your element, and its handlers.
To do this, make a constructor that's specific to your data.
function Car(props) {
this.make = props.make;
this.model = props.model;
// and so on...
this.element = document.createElement("div"); // or whatever
document.body.appendChild(this.element); // or whatever
this.element.addEventListener("click", this, false);
}
Then implement the interface:
Car.prototype.handleEvent = function(e) {
switch (e.type) {
case "click": this.click(e);
// add other event types if needed
}
}
Then implement your .click() handler on the prototype.
Car.prototype.click = function(e) {
// do something with this.element...
this.element.style.color = "#F00";
// ...and the other properties
this.inuse = !this.inuse
}
So then you can just loop over the Array, and make a new Car object for each item, and it'll create the new element and add the listener(s).
myGarage.cars.forEach(function(obj) {
new Car(obj)
})
You can use HTML5 data-* attribute to find out which row it is. You must be doing something like this
var table = $('<table>'); // Let's create a new table even if we have an empty table in our DOM. Simple reason: we will achieve single DOM operation (Faster)
for (var i=0; i<myGarbage.cars.length; i++) {
// Create a new row and append to table
var tr = $('<tr>').appendTo(table);
var carObject = myGarbage.cars[i];
// Traverse the JSON object for each car
for (var key in carObject) {
// Create other cells. I am doing the last one
var td = $('<td>').appendTo(tr);
var button = $('<button>').attr('data-carId', i).addClass('toggle-inuse').appendTo(td);
}
}
// If en ampty table awaits me in DOM
$('#tableId').html(table.html());
Now we will add event listener on button :-
$('.toggle-inuse').click(function() {
var i = $(this).data('carId');
myGarbage.cars[i].inuse = !myGarbage.cars[i].inuse; //Wow done
}
Try this out !!
You'll want some sort of ID or distinct row in your information, else you'll have to rely on the array index to do this. Either way you'll want to store the data using data attributes.
So when you loop through:
for (var i = 0, l = array.length; i < l; i++) {
var div = '<tr data-car="' + JSON.stringify(array[i]) + '" data-index="' + i + '"><td></td></tr>'
}
And on your click event:
$('button').click(function() {
var carIndex = $(this).closest('tr').attr('data-index');
var carData = $(this).closest('tr').attr('data-car');
if (carData) carData = JSON.parse(carData);
myGarage.cars[carIndex].inUse = true;
})
If you bind the data to the DOM, you may not even need to update the actual JS data. Could go over each row in the table and re-create the data-object you created the table from.
I am having trouble with JS closures:
// arg: an array of strings. each string is a mentioned user.
// fills in the list of mentioned users. Click on a mentioned user's name causes the page to load that user's info.
function fillInMentioned(mentions) {
var mentionList = document.getElementById("mention-list");
mentionList.innerHTML = "";
for (var i = 0; i < mentions.length; i++) {
var newAnchor = document.createElement("a");
// cause the page to load info for this screen name
newAnchor.onclick = function () { loadUsernameInfo(mentions[i]) };
// give this anchor the necessary content
newAnchor.innerHTML = mentions[i];
var newListItem = document.createElement("li");
newListItem.appendChild(newAnchor);
mentionList.appendChild(newListItem);
}
document.getElementById("mentions").setAttribute("class", ""); // unhide. hacky hack hack.
}
Unfortunately, clicking on one of these anchor tags results in a call like this:
loadUserNameInfo(undefined);
Why is this? My goal is an anchor like this:
<a onclick="loadUserNameInfo(someguy)">someguy</a>
How can I produce this?
Update This works:
newAnchor.onclick = function () { loadUsernameInfo(this.innerHTML) };
newAnchor.innerHTML = mentions[i];
The "i" reference inside the closure for the onclick handlers is trapping a live reference to "i". It gets updated for every loop, which affects all the closures created so far as well. When your while loop ends, "i" is just past the end of the mentions array, so mentions[i] == undefined for all of them.
Do this:
newAnchor.onclick = (function(idx) {
return function () { loadUsernameInfo(mentions[idx]) };
})(i);
to force the "i" to lock into a value idx inside the closure.
Your iterator i is stored as a reference, not as a value and so, as it is changed outside the closure, all the references to it are changing.
try this
function fillInMentioned(mentions) {
var mentionList = document.getElementById("mention-list");
mentionList.innerHTML = "";
for (var i = 0; i < mentions.length; i++) {
var newAnchor = document.createElement("a");
// Set the index as a property of the object
newAnchor.idx = i;
newAnchor.onclick = function () {
// Now use the property of the current object
loadUsernameInfo(mentions[this.idx])
};
// give this anchor the necessary content
newAnchor.innerHTML = mentions[i];
var newListItem = document.createElement("li");
newListItem.appendChild(newAnchor);
mentionList.appendChild(newListItem);
}
document.getElementById("mentions").setAttribute("class", "");
}