Redefining the context of an event handler - javascript

I'm creating a listener for the event show_keyboard. In the handler, I need to unbind from this event. In this case, the event is thrown from a native android plugin.
appView.sendJavascript("cordova.fireWindowEvent('native.showkeyboard', { 'keyboardHeight':" + Integer.toString(keyboardHeight)+"});");
In addHeightHandler, I need this to be the parent to addHeightHandler in order to unbind from it. Therefore I'm passing self when calling addHeightHandler. However, when I do this, I cannot get access to e and the keyboardHeight attribute.
Note: my employer insists it must be done like this, without anonymous functions or global setting of the self variable
/*
* When keyboard is shown, add height of keyboard to body to make scrollable.
*/
this.addHeightHandler = function (e) {
keyboardHeight = e.keyboardHeight;
//e is undefined
//do some stuff to add keyboard height
window.removeEventListener('show_keyboard', this.addHeightHandler);
};
/*
* Listen for showkeyboard events thrown by native code on Android
*/
this.addKeyboardListeners = function () {
var self = this;
window.addEventListener('native.showkeyboard', function () {
self.addHeightHandler(self)
}, false);
};
I know there are other ways of doing this, but this is the way I've been directed to do it. I believe passing self to addHeightHandler means e will be overwritten, is this correct?

Yes, the following:
this.addKeyboardListeners = function () {
var self = this;
window.addEventListener(eventConstants.nativeShowKeyboard, function () {
self.addHeightHandler(self);
});
};
Is passing self as the value of e.
What you can do is bind the addHeightHandler function to the desired context. In addKeyboardListeners, you could:
this.addKeyboardListeners = function () {
var handler = this.addHeightHandler.bind(this);
addEventListener(eventConstants.nativeShowKeyboard, handler);
};
What the above does is "bind" the context of the addHeightHandler function to this, meaning that when it is called, the this keyword inside the function will be a reference to whatever this was when you bound it.
The function will still take e as an argument, so when the event occurs and the handler is run, e will still be the event.

Related

Calling a function before any click event handler

Hi I want to call a function every time before any click event handler method.
I know, inside the click handler method I can call my function first, but this quite cumbersome as I have to do this at so many place as well as I have to keep in mind the same for any future click events.
You can set a capture event handler on the document object (or any common parent) and it will be called first before the event handler on the individual object. capture is the third argument to .addEventListener() which is normally optional and defaults to false, but if you pass true on a parent, then the event handler will be called first.
Here's an example:
document.addEventListener("click", function() {
log("document capture click");
}, true);
document.getElementById("target").addEventListener("click", function() {
log("element click");
}, false);
function log(x) {
var div = document.createElement("div");
div.innerHTML = x;
document.body.appendChild(div);
}
<div id="target">Some text to click on</div>
Here's a related question that helps to understand the capture flag: Unable to understand useCapture attribute in addEventListener
I see two solutions here.
First is to use onmousedown that is fired before click event
document.querySelector('.selector').addEventListener('mousedown', function(){
console.log('mousedown');
}
document.querySelector('.selector').addEventListener('click', function(){
console.log('click');
}
Other way is to use compose that will create new reusable function for you (npm i lodash.compose).
var compose = require(lodash.compose);
var firstFunc = function(e){
console.log('first');
return e; //if you want to use it in second function
};
var secondFunc = function(e) {
console.log('second');
};
document.querySelector('.selector').addEventListener('click', compose(secondFunc, firstFunc));
Or you could save new func in variable;
var logFirstThanSecondOnClick = compose(secondFunc, firstFunc);
document.querySelector('.selector').addEventListener('click', logFirstThanSecondOnClick);
Simply compose do next
function compose(f, g) {
return function(x) {
return f(g(x));
}
}
But lodash one is more complex inside.
Here is some math theory about function composition, if you are interested in https://en.wikipedia.org/wiki/Function_composition

Removing child-function event listeners

In building an extended input field (a complex date picker), I need to use two key event listeners. One is attached to the input field, and launches the interface. This is easy.
The second is attached to document, in order to close the complex overlay. Click on the overlay, and it does nothing. Click outside: the overlay disappears and the input field's value is updated.
It also needs to remove the event listener from the document.
This would all be straightforward… if it weren't based on object structures. I am not calling a stand-alone function. I am calling a child function of the data object associated with the field (which the field then has no way of referencing back to).
__DateField.prototype.activate = function () {
…
var t = this;
window.setTimeout(function () { document.addEventListener("click", function (ev) { t.closeDateSelector(ev) }, false); }, 0);
…
}
(I haven't figured out why that event attachment needs to be nested within the setTimeout, but if I don''t do it that way, it calls itself immediately.)
Anyhow, the problem is then that I cannot successfully call document.removeEventListener() because I it's not the same initial function.
Also, I can't approach it by attaching the function as a stand-alone, because I need the reference to the related __DateField object.
How can I remove that function from document?
I have looked at the various threads that say there is no way to inspect event listeners added via 'addEventListener`, though wonder if they may be out of date, as Firebug can list them…
To remove it, you must have a reference to the function, so the question boils down to: How can I keep a reference to the function?
The simplest answer, since you already have an object handy, is a property on the object, if you can rely on this being correct as of when you do the removal:
__DateField.prototype.activate = function () {
// …
var t = this;
window.setTimeout(function () {
t.listener = function (ev) {
t.closeDateSelector(ev)
};
document.addEventListener("click", listener, false);
}, 0);
// …
};
// To remove
__DateField.prototype.deactivate = function() {
if (this.listener != null) {
document.removeEventListener("click", this.listener, false);
this.listener = null;
}
};
Or if that's a problem for some reason, you could use a variable in a scoping function:
(function() {
var listener = null;
__DateField.prototype.activate = function () {
// …
var t = this;
window.setTimeout(function () {
listener = function (ev) {
t.closeDateSelector(ev)
};
document.addEventListener("click", listener, false);
}, 0);
// …
};
// Later, when removing
function removeIt() {
if (listener != null) {
document.removeEventListener("click", listener, false);
listener = null;
}
}
})();

What's the easiest way i can pass an element as a first argument to event handlers in JavaScript?

I know that having the value of this being changed to the element receiving the event in event handling functions is pretty useful. However, I'd like to make my functions always be called in my application context, and not in an element context. This way, I can use them as event handlers and in other ways such as in setTimeout calls.
So, code like this:
window.app = (function () {
var that = {
millerTime: function () {},
changeEl: function (el) {
el = el || this;
// rest of code...
that.millerTime();
}
};
return that;
}());
could just be like this:
window.app = (function () {
return {
millerTime: function () {},
changeEl: function (el) {
// rest of code...
this.millerTime();
}
};
}());
The first way just looks confusing to me. Is there a good easy way to pass the element receiving the event as the first argument (preferably a jQuery-wrapped element) to my event handling function and call within the context of app? Let's say I bind a bunch of event handlers using jQuery. I don't want to have to include anonymous functions all the time:
$('body').on('click', function (event) {
app.changeEl.call(app, $(this), event); // would be nice to get event too
});
I need a single function that will take care of this all for me. At this point I feel like there's no getting around passing an anonymous function, but I just want to see if someone might have a solution.
My attempt at it:
function overrideContext (event, fn) {
if (!(this instanceof HTMLElement) ||
typeof event === 'undefined'
) {
return overrideContext;
}
// at this point we know jQuery called this function // ??
var el = $(this);
fn.call(app, el, event);
}
$('body').on('click', overrideContext(undefined, app.changeEl));
Using Function.prototype.bind (which I am new to), I still can't get the element:
window.app = (function () {
return {
millerTime: function () {},
changeEl: function (el) {
// rest of code...
console.log(this); // app
this.millerTime();
}
};
}());
function overrideContext (evt, fn) {
var el = $(this); // $(Window)
console.log(arguments); // [undefined, app.changeEl, p.Event]
fn.call(app, el, event);
}
$('body').on('click', overrideContext.bind(null, undefined, app.changeEl));
Using $('body').on('click', overrideContext.bind(app.changeEl)); instead, this points to my app.changeEl function and my arguments length is 1 and contains only p.Event. I still can't get the element in either instance.
Defining a function like this should give you what you want:
function wrap(func) {
// Return the function which is passed to `on()`, which does the hard work.
return function () {
// This gets called when the event is fired. Call the handler
// specified, with it's context set to `window.app`, and pass
// the jQuery element (`$(this)`) as it's first parameter.
func.call(window.app, $(this) /*, other parameters (e?)*/);
}
}
You'd then use it like so;
$('body').on('click', wrap(app.changeEl));
For more info, see Function.call()
Additionally, I'd like to recommend against this approach. Well versed JavaScript programmers expect the context to change in timeouts and event handlers. Taking this fundamental away from them is like me dropping you in the Sahara with no compass.

How to pass parameters to a function declared like left = function()

How can I pass parameters to a function declared like something = function(){};
window.prototype.initInterface = function(){
this.mainPane = document.createElement('div');
this.mainPane.style.border="5px solid grey";
this.mainPane.style.margin="0px";
this.mainPane.style.width="420px";
this.mainPane.style.height="600px";
this.exitButton = document.createElement('input');
this.exitButton.setAttribute("type", "button");
this.exitButton.setAttribute("value", "exit");
this.exitButton.onclick = function(){
document.body.removeChild(this.mainPane);
};
this.mainPane.appendChild(this.exitButton);
document.body.appendChild(this.mainPane);
}
When the user presses the exit button I want to remove the mainPane from the body of the html page.
this.exitButton.onclick = function(this.mainPage){
document.body.removeChild(this.mainPane);
};
Does not work
How can I do this?
For your exitButton.onclick function to have access to variables you create in the enveloping initInterface function you want a to create a closure in the exitButton.onclick function by returning a function that performs the action you want and passing that the variable.
exitButton.onclick = function () {
return (function() {
document.body.removeChild(mainPane);
})(mainPane);
};
Read more on how closures work here and here and see a working example fiddle.
Alternatively, you forget about closures and walk up the DOM from the button which triggers the event to your mainPane
exitButton.onclick = function() {
// in here "this" is the object that triggered the event, exitButton
document.body.removeChild(this.parentNode);
}
As an aside, window.prototype does not exist if you are doing this in a browser; window is the object at the top of prototype chain in browser scripting. You want just window.initInterface = function () {} which is the exact same thing as function initInterface() {} because everything you do in javascript in the browser becomes a property of window.
This function is the function w/o function name. It could only be used once and you may not easy to find out what parameters should be passed.
You can create another function like :
function go(a1){}
And call it like window.prototype.initInterface = go(a1);
Or you can get some DOM parameters in this unnamed function by using functions like getDocumentById("DOM ID") etc.

How to handle events in jQuery UI widgets

I'm trying to write a jQuery widget following the model given here.
Here is a snapshot of the widget:
(function ($) {
$.widget("ui.notification", {
_create: function () {
if (!this.element.hasClass("ntfn")) {
this.element.addClass("ntfn");
}
this.elTitle = this.element.append("<div class='ntfn-title'>Notifications</div>");
this.elTitle.click(this._titleClick)
},
_titleClick: function () {
console.log(this);
}
});
})(jQuery);
Here the problem is with the scope of "this" inside the _titleClick method, inside the method this points to the title element. But I need it to point to the widget element.
I think one way of doing it will be to use a wrapper class like
var that = this;
this.elTitle.click(function() {
that._titleClick.apply(that, arguments);
});
Is this the best way to solve this problem or is there any general pattern to solve this issue?
Use the this._on() method to bind the handler. This method is provided by the jQuery UI widget factory and will make sure that within the handler function, this always refers to the widget instance.
_create: function () {
...
this._on(this.elTitle, {
click: "_titleClick" // Note: function name must be passed as a string!
});
},
_titleClick: function (event) {
console.log(this); // 'this' is now the widget instance.
},
You should look to jQuery.proxy() http://api.jquery.com/jQuery.proxy/
el.bind('evenname', $.proxy(function () {
this.isMyScope.doSomething();
}, scope));
I wrote a method my own to solve this issue
_wrapCallback : function(callback) {
var scope = this;
return function(eventObject) {
callback.call(scope, this, eventObject);
};
}
In your create, init (or somewhere in your instance) function do this:
_create: function() {
...
// Add events, you will notice a call to $.proxy in here. Without this, when using the 'this'
// property in the callback we will get the object clicked, e.g the tag holding the buttons image
// rather than this widgets class instance, the $.proxy call says, use this objects context for the the 'this'
// pointer in the event. Makes it super easy to call methods on this widget after the call.
$('#some_tag_reference').click($.proxy(this._myevent, this));
...
},
Now define your objects event hander like this:
_myevent: function(event) {
// use the this ptr to access the instance of your widget
this.options.whatever;
},
define var scope=this, and use scope in event handler.
_create: function () {
var scope = this;
$(".btn-toggle", this.element).click(function () {
var panel = $(this).closest(".panel");
$(this).toggleClass("collapsed");
var collapsed = $(this).is(".collapsed");
scope.showBrief(collapsed);
});
},
Another way to do the same thing without using closure, is to pass the widget as a part of the event data like so:
// using click in jQuery version 1.4.3+.
var eventData = { 'widget': this };
// this will attach a data object to the event,
// which is passed as the first param to the callback.
this.elTitle.click(eventData, this._titleClick);
// Then in your click function, you can retrieve it like so:
_titleClick: function (evt) {
// This will still equal the element.
console.log(this);
// But this will be the widget instance.
console.log(evt.data.widget);
};
It used to be via the jquery bind method now on is favoured.
As of jQuery 1.7, the .on() method is the preferred method for
attaching event handlers to a document. For earlier versions, the
.bind() method is used for attaching an event handler directly to
elements. Handlers are attached to the currently selected elements in
the jQuery object, so those elements must exist at the point the call
to .bind() occurs. For more flexible event binding, see the discussion
of event delegation in .on() or .delegate().
_create: function () {
var that = this;
...
elTitle.on("click", function (event) {
event.widget = that; // dynamically assign a ref (not necessary)
that._titleClick(event);
});
},
_titleClick: function (event) {
console.log(this); // 'this' now refers to the widget instance.
console.log(event.widget); // so does event.widget (not necessary)
console.log(event.target); // the original element `elTitle`
},

Categories

Resources