Cannot access js object methods from an event handler - javascript

This HTML snippet creates an Object prototype, instantiates it, then unsuccessfully tries to use that object's methods from an Event.
<body>
<button type="button" onclick="TestLogic()">Test Logic</button>
<script>
function onOff() //Object prototype
{
this.state = false;
function setState(newState) {
this.state = newState;
}
}
var inputOne = new onOff(); //Instantiate object prototype
function TestLogic() //buttonClick Event Handler
{
inputOne.setState(true);
// generates Uncaught Type Error undefined is not a function */
document.inputOne.setState(true);
// generates Uncaught Type Error Cannot read property setState of undefined
}
</script>
</body>

The function in onOff is private, it's not being published as a public property. Change function setState(newState) { ... } to this.setState = function(newState) { ... }.

You have misunderstanding about javascript contexts. MDN has good article about it. See comments to know where you was wrong:
<body>
<button type="button" onclick="TestLogic()">Test Logic</button>
<script>
function onOff() //Object prototype
{
this.state = false;
//set method to context of creating Object
this.setState = function (newState) {
this.state = newState;
}
}
var inputOne = new onOff(); //Instantiate object prototype
function TestLogic() //buttonClick Event Handler
{
inputOne.setState(true);
//document is not a scope, window is global scope
window.inputOne.setState(true);
}
</script>
</body>

Try this
<html>
<body>
<button type="button" onclick="TestLogic()">Test Logic</button>
<script>
function onOff() //Object prototype
{
this.state = false;
this.setState=function(newState)
{
this.state = newState;
console.log(this.state);
}
}
var inputOne = new onOff();
function TestLogic()
{
inputOne.setState(true);
}
</script>
</body>
</html>

If you want to use prototypes then use prototypes like this
function Object () {
this.instanceMethod = false;
}
Object.prototype.prototypeMethod = function () {};
Also you should avoid using inline event listeners and do it like this instead
document.querySelector('button').addEventListener('click', function () {
// logic
});
And here is an example of it all used together (http://jsfiddle.net/sAH35/)
<body>
<button type="button">Test Logic 1</button>
<button type="button">Test Logic 2</button>
<button type="button">Test Logic 3</button>
<script>
//Object prototype
function OnOff(element) {
this.element = element;
this.state = false;
}
OnOff.prototype.setState = function (newState) {
this.state = newState;
if (this.state) {
this.element.style.backgroundColor = 'red';
} else {
this.element.style.backgroundColor = 'grey';
}
};
// bind to elements
var elements = document.querySelectorAll('button');
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('click', function () {
if (!this.onOff) this.onOff = new OnOff(this);
this.onOff.setState(!this.onOff.state);
});
}
</script>
</body>

Related

Use prototype javascript in dom element

Forgot my initial typo in original snippet here is exaly want I trying to do:
How can I use prototyped variable in Dom element event?
Suppose :
function MyProto(){
this.valeur = "toto";
}
MyProto.prototype = {
func1: function() {
var t = document.createTextNode("func1 called ");
document.body.appendChild(t);
var br = document.createElement("br");
document.body.appendChild(br);
this.func2();
},
func2: function() {
var t = document.createTextNode("func2 called");
document.body.appendChild(t);
}
};
var proto = new MyProto();
document.getElementById("myButton").addEventListener("click",proto.func1);
<button id="myButton">Press here</button>
In this example, when I press button it throw me this.func2 is not a function. I must to mention that ultimately the Dom element will be generated by HtmlHelper from Asp.Net MVC.
First Problem
That's just a typo, you are calling funct1 instead of func1
Second Problem (Update)
The problem is when you add the listener your way:
.addEventListener("click",proto.func1)
this will be the clicked element, not your proto instance, to solve this problem you can wrap it in another function clause, like the snippet below.
function MyProto() {
this.valeur = "toto";
}
MyProto.prototype = {
func1: function() {
var t = document.createTextNode("func1 called ");
document.body.appendChild(t);
var br = document.createElement("br");
document.body.appendChild(br);
this.func2();
},
func2: function() {
var t = document.createTextNode("func2 called");
document.body.appendChild(t);
}
};
var proto = new MyProto();
document.getElementById("myButton2").addEventListener("click", function() {
proto.func1()
});
<button id="myButton1" onclick="proto.func1()">First Button</button>
<button id="myButton2">Second Button</button>
Answering the initial question: Fixing the typo works with your inline event
Answering SECOND question - how to use addEventListener and retain this:
Safe solution - wrap call in a function in the event handler:
function MyProto(){
this.valeur = "toto";
}
MyProto.prototype = {
func1: function() {
var t = document.createTextNode("func1 called ");
document.body.appendChild(t);
var br = document.createElement("br");
document.body.appendChild(br);
console.log(this)
this.func2();
},
func2: function() {
var t = document.createTextNode("func2 called");
document.body.appendChild(t);
}
};
var proto = new MyProto();
document.getElementById("myButton1")
.addEventListener("click",() => proto.func1() )
.as-console-wrapper {
height: 125px;
opacity: 0.3;
}
<button type="button" id="myButton1">addEventListener now works</button>
<hr/>
Trials to find how to retain the prototype this when using addEventlListener WITHOUT wrapping in a function.
NOTE button 2 shows the code I wrote which is now used by OP for followup question
function MyProto(){
this.valeur = "toto";
}
MyProto.prototype = {
func1: function() {
var t = document.createTextNode("func1 called ");
document.body.appendChild(t);
var br = document.createElement("br");
document.body.appendChild(br);
console.log(this)
this.func2();
},
func2: function() {
var t = document.createTextNode("func2 called");
document.body.appendChild(t);
}
};
var proto = new MyProto();
document.getElementById("myButton2").addEventListener("click",proto.func1)
document.getElementById("myButton3").addEventListener("click", proto.func1.bind(proto))
.as-console-wrapper {
height: 125px;
opacity: 0.3;
}
<button type="button" id="myButton1" onclick="proto.func1()">Here <i>this</i> is the prototype</button>
<button type="button" id="myButton2">addEventListener has unexpected <i>this</i></button>
<button type="button" id="myButton3">addEventListener bound <i>this</i></button>
<hr/>

How to unbind document keypress event with anonymous function

This is page's code.
I can't modify this.
var Example = {};
Example.create = function() {
var obj = new Example.object();
return obj;
}
Example.object = function(){
this.initialize = initialize;
function initialize() {
window.addEventListener('load', activate);
}
function activate() {
document.addEventListener('keypress', keyPressed);
}
function keyPressed(e) {
alert("Hello!");
}
};
Example.defaultObject = Example.create();
Example.defaultObject.initialize();
I have tried many things...
document.onkeypress = null;
document.keypress = null;
document.removeEventListener('keypress');
$(document).unbind('keypress');
$(document).off("keypress");
$("*").unbind('keypress');
$(document).bind('keypress', function(e) { e.stopPropagation(); });
but all failed.
How can I unbind event of document keypress?
You have to pass the listener to remove it: (a variable pointing the function aka the function name)
document.removeEventListener('keypress', keyPressed);
https://developer.mozilla.org/de/docs/Web/API/EventTarget/removeEventListener
You will have to save it somewhere to remove it later
Root cause of the issue is removeEventListener method. This method expect second parameter which is listener method
document.removeEventListener('keypress', Example.defaultObject.keyPressed);
Here you go for Solution on your problem.
var Example = {};
Example.create = function() {
var obj = new Example.object();
return obj;
}
Example.object = function(){
this.initialize = initialize;
function initialize() {
window.addEventListener('load', activate);
document.getElementById('disable').addEventListener('click', deActivate);
}
function activate() {
document.addEventListener('keypress', keyPressed);
}
function deActivate() {
document.removeEventListener('keypress', keyPressed);
document.querySelector('h1').innerHTML = 'Page Key Press Listener Removed';
}
function keyPressed(e) {
alert("Hello!");
}
};
Example.defaultObject = Example.create();
Example.defaultObject.initialize();
<body>
<h1>Page has Key Press Listener</h1>
<input id="disable" type="button" value="deactivate">
</body>

Assign event listener from js class

I want to assign onclick event listener to an object from within a class
and then get some variable from the instance that created that onclick
function myclass() {
this.myvar;
this.myfunc = function()
{
alert(this.myvar);
document.onmousedown = this.mouseDown;
}
this.mouseDown = function(e)
{
alert(this.myvar); //does not work of course
//how could I access myvar from current myclass instance
}
}
var myclass_instance = new myclass();
myclass_instance.myvar = 'value'
myclass_instance.myfunc();
http://jsfiddle.net/E7wK4/
this in the mouseDown event is not the this of the instance.
Try this instead:
function myclass() {
var _this = this;
this.myvar;
this.myfunc = function()
{
alert(this.myvar);
document.onmousedown = this.mouseDown;
}
this.mouseDown = function(e)
{
alert(_this.myvar); //<<<<
}
}
Demo: http://jsfiddle.net/maniator/E7wK4/1/
As an alternative to #Neal you could bind this.
document.onmousedown = this.mouseDown.bind(this);

Javascript "this" issue in Model View Presenter design

The issue is in the model, when recalled by the presenter it does not work like I assumed, in fact it works as if the this keyword refers to an [object HTMLTextAreaElement], not to a Model object.
/** PRESENTER **/
function Presenter() {
var toolbarView;
var outputView;
var sourceCodeModel;
var public = {
setToolbarView: function (view) {
toolbarView = view;
},
setOutputView: function (view) {
outputView = view;
},
setModel: function (_model) {
sourceCodeModel = _model;
},
init: function () {
toolbarView.setNewTableHandler(function () {
outputView.updateSource(sourceCodeModel.string);
});
toolbarView.setNewConstraintHandler(function () {
/*stub*/
alert("new constraint");
});
}
}
return public;
}
/** TOOLBAR VIEW **/
function toolbarView() {
this.setNewTableHandler = function (handler) {
$("#newTable").click(handler);
}
this.setNewConstraintHandler = function (handler) {
$("#newConstraint").click(handler);
}
}
/** OUTPUT VIEW **/
var outputView = {
updateSource: function (newVal) {
$("#sourcetext").val(newVal);
},
draw: function () {
//stub
}
};
/** MODEL **/
var model = new Object(); //o {};
model.source = [];
model.string = function () {
/* ALERT(this) returns [object HTMLTextAreaElement] wtf? */
var stringa = "";
for (var i = 0; i < this.source.length; i++) { //this does not work, since this = HTMLTextAreaElement
stringa += this.source[i] + "\n";
}
return stringa;
}
$(document).ready(function () {
var presenter = new Presenter();
var view1 = new toolbarView();
presenter.setToolbarView(view1);
presenter.setOutputView(outputView);
presenter.setModel(model);
presenter.init();
});
and the HTML is pretty simple:
<!doctype html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="mvp.js"></script>
<meta charset="UTF-8">
<title>Titolo documento</title>
<style type="text/css">
/*unnecessary here*/
</style>
</head>
<body>
<div id="container">
<div id="toolbar">
<button id="newTable">New table</button>
<button id="newConstraint">New Constraint</button>
</div>
<div id="source">
<textarea id="sourcetext"></textarea>
</div>
<button id="update">Update</button>
<div id="output"></div>
</div>
</body>
</html>
What am i doing wrong on the model object?
When you pass a function as a listener the this property will not be available inside the function:
var obj = {
a: function() {
alert(this)
}
};
$('body').click(obj.a);
When body is clicked the function's this property will be document.body.
To prevent this you must bind the function:
$('body').click(obj.a.bind(obj));
Or in older browsers wrap it:
$('body').click(function() {
obj.a();
});
So you must bind the function before pass it:
outputView.updateSource(sourceCodeModel.string.bind(sourceCodeModel));
More info about javascript function's context: http://www.quirksmode.org/js/this.html
this line: var public = { try to do not to use public, that is reserved word.
And a general note, try to bind this to a variable, because this changes context where it is currently.
/** TOOLBAR VIEW **/
function toolbarView() {
var that = this;
that.setNewTableHandler = function (handler) {
$("#newTable").click(handler);
}
that.setNewConstraintHandler = function (handler) {
$("#newConstraint").click(handler);
}
}

Class methods as event handlers in JavaScript?

Is there a best-practice or common way in JavaScript to have class members as event handlers?
Consider the following simple example:
<head>
<script language="javascript" type="text/javascript">
ClickCounter = function(buttonId) {
this._clickCount = 0;
document.getElementById(buttonId).onclick = this.buttonClicked;
}
ClickCounter.prototype = {
buttonClicked: function() {
this._clickCount++;
alert('the button was clicked ' + this._clickCount + ' times');
}
}
</script>
</head>
<body>
<input type="button" id="btn1" value="Click me" />
<script language="javascript" type="text/javascript">
var btn1counter = new ClickCounter('btn1');
</script>
</body>
The event handler buttonClicked gets called, but the _clickCount member is inaccessible, or this points to some other object.
Any good tips/articles/resources about this kind of problems?
ClickCounter = function(buttonId) {
this._clickCount = 0;
var that = this;
document.getElementById(buttonId).onclick = function(){ that.buttonClicked() };
}
ClickCounter.prototype = {
buttonClicked: function() {
this._clickCount++;
alert('the button was clicked ' + this._clickCount + ' times');
}
}
EDIT almost 10 years later, with ES6, arrow functions and class properties
class ClickCounter {
count = 0;
constructor( buttonId ){
document.getElementById(buttonId)
.addEventListener( "click", this.buttonClicked );
}
buttonClicked = e => {
this.count += 1;
console.log(`clicked ${this.count} times`);
}
}
https://codepen.io/anon/pen/zaYvqq
I don't know why Function.prototype.bind wasn't mentioned here yet. So I'll just leave this here ;)
ClickCounter = function(buttonId) {
this._clickCount = 0;
document.getElementById(buttonId).onclick = this.buttonClicked.bind(this);
}
ClickCounter.prototype = {
buttonClicked: function() {
this._clickCount++;
alert('the button was clicked ' + this._clickCount + ' times');
}
}
A function attached directly to the onclick property will have the execution context's this property pointing at the element.
When you need to an element event to run against a specific instance of an object (a la a delegate in .NET) then you'll need a closure:-
function MyClass() {this.count = 0;}
MyClass.prototype.onclickHandler = function(target)
{
// use target when you need values from the object that had the handler attached
this.count++;
}
MyClass.prototype.attachOnclick = function(elem)
{
var self = this;
elem.onclick = function() {self.onclickHandler(this); }
elem = null; //prevents memleak
}
var o = new MyClass();
o.attachOnclick(document.getElementById('divThing'))
You can use fat-arrow syntax, which binds to the lexical scope of the function
function doIt() {
this.f = () => {
console.log("f called ok");
this.g();
}
this.g = () => {
console.log("g called ok");
}
}
After that you can try
var n = new doIt();
setTimeout(n.f,1000);
You can try it on babel or if your browser supports ES6 on jsFiddle.
Unfortunately the ES6 Class -syntax does not seem to allow creating function lexically binded to this. I personally think it might as well do that. EDIT: There seems to be experimental ES7 feature to allow it.
I like to use unnamed functions, just implemented a navigation Class which handles this correctly:
this.navToggle.addEventListener('click', () => this.toggleNav() );
then this.toggleNav() can be just a function in the Class.
I know I used to call a named function but it can be any code you put in between like this :
this.navToggle.addEventListener('click', () => { [any code] } );
Because of the arrow you pass the this instance and can use it there.
Pawel had a little different convention but I think its better to use functions because the naming conventions for Classes and Methods in it is the way to go :-)

Categories

Resources