I have the following code where I want to pass an order object to deleteOrder function using template literal.
function updateUI() {
let orderItems = orders.map(function(order){
return `<li class="order">
<label>${order.name}</label>
<p>${order.coffee}</p>
<button onclick="deleteOrder(\"${order.key}\")">Remove</button>
</li>`
})
ordersList.innerHTML = orderItems.join('')
}
SOLUTION:
function updateUI() {
let orderItems = orders.map(function(order){
return `<li class="order">
<label>${order.name}</label>
<p>${order.coffee}</p>
<button onclick="deleteOrder('${order.key}')">Remove</button>
</li>`
})
ordersList.innerHTML = orderItems.join('')
}
Inline handlers are bad practice and difficult to manage, as you're finding out. Also, any way you represent an object in HTML, there's no way to make it so that it's === to the original object (so, for example, you couldn't use includes to check to see if an array contains that object). Attach the handler properly using Javascript instead so that order can be referenced via its closure:
let orderItems = orders.map((order) => {
const li = document.createElement('li');
li.className = 'order';
li.innerHTML = `
<label>${order.name}</label>
<p>${order.coffee}</p>
<button>Remove</button>`;
li.children[2].addEventListener('click', () => {
deleteOrder(order);
});
return li;
});
If you don't want to use .children[index], if your li has only one button, you can select it with querySelector:
li.querySelector('button').addEventListener...
Note that this will result in orderItems returning an array of elements rather than an array of strings, so rather than using the result to assign to some innerHTML, call appendChild on the container to append the li. For example:
const lis = orderItems(orderArr);
lis.forEach((li) => {
container.appendChild(li);
});
If you want to pass order.key to deleteOrder instead of passing order, then replace
deleteOrder(order);
with
deleteOrder(order.key);
Although inline handlers shouldn't be used, the way to do it would be to use double quotes around the whole onclick attribute, and single quotes around the order.key string, making sure to escape any single or double quotes in order.key:
const escapedKey = order.key.replace(/['"]/g, '\\$&');
// ...
<button onclick="deleteOrder('${escapedKey}')">Remove</button>
But the escaping is ugly and inelegant - attach the handler with Javascript instead.
Related
Well this is a thing i need help of some one who know how to get the element from a foreach that innerHTML into a table data that comes from a data base, i did it by this way but it is not the most optimize way, so i need help to do it by other way, i add an onclick event with javascript and the only thing i make by doing this is that the only button that works is the one on top of the table or the bottom button of the table.
This is the way i make it work:
//Preload
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld(
"electron", {
printmovieonpreload: (results) => ipcRenderer.on("send-movie", (event, results) => {
mylist.innerHTML = " "
results.forEach(elements => {
mylist.innerHTML += `<tr><td> ${elements.movie-name} </td>
<td> ${elements.movie-duration} min </td><td><button id="btn" value="${elements.id-movie}" "/*this get all the onclick events of the page*/${onclick = deletefromtable}" type="button" class="fas cli fa-trash-alt"></button></td>
</tr>`;
});
})
});
async function deletefromtable(e) {
/* this is were i filter from id of the onclick event and how i reduce the value of the button to delete it*/
if (e.srcElement.id == "btn") {
const obj = {
id: e.srcElement.value
}
await ipcRenderer.invoke('delete_movie', obj);
}
}
It's better to not do this by string interpolation, but by letting the browser (in Electron's terms, Chromium) create elements of the wanted type and then setting their contents and appending them to the parent. This can (and should) be done like so:
// inside your forEach function
var tr = document.createElement ("tr");
var td1 = document.createElement ("td");
var td2 = document.createElement ("td");
var td3 = document.createElement ("td");
var btn = document.createElement ("button");
td1.textContent = elements ["movie-name"];
td2.textContent = elements ["movie-duration"] + " min";
btn.id = "btn";
btn.classList.add ("fas", "cli", "fa-trash-alt");
btn.type = "button";
btn.value = elements ["id-movie"];
btn.onclick = delete_from_table;
td3.appendChild (btn);
tr.appendChild (td1);
tr.appendChild (td2);
tr.appendChild (td3);
myList.appendChild (tr);
For this to work, however, you must meet the following conditions:
The index strings for the elements array must be correct. I'm pretty sure that your code using string interpolation would throw errors, because - is an operator and cannot be used when referencing indices using the dot operator.
delete_from_table must be a function, not a string. Since it is not included in the code snippet you have posted, I cannot verify if my solution would work based off of that. However, it seems that you would rather like to bind updatestatuscero to the onclick event of the button. If so, simply replace delete_from_table with this function's name.
Just some more tips regarding general JavaScript:
Use braces wherever possible to make the code more intelligible. For example, you could have written your code like so:
contextBridge.exposeInMainWorld(
"electron", {
printmovieonpreload: (results) => {
ipcRenderer.on("send-movie", (event, results) => {
mylist.innerHTML = " "
results.forEach(elements => {
mylist.innerHTML += `<tr><td> ${elements.movie-name} </td>
<td> ${elements.movie-duration} min </td><td><button id="btn" value="${elements.id-movie}" "/*this get all the onclick events of the page*/${onclick = delete_from_table}" type="button" class="fas cli fa-trash-alt"></button></td>
</tr>`;
});
});
}
}
});
This way, the scope of the functions can be understood at first glance. Both you and any other person having to work with this code in the future will be very pleased if you keep it organised.
When using string interpolation, you can execute operations. This is why I stated that - is not valid in an index above. The reason for this is that any expression inside ${} in an interpolated string will be evaluated and its return value will be included in the string. Thus,
var testObject = {
"test-prop": "def"
};
var stringA = `abc ${testObject.test-prop} ghi`; // Throws: ReferenceError, prop is not defined
var stringB = `abc ${testObject["test-prop"]} ghi`; // yields "abc def ghi"
What the expression inside stringA tries to do is to actually subtract prop from testObject.test, neither of which are defined.
Also, with string interpolation, you have executed an assignment (${ onclick = delete_from_table}) which caused the new variable onclick to be defined, the method to be (correctly) written to the HTML, but not bound to the buttons' onclick event! Instead, you could have done onclick="${delete_from_table}" just like you did with the value property.
Don't ever insert HTML by using element.innerHTML! Take a look at MDN -- this method has some pretty unexpected implications. Better sanitise your HTML first, use element.insertAdjacentHTML() or simply create elements and add them to a parent element by using element.appendChild() as I showed you above. This way is also, in my opinion, but tastes differ, the cleanest way possible.
I have two the same forms on the same page and script that works only for the first form.
I'm a beginner and this is a challenge for me; I tried add the `for (var i = 0; i < input.length; i++) but it doesn't work out. I will be grateful for any help.
var el = document.querySelector(".js-tac");
input = document.querySelector('.js-tel')
input.addEventListener('input', evt => {
const value = input.value
if (!value) {
el.classList.remove("is-visible");
return
}
const trimmed = value.trim()
if (trimmed) {
el.classList.add("is-visible");
} else {
el.classList.remove("is-visible");
}
})
document.querySelector return the first matched element. So you need document.querySelectorAll which will give a collection. Then iterate that collection like this
document.querySelectorAll('.js-tel').forEach((input)=>{
// not using arrow function since using this to target the element
input.addEventListener('input', function(evt){
const value = this.value
// rest of the code
})
})
The problem is that you are only getting one input element. (querySelector returns the first matching element, not all matching elements). You likely want to use querySelectorAll to get a NodeList (which will contain all matching nodes). You can iterate over those.
Based on how you seem to be using it, I'd recommend making sure your js-tac and js-tel are wrapped in some common parent, and use querySelectorAll to find those. Then, you can use querySelector to find the js-tel and js-tac.
var nodes = document.querySelectorAll('.js-parent')
//If you don't export forEach to be available, you can also just do a standard
//for loop here instead.
nodes.forEach((parent) => {
var el = parent.querySelector(".js-tac");
input = parent.querySelector('.js-tel')
...
})
I declared array and added variables inside so now I want to create one click function with javascript but I have no idea how I tried this but not working
function volumeUpDown(){
let a = $('._1');
let b = $('._2');
let c = $('._3');
let d = $('._4');
let e = $('._5');
let f = $('._6');
let g = $('._7');
let ar = [a,b,c,d,e,f,g];
ar.forEach().click(function(){
$(this).addClass('volactive');
})
}
volumeUpDown();
You can do the whole thing in one jQuery call:
$('._1, ._2, ._3, ._4, ._5, ._6, ._7').click(function() {
$(this).addClass("volactive");
});
The comma in the selector string lets you ask for multiple different matches to be collected into the jQuery object. The jQuery object is already an array (sort-of) and jQuery functions like .click() will do their work for all elements in the jQuery object.
Just for dynamic purposes, in case you need to someday add more dynamically (using still a single jQuery call, which is the correct approach anyway)
$(Array.from({length: 7},(_,i) => `._{$i + 1}`).join(', ')).click(function() {
$(this).addClass("volactive")
});
The first part will generate the selector:
const selector = Array.from({length: 7},(_,i) => `._${i + 1}`).join(', ');
console.log(selector);
You need to take the object and apply the callback.
ar.forEach(o => o.click(function() {
$(this).addClass('volactive');
}));
That's not how you use forEach:
ar.forEach(elem => elem.click(function() {
$(this).addClass("volactive");
}));
this is my code:
function newTodo() {
const text = prompt('What needs to be done?').trim();
if (!!text) {
id += 1;
const todoId = 'todo' + id;
// checkbox
const todoCheckbox = document.createElement('input');
todoCheckbox.type = 'checkbox';
todoCheckbox.id = todoId;
todoCheckbox.classname = classNames.TODO_CHECKBOX;
todoCheckbox.setAttribute('onclick', `handleCheck(${todoId})`);
...
} else {
alert('Please enter a valid todo');
}
}
function handleCheck(todoId) {
console.log('checked!', todoId);
}
but handleClick is logging the html element the function is attached to:
<input type="checkbox" id="todo2" onclick="handleCheck(todo2)">
I wanted to log the id only, so i am expecting it to be:
todo2
what am i doing wrong?
Your handler:
onclick="handleCheck(todo2)"
attempts to reference a global variable named "todo2" and pass it to the handleCheck function. Because (confusingly) element IDs are automatically added to the global object, this results in the element you just created being passed to the function.
Surround the todoId in quotes instead, to ensure it gets passed as a string:
todoCheckbox.setAttribute('onclick', `handleCheck('${todoId}')`);
But, it would be significantly more elegant to add the handler properly using Javascript instead of an inline HTML attribute:
todoCheckbox.onclick = () => handleCheck(todoId);
Dynamic IDs are a code smell, though - they're rarely necessary. If you need data unique for each element, you might consider using data- attributes, or a Map.
It should be <input type="checkbox" id="todo2" onchange="handleCheck(todo2)">
I am mostly familiar with java selenium, and I am new to both JS and Protractor. Lets say I am trying to click an option from a list of options with a common identifier..
var options = $('.options');
How could I get all elements with that common identifier, and then select one by its text? I can not do driver.findElements like I could in java since there is no reference to driver..
This is what I have tried so far but its not working and I think its due to my inexperience with JS
this.selectCompanyCode = function(companyCode) {
dropDownMenus[0].click();
var companyCodeOptions = $('[ng-bind-html="companyCode"]');
companyCodeOptions.filter(function (elem) {
return elem.getText().then(function text() {
return text === companyCode;
});
}).first().click();
};
Select all elements with common identifier: $$('.options'); That selects all elements with a class of .options -- equivalent of element.all(by.css('.options')). This returns an ElementArrayFinder. Also see .get() for how to choose an element by index from the ElementArrayFinder.
Find by text, you could use cssContainingText(css, text). For example,
var loginBtn = element(by.cssContainingText('button.ng-scope', 'Login'));
But if for some reason those are not providing the expected results, you can use .filter() (docs here) on an ElementArrayFinder to go through the array of elements and find an element based on a condition you specify. For example,
var allOptions = $$('.options');
allOptions.filter(function (elem) {
return elem.getText().then(function (text) {
return text === 'What you want';
});
}).first().click();
And, although I've never used regular Java Selenium (so I don't know if this is the same), but there is indeed a browser reference (and therefore findElements function): http://www.protractortest.org/#/api?view=ProtractorBrowser.
Hope it helps!
Edit:
Using your code:
this.selectCompanyCode = function(companyCode) {
// where is dropDownMenus defined? This has function no reference to it.
dropDownMenus.get(0).click(); // should be this
var companyCodeOptions = $$('[ng-bind-html="' + companyCode + '"]');
return companyCodeOptions.filter(function (elem) {
return elem.getText().then(function text() {
return text === companyCode;
});
}).first().click();
};
second edit:
Assuming company code is unique, you probably don't need to use filter. Try this:
this.selectCompanyCode = function(companyCode) {
dropDownMenus.get(0).click();
var companyCodeOptions = $('[ng-bind-html="' + companyCode + '"]');
return companyCodeOptions.click();
};
Use cssContainingText
element(by.cssContainingText(".option", "text")).click();
http://www.protractortest.org/#/api?view=ProtractorBy.prototype.cssContainingText