Javascript/Ajax - Manually remove event handler from a Sys.EventHandlerList() - javascript

I have two script controls, one holds the other, and I have successfully been able to handle events from the child on the parent using:
initialize: function()
{
this._autoComplete = $get(this._autoCompleteID);
this._onAutoCompleteSelected = Function
.createDelegate(this, this.handleAutoCompleteSelected);
var autoControl = this._autoComplete.control;
autoControl.addItemSelected(this._onAutoCompleteSelected);
...
}
Where addItemSelected(on the child) is:
addItemSelected: function(handler)
{
list = this.getEvents();
list.addHandler('userItemSelected', handler);
},
and getEvents is:
getEvents: function()
{
if (this._events == null)
{
this._events = new Sys.EventHandlerList();
}
return this._events;
},
Problem is that on dispose of the parent, I want to do the same thing:
dispose: function()
{
var autoControl = this._autoComplete.control;
autoControl.removeItemSelected(this._onAutoCompleteSelected);
...
}
but, .control no longer exists. I'm guessing this is because the child control has already been disposed and thus the .control property no longer works.
In light of this, I decided to run though the event list on the child and remove all the event handlers in it.
dispose: function()
{
list = this.getEvents();
for(var item in list._list)
{
var handler;
handler = list.getHandler(item);
list.removeHandler(item, handler);
}
....
}
Is there a better way to do this?

I'm not sure that the "control" expando property on the DOM element is the right way to reference the control object. It is managed by the framework and as you're seeing, I think it's already munged by the time your dispose is called.
Have you tried using $find instead of $get and reworking your references this way?:
initialize: function()
{
this._autoControl = $find(this._autoCompleteID);
this._onAutoCompleteSelected = Function
.createDelegate(this, this.handleAutoCompleteSelected);
this._autoControl.addItemSelected(this._onAutoCompleteSelected);
}
dispose: function()
{
this._autoControl.removeItemSelected(this._onAutoCompleteSelected);
this._autoControl = null;
}
Oh yeah and where you reference the DOM element stored in this._autoComplete you instead go through the control object itself:
this._autoControl.get_element();
So basically invert the logic of "get element => get control object" to "get control object => get element".

Related

Module pattern in JavaScript

I'm practicing my JS skills (I'm new at it). I'm trying to get the specific element that triggered the event and display it in a span element. But I don't know what I'm doing wrong, when I click the button nothing happens.
This is for a calculator program that I'm doing but using module pattern I think it's called.
var Calculator = {
init: function(){
var button = document.querySelectorAll("[class^='button']");
button.onclick = this.writeEvent;
},
write: function (element){
document.getElementById("display").innerHTML = element;
},
writeEvent: function(event){
write(target.event)
}
}
Calculator.init();
There are several problems with the posted code.
var button = document.querySelectorAll("[class^='button']");
button.onclick = this.writeEvent;
The result of querySelectorAll is a NodeList.
Assigning to its onclick property will not achieve what you want.
You want to assign to the onclick property of each individual node.
But actually that's not so simple, we'll need to come back to this.
writeEvent: function(event){
write(target.event)
}
One problem here is that target is undefined.
Surely you meant event.target.
Another problem is that write is also undefined.
Perhaps you meant this.write,
but that won't actually work well.
The problem is that when writeEvent is called from a click event,
it won't be called on the object,
so this will not be bound to the calculator object,
and the this.write call will raise an exception.
There's a way to overcome this,
by binding the onclick handler function to the object when setting it.
Putting the above together:
var Calculator = {
init: function() {
var nodeList = document.querySelectorAll("[class^='button']");
var callback = this.writeEvent.bind(this);
nodeList.forEach(item => item.onclick = callback);
},
write: function(element) {
document.getElementById("display").innerHTML = element;
},
writeEvent: function(event) {
this.write(event.target);
}
}
Calculator.init();

ES6 - How to access `this` element after binding `this` class?

How can I access this element after binding this class?
For example, without binding this:
$(".button-open").click(function(event) {
console.log(this); // Open
this.openMe();
});
With binding this:
$(".button-open").click(function(event) {
console.log(this); // Polygon {windowHeight: 965, scrollNum: 0}
this.openMe();
}.bind(this));
How can I get and access Open again after binding this?
Full code:
class Polygon {
constructor() {
this.windowHeight = $(window).height();
this.scrollNum = 0;
}
// Simple class instance methods using short-hand method
// declaration
init() {
var clickMe = this.clickMe.bind(this);
return clickMe();
}
clickMe() {
$(".button-open").click(function(event) {
console.log(this);
this.openMe();
}.bind(this));
$(".button-close").click(function(event) {
this.closeMe();
}.bind(this));
}
openMe() {
console.log(this.scrollNum); // 0
this.scrollNum = 200;
console.log(this.scrollNum); // 200
return false;
}
closeMe() {
console.log(this.scrollNum); // 200
return false;
}
}
export { Polygon as default}
Any ideas?
EDIT:
The same issue with jQuery animate:
$(".element").animate({}, 'fast', 'swing', function(event) {
console.log(this); // the element
}.bind(this));
After binding:
$(".element").animate({}, 'fast', 'swing', function(event) {
console.log(this); // undefined
}.bind(this));
Any global or bulletproof way of getting the element again?
1. The best option would be to store the context in a variable and don't overwrite this:
var context = this;
$('.element').on('click', function(event) {
// context would be the this you need
// this is the element you need
});
2. If you're only targeting a single element, you can do the reverse from above and save the element on which you're binding the handler into a variable and then use the variable inside the handler:
var el = $('.element');
el.on('click', function(event) {
// use el here
}.bind(this));
Since you tagged the question with ES6, it might be better to bind the context with an arrow function because using bind is more verbose and also creates an additional function:
var el = $('.element');
el.on('click', (event) => {
// this is the same as in the outer scope
// use el here
});
3. Another option is to use the target property of the event object but this can also be any child within your element (the target is the element that dispatches the event, not the element on which you bounded the handler), thus it might require traversing up the DOM tree to find the element you need, which is less efficient.
var el = $('.element');
el.on('click', ({ target }) => {
while (target.parentNode && !target.classList.contains('element')) {
target = target.parentNode;
}
// here the target should be the element you need
});
There is no generic way to get access to what the value of this would have been if you didn't use .bind(). Javascript doesn't have a way to unbind and get back what this would have been. Instead, you have to look at each individual situation and see if there is some other way to get to the whatever this would have been.
For example, as several of us have said, in a click handler, you can access event.target.
The jQuery animate does not pass any arguments to its callback so if you override this, then there is no generic way to get back to the triggering element. You'd have to go back to the selector again or have saved the value in a containing closure (folks commonly use a variable named self for that).
The only generic way to avoid this issue is to not use .bind() so the value of this is not replaced. You can do something like this:
clickMe() {
var self = this;
$(".button-open").click(function(event) {
// self is our ES6 object
// this is the item that triggered the event
console.log(this);
self.openMe();
});
If you bound your handler, then you can still get the item that was clicked on through event.target within the handler.
https://api.jquery.com/on/
As an alternative you can simply do
const self = this;
or
const me = this;
before any of your declarations of event listeners and without binding any functions. Then within handlers you can both use this to refer to the current element and self or me to refer to the parent scope.
It is already answered, but here is the pattern which I usually use:
If there is single '.element', the below code will work
var el = $('.element');
el.click(function(target, event){
// target is the original this
// this is the scope object
}.bind(this, el[0]));
But if '.element' refers to multiple elements then below code will handle that
var clickHandler = function(target, event){
// target is the original this
// this is the scope object
}.bind(this);
$('.element').click(function(e) {
return clickHandler(this, e);
});

How to replace jquery with the mithril equivalent?

Something like :
peer.on('open', function(id){ // this is a non jquery event listener
$('#pid').text(id);
});
With something like...this is not correct:
peer.on('open', function(id){
m('#pid',[id])
});
Is this even the right approach? Should I be establishing a controller and model before I attempt to convert from jquery?
More details:
I am trying to rewrite the connect function in the PeerJS example: https://github.com/peers/peerjs/blob/master/examples/chat.html
If your event listener is something like websockets, then the event happens outside of Mithril, which means you need to manage redrawing yourself. This is what you'll need to do:
Store your data in an independent model
Use that model when rendering your Mithril view
On the open event, update your model, then call m.redraw()
Conceptual example:
var myModel = { id: 'blank' }
var MyComponent = {
view: function () {
return m('#pid', myModel.id)
}
}
m.mount(document.getElementById('app'), MyComponent)
// This happens outside mithril, so you need to redraw yourself
peer.on('open', function(id) {
myModel.id = id
m.redraw()
})
In Mithril, you should not try to touch the DOM directly. Your event handler should modify the View-Model's state, which should be accessed in your View method. If you post more code, I could give a more detailed explanation of how it pieces together.
Here is a bare-bones example that shows the data flowing through Mithril. Your situation will need to be more complicated but I'm not currently able to parse through all of that peer.js code.
http://codepen.io/anon/pen/eNBeQL?editors=001
var demo = {};
//define the view-model
demo.vm = {
init: function() {
//a running list of todos
demo.vm.description = m.prop('');
//adds a todo to the list, and clears the description field for user convenience
demo.vm.set = function(description) {
if (description) {
demo.vm.description(description);
}
};
}
};
//simple controller
demo.controller = function() {
demo.vm.init()
};
//here's the view
demo.view = function() {
return m("html", [
m("body", [
m("button", {onclick: demo.vm.set.bind(demo.vm, "This is set from the handler")}, "Set the description"),
m("div", demo.vm.description())
])
]);
};
//initialize the application
m.module(document, demo);
Notice that the button is calling a method on the View-Model (set), which is setting the value of a property (vm.description). This causes the View to re-render, and the div to show the new value (m("div", demo.vm.description())).

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;
}
}
})();

JS method binding with 2 different this

I have this small code:
ScenesController.prototype.viewAction = function() {
this.flash = this.di.HelperFlash.hasSupport();
this.$playerElem = !this.flash? $('#html_player') : $('#flash_player');
// the first click is just a sample, I need the same object in the Quailty Change method
$('.scenes_view_video_quailty').on('click', function() { echo($(this));});
$('.scenes_view_video_quailty').on('click', this.viewVideoQuailtyChange.bind(this));
};
ScenesController.prototype.viewVideoQuailtyChange = function(e) {
e.preventDefault();
if (!this.flash) {
echo(this);
echo($(this));
}
};
When I'm clicking the link, I would need pass 2 this variable to the QualityChange method. One with the object (in the bind) and the other is the click event, because I need the clicked element too.
I was trying with the .on('click', {$this: $(this)}, this.method) solution, but dosen't work, the evend.data.$this looks a different object.
I need the same object as I have in the first click method.
(echo = console.log)
Alias the this that refers to the current instance as something else (traditionally, self) and use this to refer to the clicked element
ScenesController.prototype.viewAction = function() {
var self = this;
this.flash = this.di.HelperFlash.hasSupport();
this.$playerElem = !this.flash? $('#html_player') : $('#flash_player');
$('.scenes_view_video_quailty').on('click', function() { echo(self, $(this));});
};
To call a method setting it's this reference you would use Function.apply, for example:
$('.scenes_view_video_quailty').on('click', function(){
self.viewVideoQuailtyChange.apply(self, [$(this)])
});
You can attach any number of variables with bind
You could have a proper method like:
ScenesController.prototype.viewVideoQuailtyChange = function(secondThis) {
}
then use the bind as:
this.viewVideoQuailtyChange.bind(this, $(this));
With this solution you do lose the event though, so it might need some more thought. I'll look into it and update the answer :)

Categories

Resources