How to add event object to a callback function? - javascript

Adding an event handler is pretty easy.
Now I'd like to inhibit default behavior, e.g. highlighting a text when keeping it touched for a while.
I found the preventDefault method. However I don't see how to provide the required event object to myFunction
<!DOCTYPE html>
<html>
<body>
<h1>Event Handling</h1>
<p ontouchstart="myFunction(true)" ontouchend="myFunction(false)">Touch this! </p>
<p id="demo">demo</p>
<script>
function myFunction(state) {
document.getElementById("demo").innerHTML = (state? "Hello World": " ");
event.preventDefault(); // how to get access to the event object ?
}
</script>
</body>
</html>
Or am I on the wrong search path?

Adding an event handler is pretty easy.
It is. But, you're doing it the 25+ year old legacy way, which should not be used today.
The event object is not automatically passed to callbacks when you use the legacy inline event attributes, such as onXyz.
Instead, use the modern approach to event handling and use .addEventListener() separately in your JavaScript. Then, the event object reference will automatically be passed as the first argument to your callback.
Now, because you want to pass your own argument to your callback, you'll need to wrap your actual callback in an anonymous function that will be the the "official" event handler and that one will receive the event argument. Then, in that wrapper, you can call your desired function and pass along the event as well as your custom argument(s).
Since not all devices support touch events, you need to check to see if they are supported by the device before you use them. Below, I've done a basic feature detection check, but read this for more comprehensive approaches.
In addition, you may also want to prevent the event from bubbling up to ancestor elements that may have event handlers registered to them as well. If so, you should also add event.stopPropagation().
Also, you should avoid .innerHTML when you can (almost always) as it has performance and security implications. Since you aren't actually getting or setting any HTML, use .textContent.
<!DOCTYPE html>
<html>
<body>
<h1>Event Handling</h1>
<p class="touch">Touch this! </p>
<p id="demo">demo</p>
<script>
// Get your DOM references just once instead of each time your functions run
const touchP = document.querySelector(".touch");
const demo = document.getElementById("demo");
// Not all devices support touch events, you should check first.
if(touchP.touchstart){
// The registered callback function will automatically be passed an event reference
touchP.addEventListener("touchstart", function(event){
// You can now call your desired function and pass the event and your argument
myFunction(event, true);
});
touchP.addEventListener("touchend", function(event){
myFunction(event, false)
});
} else {
demo.textContent = "Touch not supported on this device.";
}
function myFunction(event, state) {
event.preventDefault();
event.stopPropagation(); // If you don't want the event to bubble
// You probably didn't want a space character returned if state == false.
// Instead, return an empty string: ""
demo.textContent = state ? "Hello World" : "";
}
</script>
</body>
</html>

You have to pass the event to your function when you call it.
<p ontouchstart="myFunction(event, true)" ontouchend="myFunction(event, false)">Touch this! </p>
function myFunction(event, state) {
document.getElementById("demo").innerHTML = (state? "Hello World": " ");
event.preventDefault(); // how to get access to the event object ?
}
The event exist in the event declarations.

Related

What is the returned value of a clicked <button>?

I want to get the returned value of a clicked button, and then use it to make an if statement. All the answers that I read here about people trying to do that are either very old using old script that doesn't work anymore, or not the same case.
function remove() {
if (document.getElementById("removing").value == true) {
document.getElementById("test").style.backgroundColor = "red";
}
}
<div id="test">test</div>
<button id="removing" onclick="remove()">Remove a word</button>
I have tried using the value property, and onclick, but non of them equal true when the button is clicked.
I tried using alert to display the value, but it displays nothing.
Does clicking a button actually returns a value, and if so, what is it?
DOM Events are handled by an EventListener's callback function.
Thus such a handler function, if triggered by an event and forwarded by an event listener's handleEvent method, always will be invoked with an event object as this function's single argument.
All information related to this event are carried by the event itself. Its properties can be read and some even can be written/changed.
It is obvious from the provided example that the OP wants to assure that an event handler has been triggered by just a specific html element. Thus any valid approach just needs to look into the event's currentTarget property ...
// the way the OP might want to handle the problem.
function handleRemoveWord(evt) {
const elmNode = evt.currentTarget;
// make sure the handler was
// triggered by the intended element ...
// ... here by comparing node properties.
if (
(elmNode.tagName.toUpperCase() === 'BUTTON')
&& (elmNode.id === 'remove')
) {
document
.getElementById('test')
.style.backgroundColor = 'red';
}
}
// another way the OP might want to handle the problem.
function handleRemoveAnotherWord(evt) {
// `this` referres to the element which got
// bound to the handler via `addEventListener`.
const targetNode = this;
// make sure the handler was
// triggered by the intended element ...
// ... here by comparing node references.
if (targetNode === evt.currentTarget) {
document
.getElementById('test')
.style.backgroundColor = 'cyan';
}
}
// an alternative way of solving the problem
// of always being assured about the correct
// element having triggering the event handling.
function handleRestoreWordWithBoundContext(evt) {
const context = this;
const { elmTest, elmRestore } = context;
// make sure the handler was
// triggered by the intended element ...
// ... here by comparing node references.
if (elmRestore === evt.currentTarget) {
elmTest.style.backgroundColor = '';
}
}
function initialize() {
// the way(s) the OP might want to handle the problem.
document
.getElementById('remove')
.addEventListener('click', handleRemoveWord);
document
.querySelector('#removeAnother')
.addEventListener('click', handleRemoveAnotherWord);
// an alternative way of soving the problem
// of always being assured about the correct
// element having triggering the event handling.
const elmTest = document.querySelector('#test');
const elmRestore = document.querySelector('#restore');
elmRestore.addEventListener(
'click',
handleRestoreWordWithBoundContext.bind({
elmTest,
elmRestore,
})
);
}
initialize();
<div id="test">test</div>
<button id="remove">Remove a word</button>
<button id="removeAnother">Remove another word</button>
<button id="restore">Restore a word</button>
As one might have noticed, the example features a third button with yet another way of implementing an event handler. This additional handler assumes its this context to carry additional information. This is possible by invoking bind on this handler function and providing exactly the information one wants to be present as the event handlers this context. Every time this function specific method is invoked it creates another function which does have access to the bound information via the this keyword.
Simply change background color onClick of button as:
function remove() {
document.getElementById("test").style.backgroundColor = "red";
}
<div id="test">test</div>
<button id="removing" onclick="remove()">Remove a word</button>
Dispatching Javascript event handlers always returns true, even if it returns false, which we all know is used to prevent default behaviour of an event. We don't usually use the return values of Event handlers or even return anything for that matter.
In your case, I think you're trying to acess the value of the currentTarget element(the button 'removing' in your case). For this you can use the event object, which gets passed on as parameter to your event handler.
event.currentTarget is a way of referencing the element on which an event is being dispatched(triggered) on. It's just like using 'this' inside the event handler, except it also works on arrow functions.
So do something like this:
function remove(event) {
let button = event.currentTarget;
if (buttton.value) {
document.getElementById("test").style.backgroundColor ="red";
}
}
and in HTML,
<div id="test">test</div>
<button id="removing" onclick="remove(event)">Remove a word</button>
Notice I've used remove(event).
Edit Based on comment below:
Using onclick will require you to create you a global 'remove' function.
If you do, '...onclick="remove(event)" what it basically does is creates the function below, a wrapper basically:
// In the global scope
[reference element].onclick = () => {
remove(event);
}
So you must have a global 'remove' function. So this won't work in modules cause each modules have their own top level scope. And you're gonna wanna have to use modules if you plan to work on sophisticated projects.
NOTE Using inline 'onclick' attributes in html has following disadvantages on heavy requests from a comment below:
-separation of concern : You usually don't want to mix up your UI logic(what happens on clicking a button) with presentation. You want a clear split between content, style and script.
-only one handler can be assigned using onclick.
-if an event is specified inline, the JS is specified as a string (attribute values are always strings) and evaluated when the event fires.(extra wrapper code builds internally).
-as I've mentioned before, you are faced with having to reference named functions. This is not ideal and has implications on the function needing to be global which will really bite you back when you use modules.
In short, handle events centrally via the dedicated addEventListener API.

Add or subtract functions from onSubmit event handler?

I have the following code:
function showAddrBox() {
var prompt = document.getElementById('addr_prompt');
prompt.style.display = 'block';
document.generic_form.address.style.display = 'block';
document.generic_form.onsubmit = validateAddrForm;
}
function hideAddrBox() {
var prompt = document.getElementById('addr_prompt');
prompt.style.display = 'none';
document.generic_form.address.style.display = 'none';
document.generic_form.onsubmit = null;
}
The problem is that sometimes I have additional functions attached to onSubmit that I want to preserve. I want to be able to add and remove individual functions from the onSubmit event, not just set them with onsubmit =. In other words, I need a way to accomplish something like this:
document.form.onsubmit += function;
document.form.onsubmit -= function;
Any ideas?
Quirksmode has a wonderful article about advanced event registration.
Short form is: You can bind multiple events using addEventListener (attachEvent in older versions of IE).
if (someform.addEventListener) {
someform.addEventListener('submit', somefunc, false);
} else {
someform.attachEvent('onsubmit', somefunc);
}
To remove them you can use removeEventListener and detachEvent respectively.
Pretty quickly you'll get annoyed by repetitively typing addEventListener and attachEvent, and you might consider making some generic code to bind events for you. Fortunately, other programmers have had the same idea, and many libraries are available that handle event management elegantly. jQuery tends to be the library of choice, because binding an event is as simple as:
$('#formid').submit(somefunc);
the generic event binding method is:
$('#formid').on('submit', somefunc);
to unbind you can use:
$('#formid').off('submit', somefunc);
Although all of this is well documented in the jQuery API.
Use element.addEventListener("eventName", callbackFunction, false) and element.removeEventListener("eventName", callbackFunction).
Where eventName is the name of the handler without the 'on'. So onsubmit becomes submit.
For documentation of the two functions, see:
https://developer.mozilla.org/en/DOM/element.addEventListener
https://developer.mozilla.org/en/DOM/element.removeEventListener
What I would do is have one onSubmit function right on the form that orchestrates the rest of the functions, performs logic on what to do when. At the end of that function execution you can return true if you want to proceed with the submission or return false if you don't.
u can mention two or more functions for a form like below.
<form name="formaname" onsubmit="function1(),function2()">
more on onsubmit

Tracking Clicks; Performance Implications to Searching the DOM

I have a Javascript plugin that searches the DOM for any elements starting with the class name "tracking" and adds a click event listener (or another type of listener, if specified) to that element. The idea is that every time that event occurs on that element, that it runs a Javascript function that sends data to our traffic servers. Here's what the code looks like:
// Once the page is completed loaded
window.mmload(function() {
// Get the container object
obj = document.getElementById(name);
if ( obj.length < 0 )
throw ("The Id passed into the tracker does not exist ("+name+")");
// Find all the elements belonging to the tracking class
var trackingClass = new RegExp( /tracking\[[a-zA-Z0-9\.\-_]+\]/g );
var myElements = getElementsByRegex( trackingClass, obj );
//For each of those elements...
for( var i in myElements ) {
var elm = myElements[i];
var method = elm.className.match( /tracking\[[a-zA-Z0-9\.\-_]+\]/ )[0].split('[')[1].replace(']','').split('.')[2];
method = typeof( method ) == 'undefined' ? 'click' : method;
// Add a click event listener
myElements[i].addEventListener( method, function(e){
// Get the element, the link (if any), and the args of the event
var link = elm.getAttribute('href') == null ? "" : elm.getAttribute('href');
var args = elm.className.match( /tracking\[[a-zA-Z0-9\.\-_]+\]/ )[0].split('[')[1].replace(']','').split('.');
// If a link existed, pause it, for now
if ( link != '' )
e.preventDefault();
// Track the event
eventTracker( args[0], args[1], ( method == 'click' ? 'redirect' : 'default' ), link );
return false;
}, true);
}
});
Right now I've got this chuck of code running once the window has completely loaded (window.mmload() is a function I made for appending window.onload events). However, there maybe times when I need to run this function again because I added new elements to the DOM via Javascript with this class name and I want to track them too.
My initial solution was to run this function using setInterval to check the DOM every few milliseconds or second or whatever makes the most sense. However, I was worried if I took this approach that it might slow down the website, especially since this is running on a mobile website for smartphones. I'm not sure what kind of a performance hit I might take if I'm searching to DOM every so often.
The other approach I had in mind was to simply call the function after adding traceable elements to the DOM. This is probably the most efficient way of handling it. However, the people that I'm working with, granted very smart individuals, are Web Designers who don't often think about nor understand very well code. So the simpler I can make this, the better. That's why I liked the setInterval approach because nothing additional would be required of them. But if it noticeably slows down the site, I might have to take the other approach.
You should consider even delegation.
You just add one event listener to the document root and check the class of the element the event originated from (event.target). If you want to include also clicks from descendants, you'd have to traverse the DOM up form the target and check whether any of the ancestors contains the class.
I see two main advantages:
It works for newly generated elements without any extra steps (so the other developers don't have to do anything special).
It adds only one event handler instead of potentially many, which saves memory.
Disadvantages:
If other event handlers are registered along the path and they prevent the event from bubbling up, you cannot register this event.
A bit more information:
An event handler gets an event object as first argument. This object has several properties, among others, which element the event originated form.
E.g. to get the target element:
var element = event.target || event.srcElement;
This will be a DOM element and you can access the classes via element.className.
So your event listener could look like this (note that IE uses another method to attach event listeners and the event object is not passed but available via window.event):
function handler(event) {
event = event || window.event;
var target = event.target || event.srcElement;
if(target.className.match(/tracking\[[a-zA-Z0-9\.\-_]+\]/g) {
// do your stuff
}
}
if(document.addEventListener) {
document.addEventListener('click', handler, false);
}
else {
document.attachEvent('onclick', handler);
}
But as I said, this would miss events that are prevented from bubbling up. At least in the browsers following the W3C model (so not IE), you can handle the events in the capture phase by setting the last parameter to true:
document.addEventListener('click', handler, true);
If you can live without IE, then there is a change event which you can hook into for the window/document/dom element. Simply hook into the event at the document level, and it'd fire anytime something's changed in the page (stuff inserted, deleted, changed). I believe the event's context contains what got changed, so it should be fairly trivial to find any new trackable elements and attach your spy code to it.
A third option would be to write a method for manipulating the innerHTML of an element. At the end of that method simply call your function that refreshes everything.
example:
var setHtml = function(element, newHtml){
element.innerhtml = newHtml;
yourRefreshFunction();
}
So obviously this requires that you have your web developers user this method to update the dom. And you'll have to do it for anything that is more complicated than simple html edits. But that gives you the idea.
Hope that helps!

Preventing form submission with Dojo

I have a Dojo SubmitButton with jsId="saveParamButtonWidget". I overrided its onClick method by putting:
saveParamButtonWidget.onClick = editParam
I defined the editParam() function like this:
function editParam(eventObj) {
dojo.stopEvent(eventObj);
// ...
}
dojo.stopEvent() is supposed to stop event bubbling and default processing. However, the browser will submit the form anyway. I also tried with the following:
function editParam(eventObj) {
eventObj.stopPropagation();
eventObj.preventDefault();
// ...
}
Same thing. The only way I've managed to prevent form submission is by returning "false" from the event handler:
function editParam(eventObj) {
// ...
return false;
}
Can someone tell me why the first two ways did not work? Thanks.
Okay, after doing some digging through the source, I believe I can answer your question definitively.
The reason dojo.stopEvent() doesn't work, but return false does, is entirely due to how dijit.form.Button is coded. If you're interested, it's time for a little field trip. Keep your hard hats on.
When a dijit.form.Button is clicked...
The button's _onButtonClick method is invoked. (This is hooked up in the template, to the special ondijitclick event which captures not only mouse click but also certain keypresses, for a11y purposes.)
The _onButtonClick method first invokes the _onClick method, which, presuming the button is not disabled (which it's not in this case), invokes and returns the result of the onClick method. This is of particular interest since it's the method you're overriding!
Coming back to _onButtonClick, if _onClick returned precisely false (e.g. if your onClick handler returned false), _onButtonClick immediately bails out. This is why returning false makes your code work as desired. But what happens if it doesn't bail out there? Let's follow the trail further...
Next, _onButtonClick checks whether this button not a descendant of an actual HTML form, but is a descendant of a widget with an _onSubmit method (duck-typing). I'm assuming that in your case it is inside a real form (dijit.form.Form counts), so we'll skip over this. (I am under the impression that this code path wouldn't actually end up submitting, whereas yours apparently does.)
One final condition is checked: if the button has a valueNode defined (it does), the click method of this node is invoked. Unfortunately, this produces an entirely new event object on an invisible input type="submit" node under your form, and thus anything you tried to tell the original event is rendered immaterial, and the form goes on to submit! This is why dojo.stopEvent did not work - this code in dijit.form.Button pays it absolutely no heed.
I cooked this up as a somewhat-limited proof of concept (be sure to open firebug/etc. to get the logs): http://jsfiddle.net/Bf5H8/
Perhaps this is something that should be logged as a bug, but I suppose the initial thought may have been that supporting the well-known return false mechanism would be enough.
All this being said, it's quite possible that overriding onSubmit of the form is more in-line with your interests than overriding the button's onClick anyway (as S.Jones suggested), but at least this should solve the mystery.
Interesting question. +1
I believe you have to use dojo.connect to connect your function to a DOM event to get access to those methods with an event object.
See: The Event Object (DojoTollkit.org Reference Guide)
The Event Object
When you connect a function to a DOM
event with dojo.connect,
Dojo passes your function a normalized
event object. This means that,
regardless of the client's browser,
you can count on a set of standard
attributes about the event and a set
of methods to manipulate the event.
Assume that your function has been
called by dojo.connect and takes an
argument named event, like:
dojo.connect(dojo.byId("node"), "onclick", function(event){
// the var 'event' is available, and is the normalized object
});
...
Dojo normalizes the following methods with an event object:
event.preventDefault — prevent an event's default behavior (e.g., a link from loading a new page)
event.stopPropagation — prevent an event from triggering a parent node's event
Additionally, dojo.stopEvent(event)
will prevent both default behavior any
any propagation (bubbling) of an
event.
That said, placing a function like the one below in your form to perform some logic before submitting it, is a fairly clean, easily understood & maintainable approach.
<script type="dojo/method" event="onSubmit">
if (!this.validate()) { // or whatever else you'd like to evaluate
// insert calls here...
return false;
}
return true;
<script>
Cheers.
I had the same issue for using dojo.stopEvent
This issue is solved the form submission issue like this - here it is a simple form used to connect through dojo:
this.formId = dojo.byId("formId");
dojo.connect(this.formId, 'onsubmit', function(evt) {
var val_main = validate_this_form(0);
if(val_main == false)
dojo.stopEvent(evt);
});

How can I programmatically invoke an onclick() event from a anchor tag while keeping the ‘this’ reference in the onclick function?

The following doesn't work... (at least not in Firefox: document.getElementById('linkid').click() is not a function)
<script type="text/javascript">
function doOnClick() {
document.getElementById('linkid').click();
//Should alert('/testlocation');
}
</script>
<a id="linkid" href="/testlocation" onclick="alert(this.href);">Testlink</a>
You need to apply the event handler in the context of that element:
var elem = document.getElementById("linkid");
if (typeof elem.onclick == "function") {
elem.onclick.apply(elem);
}
Otherwise this would reference the context the above code is executed in.
The best way to solve this is to use Vanilla JS, but if you are already using jQuery, there´s a very easy solution:
<script type="text/javascript">
function doOnClick() {
$('#linkid').click();
}
</script>
<a id="linkid" href="/testlocation" onclick="alert(this.href);">Testlink</a>
Tested in IE8-10, Chrome, Firefox.
To trigger an event you basically just call the event handler for that
element. Slight change from your code.
var a = document.getElementById("element");
var evnt = a["onclick"];
if (typeof(evnt) == "function") {
evnt.call(a);
}
Granted, OP stated very similarly that this didn't work, but it did for me. Based on the notes in my source, it seems it was implemented around the time, or after, OP's post. Perhaps it's more standard now.
document.getElementsByName('MyElementsName')[0].click();
In my case, my button didn't have an ID. If your element has an id, preferably use the following (untested).
document.getElementById('MyElementsId').click();
I originally tried this method and it didn't work. After Googling I came back and realized my element was by name, and didn't have an ID. Double check you're calling the right attribute.
Source: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click
$("#linkid").trigger("click");
Old thread, but the question is still relevant, so...
(1) The example in your question now DOES work in Firefox. However in addition to calling the event handler (which displays an alert), it ALSO clicks on the link, causing navigation (once the alert is dismissed).
(2) To JUST call the event handler (without triggering navigation) merely replace:
document.getElementById('linkid').click();
with
document.getElementById('linkid').onclick();
Have a look at the handleEvent method
https://developer.mozilla.org/en-US/docs/Web/API/EventListener
"Raw" Javascript:
function MyObj() {
this.abc = "ABC";
}
MyObj.prototype.handleEvent = function(e) {
console.log("caught event: "+e.type);
console.log(this.abc);
}
var myObj = new MyObj();
document.querySelector("#myElement").addEventListener('click', myObj);
Now click on your element (with id "myElement") and it should print the following in the console:
caught event: click
ABC
This allows you to have an object method as event handler, and have access to all the object properties in that method.
You can't just pass a method of an object to addEventListener directly (like that: element.addEventListener('click',myObj.myMethod);) and expect myMethod to act as if I was normally called on the object. I am guessing that any function passed to addEventListener is somehow copied instead of being referenced. For example, if you pass an event listener function reference to addEventListener (in the form of a variable) then unset this reference, the event listener is still executed when events are caught.
Another (less elegant) workaround to pass a method as event listener and stil this and still have access to object properties within the event listener would be something like that:
// see above for definition of MyObj
var myObj = new MyObj();
document.querySelector("#myElement").addEventListener('click', myObj.handleEvent.bind(myObj));
If you're using this purely to reference the function in the onclick attribute, this seems like a very bad idea. Inline events are a bad idea in general.
I would suggest the following:
function addEvent(elm, evType, fn, useCapture) {
if (elm.addEventListener) {
elm.addEventListener(evType, fn, useCapture);
return true;
}
else if (elm.attachEvent) {
var r = elm.attachEvent('on' + evType, fn);
return r;
}
else {
elm['on' + evType] = fn;
}
}
handler = function(){
showHref(el);
}
showHref = function(el) {
alert(el.href);
}
var el = document.getElementById('linkid');
addEvent(el, 'click', handler);
If you want to call the same function from other javascript code, simulating a click to call the function is not the best way. Consider:
function doOnClick() {
showHref(document.getElementById('linkid'));
}
In general I would recommend against calling the event handlers 'manually'.
It's unclear what gets executed because of multiple registered
listeners
Danger to get into a recursive and infinite event-loop (click A
triggering Click B, triggering click A, etc.)
Redundant updates to the DOM
Hard to distinguish actual changes in the view caused by the user from changes made as initialisation code (which should be run only once).
Better is to figure out what exactly you want to have happen, put that in a function and call that manually AND register it as event listener.

Categories