Can I create event attributes? - javascript

Can I create my own attributes on HTML elements that are treated as JavaScript code, and able to be executed when my code so desires?
For example, say I want to create a custom event onpagerefresh, and at the same time, allow the person who writes the HTML to add attributes that handle this event.
<body onpagerefresh="updateContents()">
I am able to parse the HTML to get the value of this new onpagerefresh attribute, but how do I tell JavaScript to treat anything in this attribute as JavaScript code rather than a string?

You can use new Function(), the function constructor, to get a function object from your string and then assign it to an attribute of your object. Ex:
var divElement = document.getElementById('mydiv');
divElement.onpagerefresh = new Function("èvent", "alert();");
Whether that's better than eval() in any meaningful way is debatable (though it does give you a distinct scope), but that would be how you do it. Possibly a better option in your case would be to allow the person implementing the HTML to use a preexisting function name (onpagerefresh="updateContents") and then simply execute that one as e.g. window["updateContents"](). That way you don't let any old DOM insertion write random javascript into your code. But of course that limits the functionality.

In case you want to create an actual event accompanied with support for listeners, this Mozilla article describes a way to do so:
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
In your case:
var event = new Event('onpagerefresh');
// Listen for the event.
elem.addEventListener('onpagerefresh', function (e) {
updateContents();
}, false);
// Dispatch the event.
elem.dispatchEvent(event);
Though, be wary of cross-browser support.

Related

Is using event.target necessary when capturing input data?

I'm currently following a tutorial about Javascript events. Basically, what the instructor is walking us through is how to listen to user input data from the HTML input tag using the keyup Javascript event and then logging the value on the console.
Here's the code snippet:
document.getElementById("searchInput").addEventListener("keyup", function(event) {
let searchQuery = event.target.value.toLowerCase();
console.log(searchQuery);
});
It's straightforward, however, I'm curious about the significance of adding the event parameter and accessing the target property of such event. I went ahead and removed the event parameter, as well as the target property, and made it so as to log the value, too, and, as far as I am aware, it behaved identically.
document.getElementById("searchInput").addEventListener("keyup", function () {
let searchQuery = document.getElementById("searchInput").value.toLowerCase();
console.log(searchQuery);
});
Is there a unique reason why the instructor chose to do the former or is it simply for brevity reasons?
It’s useful if you want to write a more general function that would work with whatever element you pass into it. Currently you are specifying the id of the element you’d like to access within the function. But at some point you may want to write a function that you can reuse with multiple elements at once, this is where event.target comes in.

Is attaching an onchange object a closure?

I have searched prior SO posts here, here and here, and couldn't an answer that made sense to me. This should be a basic question, but I'm not understanding the posts I find. They don't seem to address using a this parameter.
I want to programatically add an input with an onchange event, such that the final result is this:
<input type="button" onchange="handleButtonOnChange(this)">ClickMe</input>
I am working on a project that is using an embedded IE6 browser inside a old Delphi application, so I have to have a solution that is IE6 compatible (yes, IE6 is horrible, but there are reasons I am stuck with it for now).
My initial attempt was this:
var DaySelect = document.createElement("select");
DaySelect.id = ParentID+"-day";
DaySelect.disabled = true;
MonthSelect.onchange="handleDayChange(this);" //<--- not correct
Parent.appendChild(DaySelect);
I then read that the .onchange should be assigned an object, not a string, and one should use this instead:
MonthSelect.onchange=handleDayChange; //<--- '(this)' removed
But it seem to me that this will result in this element (notice the missing this parameter)
<input type="button" onchange="handleButtonOnChange">ClickMe</input>
If I use the line below, instead, won't this make a closure, and the 'this' will refer to the event at the time the object is assigned to the .onchange property, instead of being the event at the time of the change event?
//Does the line below make a closure?
MonthSelect.onchange=handleDayChange(this); //<-- What does 'this' refer to?
I'm a relatively new web programmer, but long time Delphi programmer. Closures still make my head hurt. I appreciate any help in this.
Also, I read here about using addEventListener and the problems with older versions of IE, and the last post on the page provides a work around. But I don't understand how it works.
EDIT -- And what about passing other parameters? It seems that many event handlers will need to have parameters specific for the attached element. It seems that it is just not possible to add a listener with any parameters.
A simple closure if you are creating the elements in JS as you show:
var DaySelect = document.createElement("select");
DaySelect.id = ParentID+"-day";
DaySelect.disabled = true;
MonthSelect.onchange=function(){handleDayChange(DaySelect);};
Parent.appendChild(DaySelect);
Since the function is created inside the scope that you create the element in, the same variables will be available to it.
EDIT:
Additional parameters can be passed with this method, for example, the anonymous function we create and attach as the handler will still have the event object sent to it:
function(e){handleDayChange(DaySelect, e);};
In the event object you will have access to the event target, but in your example the event target and "this" are not the same element, so there would be no way for the handler to know about the DaySelect element.
jQuery makes a lot of event handling much simpler which is one of the reasons many people use it, it also normalizes it's methods between various browsers so you don't have to write multiple versions of the same code (in most cases)

How to bind to all custom events in jQuery

I know it's not possible to bind to all DOM events and I know you can bind to multiple events by supplying a space-separated list.
But is it possible to bind to all custom events (preferably filtered by a wildcard pattern like 'abc*' or name-space)?
Edit:
To clarify, I have created some custom widgets that respond to some custom events. For example, they all handle an event called stepReset and resets their internal models.
After I've written these, I realized events don't bubble down, so the call $(body).trigger('stepReset') basically does nothing. As a result, I am considering adding an umbrella event handler on all widgets' parent elements to propagate all relevant events down.
(I know this is not an elegant solution, but I forgot to tag elements with handlers with a common class, so there's no easy way to use select them all.)
With regards to your upcoming edit, you can retrieve all bound events by accessing the object's data:
var boundEvents = $.data(document, 'events');
From here, you can iterate over the resulting object and check each property for your chosen wildcard character, or iterate over that property's array elements and check the namespace property of each.
For instance,
$.each(boundEvents, function () {
if (this.indexOf("*")) // Checks each event name for an asterisk *
alert(this);
// alerts the namespace of the first handler bound to this event name
alert(this[0].namespace);
});
If I understood you correctly, you can iterate over the special events object to get a list of custom events (including those specified in the jQuery source code). Here's an ES5 example, you will need to adapt it yourself for older browsers or use a polyfill for Object.keys:
var evts = Object.keys(jQuery.event.special).join(" ");
$("#myDiv").on(evts, function (e) {
// your code here
});

Save and restore "onclick" action on jQuery objects

I want to disable a whole bunch of objects on the page, and then re-enable them later. Since some of them are tags rather than buttons, I disable them by removing their onclick attr. I've tried to store the old handler in a .data(), but unfortunately when I attempt to restore them with $(obj).attr('onclick',$(obj).data('onclick')), it calls the function rather than restoring it to the attribute. And if I try to store it in a different attribute instead of a data, it doesn't store the function, it stores the return value for the function.
Is there any way to accomplish this without re-writing every tag and every onclick handler on my page?
if( doEnable) {
$(obj).attr('href', $(obj).data('href'));
$(obj).attr('onclick', $(obj).data('onclick'));
$(obj).removeClass(EIS.config.classes.disabled);
$(obj).show();
}
else {
// Save the things you're going to remove
$(obj).data('onclick', $(obj).attr('onclick'));
$(obj).data('href', $(obj).attr('href'));
$(obj).prop("href", null);
$(obj).prop("onclick", null);
$(obj).addClass(EIS.config.classes.disabled);
$(obj).show();
}
By the way, this code seems to work fine in Chrome and Firefox, but only sometimes in IE8 and never in IE6. Unfortunately the client tests first in IE6.
$(obj).attr('onclick', ...
is ambiguous, has results that differ in different versions of jQuery and different browsers. It probably doesn't do what you want. You should avoid using attr on event handlers.
The problem is the disconnect between the onclick attribute and the onclick property. jQuery has tried to brush the difference between an attribute and a property under the carpet in the past, using attr to access both, but they're quite different. This was changed in jQuery 1.6, and partially reverted in 1.6.1, to widespread controversy, confusion and incompatibility.
For many properties, the values of an attribute and the corresponding DOM property are the same; for others, including all properties that aren't strings, they aren't. Event handlers certainly aren't: the property is a Function object, whereas the string attribute might be (a) the original string of the onclick="..." attribute in the HTML, (b) nothing (if the onclick was assigned from script to be a Function object) or (c) unavailable (in older IE).
To access the event handler Function property, use prop() in jQuery 1.6:
$(obj).data('onclick', $(obj).prop('onclick'));
...
$(obj).prop('onclick', $(obj).data('onclick'));
or just use plain old JavaScript which is actually simpler and more readable; jQuery wins you nothing here.
obj._onclick= obj.onclick;
...
obj.onclick= obj._onclick;
Either way this is not going to reliably ‘disable’ elements since they can (and very likely will, if you're using jQuery) have other event listeners registered on them, using addEventListener/attachEvent rather than the old-school event handler interfaces.
It looks like saving a function via .data() works just fine:
var f1 = function() { console.log('invoked'); };
$('a').data('func', f1)
var f2 = $('a').data('func'); // 'invoked' is not printed
f1 === f2 // true
so how are you storing the function via .data? if you're doing something like
a = $('a');
a.data('onclick', a.click()); // click handler is invoked here
then you're actually invoking the click handler(s) prematurely, and storing the return value with .data().
--edit--
it appears that .attr(function) invokes the passed function. This is a feature of jQuery. I'd suggest using jQuery's .click() method to attach the function as a click handler.
a = $('a');
a.each(function() {
this.data('onclick', handler_fn);
this.bind('click', handler_fn);
});
// later
a.each(function() {
this.unbind('click');
});
// even later
a.each(function() {
this.bind('click', this.data('onclick'));
});
What about binding the event in jQuery instead of setting the onclick attribute?
$(obj).click($(obj).data('onclick'));
Can we see the code that you use to set the data attribute?

JavaScript Inline Events or Adding Events Afterwards

I have a question, which I can't seem to decide on my own so I'll ask here. The question is simple: whether to use inline JavaScript events or adding them afterwards? The theory in the background isn't that simple though:
I have a JS object that returns HTML. Whenever you create this object, the returned HTML will be used for another object's HTML. Therefore, adding events is not straight-forward. See:
secret.object = function() {
this.init = function() {
var html = '<div>and lots of other HTML content</div>';
return html;
};
}
This is a sample object that is created within this code:
for ( var i = 0; i < countObjects; i++) {
var obj = arguments[0].content[i];
generatedContent += spawnSecret(); /* The spawnSecret() is a method that initializes the object, and calls its init() method that returns the HTML.
}
and then later on I create a new object whose property "content" will be set to "generatedContent". It needs to add the events within the secret object I have, nowhere else. And since my system is built like this, I see only two ways around this: use inline events or build HTML using method calling instead of returning.
Hopefully, this wasn't too hard to understand.
If you created the elements using document.createElement() (but didn't append them to the DOM) and kept a reference to them, then you could populate them with the text content and attach event handlers to them, without having to use inline events.
When you are ready to reveal your 'secret' you could then append them to the DOM, rather than dumping in a text string of HTML tags and content.
I cant see it making much of a difference - if you just render your events using onclick etc. JavaScript event handlers they will be evaluated as soon as you append your generated HTML to the document, rather than you having to call attachEvent() or whatever.

Categories