i want to add event handler to object's class (with JQUERY), but i cannot find solution to my problem. I have code like this:
class Obstacle
{
constructor()
{
this.points = [];
}
}
class Editor
{
constructor()
{
this.obstacle = new Obstacle;
}
addPoint()
{
this.obstacle.points.push(canvas.click( function(e) { return [e.clientX, e.clientY]; }));
alert(this.obstacle.points.length);
}
}
Of course without any results, so i'm asking for any help :).
You've to push the coordinates inside the event handler. In the current code there's no receiver to the return value from the event.
Because this in the click handler refers to the element the event is attached, you need a separate reference to this. A simple way is to store this value to a variable in the outer scope and use that to refer the actual object. Like this:
addPoint() {
const that = this;
canvas.click(function(e) {
that.obstacle.points.push([e.clientX, e.clientY]);
});
}
Related
Is there a way I can dynamically bind a string and the text it outputs without using setInterval? I want it to be similar to Angular and Vue though I want to do this with vanilla JS. I want to be able to open the console and change the value at any time and see the change output on my element. Thank you in advance!
I think your only two options are:
A. Edit the element directly, e.g.
myPublicElemeVariable.innerText = 'Bla'
B. Use a setter (or Proxy):
obj = {
get str() { return this.myStr; }
set str(val) {
elem.innerText = val;
this.myStr = val;
}
}
C. Just use a function/method!
If you mean you want change to be event-driven, there is already a very simple event framework in javascript - the EventTarget class as demonstrated by this Code Sandbox
//define a watchable thing
class ValueTarget extends EventTarget {
constructor(value = "") {
super();
this.setValue(value);
}
getValue() {
return this._value;
}
setValue(value) {
this._value = value;
this.dispatchEvent(new CustomEvent("change", { detail: { value } }));
}
}
//get the page elements
const inputElement = document.querySelector("input");
const outputElement = document.querySelector("h1");
//create a watchable thing
const fieldTarget = new ValueTarget("");
//wire events that will change the watchable
inputElement.addEventListener("input", function (e) {
fieldTarget.setValue(e.target.value);
});
//receive notifications from the watchable
fieldTarget.addEventListener("change", (e) => {
outputElement.textContent = e.detail.value;
});
You may be as well to build your own given how simple it is - maintains a list of listeners and calls them when notified. My work recently needed such a thing which I knocked up in Typescript at https://github.com/cefn/lauf/blob/main/modules/lauf-store/src/core/watchable.ts#L3-L21 and would therefore be very easy to redo in javascript.
I want to remove an event listener from an element and later assign it back. Is there a way to get it and store in a variable smth like var storedListener = Element.getEventListener('click'), so that later I can do something like Element.addEventListener('click', storedListener)?
UPDATE
Listener is assigned inside template, I use Angular 2. It's
<div *ngFor="let entry of data; let i=index">
<div class="element-description" (click)="editElementDescription(i)">{{entry.description}}</div>
</div>
What I want to do is to make contents of inside <div> an <input> after I click it, so that I can change value and send it to server. And inside editElementDescription() I do the following:
public editElementDescription(index: number): void {
var elementDescription: HTMLDivElement = <HTMLDivElement>document.getElementsByClassName('element-description')[index];
elementDescription.removeEventListener('click');
elementDescription.innerHTML = '<input id="change-description-input" type="text" value="' + elementDescription.innerHTML + '"/>';
}
I remove that click listener because otherwise contents of <input> will get that innerHTML if I click it one more time. So the idea is to assign that <input> element a change listener, which will replace <input> by it's value and bring the parent <div> it's original listener back.
In order to remove a listener added with .addEventListener(), you must keep track of the listener function reference and remove it later with .removeEventListener().
Something like that:
var btn = document.getElementById('btn');
var btn_add = document.getElementById('btn-add-listener');
var btn_remove = document.getElementById('btn-remove-listener');
var fnListener = function(e) {
alert('Clicked!');
};
btn_add.addEventListener('click', function() {
btn.addEventListener('click', fnListener);
});
btn_remove.addEventListener('click', function() {
btn.removeEventListener('click', fnListener);
});
Working demo: https://jsfiddle.net/mrlew/k5m1nog3/
Other approach (added after question update)
Considering your actual problem, I suggest another approach: instead of handle events, you can set a data- attribute in the element indicating it's open. Then you just modify your inner HTML if the attribute is not present.
Something like this:
function editElementDescription(index) {
var elementDescription = document.getElementsByClassName('element-description')[index];
var isOpen = elementDescription.getAttribute('data-isOpen');
if (!isOpen) {
elementDescription.setAttribute('data-isOpen', 'true');
elementDescription.innerHTML = '<input id="change-description-input" type="text" value="' + elementDescription.innerHTML + '"/>';
}
}
Working demo: https://jsfiddle.net/mrlew/e0yrL08v/
You are getting it a bit wrong... there is no list of event listeners accessible from JavaScript... the only thing you can do is to remove/add an event if you know the origin.
There are 2 functions to manipulate event listeners:
addEventListener(event_type,function);
removeEventListener(event_type,function);
One object can have multiple events of same type... the only way to distinguish them is by giving an exact function being called.
Please note that if it's jQuery, it is possible, as it has own event stack... example below:
var events = $("#object1").data('events');
var $object2 = $("#object2");
if (events) {
for(var eventType in events){
for(var idx in events[eventType]){
$object2[eventType](events[eventType][idx].handler);
}
$('#object1').off(eventType);
}
}
No, this isn't possible since .getEventListener is only available for debugging purposes.
There is unfortunately no way in standard JavaScript to programmatically get back the EventListeners attached to an object, and any library that tries to accomplish this will rely on unstable non-standard interfaces that may be discontinued any day.
So if your goal was to manipulate the listeners added by a library you have no control over, you're out of luck.
On the other hand if you control the environment then you can store a reference to the attached callback if you want to attach the same listener to multiple objects, or remove it afterwards with .removeEventListener.
You could actually monkey-patch EventTarget.prototype.addEventListener to support this before anything else runs on your page, that wouldn't be a very clean solution to whatever problem you are having but if you think you really need to, here is a quick imperfect implementation of it (doesn't support useCapture argument):
// getEventListener polyfill, run this before anything else on your page.
(function monkeyPatchGetEventListeners(EventTarget) {
const eventListeners = new WeakMap();
const origAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function patchedAddEventListener(eventType, listener, ...args) {
let allListeners;
if (eventListeners.has(this)) {
allListeners = eventListeners.get(this);
} else {
allListeners = new Map();
eventListeners.set(this, allListeners);
}
let listeners;
if (allListeners.has(eventType)) {
listeners = allListeners.get(eventType);
} else {
listeners = [];
allListeners.set(eventType, listeners);
}
listeners.push(listener);
return origAddEventListener.call(this, eventType, listener,...args);
}
const origRemoveEventListener = EventTarget.prototype.removeEventListener;
EventTarget.prototype.removeEventListener = function patchedRemoveEventListener(eventType, listener, ...args) {
const call = () => origRemoveEventListener(eventType, listener, useCapture, ...args);
const allListeners = eventListeners.get(this);
if (!allListeners) { return call(); }
const listeners = allListeners.get(this);
if (!listeners) { return call(); }
const index = listeners.indexOf(listener);
if (index === -1) { return call(); }
index.splice(index, 1);
return call();
}
EventTarget.prototype.getEventListeners = function patchedGetEventListeners(eventType) {
const allListeners = eventListeners.get(this);
return allListeners && allListeners.get(eventType);
}
})(EventTarget);
I need to use jQuery events on non-DOM-related objects.
This works fine:
var o = {
}
$(o).on('bump', function () {
alert('ouch')
})
$(o).trigger('bump')
http://jsfiddle.net/d35bf35y/
But instead I need to attach an event on a property... the following code does not work.
var o = {
prop: 'test'
}
// Bind an event handler
$(o.prop).on('bump', function () {
alert('ouch')
})
// Trigger an event
$(o.prop).trigger('bump')
http://jsfiddle.net/d35bf35y/1/
In my real application that property will have an object.
I would like to know if is possible use jQuery in this way or an alternative solution.
"In my real application that property will have an object."
That part is rather important. This works:
var o = {
prop: {}
}
// Bind an event handler
$(o.prop).on('bump', function () {
alert('ouch')
})
// Trigger an event
$(o.prop).trigger('bump')
You can attach one on the Object and everytime it fires, you run a function that does something with your properties. (check if they're the same, or else)
var o = {
prop: 'test'
};
$(o).on('bump', function (e) {
var props = e.delegateTarget;
// do what you gotta do, like check if something has changed from before.
})
$(o).trigger('bump')
Or if your property is also an object, it should work.
I've learned that for scope reasons the this keyword inside an event listener, which is embedded in an object, doesn't refer to the global object but rather to the element which triggered the event.
Now, I understand that if I want to fetch a property I can save it to a variable before the event handler is called. But what can I do if I want to manipulate the property's value?
In the following piece of code I am trying to manipulate the drugCount property within the removeDrug event listener.
var Drugs = {
drugs: $("#drugs_table"),
drugRow: $("#drug").html(),
drugCount: 0,
init: function() {
this.addDrugRow();
this.removeDrugRowHandler();
},
addDrugRow: function() {
this.drugCount++;
this.drugs.append(this.drugRow.replace(/{{id}}/,this.drugCount));
$(".drugsSelect").select2();
},
removeDrugRowHandler: function() {
drugCount = this.drugCount;
// also a problematic solution, because it only retains the inital drugCount.
// i.e I need a way to access the "live" count from within the event
$(document).on("click",".removeDrug",function(){
if (drugCount>0) {
$(this).parents("tr").remove();
this.drugCount--; // how should I approach this?
}
});
}
}
Try This
var Drugs = function() {
var me = this;
me.drugs = $("#drugs_table");
me.drugRow = $("#drug").html();
me.drugCount = 0;
me.init = function() {
this.addDrugRow();
this.removeDrugRowHandler();
};
me.addDrugRow = function() {
this.drugCount++;
this.drugs.append(this.drugRow.replace(/{{id}}/,this.drugCount));
$(".drugsSelect").select2();
};
me.removeDrugRowHandler= function() {
var drugCount = me.drugCount;
$(document).on("click",".removeDrug",function(){
if (drugCount>0) {
$(this).parents("tr").remove();
me.drugCount--;
}
});
}
}
As it turns out the easy solution is to use the object name instead of the contextual this.
So instead of this.drugCount I used Drugs.drugCount.
However, this solution only works if I am in the context of a single object. If I were to write a "class" (i.e var Drugs = function(){ ... }) this won't work.
I have this code:
function Keyboard() {
this.log = $('#log')[0];
this.pressedKeys = [];
this.bindUpKeys = function() {
$('body').keydown(function(evt) {
this.pressedKeys.push(evt.keyCode);
var li = this.pressedKeys[evt.keyCode];
if (!li) {
li = this.log.appendChild(document.createElement('li'));
this.pressedKeys[evt.keyCode] = li;
}
$(li).text('Down: ' + evt.keyCode);
$(li).removeClass('key-up');
});
}
this.bindDownKeys = function() {
$('body').keyup(function(evt) {
this.pressedKeys.push(evt.keyCode);
var li = this.pressedKeys[evt.keyCode];
if (!li) {
li = this.log.appendChild(document.createElement('li'));
}
$(li).text('Up: ' + evt.keyCode);
$(li).addClass('key-up');
});
}
}
I get these errors:
TypeError: 'undefined' is not an object (evaluating 'this.pressedKeys.push')
It doesn't matter what I want to do with the Array, it just keeps giving me access errors, as if it doesn't exists inside the prototype.
What am I doing wrong? :( I'm just accessing the array as any other value inside the prototype). Are there problems with objects inside objects?
The problem is that inside the event handler this is not what you think. You can bind the event handler function with the bind method (or, since it looks like you're using jQuery, $.proxy):
this.bindUpKeys = function() {
var that = this;
$('body').keydown($.proxy(function(evt) {
//Use `this` as normal inside here
}, this));
}
Or you can store a reference to this outside of the event handler e.g.
this.bindUpKeys = function() {
var that = this;
$('body').keydown(function(evt) {
//Use `that` instead of `this` in here
});
}
as if it doesn't exists inside the prototype.
No, it does.
I'm just accessing the array
That's what you don't. this does not point to your Keyboard instance inside an event listener.
When the function is called as an event listener the DOM element will be the context (jQuery does that, too). See MDN's overview for the this keyword. You can use a closure-scoped variable to hold a reference to the actual instance, as for example described here (there are many questions about that).
Possible quick-fixes:
$('body').keydown( (function(){...}).bind(this))
var that=this; $('body').keydown(function(){ that.pressedKeys...; });