Object.defineProperty to override HTMLElement eventhandler not work on event attributes - javascript

I want to override HTMLElement's on-event handler method with Object.defineProperty.
here is the code.
var desc = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'onclick');
var originalGet = desc.get;
desc.get = function () {
console.log('get onclick');
return originalGet.apply(this, arguments);
}
var originalSet = desc.set;
desc.set = function() {
console.log('set onclick', arguments);
return originalSet.apply(this, arguments);
}
The code will work when I add event listener in this way,
var btn = document.getElementById("btn");
btn.onclick = function() {
console.log("button clicked");
}
But will not work when I define the onclick handler in html inline.
<button onclick="btnClick(event)">ok</button>
The getter is called, but setter is not called.
I can't figure out why?

Related

Vanilla Javascript how override added function on event listener?

I have a bootstrap modal that has some custom events, like hidden.bs.modal, depending on where the user does, I want the function in this event to be replaced, maybe it's better to understand with a simple example, consider:
const currentModal; // imagine an any modal here.
window.addEventListener('load', () => {
currentModal.addEventListener('hidden.bs.modal', standardFunction );
});
function standardFunction(){
alert('hi there');
// this is standard output to modal closed
}
function buttonClickedChange(){
// Here, i need override standardFunction
this.standardFunction = function(){
alert('modal event hidden.bs.modal changed with success!');
// this must be override previous output
};
}
What happens is that regardless of the redeclaration of the function, the output for the method is still standard, this is because the eventlistener does not refer to the stored function but only "copy" its content and creates its scope only inside.
The problem you have is when you bind the event, you are referencing that function. When you replace it does not update the reference to that function. You can clearly see that this will not work with the example
var btn = document.querySelector("button");
function myFunc () {
console.log(1);
}
btn.addEventListener("click", myFunc);
myFunc = function() {
console.log(2);
}
<button>Click Will Show 1</button>
Just remove the event listener and bind a new event.
currentModal.removeEventListener('hidden.bs.modal', standardFunction );
currentModal.addEventListener('hidden.bs.modal', myUpdatedFunction );
function myFunc () {
console.log(1);
}
var btn = document.querySelector("button");
btn.addEventListener("click", myFunc);
function myFunc2() {
console.log(2);
}
btn.removeEventListener("click", myFunc);
btn.addEventListener("click", myFunc2);
<button>Click</button>
If for some reason you can not remove the event, the only way around it would not to bind directly to the function, but to have another function call that function.
var btn = document.querySelector("button");
var myFunc = function() {
console.log(1);
}
function clickFunction () {
myFunc();
}
btn.addEventListener("click", clickFunction);
myFunc = function() {
console.log(2);
}
<button>Click</button>
Or how most people would do it is to add the logic into the function on what to do
var btn = document.querySelector("button");
var state = 0;
var myFunc = function() {
if (state===0) {
console.log(1);
} else {
console.log(2);
}
}
state = 1;
btn.addEventListener("click", myFunc);
<button>Click</button>
You used function expressin instead of function declaration. When you use function yourFunction it is moved to the top with all of the other declared functions. When you use var yourFunction = function () it is right there where you declared it. This means that hidden.bs.modal is searching for the top most function with that name which in this case is the first one you declared or the standard one. One of the ways is that you can declare your function in the global scope: function standardFunctionOverride() and add that to the listener.

Override (wrap) an existing jQuery click event with another in javascript

Say I have an existing button and attach a click to it via jQuery:
var $button = $('#test').click(function () { console.log('original function') });
Now, say I want to override that click so that I can add some logic to the function before and after it. I have tried binding and wrapping using the functions below.
Function.prototype.bind = function () {
var fn = this;
var args = Array.prototype.slice.call(arguments);
var object = args.shift();
return function () {
return fn.apply(object, args.concat(Array.prototype.slice.call(arguments)));
}
}
function wrap(object, method, wrapper) {
var fn = object[method];
return object[method] = function() {
return wrapper.apply(this, [fn.bind(this)].concat(
Array.prototype.slice.call(arguments)));
}
}
so I call wrap with the object that the method is a property of, the method and an anonymous function that I want to execute instead. I thought:
wrap($button 'click', function (click) {
console.log('do stuff before original function');
click();
console.log('do stuff after original function');
});
This only calls the original function. I have used this approach on a method of an object before with success. Something like: See this Plunker
Can anyone help me do this with my specific example please?
Thanks
You could create a jQuery function that gets the original event handler function from data, removes the click event, then adds a new event handler. This function would have two parameters (each functions) of before and after handlers.
$(function() {
jQuery.fn.wrapClick = function(before, after) {
// Get and store the original click handler.
// TODO: add a conditional to check if click event exists.
var _orgClick = $._data(this[0], 'events').click[0].handler,
_self = this;
// Remove click event from object.
_self.off('click');
// Add new click event with before and after functions.
return _self.click(function() {
before.call(_self);
_orgClick.call(_self);
after.call(_self);
});
};
var $btn = $('.btn').click(function() {
console.log('original click');
});
$btn.wrapClick(function() {
console.log('before click');
}, function() {
console.log('after click');
});
});
Here is a Codepen
After a long search I reached the same answer as #Corey, here is a similar way of doing it considering multiple events:
function wrap(object, method, wrapper) {
var arr = []
var events = $._data(object[0], 'events')
if(events[method] && events[method].length > 0){ // add all functions to array
events[method].forEach(function(obj){
arr.push(obj.handler)
})
}
if(arr.length){
function processAll(){ // process all original functions in the right order
arr.forEach(function(func){
func.call(object)
})
}
object.off(method).on(method, function(e){wrapper.call(object,processAll)}) //unregister previous events and call new method passing old methods
}
}
$(function(){
$('#test').click(function () { console.log('original function 1') });
var $button = $('#test').click(function () { console.log('original function 2') });
wrap($button, 'click', function (click,e) {
console.log('do stuff before original functions');
click()
console.log('do stuff after original functions');
});
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div id='test'>click me</div>

How to unbind document keypress event with anonymous function

This is page's code.
I can't modify this.
var Example = {};
Example.create = function() {
var obj = new Example.object();
return obj;
}
Example.object = function(){
this.initialize = initialize;
function initialize() {
window.addEventListener('load', activate);
}
function activate() {
document.addEventListener('keypress', keyPressed);
}
function keyPressed(e) {
alert("Hello!");
}
};
Example.defaultObject = Example.create();
Example.defaultObject.initialize();
I have tried many things...
document.onkeypress = null;
document.keypress = null;
document.removeEventListener('keypress');
$(document).unbind('keypress');
$(document).off("keypress");
$("*").unbind('keypress');
$(document).bind('keypress', function(e) { e.stopPropagation(); });
but all failed.
How can I unbind event of document keypress?
You have to pass the listener to remove it: (a variable pointing the function aka the function name)
document.removeEventListener('keypress', keyPressed);
https://developer.mozilla.org/de/docs/Web/API/EventTarget/removeEventListener
You will have to save it somewhere to remove it later
Root cause of the issue is removeEventListener method. This method expect second parameter which is listener method
document.removeEventListener('keypress', Example.defaultObject.keyPressed);
Here you go for Solution on your problem.
var Example = {};
Example.create = function() {
var obj = new Example.object();
return obj;
}
Example.object = function(){
this.initialize = initialize;
function initialize() {
window.addEventListener('load', activate);
document.getElementById('disable').addEventListener('click', deActivate);
}
function activate() {
document.addEventListener('keypress', keyPressed);
}
function deActivate() {
document.removeEventListener('keypress', keyPressed);
document.querySelector('h1').innerHTML = 'Page Key Press Listener Removed';
}
function keyPressed(e) {
alert("Hello!");
}
};
Example.defaultObject = Example.create();
Example.defaultObject.initialize();
<body>
<h1>Page has Key Press Listener</h1>
<input id="disable" type="button" value="deactivate">
</body>

Assign event listener from js class

I want to assign onclick event listener to an object from within a class
and then get some variable from the instance that created that onclick
function myclass() {
this.myvar;
this.myfunc = function()
{
alert(this.myvar);
document.onmousedown = this.mouseDown;
}
this.mouseDown = function(e)
{
alert(this.myvar); //does not work of course
//how could I access myvar from current myclass instance
}
}
var myclass_instance = new myclass();
myclass_instance.myvar = 'value'
myclass_instance.myfunc();
http://jsfiddle.net/E7wK4/
this in the mouseDown event is not the this of the instance.
Try this instead:
function myclass() {
var _this = this;
this.myvar;
this.myfunc = function()
{
alert(this.myvar);
document.onmousedown = this.mouseDown;
}
this.mouseDown = function(e)
{
alert(_this.myvar); //<<<<
}
}
Demo: http://jsfiddle.net/maniator/E7wK4/1/
As an alternative to #Neal you could bind this.
document.onmousedown = this.mouseDown.bind(this);

I need to access event context AND object context in event handler

I've this piece of code:
function ActivityDialog(_divId, _title) {
function addButton() {
var buttonElement = document.createElement('input');
buttonElement.setAttribute('type','button');
buttonElement.setAttribute('class','button');
buttonElement.setAttribute('id','updateButton-' + id););
buttonElement.onclick = this.updateAction;
};
function updateAction() {
var buttonId = this.id; // correct: this is the Button
this.sendUpdateRequest(stringUrl); // not defined: Need to reference the current ActivityDialog!!!
};
function sendUpdateRequest(url) {
// something...
};
}
As you can see the problem is when I call function sendUpdateRequest; how can I, at the same time, retrieve button infos and call a function?
You might try this...
function ActivityDialog(_divId, _title) {
// Store the ActivityDialog context
var self = this;
function addButton() {
var buttonElement = document.createElement('input');
buttonElement.setAttribute('type','button');
buttonElement.setAttribute('class','button');
buttonElement.setAttribute('id','updateButton-' + id););
buttonElement.onclick = this.updateAction;
};
function updateAction() {
var buttonId = this.id;
self.sendUpdateRequest(stringUrl); // <---------------------
};
function sendUpdateRequest(url) {
// something...
};
}
Because your using updateAction as a event handler, you correctly see that this will be the button that generates the event. Storing the initial context of the ActivityDialog will allow you to maintain access to it even within event handlers.

Categories

Resources