Advice on Template String Loop with Buttons - javascript

I am currently working on a mini web application and would like some advice. I know the reason for my issue and I know one way to get around it but it would mean undoing a lot of the work that I have already done.
The issue I have is I am currently using a template string inside a for loop to print data and I have a delete button with a listener. Currently it is only working for the last button the in the list and I know it is because I am destroying the innerHTML every time I use html = x.
What I would like to know is....is there an easy way around this or should I just use append child etc instead?
for(let prod of products){
console.log("Doc ID",doc.id);
const li1 = `
<p><div>${prod.name} <div class="right"><font class=pink-text>Location:</font> $${prod.location} <button data-remove-button-id="${doc.id}" class="btn-remove">Delete</button></div></div></p>
`;
html += li1;
} //end for loop
const li2 = `
<br />
<button class="btn blue darken-2 z-depth-0 right modal-trigger" data-target="modal-prodedit">Add/Edit Attributes</button><br />
</div>
</li>
`;
html += li2;
productList.innerHTML = html;
const removeBtn = document.querySelector(`[data-remove-button-id="${doc.id}"]`);
console.log("removeBTN",removeBtn);
removeBtn.addEventListener('click', (e) => {
e.preventDefault();
console.log(`deleted row ${e.target.dataset.removeButtonId}`);
});

You are using querySelector() method which returns the first element it finds. If you want to attach that listener to all rows you need to use Document.querySelectorAll(). You'll need to differentiate between which row was selected, which doesn't seem to be in your DOM model. So I'd add a data-product-id attribute to your button, and the listener can use that to know which product was deleted.

Thanks to everyone for the help with this one.
The SelectAll worked to some extent but I decided to redo my code from scratch using DOM elements to appendChild. Once done, this made everything sooooo much easier. Decided to change it to give myself more flexibility with the code.

Related

JS querySelector + ID with dynamic values

Im trying to make a simple quiz with a dynamic questions using Jinja (so a bit of python as well) + some SQL + JS.
Since im quite new to this, I was trying to do a simple "click here -> change color to green if your answer is the right one"
Here's the thing: to not complicate things, i want every answer to change the color to red (if wrong) or green (if right). Right know, thanks to this thread Javascript getElementById based on a partial string i manage to create a function that turns the right answer to green wih the code, no matter where the user clicks (as long its inside the question box answers):
document.querySelector('[ id$="{{ question.correct_answer }}"]').style.backgroundColor="rgb(0, 221, 135)";
I thought i could do something like "id$!=" and that would solve my problem, but that didnt work. So i tried to search for other stuff like the :not or not() selectors, but that showed me a lot of jquery stuff, which im not studying/learning right now. So, is there any way to write:
"if the id$ does not match the value {{ question.correct_answer }}, turn red" in plain JS?
Some important stuff about the code:
All answers have id="answer_a", "answer_b" etc.
That matches the way i save que "correct_answer" in the database, which comes exactly like the ID (so if the correct_answer is answer_d, i can call "{{ question.correct_answer }}" and that will always turn D into GREEN;
my HTML looks like <div class=question_answer id="answer_d" onclick="selecResposta()"> {{ question.answer_d }} </div> <br>. These are inside a DIV called "question_options" which i can also put the "onclick" function and everything works the same.
I can provide more information if necessary.
Thanks a lot for the help and sorry if this is something easy to solve. Any guidance (if you dont wanna say the answer) is quite welcome as well.
UPDATE:
Thanks to #connexo and #Vijay Hardaha, i manage to mix both answers and create a code that helped me. It might not be pretty, but its doing what i want so its perfect. Here's the solution:
html part:
<div class=question_answer data-answer="answer_a"> {{ question.answer_a }} </div> <br>
etc.etc
js:
function selecRightAnswer() {
document.querySelector("[data-answer={{ question.correct_answer }}]").style.backgroundColor="rgb(0, 221, 135)";
}
function selectWrongAnswer() {
const elements = document.querySelectorAll("div.question_answer:not([data-answer={{ question.correct_answer }}])");
elements.forEach(function (element) {
element.style.backgroundColor = "red";
});
}
Selects div with class question_answer when div has id=answer_a with an exact match.
document.querySelector("div.question_answer[id=answer_a]");
Selects div with class question_answer when div doesn't have id=answer_a with an exact match.
document.querySelector("div.question_answer:not([id=answer_a])");
document.querySelector will only selector first matched div. so if you have to work with all
unmatched with answer_a then you need to use document.querySelectorAll
and then you'll have to loop reach element and work with each element inside the loop.
Example
const elements = document.querySelectorAll(".box");
elements.forEach(function (element) {
element.style.color = "green";
});

How to get variable, array, nodelist from different function?

I am trying to write a ToDoList with JavaScript.
I have an input-element. Whenever I type something and press enter, it creates a new fieldset(in my example its a fieldset but it can also be a Div) with the class name ".fieldListClass" and a P-Tag as a child of fieldset. the P-tag innerHTML is the the value of input. I used Click-EventListener for that.
After each click, I assigned the query selector of all .fieldListClass to a nodeList "fieldListQuery". I even converted this nodeList into an Array but no result.
Now I want to create an addEventListner but outside the previous one. it should be a new one. And It should be a click-EventListener for all fieldListQuery which where created inside the previous function.(this part is at the bottom of my code)
When I click on it something should happen like removing the current target etc. But it wont work because outside the function it always says that this variable is undefined. I don't get it because I declared it global outside of the function.
I don't want to use DOMNodeInserted or MutationObserver yet for detecting changes inside the DOM. Simple because the first one is not recommended anymore it and the last one I have no idea how to use it. Many people saying that this is not a safe way.
Any Help please?
let addDiv = document.createElement("div"); addDiv.id = "addDivId";
let listDiv = document.createElement("div"); listDiv.id = "listDivId";
let inputText = document.createElement("input"); inputText.id = "inputTextId";
let fieldList; // = document.createElement("fieldset");
let fieldDiv; // = document.createElement("div");
let fieldDivP; // = document.createElement("P");
let fieldListArr;
let fieldListQuery;
document.body.appendChild(addDiv);
addDiv.appendChild(inputText);
document.body.appendChild(listDiv);
inputText.addEventListener("keypress", event => {
if (event.key === "Enter") {
fieldList = document.createElement("fieldset");
fieldDiv = document.createElement("div");
fieldDivP = document.createElement("P");
listDiv.appendChild(fieldList);
fieldList.className = "fieldListClass";
fieldList.appendChild(fieldDiv);
fieldDiv.appendChild(fieldDivP);
fieldDivP.innerHTML = inputText.value;
fieldListQuery = document.querySelectorAll(".fieldListClass") ;
}
})
fieldListQuery.forEach(element => { // <- it say fieldListQuery is undefined.
fieldListQuery.addEventListener("click", e => {
e.currentTarget.innerHTML="test";
})
});
´´´
Since I offered critique of your approach, I thought it is only fair I at least try to offer you some code that accomplishes (on the overall level, in light of absence of much detail about your solution) something along of what you have.
First off, I think creating trees of elements through a script when other solutions are more viable, tends to show an anti-pattern. Your script is invariably loaded in the context of an HTML document, which may already contain a lot of useful markup -- including an input field (that you were creating with createElement). If the input field is a "constant" there is no need to waste code on creating it -- just put it in your markup.
Second, even for elements or hierarchies of elements that are created "on demand" -- as a reaction to an event or however else -- it typically is much more readable and manageable to use templates. As a fallback -- if template cannot be used for some reason -- using innerHTML to create entire element trees is actually an appealing and more readable option than a lot of "boilerplate" containing createElement, appendChild, etc.
Third, you should always try to see if you can have your interactive controls be part of a form. I won't go into all reasons to do so, but suffice to say it helps user agents that screen-read content and for other accessibility systems, to name one. There are exceptions to this rule, but I don't recall looking at code where a control should not be part of a form -- so the rule is a good one.
Here is a proof-of-concept bare-bones to-do application:
<html>
<head>
<script>
function submit_create_todo_item_form() {
const new_todo_fragment = document.getElementById("todo-item-template").content.cloneNode(true);
new_todo_fragment.querySelector(".body").textContent = document.forms[0].elements[0].value;
document.body.appendChild(new_todo_fragment);
}
</script>
<template id="todo-item-template">
<div class="todo-item">
<p class="body"></p>
</div>
</template>
</head>
<body>
<form action="javascript: submit_create_todo_item_form()">
<input>
</form>
</body>
<html>
Take note that I use textContent instead of innerHTML to create content for a to-do item's body. innerHTML invokes the HTML parser and unless you plan to be typing hypertext into that single line of input field, innerHTML only costs you extra for no clear benefit. If you need to interpret the value verbatim, textContent is instead exactly what's needed. So, approach your solution with that in mind.
I hope this is useful, I worked with what I thought I had.

How to add onClick on the component

I am using https://github.com/winhtaikaung/react-tiny-link for displaying some posts from other blogs. I am able to get the previews correctly. I want to capture the views count through onClick() but this module(react-tiny-link) doesn't seems to support the same, please help.
<ReactTinyLink
cardSize="large"
showGraphic={true}
maxLine={0}
minLine={0}
header={""}
description={""}
url={url}
onClick={() => this.handleViewCount(id)} />
I tried adding div around the component but it affects the css.
You can wrap your link with a div and attach your onClick callback on that div instead.
<div onClick={() => this.handleViewCount(id)}>
<ReactTinyLink
cardSize="large"
showGraphic={true}
maxLine={0}
minLine={0}
header={""}
description={""}
url={url}
/>
</div>
Your library - ReactTinyLink does not support onClick attribute.
Since you've tagged javascript - I can give you a small JS hack for the same.
Run the following code at the end of your React Rendering
var cards = document.getElementsByClassName('react_tinylink_card');
linksClicked = [];
for(var i = 0; i<cards.length; i++){
cards[i].onclick = function(){
linksClicked.push(this.href);
}
}
The above code will go through each and every cards and will attach onClick handlers on them, once clicked - the 'this' object will be your anchor tag's element, so I am storing it's href. (you're free to store anything you want)
In the following example - https://winhtaikaung.github.io/react-tiny-link/
I tried the same snippet - and got the following result
Hope this would be a good starting point for what you're trying to achieve.

How do I iterate an output data from an array in an object

I am trying to create a daily weather app and i'm having issues trying to figure out how to output the data into cards for all seven days. its currently only outputting the last day. I know that its because if am setting it to $("#card__days").html( but im not sure how to add onto the current html.
Is there also any easier way to output this information? I feel like I did way too much for a simple task. Thank you
function updateDaily(data) {
Object.keys(data.daily.data).forEach(function (i) {
// call to find what the day is.
let date = calculateDay(data.daily.data[i].time);
console.log(data.daily.data[i]);
let iteration = i;
let high = data.daily.data[i].temperatureHigh;
let low = data.daily.data[i].temperatureLow;
let feels = data.daily.data[i].apparentTemperature;
let desc = data.daily.data[i].summary;
let icon = data.daily.data[i].icon;
let skycons = new Skycons({ color: "#3e606f" });
$("#card__days").html(`
<div class="card__daily">
<h2 id="daily__date"${iteration}">${date}</h2>
<canvas src="" alt="icon" class="icon" id="daily__icon${iteration}"></canvas>
<div class="degrees">
<h3 id="daily__high${iteration}" class="temp">${high}℉ / ${low}℉</h3>
</div>
<h3 id="daily__desc${iteration}">${desc}</h3>
</div>
`);
skycons.add(
document.getElementById("daily__icon" + i),
icon
);
skycons.play();
});
}
EDIT: Here is what it currently looks like and some data from the API.
Current Visual Output
Some data from Object
If you want to make all seven cards appear, use .append() instead:
$("#card_days").append(/* Your card HTML */);
You only see the last card because in each iteration the for loop overwrites the html with the latest ${iteration} data, instead of creating a new piece of data and a new html element. To fix this:
You could use the $("#card_days").append method, from the other comment in this thread, but personally in a similar situation I used:
$("#card_days").insertAdjacentHTML('beforeend', html) method, it will insert the code you give it as an html parameter just before the closing tag of the element you selected.
As a side note, I only used insertAdjacentHTML because I needed the user to be able to create and delete these html elements, so their amount could not be predefined. In your case, it seems like you know you need exactly 7 elements. If this is the case, I would suggest to stick with html and css, and the only usage of JS would be to update a placeholder text with the newly-fetched data. Hard-coding in this case seems a bit more error-proof :)

Listen for changes in DOM

I am working on a reflection of part of my website. This is the relevant HTML:
<div id = "original">
<img src = "picture.png" alt = "A picture" id = "picture">
<p id = "text">Some text</p>
</div>
<div id = "reflection"></div>
My Idea is copying the content of div#original to div#reflection like this:
<script>
function reflect()
{
var original = document.getElementById("original");
var reflection = document.getElementById("reflection");
reflection.innerHTML = original.innerHTML;
}
</script>
I am aware, that this will make the HTML invalid, in the whole project I iterate through the elements copied and set the IDs to not have that side effect. I just thought that this would be unnecessary to include here.
The problem I have is that the HTML I want to reflect is dynamic, so it may change. Then the reflection is wrong. I have searched for an event handler for this, but haven't found one. The only one near I found was onchange, but apparently this listens to changes in the attributes, not the child elements. Is there another one for this, or did I just use onchange wrong?
Thank you for your help!
GeF
I am aware that I could add onchange to every element, but this seemed not good style to me.
The simplest thing you can do is add a callback to reflect() every time you change the contents of the #original.
If this is not an option, you can look into a MutationObserver (documetation), as suggested here: Is there a JavaScript/jQuery DOM change listener?

Categories

Resources