This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Javascript prototype ‘this’ issue
I have an event listener which of course calls a method on an event. This method tries unsuccessfully to hold a reference to the holding objects this so that it can access other properties of the object.
There is a single comment denoting where the behavior is not understood. this_hold.Name is not accessible there as I thought it would be.
/*MUserExist
**
**
**
*/
$A.module({
Name: 'MUserExist',
S: {
ClientStorage: SClientStorage,
ComMessage: SComMessage,
ComText: SComText,
DynSma: SDynSma,
DynTwe: SDynTwe,
DynArc: SDynArc,
AniMorphLabel: SAniMorphLabel,
AniFlipPage: SAniFlipPage
},
E: {
but: $A('#ue_but')[0],
text: $A('#ue_go')[0],
form: $A('#ue_fo')[0],
check: $A('#ue_check')[0]
},
J: {
box: $('#ue_box')
},
init: function () {
var pipe = {},
this_hold = this;
this.J.box.draggable();
this.E.but.addEventListener("click", function () {
pipe = $A.definePipe(this_hold.Name);
$A.machine(pipe);
}, false);
this.E.text.addEventListener("keypress", this.enter, false);
this.S.AniMorphLabel.run(["ue_email",
"ue_email_lab",
"ue_go",
"ue_pass_lab"
]);
},
enter: function (event) {
var pipe = {},
this_hold = this;
if (event.keyCode === 13) {
pipe = $A.definePipe(this_hold.Name); // fails here what does 'this' point to?
$A.machine(pipe);
event.preventDefault();
}
},
pre: function (pipe) {
var form_elements = this.E.form.elements,
text_object = new this.S.ComText(form_elements);
pipe.enter = this.enter;
if ($A.Un.get('load') === '1') {
if (!text_object.checkFull()) {
pipe.type = 'empty';
return this.S.ComMessage.message(pipe);
}
if (!text_object.checkPattern('email')) {
pipe.type = 'email';
return this.S.ComMessage.message(pipe);
}
if (!text_object.checkPattern('pass')) {
pipe.type = 'pass';
return this.S.ComMessage.message(pipe);
}
}
pipe.page = text_object.getArray();
pipe.proceed = true;
pipe.page.remember = this.E.check.checked;
return pipe;
},
post : function (pipe) {
if (pipe.proceed === true) {
this.S.ComMessage.resetView('ue_email');
this.S.ComMessage.resetView('ue_go');
this.S.ClientStorage.setAll(pipe.server.smalls);
this.S.DynSma.run(pipe.server.smalls);
this.S.DynArc.run(pipe.server.arcmarks);
this.S.DynTwe.run(pipe.server.tweets);
this.S.AniFlipPage.run('ma');
} else {
return this.S.ComMessage.message(pipe);
}
}
});
this likely points to the DOM node from which the event was triggered. Have you tried writing this to the console to inspect it?
console.log(this);
this is the DOM object that generated the event. It is NOT your javascript object.
When you pass this.enter as the method for the event handler, the method enter does not stay bound to your object. If you want that to happen, you have to change your code to cause that to happen by doing something like this:
// save local copy of my object so I can refer to it in
// the anonymous function
var obj = this;
this.E.text.addEventListener("keypress", function(event) {obj.enter(event)}, false);
It is important to remember that this is set by the caller of a method/function. In this case the caller of the event handler is the event sub-system in the browser. It does not know what your object is and it's designed behavior is to set this to the DOM object that caused the event. So, if you want to call your obj.enter method, you can't just pass enter as the event handler. Instead, you make a separate function that gets called as the event handler and you then call obj.enter() from that using your object as the base so that this gets set correctly.
Another solution would be to use .bind() which also creates a stub function that binds the right this to a function call, but I don't use .bind() myself because it doesn't work in all older browsers.
Try to change how the event is being bound
this.E.text.addEventListener("keypress", this.enter, false);
to
var that = this;
this.E.text.addEventListener("keypress", function(event) {
that.enter(event);
}, false);
Related
Why do I always get the last value assigned to the variable
even though I already enclosed it in a function?
When the event mouse up is triggered and getGoogleFiles is called, the last value assigned to resourceId is called. I don't get it.
for ( var i in arrayObj) {
var resourceId = arrayObj[i].ResourceId;
entity_list.onmouseup = function(event) {
parent.changeButtonState(this, event);
(function(resourceId) {
getGoogleFiles(resourceId);
})(resourceId);
}
}
Note: This is different to other JavaScript questions because the onmouseup is not triggered
I followed the creating of another function mentioned here:
JavaScript closure inside loops – simple practical example
for ( var i in arrayObj) {
entity_list.onmouseup = function(event) {
parent.changeButtonState(this, event);
testing(arrayObj[i].ResourceId);
}
}
function testing(index){
return function() { getGoogleFiles(index); };
}
But when the element of "entity_list" is triggered, nothing happens.
I can't use let because the specific browser that I'm using returns a SyntaxError
SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode
Thank you!
You need to use testing() to create the listener function, not something you call inside it.
for (var i in arrayObj) {
entity_list.onmouseup = testing(arrayObj[i].ResourceId, parent);
}
function testing(index, parent) {
return function(event) {
parent.changeButtonState(this, event);
getGoogleFiles(index);
};
}
But you wouldn't have to go through any of this if you use forEach() instead of a for loop, since it creates a new scope for obj in each iteration.
arrayObj.forEach(function(obj) {
entity_list.onmouseup = function(event) {
parent.changeButtonState(this, event);
testing(obj.ResourceId);
}
});
You can't use a var scoped variable here.
But you could assign the resourceId to a data attribute on the relative html element, so you can read it when the event fires.
var arrayObj = [{ResourceId: "test1"}, {ResourceId: "test2"}, {ResourceId: "test3"}];
var entity_list = document.getElementsByClassName("entity_list");
for ( var i in arrayObj) {
entity_list[i].dataset.resourceId = arrayObj[i].ResourceId;
entity_list[i].onmouseup = function(event) {
getGoogleFiles(this.dataset.resourceId);
}
}
function getGoogleFiles(resourceId) {
console.log(resourceId);
}
<span class="entity_list">entity list (item 1)</span>
<span class="entity_list">entity list (item 2)</span>
<span class="entity_list">entity list (item 3)</span>
I've got a variable timekeep.
var timeKeep;
and I define it thusly:
timeKeep = Class.create({
initialize: function() {
this.initObservers();
},
initObservers: function() {
$$('input').each( function(el) {
el.observe('keypress', function(ev) {
// the key code for 'enter/return' is 13
if(ev.keyCode === 13){
timeKeep.submit();
// Uncaught TypeError: Object function klass() {
// this.initialize.apply(this, arguments);
// } has no method 'submit'
}
});
});
},
submit: function() {
alert('Submitted!');
}
})
The error I am getting is commented out below the line that it occurs. It's got something to do with calling a timeKeep method within a different scope I think?
Is there a problem calling timeKeep.method() inside a foreach statement?
Problem is with you OOP style. Use a closure so you call the current instance of your class.
initObservers: function () {
var that = this;
$$('input')
.each(function (el) {
el.observe('keypress', function (ev) {
// the key code for 'enter/return' is 13
if (ev.keyCode === 13) {
that.submit();
}
});
});
},
You could also look at bind
initObservers: function() {
var submit = this.submit.bind(this);
$$('input')
.each(function (el) {
el.observe('keypress', function (ev) {
// the key code for 'enter/return' is 13
if (ev.keyCode === 13) {
submit();
}
});
});
},
You are assuming that Class.create returns an instance of an object of the type you are defining, but no, it returns a constructor function for creating instances of the class you are defining.
You can add the new keyword to the assignment and then you will have in timeKeep what you want to:
timeKeep = new Class.create({
...
})()
As suggested - using Function#bind() will solve your problem but there is a cleaner way so that you can continue to use this inside the class scope.
Also look into the invoke() method as this is a perfect opportunity to use it. $$() returns a list of elements and if you want to perform the same function on all of the elements invoke() will handle iterating over the list.
timeKeep = Class.create({
initialize: function() {
this.initObservers();
},
initObservers: function() {
$$('input').invoke('observe','keypress',function(ev) {
// the key code for 'enter/return' is 13
if(ev.keyCode === 13){
timeKeep.submit();
// Uncaught TypeError: Object function klass() {
// this.initialize.apply(this, arguments);
// } has no method 'submit'
}
//Add the bind method to the closure to bind 'this' inside that function scope
//to 'this' of the class
//the binding will allow you to call this.submit() instead of timeKeep.submit
//as well as access any of the class properties and methods inside this closure
}.bind(this));
} ,
submit: function() {
alert('Submitted!');
}
});
PrototypeJS documentation on observers http://api.prototypejs.org/dom/Event/observe/ - look at the heading Using an Instance Method as a Handler
I'm trying to understand jQuery classes but it is not going very well.
My goal is to use a class this way (or to learn a better way to do it):
var player = new Player($("playerElement"));
player.InitEvents();
Using other people's examples, this is what I tried:
$.Player = function ($) {
};
$.Player.prototype.InitEvents = function () {
$(this).keypress(function (e) {
var key = e.which;
if (key == 100) {
MoveRight();
}
if (key == 97) {
MoveLeft();
}
});
};
$.Player.prototype.MoveRight = function () {
$(this).css("right", this.playerX += 10);
}
$.Player.prototype.MoveLeft = function () {
$(this).css("right", this.playerX -= 10);
}
$.Player.defaultOptions = {
playerX: 0,
playerY: 0
};
The end goal is to have a character moving on the screen left and right using the keyboard letters A and D.
I have a feeling that I'm doing something very wrong with this "class"
but I'm not sure why.
(sorry for my English)
An important issue is that you have to assign the passed jQuery object/element to a this.element - or another this.propertyName - so you can access it later inside the instance's methods.
You also cannot call MoveRight()/MoveLeft() directly like that because those functions are not defined up in the scope chain, but rather in the prototype of your instance's Constructor, hence you need a reference to the instance itself to call these.
Updated and commented code below:
(function ($) { //an IIFE so safely alias jQuery to $
$.Player = function (element) { //renamed arg for readability
//stores the passed element as a property of the created instance.
//This way we can access it later
this.element = (element instanceof $) ? element : $(element);
//instanceof is an extremely simple method to handle passed jQuery objects,
//DOM elements and selector strings.
//This one doesn't check if the passed element is valid
//nor if a passed selector string matches any elements.
};
//assigning an object literal to the prototype is a shorter syntax
//than assigning one property at a time
$.Player.prototype = {
InitEvents: function () {
//`this` references the instance object inside of an instace's method,
//however `this` is set to reference a DOM element inside jQuery event
//handler functions' scope. So we take advantage of JS's lexical scope
//and assign the `this` reference to another variable that we can access
//inside the jQuery handlers
var that = this;
//I'm using `document` instead of `this` so it will catch arrow keys
//on the whole document and not just when the element is focused.
//Also, Firefox doesn't fire the keypress event for non-printable
//characters so we use a keydown handler
$(document).keydown(function (e) {
var key = e.which;
if (key == 39) {
that.moveRight();
} else if (key == 37) {
that.moveLeft();
}
});
this.element.css({
//either absolute or relative position is necessary
//for the `left` property to have effect
position: 'absolute',
left: $.Player.defaultOptions.playerX
});
},
//renamed your method to start with lowercase, convention is to use
//Capitalized names for instanceables only
moveRight: function () {
this.element.css("left", '+=' + 10);
},
moveLeft: function () {
this.element.css("left", '-=' + 10);
}
};
$.Player.defaultOptions = {
playerX: 0,
playerY: 0
};
}(jQuery));
//so you can use it as:
var player = new $.Player($("#playerElement"));
player.InitEvents();
Fiddle
Also note that JavaScript does not have actual "classes" (at least not until ES6 gets implemented) nor Methods (which by definition are associated exclusively to Classes), but rather Constructors which provide a sweet syntax that resembles classes. Here's an awesome article written by TJ Crowder regarding JS's "fake" methods, it is a little advanced but everyone should be able to learn something new from reading it:
http://blog.niftysnippets.org/2008/03/mythical-methods.html
When you use this inside your Player prototype functions, this points to the current Player object.
But when you use $(this).keypress it requires that this points to an HTML element.
The two simply are incompatible. There is only one this and it points to the current Player object, not to an HTML element.
To fix your problem, you will need to pass the HTML element into the Player object upon its creation or into the relevant function calls.
You can pass the element into the Player object upon construction like this:
$.Player = function ($, element) {
this.element = element;
};
$.Player.prototype.InitEvents = function () {
$(this.element).keypress(function (e) {
var key = e.which;
if (key == 100) {
MoveRight();
}
if (key == 97) {
MoveLeft();
}
});
};
$.Player.prototype.MoveRight = function () {
$(this.element).css("right", this.playerX += 10);
}
$.Player.prototype.MoveLeft = function () {
$(this.element).css("right", this.playerX -= 10);
}
$.Player.defaultOptions = {
playerX: 0,
playerY: 0
};
I have a problem, I want to create a JavaScript class:
function Calculatore(txt,elements) {
this.p= new Processor();
this.output=txt;
$(elements).click(this.clickHandler);
}
Calculatore.prototype.clickHandler = function() {
var element=$(this);
// Code Here
// "this" contains the element.
// But what if I want to get the "output" var?
// I tried with Calculatore.prototype.output but no luck.
}
So how can I solve this?
You can use jQuery's $.proxy:
function Calculatore(txt,elements) {
this.p= new Processor();
this.output=txt;
$(elements).click($.proxy(this.clickHandler, this));
}
Calculatore.prototype.clickHandler = function(event) {
var clickedElement = event.target;
alert(this.output);
}
Edited. Jason brought up a good point in the comments. It's probably better to use event.target which references only the element clicked, rather than elements which may reference an array of objects matching the selection.
You have a collision between this values. You currently don't have access to the instance because this has been set to the element inside a click handler.
You could make a proxy function to pass both the this value (the element) and the instance:
function Calculatore(txt,elements) {
this.p= new Processor();
this.output=txt;
var inst = this; // copy instance, available as 'this' here
$(elements).click(function(e) {
return inst.clickHandler.call(this, e, inst); // call clickHandler with
// 'this' value and 'e'
// passed, and send 'inst'
// (the instance) as well.
// Also return the return
// value
});
}
Calculatore.prototype.clickHandler = function(e, inst) {
var element = $(this);
var output = inst.output;
};
I know I could do this with closures (var self = this) if object was a function:
click here
<script type="text/javascript">
var object = {
y : 1,
handle_click : function (e) {
alert('handling click');
//want to access y here
return false;
},
load : function () {
document.getElementById('x').onclick = this.handle_click;
}
};
object.load();
</script>
The simplest way to bind the call to handle_click to the object it is defined in would be something like this:
var self=this;
document.getElementById('x').onclick =
function(e) { return self.handle_click(e) };
If you need to pass in parameters or want to make the code look cleaner (for instance, if you're setting up a lot of similar event handlers), you could use a currying technique to achieve the same:
bind : function(fn)
{
var self = this;
// copy arguments into local array
var args = Array.prototype.slice.call(arguments, 0);
// returned function replaces first argument with event arg,
// calls fn with composite arguments
return function(e) { args[0] = e; return fn.apply(self, args); };
},
...
document.getElementById('x').onclick = this.bind(this.handle_click,
"this parameter is passed to handle_click()",
"as is this one");
So, the event handler part wires up just fine (I tested it myself) but, as your comment indicates, you have no access to the "y" property of the object you just defined.
This works:
var object = {
y : 1,
handle_click : function (e) {
alert('handling click');
//want to access y here
alert(this.y);
return false;
},
load : function () {
var that = this;
document.getElementById('x').onclick = function(e) {
that.handle_click(e); // pass-through the event object
};
}
};
object.load();
There are other ways of doing this too, but this works.
I see how to do it with Jason's latest one. Any way to do it without the anonymous function?
We can directly pass an object with a handler method thanks to AddEventListener, and you will have access to its attributes:
http://www.thecssninja.com/javascript/handleevent
Hope this will help those who, like me, will look for this topic some years after!