Outputting an object to a page that keeps close function - javascript

I posted this question before, among others. But it was suggested I need to ask a more specific or focused question.
I am working on an output history log on a single page. And I want to make it so each output it's self is contained in box object that can be closed or deleted individually. Like this.
Now I have managed to get everything working to the point where it will nicely output to a box with a close button. However the close button it's self will not function in this case.
So, I am trying to output it like this...
HTML:
<p>History log:</p><br><div style="white-space:pre-wrap"><ul
id="outputListItem" class="boxcontainer"></ul></div>
SCRIPT:
document.getElementById("Add").onclick = function(e) {
convertOutput();
}
function convertOutput(){
//this is the part I have been trying to get working
convertOutput.addEventListener('close', function() {
this.parentElement.style.display = 'none';
}
});
var output = document.getElementById("output").value;
var li = document.createElement('li');
li.className = "containedboxes";
var dateTime = todayDateTime();
li.innerHTML = "<time id='time'>" + dateTime +"</time><br /> <br />"+ output
+"<br /><br /><span class='close'>×</span>";
document.getElementById('outputListItem').prepend(li);
}
And the script to close the box:
var closebtns = document.getElementsByClassName("close");
var i;
for (i = 0; i < closebtns.length; i++) {
closebtns[i].addEventListener("click", function() {
this.parentElement.style.display = 'none';
});
}
It was suggested to me on the last question I posed I should use convertOutput() right after addEventListener() loop immediately after it. If this is how you do it, i am still quite new to JavaScript, so not sore how to properly do this. I created a fiddle for this also, but for some reason I can't get the script to run properly in the fiddle, But all the code is there to see.
I am looking to solve this using vanilla JavaScript.

I created an example for you. Hopefully this helps you get going :) A couple things to note, I use a data attribute to store the index for the item in the array, so you can delete it when you click on the list item.
document.addEventListener('DOMContentLoaded', function(){
let nameEl = document.querySelector("#name");
let submitEl = document.querySelector("#submit-name");
let historyEl = document.querySelector(".history-list");
let historyList = [
{ name: 'Mitch'},
{ name: 'Max'},
{ name: 'Mike'},
];
function addToList(arr) {
// Clear up list and then update it
while(historyEl.firstChild) {
historyEl.removeChild(historyEl.firstChild);
}
// Update the list with the historyList
for(let item in historyList) {
let name = historyList[item].name;
let listContent = document.createElement("li");
listContent.textContent = name;
// We will use the index to remove items from the list
listContent.setAttribute('data-value', item);
listContent.addEventListener("click", removeFromList)
historyEl.appendChild(listContent);
}
}
function removeFromList(index) {
// Takes the index of the object, and will later remove it
console.log("Removed Item " + this.dataset.value);
historyList.splice(index, 1);
addToList(historyList);
}
addToList(historyList);
submitEl.addEventListener("click", function(event) {
if(nameEl.value) {
// Add the name to the start of the history list array.
historyList.unshift({ name: nameEl.value})
nameEl.value = '';
// Update the dom with the new array
addToList(historyList);
}
});
});
<label for="name">Type Name</label>
<input type="text" name="name" id="name">
<button id="submit-name">Submit Name</button>
<ul class="history-list"></ul>
Hopefully this gives you a good idea on how to get the task done and let me know if you have any questions :)

Your boxes don't respond to the click event simply because your script crashes before the events even get attached to it.
The following block right at the beginning:
document.getElementById("Add").onclick = function(e) {
convertOutput();
}
tries to add a click listener to the HTML element Add which does not exist. If you either remove the code block or add the appropriate element your boxes will have it's click functionality.

Related

How to save data in local storage

I am creating a to-do-list in javascript and everything was good until I had to deal with the local storage. I want to be able to refresh the page without losing what I put in my list just before.
I searched in many forums but I didn't find a similar case.
Here is a part of my HTML code : Just an ul and an input
Here is a part of my JS code : My function which creates li inside my ul
And here is a preview of my to do list : Hope it helps
So if I didn't explain my problem well enough, let me know and I will bring more precisions.
Thank for reading !
PS : I should specify that I already read the documentation on MDN and others websites and I understood the principle of localStorage but I'm struggling with the integration of this in my code. I saw lot of examples of its use but they are often too simple or on the contrary too different/hard to understand.
This is why I ask your help to have a little bit more personal response.
window.addEventListener('load', function()
{
var yourToDo = document.getElementById('myInput');
yourToDo.addEventListener('keydown', myFunction);
function myFunction(e)
{
if (e.keyCode == 13)
{
var line = document.createElement('div'); //This is my div which contains the 3 items which constitute a line
line.classList.add('myLine');
document.getElementById('myUl').appendChild(line);
var circle = document.createElement('i'); //The first item is a circle which can be check or unchecked
circle.id = 'myCircle';
document.querySelector('.myLine:last-child').appendChild(circle);
circle.addEventListener('click', function()
{
this.classList.toggle('fas');
this.classList.toggle('fa-check-circle');
this.classList.toggle('unstyled');
task.classList.toggle('crossedOut')
});
var task = document.createElement('li'); //The second item is a <li> which contains the value of the input
task.innerHTML = yourToDo.value.charAt(0).toUpperCase() + yourToDo.value.slice(1);
document.querySelector('.myLine:last-child').appendChild(task);
var trash = document.createElement('i'); //The third item is a trash which suppresses the whole line
trash.classList.add('fas');
trash.classList.add('fa-trash-alt');
document.querySelector('.myLine:last-child').appendChild(trash);
trash.addEventListener('click', function()
{
this.parentNode.remove();
});
yourToDo.value = '';
}
}
<section>
<ul id="myUl"></ul>
<label>
<input id="myInput" type="text" placeholder="Add your to do task" onfocus="this.placeholder = ''" onblur="this.placeholder = 'Add your to do task'">
</label>
</section>
I don't think that localStorage works in the code-snippets but try this in your own code.
I added a function to your code that gets all localStorage items and creates a list item for each on page load. Also added where you should save it to localStorage in your original function.
Comments are added throughout.
Hope this helps
window.addEventListener('load', function() {
var yourToDo = document.getElementById('myInput');
yourToDo.addEventListener('keydown', myFunction);
function myFunction(e) {
if (e.keyCode == 13) {
var line = document.createElement('div'); //This is my div which contains the 3 items which constitute a line
line.classList.add('myLine');
document.getElementById('myUl').appendChild(line);
var circle = document.createElement('i'); //The first item is a circle which can be check or unchecked
circle.id = 'myCircle';
document.querySelector('.myLine:last-child').appendChild(circle);
circle.addEventListener('click', function() {
this.classList.toggle('fas');
this.classList.toggle('fa-check-circle');
this.classList.toggle('unstyled');
task.classList.toggle('crossedOut')
});
var task = document.createElement('li'); //The second item is a <li> which contains the value of the input
task.innerHTML = yourToDo.value.charAt(0).toUpperCase() + yourToDo.value.slice(1);
//Set loacl storage item
localStorage.setItem(yourToDo.value.charAt(0).toUpperCase() + yourToDo.value.slice(1), yourToDo.value.charAt(0).toUpperCase() + yourToDo.value.slice(1));
document.querySelector('.myLine:last-child').appendChild(task);
var trash = document.createElement('i'); //The third item is a trash which suppresses the whole line
trash.classList.add('fas');
trash.classList.add('fa-trash-alt');
document.querySelector('.myLine:last-child').appendChild(trash);
trash.addEventListener('click', function() {
this.parentNode.remove();
});
yourToDo.value = '';
}
}
});
function load() {
//Create each item (as you did above)
function create(item) {
var yourToDo = document.getElementById('myInput');
var line = document.createElement('div'); //This is my div which contains the 3 items which constitute a line
line.classList.add('myLine');
document.getElementById('myUl').appendChild(line);
var circle = document.createElement('i'); //The first item is a circle which can be check or unchecked
circle.id = 'myCircle';
document.querySelector('.myLine:last-child').appendChild(circle);
circle.addEventListener('click', function() {
this.classList.toggle('fas');
this.classList.toggle('fa-check-circle');
this.classList.toggle('unstyled');
task.classList.toggle('crossedOut')
});
var task = document.createElement('li'); //The second item is a <li> which contains the value of the input
//Set innerHTML to item that you ar passing in for loop below
task.innerHTML = item;
document.querySelector('.myLine:last-child').appendChild(task);
var trash = document.createElement('i'); //The third item is a trash which suppresses the whole line
trash.classList.add('fas');
trash.classList.add('fa-trash-alt');
document.querySelector('.myLine:last-child').appendChild(trash);
trash.addEventListener('click', function() {
this.parentNode.remove();
});
yourToDo.value = '';
}
//Create an array to store all local storage items
var values = [],
keys = Object.keys(localStorage),
a = keys.length;
//Push items to array
while (a--) {
values.push(localStorage.getItem(keys[a]));
}
//Create a for loop and loop through all array items and pass each item value to the create funtion above
for (let i = 0; i < Object.keys(localStorage).length; i++) {
create(values[i])
}
}
//Call load on page load up (it would actually be better if you add this in your window "load" listener above )
load();
//Hope this helps!
<section>
<ul id="myUl"></ul>
<label>
<input id="myInput" type="text" placeholder="Add your to do task" onfocus="this.placeholder = ''" onblur="this.placeholder = 'Add your to do task'">
</label>
</section>

How do I set up an on.click for multiple generated elements?

As a student project, we are trying to build a website that gives recommendations for movies. After recommendations are generated we want the user to be able to click on any of the movie posters in order to pull up more information about that specific movie. The .on('click') currently selects all of the results which are not ideal...
As it stands this is what we have:
axios.get(omdbCall + movieTitles)
.then(function(response){
let movies = response.data.results;
for (i=0; i < movies.length; i++) {
var posterPath = movies[i].poster_path;
var movieID = movies[i].id;
var movTitle = movies[i].title;
var movImg = "https://image.tmdb.org/t/p/w92";
$('#movPoster').append('<img class="posters" src=' + movImg + posterPath + '>');
}
$(".posters").on("click", function () {
console.log("I clicked a poster!");
})
})
We also tried changing the rendered img tag to include an id based on the movie title or its imdbID. We tried using this selector for both attempts:
$("#" + movTitle)
With this change in the append function:
$('#movPoster').append('<img id=' + movTitle + ' src=' + movImg + posterPath + '>');
I expected to be able to select just one element but that ain't what's happening. I hope I explained properly and in enough detail. Any help would be greatly greatly appreciated. Thank you!
you are making .on('click') event direclty on dynamically generated html. This won't work. Because when the script was loaded initially, there is no element with class posters.
You have to use something like this
$("#not_dynamic_element").on("click", ".posters", function(){
// Code here
});
The logic is you have to select an element that is not dynamically loaded. i.e, a static element which is an ancestor of posters class.
For example say you have a div with class posters-container which is already present on page load. You are appending the img tag with class posters to this div. So you need to get click on all img tag with class posters, you could write,
$(".posters-container").on("click", ".posters", function(){
// Code here
});
Hope you understood the logic and what the issue was.
UPDATE - LOGIC ISSUE IN FIDDLE
I see what's wrong in your fiddle. I am trying to make it simple. So check this code you have written
axios.get(finalSearch)
.then(function(response){
// console.log(response);
let movies = response.data.Similar.Results;
// let posters = response.data.results.poster_path;
for (i=0; i < movies.length; i++){
// console.log(movies[i].Name);
var movArr = movies[i].Name;
var movStr = movArr.split(" ");
var movieTitles = movStr.join("+")
getMoviePosters(movieTitles);
}
})
.catch(function(err) {
console.log(err);
})
In this code you can see that you are calling the function getMoviePosters(movieTitles) inside a for loop. Your for loop contains the following line which you use to select the dynamically generated movie poster element.
$("#movPoster").on("click", function () {
console.log("I clicked a poster!");
})
So i would suggest you to call this click function after the for loop as shown below (Remove the previous code). Also add posters class to click function.
axios.get(finalSearch).then(function(response){
// console.log(response);
let movies = response.data.Similar.Results;
// let posters = response.data.results.poster_path;
for (i=0; i < movies.length; i++){
// console.log(movies[i].Name);
var movArr = movies[i].Name;
var movStr = movArr.split(" ");
var movieTitles = movStr.join("+")
getMoviePosters(movieTitles);
}
$("#movPoster").on("click", '.posters', function () {
console.log("I clicked a poster!");
})
})
reason
Maybe when the code$(".posters").on("click",...) runs while img.posters or #movPoster still not rendered in html.So the click events not triggered.
solution
You can try to move your code inner$(function() { // move your codes here });(related question!), or just add console.log($('#movPoster'), $('#movPoster .posters')) before $(".posters").on("click",...) to verify whether the target elements exist or not.
And bind the click events to #movPoster instead of img.posters。
advice
For better performance, you should refactor your code:
Bind the click events to #movPoster instead of img.posters which makes performance worser for too much events listener.
the code $('#movPoster').append(element) in the loop will cause unneccessay repaint for each loop will insert element inner #movPoster. You could rewrite it like this:
var dom = '';
for(var i=0; i<3; i++) {
dom += '<img src="">'
}
$('#movPoster').append(dom) // only insert dom 1 time, not 3 times

Changing a dynamically created label's text with keyup() issue

I am creating a form dynamically and therefore edit the form elements’ properties. When attempting to change the label, assigning an auto-generated id works fine but when changing this label using the generated id, the function or keyup() from jQuery keeps calling all the previously created label id(s). this means when i want to edit one label, it ends up editing every label.
HTML
<input type="text" id="change-label"><br><br>
<button id="add-button">add label</button>
<div id="add-label"></div>
JavaScript/jQuery
$('#add-button').click(function(){
var div = document.createElement('div');
var textLabel = document.createElement('label');
var labelNode = document.createTextNode('untitled');
textLabel.appendChild(labelNode);
textLabel.id = autoIdClosure();
$('#change-label').val('untitled');
div.appendChild(textLabel);
$('#add-label').append(div);
});
var autoIdClosure = (function(){
var counter = 0;
var labelId = "textInputLabel";
return function(){
counter += 1;
var id = labelId + counter;
editLabelWrapper(id)
return id;
}
})();
function editLabelWrapper(id){
function editLabel(){
var value = $(this).val();
$("#"+id).text(value);
}
$("#change-label").keyup(editLabel).keyup();
}
I’ve already found an alternative using onkeyup="$('#'+globaID).text($(this).val());", but I need to understand what I was doing wrong so I can learn from it.
JSFiddle
I think you are overthinking the matter...
Instead of using an unique id, rather use classes, makes it easier to handle.
So change <div id="add-label"></div> to <div class="add-label"></div>
Then what you want to do is, when a value is given in #change-label you want it in the last div.add-label.
So the function will become this:
$("#change-label").on('keyup', function() {
$('.add-label:last').text( $(this).val() );
});
Next what you want to do is bind a function to #add-button. Once it gets clicked, we want to add a new div.add-label after the last one. And empty the #change-label. You can do that by using this function:
$('#add-button').on('click', function() {
$('.add-label:last').after('<div class="add-label"></div>');
$('#change-label').val('');
});
Updated Fiddle

Javascript - remove button and parent

Hi I am just learning Javascript and after following some tutorials I thought it would be nice to practise some Javascript by making stuff.
So now I am trying to make a very easy to-do-list. Just for practise, but I get stuck.
I managed to add items with a remove-button to an UL with JS. But, BUT:
How do I make it so; when you click on the removeMe button, that only that Li will be removed?
What should I use?
Here's my code:
var buttonAdd = document.getElementById('but1');
var buttonRemove = document.getElementById('but2');
var ul = document.getElementById('myUl');
function addLi() {
var newLi = document.createElement('li');
var removeThis = document.createElement('button');
var textInput = document.getElementById('inputText').value;
if(textInput === ""){
alert('Add text');
}else{
newLi.innerHTML = textInput;
newLi.appendChild(removeThis);
removeThis.innerHTML = "Remove me";
removeThis.setAttribute("onClick", "removeMe(this);");
ul.appendChild(newLi);
}
}
buttonAdd.onclick = function() {
addLi();
};
buttonRemove.onclick = function() {
ul.innerHTML = "";
};
function removeMe(item){
//get called when clicked on the remove button
}
and my HTML:
<body>
<ul id="myUl"></ul>
<input id="inputText" type="text"><br />
<button id="but1">Add stuff</button><br />
<button id="but2">Remove all</button>
</body>
Thanks
The function remove() is a brand new DOM 4 method and not very widely supported yet. The clunky, but bulletproof way would be:
function removeMe(item){
item.parentElement.parentElement.removeChild(item.parentElement);
}
or with a bit more elegance:
function removeMe(item){
var parent = item.parentElement;
parent.parentElement.removeChild(parent);
}
http://jsfiddle.net/BtbR4/
Also be careful with this:
removeThis.setAttribute("onClick", "removeMe(this);");
Handing a function reference as a string is always a bad idea for several reasons (eval'ing the string, messing up the scope). There are several better options:
removeThis.onclick = removeMe;
or if you need to hand over parameters
removeThis.onclick = function(){removeMe(your,parameters)};
The best option however is to attach eventhandlers always like this:
Element.addEventListener("type-of-event",functionReference);
You just need to remove the parent node (the li), as I've shown using jsbin.
function removeMe(item){
item.parentNode.remove();
}
Please note Blue Skies's comment that this may not work across all browsers, an alternative is:
var par = item.parentNode; par.parentNode.removeChild(par);
a cleaner way to do things is to add
removeThis.onclick = removeMe;
and
function removeMe(mouseEvent){
this.parentNode.remove();
}
This is consistent with how you add the other onclick functions in your code. Since you said you are learning js, it is a good idea to learn how events and functions work. So, the take away from this is that the 'this' of a function that is attached to an object is the object itself (the removeThis object in this case), and event handlers give you access to the event that invoked them (mouseEvent) in the argument list.
JSfiddle: http://jsfiddle.net/QT4E3/

Recursive IDs and duplicating form elements

I have the following fiddle:
http://jsfiddle.net/XpAk5/63/
The IDs increment appropriately. For the first instance. The issue is when I try to add a sport, while it duplicates, it doesn't duplicate correctly. The buttons to add are not creating themselves correctly. For instance, if I choose a sport, then fill in a position, and add another position, that's all fine (for the first instance). But when I click to add another sport, it shows 2 positions right away, and the buttons aren't duplicating correctly. I think the error is in my HTML, but not sure. Here is the JS I am using to duplicate the sport:
$('#addSport').click(function(){
//increment the value of our counter
$('#kpSport').val(Number($('#kpSport').val()) + 1);
//clone the first .item element
var newItem = $('div.kpSports').first().clone();
//recursively set our id, name, and for attributes properly
childRecursive(newItem,
// Remember, the recursive function expects to be able to pass in
// one parameter, the element.
function(e){
setCloneAttr(e, $('#kpSport').val());
});
// Clear the values recursively
childRecursive(newItem,
function(e){
clearCloneValues(e);
});
Hoping someone has an idea, perhaps I've just got my HTML elements in the wrong order? Thank you for your help! I'm hoping the fiddle is more helpful than just pasting a bunch of code here in the message.
The problem is in your clearCloneValues function. It doesn't differentiate between buttons and other for elements that you do want to clear.
Change it to:
// Sets an element's value to ''
function clearCloneValues(element){
if (element.attr('value') !== undefined && element.attr('type') !== 'button'){
element.val('');
}
}
As #PHPglue pointed out in the comments above, when new positions are added, they are incorrectly replicated (I'm assuming here) to the newly cloned for
There is a similar problem with the add years functionality.
A quick fix would be to initialize a variable with a clone of the original form fields:
var $template = $('div.kpSports').first().clone();
Then change your addSport handler to:
$('#addSport').click(function () {
//increment the value of our counter
$('#kpSport').val(Number($('#kpSport').val()) + 1);
//clone the first .item element
var newItem = $template.clone();
…
});
However, there are no event bindings for the new buttons, so that functionality is still missing for any new set of form elements.
Demo fiddle
Using even a simple, naive string based templates the code can be simplified greatly. Linked is an untested fiddle that shows how it might be done using this approach.
Demo fiddle
The code was simplified to the following:
function getClone(idx) {
var $retVal = $(templates.sport.replace(/\{\{1\}\}/g, idx));
$retVal.find('.jsPositions').append(getItemClone(idx, 0));
$retVal.find('.advtrain').append(getTrainingClone(idx, 0));
return $retVal;
}
function getItemClone(setIdx, itemIdx) {
var retVal = itemTemplate.replace(/\{\{1\}\}/g, setIdx).replace(/\{\{2\}\}/g, itemIdx);
return $(retVal);
}
function getTrainingClone(setIdx, trainingIdx) {
var retVal = trainingTemplate.replace(/\{\{1\}\}/g, setIdx).replace(/\{\{2\}\}/g, trainingIdx);
return $(retVal);
}
$('#kpSportPlayed').on('click', '.jsAddPosition', function() {
var $container = $(this).closest('.kpSports');
var containerIdx = $container.attr('data_idx');
var itemIdx = $container.find('.item').length;
$container.find('.jsPositions').append(getItemClone(containerIdx, itemIdx));
});
$('#kpSportPlayed').on('click', '.jsAddTraining', function() {
var $container = $(this).closest('.kpSports');
var containerIdx = $container.attr('data_idx');
var trainIdx = $container.find('.advtrain > div').length;
$container.find('.advtrain').append(getTrainingClone(containerIdx, trainIdx));
});
$('#addSport').click(function () {
var idx = $('.kpSports').length;
var newItem = getClone(idx);
newItem.appendTo($('#kpSportPlayed'));
});

Categories

Resources