Javascript onclick event not working with variable reference - javascript

I am reading some code and trying to replicate the same at my end step by step.
I am attaching an event to a particular button and onclick it should log a statement.
Following is the code that does not work :-
(function(){
var el = function(element){
if(element.charAt['0'] === '#'){
return document.querySelector(element);
}
return document.querySelectorAll(element);
}
var viewer = el('#viewer');
var clear = el('#clear');
console.log(clear);
var clearAll = function(){
console.log('Clearing');
};
//click event
clear.onclick = clearAll;
})();
Above a function is used to get elements.
The below code works
document.getElementById('clear').onclick = clearAll;
or
document.querySelector('#clear').onclick = clearAll;
I do not understand why the above code does not work. Please Help.

"foo".charAt['0'] is undefined because charAt is a function and doesn't have a 0 property.
You need () (to call the function), not [] (to access properties of the object).

You are using charAt as an array while it is a function.
var el = function(element){
if(element.charAt('0') === '#'){
return document.querySelector(element);
}
return document.querySelectorAll(element);
}
See it working https://jsfiddle.net/
Regards,

Related

addEventListener (and removeEventListener) function that need param

I need to add some listeners to 8 object (palms).
These object are identical but the behaviour have to change basing to their position.
I have the follow (ugly) code:
root.palmsStatus = ["B","B","B","B","B","B","B","B"];
if (root.palmsStatus[0] !== "N")
root.game.palms.palm1.addEventListener("click", palmHandler = function(){ palmShakeHandler(1); });
if (root.palmsStatus[1] !== "N")
root.game.palms.palm2.addEventListener("click", palmHandler = function(){ palmShakeHandler(2); });
if (root.palmsStatus[2] !== "N")
root.game.palms.palm3.addEventListener("click", function(){ palmShakeHandler(3); });
if (root.palmsStatus[3] !== "N")
root.game.palms.palm4.addEventListener("click", function(){ palmShakeHandler(4); });
if (root.palmsStatus[4] !== "N")
root.game.palms.palm5.addEventListener("click", function(){ palmShakeHandler(5); });
if (root.palmsStatus[5] !== "N")
root.game.palms.palm6.addEventListener("click", function(){ palmShakeHandler(6); });
if (root.palmsStatus[6] !== "N")
root.game.palms.palm7.addEventListener("click", function(){ palmShakeHandler(7); });
if (root.palmsStatus[7] !== "N")
root.game.palms.palm8.addEventListener("click", function(){ palmShakeHandler(8); });
I have two needs:
1) doesn't use an anonymous function on click event.
I wrote this code, but it doesn't work
root.game.palms.palm8.addEventListener("click", palmShakeHandler(8));
So this one works fine
root.game.palms.palm8.addEventListener("click", function(){ palmShakeHandler(8); });
But I didn't understand how remove the event listener.
I try this solution, but it doesn't work
root.game.palms.palm8.addEventListener("click", palmHandler = function(){ palmShakeHandler(8); });
root.game.palms.palm8.removeEventListener("click", palmHandler);
2) add and remove listener in a for cycle
I wrote the follow code but the behaviour is not correct.
for (i=1; i <= root.palmsStatus.length; i++){
if (root.palmsStatus[i-1] !== "N"){
root.game.palms["palm" + i].addEventListener("click", function(){ palmShakeHandler(i); });
}
}
the listeners was added but the value of the parameter passed to the palmShakeHandler is always 8.
Nobody could help me to fix these issues?
There is a actually, a perfect way to do that in JavaScript using the Function.prototype.bind method.
bind let you define extra parameters that will be passed, as arguments, of the function.
You should also keep in mind that bind creates a new function and doesn't modify the initial function.
Here is what it looks like:
function palmHandler(number) {
// your code working with `number`
}
var palmHandler8 = palmHandler.bind(null, 8)
// the palmHandler8 is now tied to the value 8.
// the first argument (here null) define what `this` is bound to in this function
This should fix your problem, and you will be able to remove handlers easily :)
Your code will look like this:
for (i=1; i <= root.palmsStatus.length; i++){
if (root.palmsStatus[i-1] !== "N"){
root.game.palms["palm" + i].addEventListener("click", palmShakeHandler.bind(null, i));
}
}
To be able to remove the handler afterward, you need to keep a reference to the function you create with bind. This would be the way to do this.
var boundHandler = handler.bind(null, i);
element.addEventListener(boundHandler);
element.removeEventListener(bounderHander);
If you want to know more about the awesome bind method in JavaScript, the MDN is your friend :) https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
BTW, the problem with you function always returning 8 is a very common question in JavaScript. This thread will explain everything (spoiler, it's a matter of scoping :) ) https://stackoverflow.com/a/750506/2745879
So in case your array of »palms« is very huge, it is basically a bad Idea to add a single event listener to each of them, because that causes performance flaws. So I would suggest a different approach:
var handlers = [function (e) {}, …, function (e) {}];
root.game.palms.forEach(functiion (palm, idx) {
palm.setAttribute('data-idx', idx);
});
<palmsparent>.addEventListener('click', function (e) {
var c = e.target, idx = -1;
while (c) {
if (c.hasAttribute && c.hasAttribute('data-idx')) {
idx = parseInt(c.getAttribute('data-idx'));
break;
}
c = c.parentNode;
}
//here you also check for the »palm status«
if (idx >= 0) {
handlers[idx](c);
}
})
One event listener for all, much easier to remove and better for performance.
In your last solution you are pasing the same var to every function and that is what make al the functions work with 8 because is the last value of the variable.
To work arround that you can use "let" ( please at least use var, otherside that "i" is global and can be changed every where in the code) but since I dont know wich browser you target I propose other solution.
for (var i=1; i <= root.palmsStatus.length; i++){
if (root.palmsStatus[i-1] !== "N"){
root.game.palms["palm" + i].addEventListener("click", (function(index)
(return function(){
palmShakeHandler(index);
}))(i);
}
}
Since its look like You are targeting modern browsers I will use let.https://kangax.github.io/compat-table/es6/
for (var i=1; i <= root.palmsStatus.length; i++){
let index = i;
let intermediateFunction = function(){palmShakeHandler(index);};
if (root.palmsStatus[i-1] !== "N"){
root.game.palms["palm" + i].addEventListener("click",intermediateFunction);
root.game.palms["palm" + i].removeHandShake = function(){this.removeEventListener("click",intermediateFunction)};
}
}
So now you just need to call "removeHandShake" and will remove the listener,
I have code this right here so it ease some minor errors to pop

String literal not being appended to DOM

I'm trying to make functions that return me an HTML element as a string so I can eventually append them to the DOM and pass them around my functions. I have paragraph, heading, and div's working but cannot for the life of me seem to get links to work.
This is the non-working codepen
The javascript in question is:
function link(href,text,css){
let target = ``;
if(css == null ){
return target;
}
return addStyles(target,css);
}
My addStyles function is:
function addStyles(target,css){
if(css == null ){
return target;
}
let props = Object.keys(css);
props.forEach((prop)=>{
switch(prop){
case 'id':
$(target).attr('id', css[prop]);
break;
case 'class':
let classes = css[prop];
if(Array.isArray(css[prop])){
classes = css[prop].toString().replace(',', ' ');
}
$(target).addClass(classes);
break;
default:
$(target).attr('data-name', 'Timi');
}
});
return target;
}
which for a long time gave me errors but only when calling it from the link function. I tried hard-coding in the href to see if maybe my string literals were giving me the errors but still no avail.
while calling it from this function it works perfectly
function createDiv(css){
let newDiv = $(div);
return addStyles(newDiv,css);
}
I say that the addStyles function works and that I think it's the link() that is giving me problems because createDiv works and appends the DOM with these calls
app.append(createDiv({id: 'testing'}));
app.append(createDiv({class: ['bar', 'snuff']}));
app.append(createDiv()).append(createDiv({class: 'timi'})).append(paragraph('Hey guys!'));
$('#testing').append(heading(1,'Hello'));
your let target = ; is a string. you should use a DOM and manipulate its attribute instead
function link(href,text,css){
let target = document.createElement('a');
let linkText = document.createTextNode(text);
target.appendChild(linkText);
target.href = href;
if(css == null ){
return target;
}
return addStyles(target,css);
}
You forgot to wrap the a tag with bling:
let target = $(``);
The reason your createDiv function is working as expected and your link function isn't is because you haven't wrapped your representation of an html element (the one you pass to addStyles) in a jQuery wrapper like you do in your createDiv function. See my below code for what I've changed to get it to work:
function link(href,text,css){
let target = `${text}`;
if(css == null ){
return target;
}
return addStyles($(target)[0],css);
}
codepen
I'm a little lost actually why we need to add [0] to $(target) but we do. Maybe someone else could chime in about why that is the case with ${text} and not with <div></div>
Edit: disregard above about the [0] and see comments on this answer.

Filling array on event and access it outside the event function handler

I'm trying to fill an array on an event (onclick) with the button's value (there will be different buttons with different values, that's why I'm using an array), and I want to be able to access that array outside the event handler function.
This is what I tried so far, but I just can't figure how to access the array outside the event handler function.
Here's the HTML:
<button value="5"> button </button>
<div> The value is: <span id="res"></span></div>
And here the script:
var n = [];
var val;
var ret;
function add(arr,val) {
arr.push(val);
return val;
}
document.body.addEventListener("click", function(event) {
if (event.target.nodeName == "BUTTON") {
val = event.target.value;
ret = add(n, val);
console.log(n); //these console.log are tests
console.log(ret);
}
console.log(ret); //also this
});
//need to access the array here
console.log(n); //obv doesn't work
console.log(ret); //same
document.getElementById("res").innerHTML = ret; //it remains undefined, obv
I know why this doesn't work (it's because I do all the thing inside the event handler function), but I can't figure it out how to do what I want to do.
Any suggestions?
You need to perform the action you want to perform in the callback, not before. The reason is that none of the code in the event handler runs before the event occurs. Just declaring the variable doesn't change the order of execution.
So you need to put
document.getElementById("res").innerHTML = ret;
inside your event handler function.
Updated (and simplified) code:
var n = [];
document.body.addEventListener("click", function(event) {
if (event.target.nodeName == "BUTTON") {
var val = event.target.value;
n.push(val);
console.log(n); //these console.log are tests
console.log(ret);
document.getElementById("res").innerHTML = val;
}
});

Javascript function scope concepts

I am reading this book "Secrets of the Javascript Ninja" where most of the code is demonstrated with the use of a custom assert. The code is as follows:
(function () {
var queue = [],
paused = false,
results;
this.test = function test(name, fn) {
queue.push(function () {
results = document.getElementById("results");
results = assert(true, name).appendChild(
document.createElement("ul"));
fn();
});
runTest();
};
this.pause = function () {
paused = true;
};
this.resume = function () {
paused = false;
setTimeout(runTest, 1);
};
function runTest() {
if (!paused && queue.length) {
queue.shift()();
if (!paused) {
resume();
}
}
}
this.assert = function assert(value, desc) {
var li = document.createElement("li");
li.className = value ? "pass" : "fail";
li.appendChild(document.createTextNode(desc));
if (results === undefined) results = document.getElementById("results");
results.appendChild(li);
if (!value) li.parentNode.parentNode.className = "fail";
return li;
};
})();
As you can see is a self invoking function.
I've been playing with it and something that I just cannot understand is why if, between the same tags, I do this:
<script type="text/javascript">
... previously shown code ...
window.onload = function(){
assert(true, "this works");
};
</script>
And then again if I just do the assert like this:
<script type="text/javascript">
... previously shown code ...
assert(true, "this does not work");
</script>
When I try to execute the assert without using the window.onload event I get the error "Uncaught TypeError: Cannot call method 'appendChild' to null" on the line "results.appendChild(li)" of the assert method.
Thank you so much for your help.
The element markup (with id="results") is not parsed by the time the code runs, so trying to fetch it with getElementById returns null, which in turn makes the .appendChild fail.
When you put your code inside the window.onload handler, it's (the code inside the handler function) guaranteed to run only after the window has been loaded at which point the document markup is fully parsed as well and the element is available.
Alternatively, you can simply have your script element come after the target element:
<ul id="results"></ul>
<script>
//your code
</script>
Because the script element comes after the target element, the target element is guaranteed to exist by the time the script runs.
Because you are calling the code before the elements on the page are rendered. So when the code looks for document.getElementById("results") it finds nothing and returns null.

Can I name a JavaScript function and execute it immediately?

I have quite a few of these:
function addEventsAndStuff() {
// bla bla
}
addEventsAndStuff();
function sendStuffToServer() {
// send stuff
// get HTML in response
// replace DOM
// add events:
addEventsAndStuff();
}
Re-adding the events is necessary because the DOM has changed, so previously attached events are gone. Since they have to be attached initially as well (duh), they're in a nice function to be DRY.
There's nothing wrong with this set up (or is there?), but can I smooth it a little bit? I'd like to create the addEventsAndStuff() function and immediately call it, so it doesn't look so amateuristic.
Both following respond with a syntax error:
function addEventsAndStuff() {
alert('oele');
}();
(function addEventsAndStuff() {
alert('oele');
})();
Any takers?
There's nothing wrong with the example you posted in your question.. The other way of doing it may look odd, but:
var addEventsAndStuff;
(addEventsAndStuff = function(){
// add events, and ... stuff
})();
There are two ways to define a function in JavaScript. A function declaration:
function foo(){ ... }
and a function expression, which is any way of defining a function other than the above:
var foo = function(){};
(function(){})();
var foo = {bar : function(){}};
...etc
function expressions can be named, but their name is not propagated to the containing scope. Meaning this code is valid:
(function foo(){
foo(); // recursion for some reason
}());
but this isn't:
(function foo(){
...
}());
foo(); // foo does not exist
So in order to name your function and immediately call it, you need to define a local variable, assign your function to it as an expression, then call it.
There is a good shorthand to this (not needing to declare any variables bar the assignment of the function):
var func = (function f(a) { console.log(a); return f; })('Blammo')
There's nothing wrong with this set up (or is there?), but can I smooth it a little bit?
Look at using event delegation instead. That's where you actually watch for the event on a container that doesn't go away, and then use event.target (or event.srcElement on IE) to figure out where the event actually occurred and handle it correctly.
That way, you only attach the handler(s) once, and they just keep working even when you swap out content.
Here's an example of event delegation without using any helper libs:
(function() {
var handlers = {};
if (document.body.addEventListener) {
document.body.addEventListener('click', handleBodyClick, false);
}
else if (document.body.attachEvent) {
document.body.attachEvent('onclick', handleBodyClick);
}
else {
document.body.onclick = handleBodyClick;
}
handlers.button1 = function() {
display("Button One clicked");
return false;
};
handlers.button2 = function() {
display("Button Two clicked");
return false;
};
handlers.outerDiv = function() {
display("Outer div clicked");
return false;
};
handlers.innerDiv1 = function() {
display("Inner div 1 clicked, not cancelling event");
};
handlers.innerDiv2 = function() {
display("Inner div 2 clicked, cancelling event");
return false;
};
function handleBodyClick(event) {
var target, handler;
event = event || window.event;
target = event.target || event.srcElement;
while (target && target !== this) {
if (target.id) {
handler = handlers[target.id];
if (handler) {
if (handler.call(this, event) === false) {
if (event.preventDefault) {
event.preventDefault();
}
return false;
}
}
}
else if (target.tagName === "P") {
display("You clicked the message '" + target.innerHTML + "'");
}
target = target.parentNode;
}
}
function display(msg) {
var p = document.createElement('p');
p.innerHTML = msg;
document.body.appendChild(p);
}
})();
Live example
Note how if you click the messages that get dynamically added to the page, your click gets registered and handled even though there's no code to hook events on the new paragraphs being added. Also note how your handlers are just entries in a map, and you have one handler on the document.body that does all the dispatching. Now, you probably root this in something more targeted than document.body, but you get the idea. Also, in the above we're basically dispatching by id, but you can do matching as complex or simple as you like.
Modern JavaScript libraries like jQuery, Prototype, YUI, Closure, or any of several others should offer event delegation features to smooth over browser differences and handle edge cases cleanly. jQuery certainly does, with both its live and delegate functions, which allow you to specify handlers using a full range of CSS3 selectors (and then some).
For example, here's the equivalent code using jQuery (except I'm sure jQuery handles edge cases the off-the-cuff raw version above doesn't):
(function($) {
$("#button1").live('click', function() {
display("Button One clicked");
return false;
});
$("#button2").live('click', function() {
display("Button Two clicked");
return false;
});
$("#outerDiv").live('click', function() {
display("Outer div clicked");
return false;
});
$("#innerDiv1").live('click', function() {
display("Inner div 1 clicked, not cancelling event");
});
$("#innerDiv2").live('click', function() {
display("Inner div 2 clicked, cancelling event");
return false;
});
$("p").live('click', function() {
display("You clicked the message '" + this.innerHTML + "'");
});
function display(msg) {
$("<p>").html(msg).appendTo(document.body);
}
})(jQuery);
Live copy
Your code contains a typo:
(function addEventsAndStuff() {
alert('oele');
)/*typo here, should be }*/)();
so
(function addEventsAndStuff() {
alert('oele');
})();
works. Cheers!
[edit] based on comment: and this should run and return the function in one go:
var addEventsAndStuff = (
function(){
var addeventsandstuff = function(){
alert('oele');
};
addeventsandstuff();
return addeventsandstuff;
}()
);
You might want to create a helper function like this:
function defineAndRun(name, func) {
window[name] = func;
func();
}
defineAndRun('addEventsAndStuff', function() {
alert('oele');
});
Even simpler with ES6:
var result = ((a, b) => `${a} ${b}`)('Hello','World')
// result = "Hello World"
var result2 = (a => a*2)(5)
// result2 = 10
var result3 = (concat_two = (a, b) => `${a} ${b}`)('Hello','World')
// result3 = "Hello World"
concat_two("My name", "is Foo")
// "My name is Foo"
If you want to create a function and execute immediately -
// this will create as well as execute the function a()
(a=function a() {alert("test");})();
// this will execute the function a() i.e. alert("test")
a();
Try to do like that:
var addEventsAndStuff = (function(){
var func = function(){
alert('ole!');
};
func();
return func;
})();
For my application I went for the easiest way. I just need to fire a function immediately when the page load and use it again also in several other code sections.
function doMyFunctionNow(){
//for example change the color of a div
}
var flag = true;
if(flag){
doMyFunctionNow();
}

Categories

Resources