Javascript/jQuery Event Handler Scope inside object using keyword this - javascript

I've been trying for days to figure this out. I have read many questions on SO as well as googled it many different ways and read/attempted everything I found. Nothing I have found so far has worked for me and I have rewritten my code a million times it seems trying out different methods for doing this.
I feel like there is some super obvious thing I am missing here, and maybe just need a push in the right direction. If I'm going about this completely wrong and need to restructure everything, I can do that too.
Basically what I am working with is a front end "controller" for lack of a better word, that initializes some variables, sets up some event listeners and responds to user actions.
I don't care if I use jQuery or pure JavaScript, I just want it to work, even if I have to re-write the whole thing. My goal is speed and performance under heavy load. Another option I was considering was node.js but I have limited experience with that, so was hoping to figure it out with jQuery.
When the page loads, I do not get an error, but when I click one of the items that I have set an event listener on, I get the error... TypeError: Cannot Read Property 'apply' of undefined. And it refers to the corresponding line that starts with var scope = this ? function(e)...
The purpose of that line is to have the this keyword refer to the controller object so I can call object methods from within the event handler method. Though it seems it might not be working as I intended.
I tried to just use .on to set up the click and change handlers, but I was having problems with scope there as well. Any help, again, is much appreciated.
(function ($) {
$(function () { //document ready
function Controller(authId, authKey) {
this.user.id = authId;
this.user.key = authKey;
this.init();
};
Controller.prototype = {
eventChange: [ "amt", "multi" ],
eventClick: [ "double", "half", "high", "low" ],
event: { refresh: ['amt', 'multi'], update: ['double', 'half'], process: ['high', 'low'] },
user: { id: '', key: '', name: '', balance: '' },
init: function () {
this.initEvents();
},
initEventz: function() {
for (var i = 0; i < this.eventChange.length; i += 1) {
var ele = document.getElementById(this.eventChange[i]);
var scope = this ? function(e) { this.handleEvent.apply(this, ["change"]); } : this.handleEvent;
if(document.addEventListener) {
ele.addEventListener("change", scope, false);
} else if(document.attachEvent) {
ele.attachEvent("onchange", scope);
}
}
for (var i = 0; i < this.eventClick.length; i += 1) {
var ele = document.getElementById(this.eventClick[i]);
var scope = this ? function(e) { this.handleEvent.apply(this, ["click"]); } : this.handleEvent;
if(document.addEventListener) {
ele.addEventListener("click", scope, false);
} else if(document.attachEvent) {
ele.attachEvent("onclick", scope);
}
}
},
handleEvent: function (e) {
var eventId = e.target.id;
for (var event in this) {
if (this.hasOwnProperty(event)) {
console.log(event);
}
}
}
};
var Controller = new Controller($("#auth").val(), $("#key").val());
}); //end document ready
})(jQuery);

You are losing the reference to this.
You can solve that with this code:
var scope = this ? function(e) { this.handleEvent.apply(this, ["click"]); }.bind(this) : this.handleEvent;
but if you want that the handler have access to the element within his scope with the reference of this you should write this:
var scope = this ? function(e) { this.handleEvent.apply(ele, ["click"]); }.bind(this) : this.handleEvent;
or this
var that = this;
var scope = this ? function(e) { that.handleEvent.apply(ele, ["click"]); } : this.handleEvent;
I have seen other mistake. Because if this is undefined then scope is going to be this.handleEvent but this is going to raise an error because undefined can't have the handleEvent property.

Related

Javascript scope issues when binding

I know there are sooo many similar questions on stack regarding this issue, but for the life of me I cannot understand what the problem is in my code.
Trying to level up in javascript so any advise would be helpful. I have created an object to manage slider functions.
var gMapSlider = {
mapSlideShow: false,
// why doesnt current place update when passed in
newMarker: null,
oldMarker: null,
mapSlideIn: function() {
this.contentSlide
$('#placeDetails').animate({right: '0'});
this.mapSlideShow = true;
},
mapSlideOut: function(func) {
if (typeof(func) != "function") func = function() {};
$('#placeDetails').animate({right: '-320px'}, null, null, func());
this.mapSlideShow = false;
},
mapSlideToggle: function() {
(this.mapSlideShow) ? this.mapSlideOut() : this.mapSlideIn();
},
contentSlide: function() {
if (this.newMarker) $('h1', '#placeDetails').text(this.newMarker.title);
},
mapSlide: function(marker) {
this.newMarker = marker;
if (this.oldMarker === this.newMarker) { //same marker showing
this.mapSlideToggle();
}
else if (this.oldMarker !== this.newMarker && !this.mapSlideShow) { //diff marker showing
this.contentSlide(marker);
this.mapSlideIn();
}
else if (this.oldMarker !== this.newMarker && this.mapSlideShow) {
var self = this;
console.log(self) //returns this object
this.mapSlideOut(function() {
console.log(self); // returns this object
self.contentSlide(this.newMarker);
self.mapSlideIn;
}).bind(self); // cannot read property 'bind' of undefined
}
this.oldMarker = this.newMarker;
}
}
A couple of questions
1) The problem is with my gMapSlider.mapSlide function. If I call the mapSlide function and the last else if statement applies I get a cannot read property of bind error. I have Google'd but found nothing of any real relevance. Can anyone help with what I am doing wrong here.
2) Is this the best way of managing functions within a namespace. Most code samples I see use functions in the global namespace so wanted a bit of clarification if it is advised to create objects like this in Javascript?
EDIT #torazaburo Thanks, feel like a proper Newbie, that was the issue. Put it as an answer and I will put as solved. Any advice on code architecture?
this.mapSlideOut(function() {
console.log(self); // returns this object
self.contentSlide(this.newMarker);
this.mapSlideIn;
}).bind(self);
bind() should be called on a function object but you'r calling it on the result of a function call
use this:
this.mapSlideOut.bind(self,function() {
console.log(this); // returns this object
this.contentSlide(this.newMarker);
this.mapSlideIn;
});
also the above call will return you a reference to the function with this bound to self

Understanding Complex Scope in Javascript Modules and Plugins

I know there's a lot of questions on Stack about JS Scope... but I ran into a specific problem that I'm unable to wrap my head around. I have a Javascript module that looks something like this (albeit dramatically simplified):
module.exports = {
$company: $('#id_company'),
$companyCtrl: null,
$jobType: $('#id_job_type'),
$jobTypeCtrl: null,
init: function() {
var _this = this;
this.$companyCtrl = this.$company.selectize({
onChange: function(value) {
_this.companyChanged(value);
}
})[0].selectize;
},
companyChanged: function() {
// Company changed has been fired and does a few things
// before it calls this:
this.updateJobType();
},
updateJobType: function() {
var _this = this;
$.ajax({
url:'/ajax-url',
data: {
'id': this.companyID
}
})
.done(function(data) {
// If our job type selectize() instance hasn't been setup,
// then create it now
if (_this.$jobTypeCtrl === null) {
// ------------
// PROBLEM BLOCK
_this.$jobTypeCtrl = _this.$jobType.selectize({
onChange: function(value) {
if (_this.currentModel !== 'wire_add') {
_this.jobTypeChanged(value);
}
}
})[0].selectize;
// ------------
}
// Reload and re-enable input
_this.$jobTypeCtrl.reloadFromOriginalInput();
_this.$jobTypeCtrl.enable();
});
},
}
Now, here's what I don't understand, if I move that "PROBLEM BLOCK" outside of the Ajax call, and put it back up into init(), it works fine. However, as far as I can tell, in it's current location, the scope (_this = this) is the exact same as it would be up in the init function.
And to be more specific, the problem I'm experiencing is that the "onChange" handler never fires when the code is inside of the Ajax handler, but the plugin instance is still created and functions as it otherwise should. However, if I move it up to the init(), the onChange handler fires without any other changes to the code
Any help to get me to wrap my head around this would be greatly appreciated. Thank you!
I had a similar issue, where you start chasing your own tail using objects.
The power of using modules, is that they have their own context. So once compiled, the file knows what vars and funcs are residing inside; this negates the need to track this bouncing from function to function, which becomes a nightmare, once you involve async callbacks.
I recommend rewriting your module with vars at the top and functions, so it's easier to call any function without trying to pass the correct _this/self context from here, there and everywhere.
Here's an untested re-write:
module.exports = {
var $company = $('#id_company'),
$companyCtrl = null,
$jobType = $('#id_job_type'),
$jobTypeCtrl = null;
function init() {
$companyCtrl = $company.selectize({
onChange: function(value) {
companyChanged(value); // <== invoke any function and treat them as black-box code
}
})[0].selectize;
}
function companyChanged() {
// Company changed has been fired and does a few things
// before it calls this:
updateJobType();
}
function updateJobType() {
$.ajax({
url:'/ajax-url',
data: {
'id': companyID
}
})
.done(function(data) {
// If our job type selectize() instance hasn't been setup,
// then create it now
if ($jobTypeCtrl === null) {
// ------------
// PROBLEM BLOCK
$jobTypeCtrl = $jobType.selectize({
onChange: function(value) {
if (currentModel !== 'wire_add') {
jobTypeChanged(value);
}
}
})[0].selectize;
// ------------
}
// Reload and re-enable input
$jobTypeCtrl.reloadFromOriginalInput();
$jobTypeCtrl.enable();
});
}
}

Overriding function in complicated prototype javascript

I'm a bit stuck on a problem which I can't solve, I performed investigation on internet and on this site, but I can't find the answer to my question.
So basically I have a javascript file, which I cannot modify, so I have another javascript file which should catch the method when it is called and override it.
Normally I know how it works and I already done the function overriding, but I don't know how to solve this issue.
I have a very big script, but I will show just a small piece of it:
Microsoft.Office.Server.Ajax.NavResizer.prototype = {
$6: null,
$7: null,
......
$20:function ($p0) {
if (this.$1E) {
$p0.preventDefault();
}
},
$21: function ($p0) {
var $0 = $p0.target;
this.$1F = ($0 === this.$A);
if (this.$1F || $0 === this.$B) {
this.$1E = $0;
this.$18 = $p0.clientX;
this.$19 = $p0.clientY;
Sys.UI.DomEvent.removeHandler(this.$1E, 'mousedown', this.$12);
var $1 = document.body; Sys.UI.DomEvent.addHandler($1, 'mouseup', this.$13);
Sys.UI.DomEvent.addHandler($1, 'mousemove', this.$14);
$1.style.cursor = (this.$1F) ? 'e-resize' : 'n-resize';
this.$1A = this.get_$42();
this.$1B = this.get_$43();
$1.focus();
Sys.UI.DomEvent.addHandler($1, 'selectstart', this.$15);
$p0.preventDefault();
}
},
$22: function ($p0) {
this.$34($p0);
var $0 = document.body;
Sys.UI.DomEvent.removeHandler($0, 'mouseup', this.$13);
Sys.UI.DomEvent.removeHandler($0, 'mousemove', this.$14);
Sys.UI.DomEvent.addHandler($0, 'selectstart', this.$15);
$0.style.cursor = 'default';
Sys.UI.DomEvent.addHandler(this.$1E, 'mousedown', this.$12);
this.$1E = null;
},
$23: function ($p0) {
this.$34($p0);
},
$24: function ($p0) {
this.$26();
},
....
Basically this is the part of the script: so lets say I want to override function: $22: function ($p0) in the script in another javascript file, how do i do that?
I would appreciate any help.
A small update, some good examples were provided but they are not working.
The environment where i run this sript is SharePoint, normally when I did override I used this method:
var oldFixRibbonAndWorkspaceDimensions = window.FixRibbonAndWorkspaceDimensions;
window.FixRibbonAndWorkspaceDimensions = function () {
this.MyFixRibbonAndWorkspaceDimensions();
};
function MyFixRibbonAndWorkspaceDimensions(){...}
And it didn't matter when i load the script as this function was only called when the default function was called not before not after. Just in the same time. But with the example which were provided here, the function is trying to execute on the document.ready()
You want to permanently override it? Just do this:
Microsoft.Office.Server.Ajax.NavResizer.prototype.$22 = function($p0) {
// your code.
};
As long as your script is executed after the original is defined, you're good.
Old post.. but this works for me:
ExecuteOrDelayUntilScriptLoaded(overrideNavResizer, "NavResizer.js");
function overrideNavResizer(){
Microsoft.Office.Server.Ajax.NavResizer.prototype.$22 = function($p0) {
// your code.
};
}
In your new script:
Microsoft.Office.Server.Ajax.NavResizer.prototype.$22 = function () {//your function code}
Assuming you have access to the prototype object (it's in global scope) and your scripts runs after it, that is easy:
var proto = Microsoft.Office.Server.Ajax.NavResizer.prototype,
oldMethod = proto.$22;
proto.$22 = function newMethod(args, …){
…
};

cannot access function within function in javascript

I need to know what I am doing wrong because I cannot call the internal functions show or hide?
(function()
{
var Fresh = {
notify:function()
{
var timeout = 20000;
$("#notify-container div").get(0).id.substr(7,1) == "1" && (show(),setTimeout(hide(),timeout));
var show = function ()
{
$("body").animate({marginTop: "2.5em"}, "fast", "linear");
$("#notify-container div:eq(0)").fadeIn("slow");
},
hide = function()
{
$("#notify-container div").hide();
}
}//END notify
}
window.Fresh = Fresh;
})();
Fresh.notify();
thanks, Richard
UPDATE
If you wanted to be able to do something like: Fresh.notify.showMessage(), all you need to do is assign a property to the function notify:
var Fresh = {notify:function(){return 'notify called';}};
Fresh.notify.showMessage = function () { return this() + ' and showMessage, too!';};
Fresh.notify();//notify called
Fresh.notify.showMessage();//notify called and showMessage, too!
This will point to the function object here, and can be called as such (this() === Fresh.notify();). That's all there is too it.
There's a number of issues with this code. First of all: it's great that you're trying to use closures. But you're not using them to the fullest, if you don't mind my saying. For example: the notify method is packed with function declarations and jQuery selectors. This means that each time the method is invoked, new function objects will be created and the selectors will cause the dom to be searched time and time again. It's better to just keep the functions and the dom elements referenced in the closure scope:
(function()
{
var body = $("body");
var notifyDiv = $("#notify-container div")[0];
var notifyDivEq0 = $("#notify-container div:eq(0)");
var show = function ()
{
body.animate({marginTop: "2.5em"}, "fast", "linear");
notifyDivEq0.fadeIn("slow");
};
var hide = function()
{//notifyDiv is not a jQ object, just pass it to jQ again:
$(notifyDiv).hide();
};
var timeout = 20000;
var Fresh = {
notify:function()
{
//this doesn't really make sense to me...
//notifyDiv.id.substr(7,1) == "1" && (show(),setTimeout(hide,timeout));
//I think this is what you want:
if (notifyDiv.id.charAt(6) === '1')
{
show();
setTimeout(hide,timeout);//pass function reference
//setTimeout(hide(),timeout); calls return value of hide, which is undefined here
}
}//END notify
}
window.Fresh = Fresh;
})();
Fresh.notify();
It's hard to make suggestions in this case, though because, on its own, this code doesn't really make much sense. I'd suggest you set up a fiddle so we can see the code at work (or see the code fail :P)
First, you're trying to use show value when it's not defined yet (though show variable does exist in that scope):
function test() {
show(); // TypeError: show is not a function
var show = function() { console.log(42); };
}
It's easily fixable with moving var show line above the point where it'll be called:
function test() {
var show = function() { console.log(42); };
show();
}
test(); // 42
... or if you define functions in more 'traditional' way (with function show() { ... } notation).
function test() {
show();
function show() { console.log(42); };
}
test(); // 42
Second, you should use this instead:
... && (show(), setTimeout(hide, timeout) );
... as it's the function name, and not the function result, that should be passed to setTimeout as the first argument.
You have to define show and hide before, also change the hide() as they said.
The result will be something like this:
(function()
{
var Fresh = {
notify:function()
{
var show = function()
{
$("body").animate({marginTop: "2.5em"}, "fast", "linear");
$("#notify-container div:eq(0)").fadeIn("slow");
},
hide = function()
{
$("#notify-container div").hide();
},
timeout = 20000;
$("#notify-container div").get(0).id.substr(7,1) == "1" && ( show(), setTimeout(hide,timeout) );
}//END notify
}
window.Fresh = Fresh;
})();
Fresh.notify();
I think order of calling show , hide is the matter . I have modified your code . It works fine . Please visit the link
http://jsfiddle.net/dzZe3/1/
the
(show(),setTimeout(hide(),timeout));
needs to at least be
(show(),setTimeout(function() {hide()},timeout));
or
(show(),setTimeout(hide,timeout));

Question regarding "this" inside anonymous function of prototype.js .each method

I've been searching for hours for a solution to this problem. I'm creating a table using prototype.js 1.6.0.1 and am having trouble with the this object in context with the .each function. here is a snippit.
var Table = Class.create({
initialize : function(id) {
this.elmnt = $(id);
this.rows = [];
},
initRows : function() {
$A(this._elmnt.tBodies).each(function(body) {
$A(body.rows).each(function(row) {
//right here is where i would like to call
// this.rows.push(row);
console.log(this); // prints DOMWindow
});
});
}
});
As you can see inside the second .each function this resolves to DOMWindow. I would like to be able to call this.rows.push(row) but I can't as "this" isn't resolving as expected.
Any help would be appreciated. I know i could do the standard (i=0; i < length; i++) loop but I was trying to make this a little cleaner. Thanks for any guidance you can offer.
The easiest way to work around this is to save this at the start of initRows and refer to in within the each functions
initRows : function() {
var self = this;
$A(this._elmnt.tBodies).each(function(body) {
$A(body.rows).each(function(row) {
//right here is where i would like to call
self.rows.push(row);
console.log(self); // prints DOMWindow
});
});
}
The problem you're running into is that this can be manipulated by the caller of the function. It's very common in callbacks to set this to an element which is relevant to the call back. In the case of each it's set to the element for the current iteration of the value.
The self trick works because it saves the this as it's bound in the function initRows and then uses that saved value in the iteration.
initRows : function() {
$A(this._elmnt.tBodies).each(function(body) {
$A(body.rows).each((function(e, row) {
e.rows.push(row);
console.log(e);
}).bindAsEventListener(this, row));
});
}

Categories

Resources