Click handler losing reference to "this" object that created it - javascript

I made JS script:
var zzz;
zzz = {
fff: function (Id) {
alert("You did it! Id="+Id);
},
main: function (Id) {
var button, elements;
button = document.createElement("input");
button.type = "submit";
button.onclick = function () {
zzz.fff(Id);
};
elements = document.getElementById(Id);
elements.appendChild(button);
}
};
and HTML, where I tested it:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8">
<title>My Web Page!</title>
<script type="text/javascript" src="test.js"></script>
</head>
<body>
<div id="div001"></div>
<div id="div002"></div>
<script type="text/javascript">
object1 = zzz;
object1.main("div001");
object2 = zzz;
object2.main("div002");
</script>
</body>
</html>
Why it works only if I write button.onclick = function () { zzz.fff(Id); }; and with this.fff(Id) it doesn't work?

When you bind an event handler (such as onclick), inside the handler this becomes the element that triggered the event (except if you used an inline onclick="" attribute, which should be avoided).
Instead of using zzz, you could also copy this to another variable that would be available inside the handler via closure:
var that = this;
button.onclick = function () {
that.fff(Id);
};
Or you could use Function.prototype.bind:
var clickHandler = button.onclick = function () {
this.fff(Id);
};
button.onclick = clickHandler.bind(this);

Related

Javascript Beginner: can't get function to execute on multiple objects

I'm working on Cole Steele's Web Developer Bootcamp on Udemy #264. Event Bubbling. I'm trying to build a function which will allow one or more objects to be passed in and to execute the same action (toggle the classList 'hide' so that the 'Click Here to Hide' button goes away and the 'Click Here to Show' button appears) on each of them.
I am able to get this working by calling the function separately, such as
const container = document.querySelector('#container')
const show = document.querySelector('#show')
function hideOneElement(ele){
ele.classList.toggle('hide');
}
show.addEventListener('click', function () {
hideOneElement(container);
hideOneElement(show);
})
However, when I try to call the function with both container (the div that says 'Click here to hide') and show at the same time, I can't get it to work. I tried writing the hide function as a for...of, such as
function hideElements(elements){
for (const ele of elements) {
ele.classList.toggle('hide')
}
}
let stuffToHide = [container,show]
hideElements(stuffToHide)
However this does not seem to work. I also tried passing in as two separate arguments but that also doesn't seem to work:
function hideElements(ele1, ele2) {
ele1.classList.toggle('hide');
ele2.classList.toggle('hide')
}
hideElements(container, show);
At this point, I'm not sure where to go, and my Google-jitsu is not finding anything useful. This isn't really part of the course exercise, but seems like I'm fundamentally missing something about calling functions.
Full code and HTML below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.hide {
display: none;
}
</style>
</head>
<body>
<section onclick="alert('sectopm clicked')">
<p onclick="alert('paragraph clicked')">I am a paragraph
<button onclick="alert('button clicked')">Click</button>
</p>
</section>
<div id="container">
Click to Hide
<button id="colorbtn">Change Color</button>
</div>
<div id="show">
Click here to Show
</div>
<script src="app.js"></script>
</body>
</html>
const makeRandColor = () => {
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
return (`rgb(${r},${g},${b})`);
}
const button = document.querySelector('#colorbtn')
const container = document.querySelector('#container')
const show = document.querySelector('#show')
function hideElements(ele1, ele2) {
// for (const ele of elements) {
// ele.classList.toggle('hide')
// }
// elements.classList.toggle('hide')
ele1.classList.toggle('hide');
ele2.classList.toggle('hide')
}
function hideOneElement(ele){
ele.classList.toggle('hide');
}
//hideElements(show); //run function once to toggle on the 'hide' class so that this is not shown by default.
hideElements(show)
button.addEventListener('click', function (e) {
container.style.backgroundColor = makeRandColor();
e.stopPropagation();
})
container.addEventListener('click', function () {
hideElements(container, show);
//hideElements(container, show); //hide the 'click here to hide' stuff
})
show.addEventListener('click', function () {
hideElements(container);
hideElements(show);
})
show.addEventListener('click', function () {
hideOneElement(container);
hideOneElement(show);
})
"I'm trying to build a function which will allow one or more objects to be passed in" so you need to be able to call hideElements as either hideElements(ele1) or hideElements(ele1, ele2)?
If that's the case you'll need to pass an array vs individual variables. You were on the right track with the commented out code here:
// for (const ele of elements) {
// ele.classList.toggle('hide')
// }
// elements.classList.toggle('hide')
But you weren't trying to pass them as an array and you can't loop through an individual element (there's only one). If you wrap your variables in an array using [] that should fix your issues.
Example:
function hideElements(elements) {
for (const ele of elements) {
ele.classList.toggle('hide')
}
}
hideElements([show]); //run function once to toggle on the 'hide' class so that this is not shown by default.
container.addEventListener('click', function() {
hideElements([container, show]);
})
show.addEventListener('click', function() {
hideElements([container, show]);
})
Working Example:
const makeRandColor = () => {
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
return (`rgb(${r},${g},${b})`);
}
const button = document.querySelector('#colorbtn')
const container = document.querySelector('#container')
const show = document.querySelector('#show')
function hideElements(elements) {
for (const ele of elements) {
ele.classList.toggle('hide')
}
}
function hideOneElement(ele) {
ele.classList.toggle('hide');
}
hideElements([show]); //run function once to toggle on the 'hide' class so that this is not shown by default.
button.addEventListener('click', function(e) {
container.style.backgroundColor = makeRandColor();
e.stopPropagation();
})
container.addEventListener('click', function() {
hideElements([container, show]);
})
show.addEventListener('click', function() {
hideElements([container, show]);
})
.hide {
display: none;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<section onclick="alert('sectopm clicked')">
<p onclick="alert('paragraph clicked')">I am a paragraph
<button onclick="alert('button clicked')">Click</button>
</p>
</section>
<div id="container">
Click to Hide
<button id="colorbtn">Change Color</button>
</div>
<div id="show">
Click here to Show
</div>
<script src="app.js"></script>
</body>
</html>
Some blocks will not work for sure - as you call hideElements with a single argument - the second arg will be undefined and there's no classList on undefined of course (causes error).
And also it's very confusing because you add event listener on the show element twice..
Copied from your post and added comments:
//hideElements(show); //run function once to toggle on the 'hide' class so that this is not shown by default.
hideElements(show) // - this will error like I said above as elem2 will be undefined..
button.addEventListener('click', function (e) {
container.style.backgroundColor = makeRandColor();
e.stopPropagation();
})
container.addEventListener('click', function () {
hideElements(container, show);
//hideElements(container, show); //hide the 'click here to hide' stuff
})
//below you make same event listener twice which is very confusing :
show.addEventListener('click', function () {
hideElements(container);
hideElements(show);
})
show.addEventListener('click', function () {
hideOneElement(container);
hideOneElement(show);
})

Refer JS object in HTML element generated by this object

My JavaScript object create some HTML elements (two buttons for example) and after user click on these buttons I should call some method of this object. So the question is how I can refer JS object in HTML element to call its method?
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Title Goes Here</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
function myObj(){
this.a = null;
this.setA = function(a){
this.a = a;
}
this.requestA = function(){
$( "body" ).append($('<input><button onclick="referenceToMyObject.setA($(this).prev().val());">Set A</button>'));
}
return this;
}
</script>
</head>
<body>
<script>
var myObjInst = myObj();
myObjInst.requestA();
</script>
</body>
Creating the event handler inline (onclick="foo()") won’t allow you to reference the object, and is discouraged in any case because you should avoid evaluating strings as code. In addition, your code bypasses JavaScript’s idea of objects somewhat. You can reformulate it as follows:
function MyObj() {
this.a = null;
}
MyObj.prototype.setA = function(a) {
const old = this.a;
this.a = a;
console.log("Updated a from", old, "to", this.a);
};
MyObj.prototype.requestA = function() {
const input = $("<input type='text'>");
const button = $("<button>Set A</button>");
button.click((e) => {
this.setA($(e.target).prev().val());
});
const body = $("body");
body.append(input);
body.append(button);
};
$(document).ready(() => {
const myObjInst = new MyObj();
myObjInst.requestA();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Here, we use button.click to define the event handler and new MyObj() to instantiate the object. Apart from that, I cleaned up the code a bit and added a bit of logging so you can see what’s going on.
You could still define setA and requestA within the constructor, as you do in your example. I chose to define them on the prototype since their behaviour is the same across instances.
Try this and please let me know if this works for you.
(working example in JSFiddle https://jsfiddle.net/galeroy/9nocztk4/1/)
<!doctype html>
<html>
<head>
<script>
var myObject = {
createButton: function(){
var p = document.getElementById('par')
var b = document.createElement('button');
b.innerHTML = 'click me';
b.setAttribute('onclick', 'myObject.myMethod()'); // this is the important part
p.appendChild(b);
},
myMethod: function(){
alert("Button created by object, when clicked, calls another method in the same object")
}
}
function init(){
myObject.createButton();
}
</script>
</head>
<body onload="init()">
<p id="par"></p>
</body>
</html>

JavaScript: function fires in different order

I don't understand why function alert() fires before setting style to indicator in this code:
JavaScript:
var MyClass = function()
{
th = this;
th.func = function(){alert('yes');};
th.Click = function(){
document.getElementById('indicator').style.color = "#0f0";
document.getElementById('indicator').innerHTML = "YES";
th.func(); // here it fires before above style changing
};
th.Start = function()
{
var a = document.getElementById('button1');
a.addEventListener('click', th.Click, false);
};
th.Init = function()
{
th.Start();
};
}
var a = new MyClass().Init();
Html:
<button id='button1'>Click Me</button>
<div id='indicator' style='color:#f00'>NO</div>
I want it to fire after.
That is because of the Single Threaded Nature of the Javascript. The alert / modal window actually stops everything else from running until it is dismissed.
That includes the changing of color. Javascript actually says hey browser start changing colors and moves on but as it hits an alert the changing of colors or whatever processes that happen will be paused and it will not start continuing again until the modal window aka alert is dismissed. A workaround might be to do something like this: codepen
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<button id='button1'>Click Me</button>
<div id='indicator' style='color:#f00'>NO</div>
<script type="text/javascript">
var MyClass = function() {
th = this;
th.func = function() { window.alert('yes'); };
th.Click = function() {
document.getElementById('indicator').style.color = "#0f0";
document.getElementById('indicator').innerHTML = "YES";
// The setTimeout fix
// ====
setTimeout(th.func, 100);
};
th.Start = function()
{
var a = document.getElementById('button1');
a.addEventListener('click', th.Click, false);
};
th.Init = function()
{
th.Start();
};
}
var a = new MyClass().Init();
</script>
</body>
</html>

Javascript function does not get passed paramrter

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
<script language="javascript" type="text/javascript">
(function setFont() {
var i;
for ( i = 0; i < document.all.length; i++) {
document.all[i].style.fontFamily = "Verdana";
document.all[i].style.fontSize = "16";
document.all[i].style.color="black";
}
})();
(function abc(a)
{
alert(a);
ansArray = ['a'];
for(i=1;i<=a;i++)
{
document.write('<input type = "button" value = "a">');
document.write('<input type = "button" value = "b">');
}
var myButton = document.getElementsByTagName("input");
//alert(myButton.length);
myButton[0].onclick = function() {
if(ansArray[0] == 'a')
myButton[0].style.backgroundColor = "green";
else
myButton[0].style.backgroundColor = "red";
};
myButton[1].onclick = function() {
if(ansArray[0] == 'b')
myButton[1].style.backgroundColor = "green";
else
myButton[1].style.backgroundColor = "red";
};
})();
setFont();
</script>
</head>
<body onload="abc(2)">
</body>
</html>
A javascript function abc(a) does not get the value 2 passed from <body onload = "abc(5)">. It says undefined. How to pass the parameters in a javascript function. I have posted it earlier as well but the parameter was not there, on giveing the parameter i found the problem.Please help me. Thanks in advance
Your function is a closure, it's not exposed to the public but it is executed right after it's created.
And then it's gone.
It's not there to look cool, it has its purpose. Just make normal functions to get it work
(function(a) {
// immediately called and 'garbaged'
})(a);
vs.
function publicAlwaysCallable(a) {
console.log(a); // call me when you like
}
You don't have to use immediate function in this case. Declare it like this:
function abc(a) { ... }
If for some reason you want to encapsulate your code into closure you can do it like this:
(function(export) {
export.abc = function(a) { ... };
...
})(window);
The normal function (not closure) function abc(a){ ... } with the button click handlers are to be called at the end of the script. Not on onload event of the page. Now it works in IE9 also
Thanks everybody for your valuable suggestions.

javascript - passing additional parameters into an event handler

I have a javascript object which has some defined variables and attaches some event handlers. I'd like the event handlers to have access to the defined variables. Is there a way to do that ? The event-handlers are within their own local scope so don't have access to the object variables. Is there a way to pass them in without using global variables ?
I have an idea that closures would solves this but I'm not sure how.
the code below will print the object name when the page loads but when you click on the map dom object it will say name is undefined.
All help much appreciated.
Colm
<!DOCTYPE html>
<html lang="en">
<head>
<title>Map Test</title>
<meta charset="utf-8" />
<script type="text/javascript">
window.onload = function() {
var e = new EvtTest();
e.printName();
e.attachEvents();
};
function EvtTest() {
this.name = "EvtTest";
}
EvtTest.prototype.name = null;
EvtTest.prototype.attachEvents = function () {
var map = document.getElementById("map");
map.addEventListener ('click', this.evtHandler, false);
};
EvtTest.prototype.printName = function () {
console.log ("This Name : " + this.name);
};
EvtTest.prototype.evtHandler = function (e) {
e.preventDefault();
console.log ("Name : " + this.name);
};
</script>
<style type="text/css">
html, body {
height:100%;
width:100%;
margin: 0;
background-color:red;
}
#map {
height: 300px;
width: 300px;
background-color:yellow;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>
A little bit of fiddling:
EvtTest.prototype.attachEvents = function () {
var that = this;
var map = document.getElementById("map");
map.addEventListener ('click', function () {
that.evtHandler();
}, false);
};
Now this inside evtHandler references the object you expected.

Categories