So i decided to make a wordle clone today and wrote some basic javascript after doing some html and css. I wanted to add an event to all the elements of the array at once using forEach using the code below.
const letterCounter = 0;
Array.from(document.querySelectorAll("button")).forEach(elem => {
elem.addEventListener('click', inputLetter(elem.innerText))
})
function inputLetter(letter){
Array.from(document.querySelectorAll("letter-box"))[letterCounter].innerText = letter
letterCounter++
}
It immediately throws me an error in the console. The problem is that it is supposed to do that when I click on an element of the array, not automatically. I tried it with other elements, like displaying hello world in the console after clicking but it also executed automatically.(btw i do not need help with the code itself, I just want know why the added event is being executed automatically and also i'm a beginner so please don't judge my code :)).
eventHandler should be a function, you passed a function call by mistake. Function calls are run immediately.
const letterCounter = 0;
Array.from(document.querySelectorAll("button")).forEach(elem => {
elem.addEventListener('click', () => inputLetter(elem.innerText))
})
function inputLetter(letter){
Array.from(document.querySelectorAll("letter-box"))[letterCounter].innerText = letter
letterCounter++
}
Related
I'm trying to add a click event listener to a bunch of newly created divs in a for loop. The issue I'm having is that only the last div keeps its event listener. I've read up about closures and read several other posts and questions and their answers, and as far as I can tell I have it set up correctly. But it still isn't working for me. Only the final div to be iterated is receiving the event listener.
function edit_entry(k) {
wrd_input.value = k;
def_input.value = lexicon[k][1];
wrd_input.onkeyup();
delete lexicon[k];
rewrite_entries();
}
function rewrite_entries(keys = null) {
if (keys === null) { keys = []; }
let sorted_keys = sort_lex_keys();
lex_body.style.color = 'rgb(200, 200, 200)';
lex_body.innerHTML = '';
sorted_keys.forEach((key) => {
if (!keys.length || keys.includes(key)) {
lex_body.innerHTML +=
`<div class='lex-entry' id=${key}><i>${key}</i>\n<p class='pronunciation'>${lexicon[key][0]}</p>${lexicon[key][1]}</div>\n`;
let entry = document.getElementById(key)
entry.addEventListener('click', edit_entry.bind(this, key) );
}
});
}
Current state of the relevant code above. If somebody knows the issue, it'd be very helpful.
If it's relevant, this code is running via Electron (17.0.0).
An attempted solution, using an anonymous arrow function instead of a bind:
entry.addEventListener('click', () => edit_entry(key) );
yields the same result.
Update:
Changing the .innerHTML attribute of something apparently strips all event listeners. So the solution was simply to create the element completely in js, add it to the container, and then add the listener. This change to the forEach loop solves the problem:
sorted_keys.forEach((key) => {
if (!keys.length || keys.includes(key)) {
let entry = document.createElement('div');
entry.className = 'lex-entry';
let word = document.createElement('p');
word.appendChild( document.createTextNode(key) );
word.style.fontStyle = 'italic';
let pron = document.createElement('p');
pron.className = 'pronunciation';
pron.appendChild( document.createTextNode(lexicon[key][0]) );
let defn = document.createTextNode(lexicon[key][1]);
entry.append(word, pron, defn);
entry.addEventListener('click', () => edit_entry(key) );
lex_body.appendChild(entry);
So a few things crosses my mind:
Firstly, edit_entry.bind(this, key);: Are you certain this is pointing to what you want? It should be pointing to the window object. Also, is there any reason to bind context for that particular function?
Secondly innerHTML: Generally it's discouraged to use innerHTML directly. I don't know how/when the browser layouts changes to innerHTML, the div might not be on the page when you call getElementById. As an alternative, you could try document.createElement("div"), set its properties accordingly and finally append it to lex_body.
Edit
From mdn:
Please note that using innerHTML to append html elements (e.g. el.innerHTML += "link") will result in the removal of any previously set event listeners. That is, after you append any HTML element that way you won't be able to listen to the previously set event listeners.
I think that binding the function every time with this is overriding the binding every time, that should be the reason that you are only getting the last one's event.
Inside of the forEach lambda, the this keyword refers to the internal class that is calling the forEach not your rewrite_entries function. Try not binding the call, like so:
entry.addEventListener('click', () => edit_entry(key) );
As an exercise I have to do a little online bike reservation app. This app begins with a header which explains how to use the service. I wanted this tutorial be optional so I wrote a welcome message in HTML and if the user doesn't have a var in his cookies saying he doesn't want to see the tutorial again, the welcome message is replaced by a slider that displays the information.
To achieve that is fetch a JSON file with all the elements I need to build the slider (three divs : the left one with an arrow image inside, the central one where the explanations occur and the right one with another arrow). Furthermore I want to put "click" events on the arrows to display next or previous slide. However, when I do so, only the right arrow event works. I thought of a closure problem since it is the last element to be added to the DOM that keeps its event but tried many things without success. I also tried to add another event to the div that works ("keypress") but only the click seems to work. Can you look at my code give me an hint on what is going on?
Here is the init function of my controller:
init: function() {
var load = this.getCookie();
if(load[0] === ""){
viewHeader.clearCode();
var diapoData = ServiceModule.loadDiapoData("http://localhost/javascript-web-srv/data/diaporama.json");
diapoData.then(
(data) => {
// JSON conversion
modelDiapo.init(data);
// translation into html
controller.initElementHeader(modelDiapo.diapoElt[0]);
controller.hideTuto();
}, (error) => {
console.log('Promise rejected.');
console.log(error);
});
} else {
viewHeader.hideButton();
controller.relaunchTuto();
}
}
There is a closer look at my function translating the JSON elements into HTML and adding events if needed :
initElementHeader: function(data){
data.forEach(element => {
// Creation of the new html element
let newElement = new modelHeader(element);
// render the DOM
viewHeader.init(newElement);
});
}
NewElement is a class creating all I need to insert the HTML, viewHeader.init() updates the DOM with those elements and add events to them if needed.
init: function(objetElt){
// call the render
this.render(objetElt.parentElt, objetElt.element);
// add events
this.addEvent(objetElt);
},
Finally the addEvent function:
addEvent: function(objet){
if(objet.id === "image_fleche_gauche"){
let domEventElt = document.getElementById(objet.id);
domEventElt.addEventListener("click", function(){
// do things
});
}
else if(objet.id === "image_fleche_droite"){
let domEventElt = document.getElementById(objet.id);
domEventElt.addEventListener("click", function(){
// do stuff
});
};
},
I hope being clear enough about my problem. Thank You.
Ok, I found the problem, even if the element was actually created, it somehow stayed in the virtual DOM for some time, when the "getElementById" in "addEvent" was looking for it in the real DOM it didn't find the target and couldn't add the event. This problem didn't occur for the last element since there was nothing else buffering in the virtual DOM.
On conclusion I took out the function adding events out of the forEach loop and created another one after the DOM is updated to add my events.
I have the following buttons:
<button id="abcd" onclick="something()">click</button>
and the following functions are attached to this button apart from the one in its html definition.
$('#abcd').on('click',function(){alert("abcd");});
$('#abcd').on('click',function(){
someAjaxCallWithCallback;
});
Now I want a new function with another ajax call to execute on this button's click, before the above mentioned functions. This new function determines whether the remaining functions would be called or not based on what data is recieved by the ajax call. That is, this pre function should complete its execution before giving control over to the rest of the functions and also determine whether they would run or not.
As an example, without changing the existing validation logics and button code, I have to add a new pre-validation function and similarly and post validation function.
I have a bindFirst method using which I can at least bring my new function to the beginning of the call stack but I have not been able to contain its execution and control further delegation because of callbacks.
If I understand correctly, you are looking for the way to do this, without modifying html and already existing js, only by adding new js-code.
First of all, if onclick handler is set and you want to control it, you should disable it on page load (maybe, saving it to some variable):
$(document).ready(function() {
var onclick = $("#abcd").attr("onclick").split("(")[0];
//to run it in future: window[onclick]();
$("#abcd").attr("onclick", "");
});
Edit: I changed my answer a little, previous approach didn't work.
Now you need to remove all already existing handlers. If number of handlers you want to control is limited, constant and known to you, you can simply call them in if-else after pre-validation inside your pre-function. If you want something more flexible, you are able to get all the handlers before removing, save them and then call them in a loop.
For that "flexible" solution in the end of $(document).ready(); you save all already existing handlers to an array and disable them. Then you write your pre-function and leave it as the only handler.
var handlers = ($._data($("#abcd")[0], "events")["click"]).slice();
$("#abcd").off("click");
$("#abcd").click(function() {
//this is your pre-func
//some code
handlers[1].handler.call();
});
Try console.log($._data($("#abcd")[0], "events")) to see, what it is.
Finally just run your post-function and do whatever you need, using conditions.
So, the general algorithm is as follows:
Disable onclick
Save all handlers
Disable all handlers
Run pre-func first
Run handlers you want to be executed
Run post-func
In fact, you just make your pre-func the only handler, which can run all other handlers you may need.
Although Alex was spot on, I just wanted to add more details to cover certain cases that were left open.
class preClass{
constructor(name,id){
if($(id) && $(id)[0] && $(id)[0]['on'+name])
{
var existing = $(id)[0]['on'+name]
$(id).bindFirst(name,existing);
$(id).removeAttr('on'+name)
alert("here");
}
if($._data($(id)[0],"events")){
this.handlers = $._data($(id)[0],"events")[name].slice();
}
else
{
this.handlers = null;
}
this.id = id;
this.name = name;
}
generatePreMethod(fn,data)
{
$(this.id).off(this.name);
$(this.id).bindFirst(this.name,function(){
$.when(fn()).then(execAll(data));
});
}
}
function exec(item,index){
item.handler.call()
}
function execAll(handlers){
return function(){ handlers.forEach(exec);}
}
This more or less takes care of all the cases.
Please let me know if there is something I missed!
I'm writing code for a message board and when the user is writing a post and clicks "preview" it creates a new DIV element that contains the parsed post. I need to detect when this preview element is created and modify its contents. The following code creates infinite recursion in Chrome, Firefox halts after 5 recursions.
$('#c_post').on('DOMNodeInserted', function(){
var $preview = $('#c_post-preview');
if($preview.length) {
$preview.html(applyForEach(funcs, $preview.html()));
}
});
It's not related to applyForEach because I just added that code and I was getting the recursion error before that but here's the code for that anyway:
function applyForEach(arr, s) {
for(var i = 0; i < arr.length; ++i) {
s = arr[i](s);
}
return s;
}
var funcs = [createGifvVideo, createGfycatVideo, createHtml5Video];
The functions simply take a string, call replace on it, and returns the string.
You may break the infinite recursion by unbinding and binding event . so it would not go into infinite call.Try following-
$('#c_post').on('DOMNodeInserted',DomInsCallback);
function DomInsCallback(){
var $preview = $('#c_post-preview');
if($preview.length) {
$('#c_post').off('DOMNodeInserted');//here unbind first
$preview.html(applyForEach(funcs, $preview.html()));
$('#c_post').on('DOMNodeInserted',DomInsCallback);//bind again
}
}
I suppose #c_post-preview is inside #c_post. So when you modify #c_post-preview, the event DOMNodeInserted is triggered again. And you catch it again, and you modify #c_post-preview, and so on ...
Most probably you have nested #c_post-preview inside of #c_post, but i can't tell for sure, since you didn't post the HTML source. Of course this would lead to an infinite loop of triggering and catching events. But besides that, i don't think you want to applyForEach the content of the post preview, but the one of the post itself.
Consider the following: http://jsfiddle.net/wpb18pyu/
compared to: http://jsfiddle.net/wpb18pyu/1/
I am trying to create a web app that will allow a user to define a custom JavaScript function and then add a button to their user interface that well preform that function.
Here is a sample of the code
var customCommands = {
command1: {
text: 'Hello Console',
cFunctionRun: function() {
console.log('hello Console!');
}
},
command2: {
text: 'Hello World',
cFunctionRun: function() {
alert('hello World!');
}
}
}
Then I wrote a small function that loops though and builds the buttons and adds them to the user interface. The problem is when I append the elements to the user interface than click on the buttons nothing works...
Here is one of the methods I tried
for (var cmd in customCommands) {
command = customCommands[cmd];
button = $('<button/>').html(command.text).on('click',
function(){
console.log(command.text);
command.cFunctionRun();
}
);
}
buttonContainer.append(button);
Now my loop builds everything just fine and even the .on('click') works, but it always displays the text of the lasted added command?
here is http://jsfiddle.net/nbnEg/ to show what happens.
When you actually click, the command variable points to last command (as the whole loop has already run). You should maintain data state per button which tells it which command to invoke. You should do this.
for(var i in customCommands) {
if(customCommands.hasOwnProperty(i)){ //this is a pretty important check
var command = customCommands[i];
button = $('<button/>').html(command.text).data("command_name", command).on('click', function(){
console.log($(this).data("command_name").text);
$(this).data("command_name").cFunctionRun();
});
$("body").append(button);
}
}
JSFiddle
all you need is passing the parameter with function, you should try this
It's a (missing) closure problem. The event handler will keep a reference to the value of command on the last iteration of the loop. To solve it you can create a new scope, using an immediately invoked function:
for(var cmd in customCommands) {
(function(command){
button = $('<button/>').html(command.text).on('click',
function(){
console.log(command.text);
command.cFunctionRun();
}
);
buttonContainer.append(button);
}(customCommands[cmd]));
}
Since the buttons should be unique (no reason for creating duplicates), I'm setting the button id to the name of the customCommands (command1 and command2 in this example). This example could easily be adapted to use any of the relative attributes (data-*, name, etc...).
Create a click event listener on document for whenever one of your buttons are pressed. Then call the function associated with the given id.
$(document).on("click", "button", function(){
customCommands[this.id].cFunctionRun();
});
for(var command in customCommands){
var button = $('<button id="' + command +'"/>').html(customCommands[command].text);
$("body").append(button);
}
EXAMPLE