Reputation: 3642
I have a button that already has an onclick-event and an assigned function. I wish to add another function call in front of it. I can imagine it should be possible to fiddle around with the onclick attribute (attr
), but I do not think this is best practice.
What would you consider best practice to prepend a function call on an existing onclick-event?
Upvotes: 6
Views: 8062
Reputation: 8796
Tested with JQuery 3.3.1 (but may work for 1.9.1 and later). See Tomalak's answer for versions before 1.9.1.
One of my company's projects had an issue (now fixed), where I noticed that:
Removing and/or altering said global-handler was not an option at the time.
JQuery calls listeners/handlers in order of registeration, meaning:
Note that I am using Type-script, but if required, you could cast to JavaScript (simply remove types and
declare global
section?).
First, define somewhere, maybe plugins.ts
file, the helpers and/or JQuery plugins, like:
import $ from 'jquery';
export type Middleware = (next: () => void) => void;
declare global {
interface JQuery<TElement = HTMLElement> {
/**
* Prepends given `middleware` to be called on enter-key and click events.
*
* Also, passes a trigger to "default click operation" as first argument
* (the `next()` callback).
*
* WARNING: registering multiple {@link Middleware} is NOT supported yet
* (if all target same element).
*/
clickMiddleware(middleware: Middleware): JQuery;
/**
* Same as {@link JQuery.trigger} for click, but cross-platform.
*
* For example, JQuery intentionally does nothing for Anchor-tags
* (which is their way to be cross-platform).
*/
clickCompat(): JQuery;
/**
* Same as {@link JQuery.on}, but prepends handler.
*
* Additionally, may replace inline-handlers with JQuery handler
* (because they get called before JQuery's handler).
*/
prependEvent<TString extends string>(
events: TString,
handler: false
| JQuery.TypeEventHandler<TElement, undefined, TElement, TElement, TString>
): this;
}
}
export function register() {
(<any>$.fn).clickMiddleware = function (middleware: Middleware) {
return $(this).prependEvent('keypress click', function (event: any) {
// May already be past middleware.
const $element = $(this);
if ($element.prop('isCallingDefault')) {
$element.prop('isCallingDefault', false);
return true;
}
// Executes middleware (but only for enter-key and click events).
const logicalKey = event.key || event.which || event.keyCode || 0;
if (logicalKey === 13 || event.type === 'click') {
if (event.stopImmediatePropagation) event.stopImmediatePropagation();
if (event.stopPropagation) event.stopPropagation();
event.preventDefault();
middleware.call(this, function () {
$element.prop('isCallingDefault', true);
$element.clickCompat();
});
return false;
}
return true;
});
};
(<any>$.fn).clickCompat = function () {
$(this).each(function (index, element: HTMLElement) {
if ((<any>document).dispatchEvent) {
let event = null;
try {
event = new MouseEvent('click', {
'view': window,
'bubbles': true,
'cancelable': false
});
} catch {
event = document.createEvent('MouseEvents');
// noinspection JSDeprecatedSymbols
event.initMouseEvent('click', true, true,
window, 0, 0, 0, 0, 0,
false, false, false, false,
0, null);
}
element.dispatchEvent(event);
} else if ((<any>document).fireEvent) {
element.click();
}
});
};
(<any>$.fn).prependEvent = function (eventList: string, handler: any) {
// Using "container" param instead of "var container;".
return $(this).each(function () {
const $element = $(this);
for (let event of eventList.split(' ')) {
// Replaces inline-handlers (if possible).
const attributeName = 'on' + event;
const oldHandler = this[attributeName];
if (oldHandler) {
$element.attr(attributeName, null);
internalPrependEvent($element, event, function () {
oldHandler.apply(this, arguments);
});
}
// Actual prepend.
internalPrependEvent($element, event, handler);
}
});
};
}
/**
* NOTE: is already called form inside `$selection.each(...)`,
* hence `$element.get(0)` is enough (no need to handle multiple elements).
*/
function internalPrependEvent(
$element: JQuery<any>, eventName: string, handler: any
) {
// Append new-listener.
$element.on(eventName, handler);
// Move above added new-listener to first in list.
const container = (<any>$)._data($element.get(0), 'events')[eventName];
container.unshift(container.pop());
}
Finally, import and use above where required.
For example, OP may use it like:
require('./plugins').register();
$(function () {
$('my-selector').prependEvent('click', function (event) {
// Do something here...
});
});
But I use it like:
require('./plugins').register();
$(function () {
$('my-selector').clickMiddleware(function (next) {
var valid = ... my logic here ...;
if (valid) {
next();
}
});
});
Note that my web-site fetches JQuery from CDN, and I have in my web-pack config something like:
externals: { jquery: 'jQuery', }
Upvotes: 0
Reputation: 5510
This appears to work in 1.9.1 through 3.1.4. See Tomalak's awesome answer for versions before 1.9.1.
$(function() {
$("input" ).mousedown( function() {
console.log( "mousedown" );
});
$("input" ).click( function() {
console.log( "click" );
});
$.fn.extend({exposeListeners:
function (fn) {
return $._data( this[0], "events") || {};
},
prependListener: function (types, fn) {
var elems = this;
$(elems).each(function() {
var elem = this;
var $elem = $(elem)
$elem.on(types, fn);
var events = $._data( $elem[0], "events")[types];
if (events.length > 1) { // its the only event, no reason to shift
events.unshift(events.pop()); // put last as first
}
return this;
});
}
})
$("#prep").click(function () {
console.log($("input").exposeListeners());
$("input:not(:last)").prependListener("click", function () {
console.log('click, prepended');
})
$("a").prependListener("click", function () {
console.log('click event created');
})
console.log($("body").exposeListeners());
})
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<input type="button" value="input, Will accept prepend"><br>
<input type="button" value="input, Will also accept prepend"><br>
<input type="button" value="input, Will not"><br>
<a href="#">Link with no pre-exting handlers</a><br><br>
<Button id="prep">Prepend an event</Button>
And here's a barebones version
$(function() {
$.fn.extend({prependListener: function (types, fn) {
var elems = this;
$(elems).each(function() {
var elem = this;
var $elem = $(elem)
$elem.on(types, fn);
var events = $._data( $elem[0], "events")[types];
if (events.length > 1) {
events.unshift(events[events.length-1]);
events.pop();
}
return this;
});
}
})
})
Upvotes: 0
Reputation: 338316
If you are not afraid to mess with the guts of jQuery:
// "prepend event" functionality as a jQuery plugin
$.fn.extend({
prependEvent: function (event, handler) {
return this.each(function () {
var events = $(this).data("events"),
currentHandler;
if (events && events[event].length > 0) {
currentHandler = events[event][0].handler;
events[event][0].handler = function () {
handler.apply(this, arguments);
currentHandler.apply(this, arguments);
}
}
});
}
});
$("#someElement").prependEvent("click", function () {
whatever();
});
See it live: http://jsfiddle.net/Tomalak/JtY7H/1/
Note that there must already be a currentHandler
or the function will not do anything.
Disclaimer: I consider this a hack. It depends on jQuery internals that might change in the next version of the library. It works now (I tested jQuery 1.7.2), but don't be surprised if it breaks with a future version of jQuery.
Upvotes: 10
Reputation: 630
I don't think there's a way to prepend a function call on an existing onclick event, however you can try this:
$('#foo').unbind('click');
$('#foo').click(function() {
// do new function call..
// call original function again...
});
Hope that helps.
Upvotes: 2
Reputation: 9212
I've written a short example here:
$("button").click(function() { // this is already existing
if ($("span").length) {
alert("ok");
}
});
var clicks = $("button").data("events").click.slice();
$("button")
.unbind("click")
.click(function() { // this is the new one which should be prepended
$("body").append($("<span>"));
});
$.each(clicks, function(i, v) {
$("button").click(v);
});
Upvotes: 6
Reputation: 22720
You can add function on single event but I try to avoid two functions on onclick event. Instead you can
1. Add new code in existing function.
2. Add call to new function from existing function.
Upvotes: 1
Reputation: 7384
If you have binded the event like this
$(elem).on('click', functionName);
you can modify that like this:
$(elem).on('click', function(){
anotherFunctionNameOrWhateverYouWant();
functionName();
});
Upvotes: -2