How to expand an onchange event with JavaScript - javascript

This is a question I ran into about expanding on an element's JavaScript onchange event. I have several select elements that conditionally will have one onchange event attached to each of them (when they change to specific values, it hides/unhides certain elements). I want to conditionally add or append to another onchange event so they set a global variable if they do change without modifying or disabling the previous function already attached to them. Is there a way to "append" an additional function or add more functionality onto the already existing one?
Here is what I believe an example would be like:
<select id="selectbox1">
<option>...</option>
<option>...</option>
</select>
if (<certain conditions>) {
document.getElementById("selectbox1").onchange = function () {
//hides elements to reduce clutter on a web form ...
}
}
....
if (<other conditions>) {
document.getElementById("selectbox1").onchange = function2 () {
//set global variable to false
}
}
Alternatively I'd like to simply add the 1-liner "set global variable to false" to the original function.

You can cheat by simply having a composite function that calls the other functions.
document.getElementById("selectbox1").onchange = function() {
function1();
function2();
}
You can also use the observer pattern, described in the book Pro JavaScript Design Patterns. I have an example of its use in an article (here).
//– publisher class —
function Publisher() {
this.subscribers = [];
};
Publisher.prototype.deliver = function(data) {
this.subscribers.forEach(function(fn) { fn(data); });
};
//– subscribe method to all existing objects
Function.prototype.subscribe = function(publisher) {
var that = this;
var alreadyExists = publisher.subscribers.some(function(el) {
if (el === that) {
return;
}
});
if (!alreadyExists) {
publisher.subscribers.push(this);
}
return this;
};

You want to look at the addEventListener() and attachEvent() functions (for Mozilla-based browsers and IE respectively).
Take a look at the docs for addEventListener() and attachEvent().
var el = document.getElementById("selectbox1");
try { //For IE
el.attachEvent("onchange", function(){ code here.. });
}
catch(e) { //For FF, Opera, Safari etc
el.addEventListener("change", function(){ code here.. }, false);
}
You can add multiple listeners to each element, therefore more than one function can be called when the event fires.

Can you use jQuery? This will allow you to bind/manipulate/unbind events pretty easily. The only hitch is event handlers are activated in the order they are bound.
if (<certain conditions>) {
$("#selectbox1").bind("change", eventdata, function1);
}
if (<other conditions>) {
$("#selectbox1").bind("change", eventdata, function1);
}
And, you can also look into triggering custom events, if your needs are complex. For example, instead of "interpreting" onChange, maybe there is a way to specifically trigger custom events. See the last example on jQuery's page.

If you use jQUery you would have something like
<select id="selectbox1">
<option>...</option>
<option>...</option>
</select>
if (<certain conditions>) {
$("#selectbox1").change(function () {
//hides elements to reduce clutter on a web form ...
});
}
....
if (<other conditions>) {
$("#selectbox1").change(function () {
//set global variable to false
});
}
This will mostly take care of browser compatibility as well.

There are currently three different methods for defining event handlers (a function which is fired when a certain event is detected): the traditional method, the W3C method, and the Microsoft method.
Traditional method
In the traditional method, event handlers are defined by setting the onevent property of an element in Javascript (as you are doing in your example code), or by setting the onevent attribute in an HTML tag (such as <select onchange="...">). While this is the simplest method to use, its use is generally frowned upon now, because as you have discovered, it is rather rigid -- it is not easy to add and remove event handlers to an element that already has an event handler attached. As well, it is not considered proper practice anymore to mix javascript in with HTML, but rather it should be contained within or loaded via a <script> tag.
W3C / Microsoft methods
The W3C (World Wide Web Consortium) and Microsoft both define their own event models. The two models works essentially the same way, but use different syntaxes. The Microsoft model is used in Internet Explorer, and the W3C model is used in other browsers (Firefox, Opera, Safari, Chrome, etc.). In both of these models, there are functions provided to add event handlers (addEventListener for W3C, attachEvent for Microsoft) and remove event handlers (removeEventListener / detachEvent). This allows you to dynamically add and remove specific handlers to an element; in your case, you could add the first handler based on the first condition and the second based on the second condition. The "problem" with these methods is that there are two of them, and thus both methods need to be used in order to ensure that your event handler will be registered in all browsers (there are also a few subtle differences between the two models, but those differences are not important to the scope of this question). In fact, if you look, you will find a large number of "addEvent" functions which use both methods as necessary (and generally fall back to the traditional method for older browsers). For example, a contest was run on the QuirksMode blog back in 2005 to build the best "addEvent" function, the result of which (along with the winning function) you can see here.
As well, if you use a javascript library such as Prototype or jQuery, they come with built in event handling functions that will take care of the above for you.

Have a look at addEventListener - https://developer.mozilla.org/en/DOM/element.addEventListener

I feel as though I may be missing something important about your question, but would this more simple solution not work for you?
Simply check for the conditions inside of the onChange event and perform the actions as desired. It would be much easier than having to dynamically re-add/remove eventListeners
document.getElementById("selectbox1").onchange = function () {
if (<certain conditions>) {
//hides elements to reduce clutter on a web form ...
}
if (<other conditions>) { ... }
}

Related

Why do many pass an event in the callback

I recently learned that you don't have to pass the event as a parameter for an event. But I wonder why many still pass the event.
Example click event
btn.addEventListener("click", myfn.bind());
function myfn(event) {
console.log(event.target);
console.log(this);
}
Is there a reason for this? Because that works to:
btn.addEventListener("click", myfn.bind());
// without passing event
function myfn() {
console.log(event.target);
console.log(this);
}
btn.addEventListener("click", myfn.bind());
// without passing event
function myfn() {
console.log(event.target);
console.log(this);
}
Above works because event can access through a global variable, window.event
The read-only Window property event returns the Event which is
currently being handled by the site's code. Outside the context of an
event handler, the value is always undefined.
function myfn(anotherArgName) {
console.log(anotherArgName === window.event); // true
}
Not recommend to use the 2nd one as MDN docs says,
Deprecated: This feature is no longer recommended. Though some
browsers might still support it, it may have already been removed from
the relevant web standards, may be in the process of being dropped, or
may only be kept for compatibility purposes. Avoid using it, and
update existing code if possible; see the compatibility table at the
bottom of this page to guide your decision. Be aware that this feature
may cease to work at any time.
Plus depending on external dependencies makes your function hard to read, test & maintain.

Prototype fire on change event issue

This is my js file content:
window.onload = function() {
obj = document.getElementById("_accountwebsite_id");
Event.observe(obj, 'change', function () {
alert('hi');
});
}
I want to fire the on change event for my dropdown: _accountwebsite_id . The prototype library it is loaded before this file. I got no errors in the console. Where am i wrong ? thx
You're doing a lot of extra work here that Prototype does for you. First off, setting the document's onload method not only is really old-school, it also will clobber any previously set observer on that event.
$(document).observe('dom:loaded', function( ... ){...});
...is the modern way to register one (or more) event listeners to the document load event.
Next, you're using getElementById here, which will work, but does not return a Prototype-extended object in some browsers.
$('element-id');
...will both get the element reference and extend it if your browser failed to respect every aspect of prototypal inheritance.
Finally, this whole thing can be made both simpler and more bulletproof by using a deferred observer. Imagine if your interface DOM was updated by Ajax -- that would make your observer miss the events fired by this select element, because it was not referring to the same (===) element, even if the ID matched.
$(document).on('change', '#_accountwebsite_id', function(evt, elm){
alert(elm.inspect());
});
This observer will respond to any change event on an element with the correct ID, even if it was added after the observer was registered with the document.

Adding a javascript function call to an event (such as 'window.resize') instead of overwriting what is already there

Is there a way to tell the browser to run an addtional java script function on an event such as 'window.resize' instead of overwriting what is already there?
Using jquery's
$(window).resize(<something>);
Seems to replace what is already there. Is there a way to tell it to do something in addition?
Is this a poor design / wrong way to do it?
I wouldn't think that jQuery would break what's there, but you could wrap the functions in a single function:
// if a function already exists...
if( window.onresize ) {
var prev_func = window.onresize; // cache the old function
window.onresize = function( event ) { // new function for resize
prev_func.call( window, event ); // call the old one, setting the
// context (for "strict mode") and
// passing on the event object
// call your code or function
};
}
EDIT: Fixed it to use onresize instead of resize.
EDIT2: Missed one! Fixed.
If you're using jQuery to bind all event handlers, then you're not breaking anything. jQuery supports multiple handlers for same event.
But if other code (not using jQuery) binds to the event, then you'll overwrite handler with your statement. The solution will be: always use jQuery for event binding or try to save old handler (see patrick dw's answer).
See element.addEventListener (element.attachEvent in IE 8 and under):
// Standards
if (window.addEventListener){
window.addEventListener("resize", callOnResize, false);
// IE 8 and under
} else if (window.attachEvent){
window.attachEvent('resize', callOnResize);
}
function callOnResize() {
console.log("resized");
}
Keep in mind this is pure JavaScript—jQuery (and pretty much any big JS library) has a method to handle creating standards and IE handlers without you needing to write each. Still, it's good to know what's happening behind the scenes.
jQuery and all other frameworks supporting custom events attach a function to the event of the elem (or observe it). That function then triggers all functions that have been bound (using bind) for a specific event type.
domelement.addEventListener does not override an other function and your function added can't be removed by other (bad) javascript, except when it would know the exact footprint of your function.

Listening and firing events with Javascript and maybe jQuery

In my JavaScript and Flex applications, users often perform actions that I want other JavaScript code on the page to listen for. For example, if someone adds a friend. I want my JavaScript app to then call something like triggerEvent("addedFriend", name);. Then any other code that was listening for the "addedFriend" event will get called along with the name.
Is there a built-in JavaScript mechanism for handling events? I'm ok with using jQuery for this too and I know jQuery makes extensive use of events. But with jQuery, it seems that its event mechanism is all based around elements. As I understand, you have to tie a custom event to an element. I guess I can do that to a dummy element, but my need has nothing to do with DOM elements on a webpage.
Should I just implement this event mechanism myself?
You have a few options:
jQuery does allow you to do this with objects not associated with the document. An example is provided below.
If you're not already using jQuery on your page, then adding it is probably overkill. There are other libraries designed for this. The pattern you are referring to is called PubSub or Publish/Subscribe.
Implement it yourself, as you've suggested, since this is not difficult if you're looking only for basic functionality.
jQuery example:
var a = {};
jQuery(a).bind("change", function () {
alert("I changed!");
});
jQuery(a).trigger("change");
I would implement such using MVVM pattern with knockjs library.
Just create an element, and use jquery events on it.
It can be just a global variable, doesn't even have to be connected to the DOM.
That way you accomplish your task easily and without any extra libs.
Isn't it possible to bind onchange events in addition to click events? For instance, if addFriend is called and modifies a list on the page, you could bind the change event to then invoke additional functionality.
$('#addFriendButton').click( function() {
// modify the #friendList list
});
$('#friendList').change( function() {
myOtherAction();
});
This is total Host independent, no need for jQuery or dom in this case!
function CustomEvents(){
//object holding eventhandlers
this.handlers_ = {};
}
//check if the event type does not exist, create it.
//then push new callback in array.
CustomEvents.prototype.addEventListner = function (type, callBack){
if (!this.handlers_[type]) this.handlers_[type] = [];
this.handlers_[type].push(callBack);
}
CustomEvents.prototype.triggerEvent = function (type){
//trigger all handlers attached to events
if (!this.handlers_[type]) return;
for (var i=0, handler; handler = this.handlers_[type][i]; i++)
{
//call handler function and supply all the original arguments of this function
//minus the first argument which is the type of the event itself
if (typeof handler === "function") handler.apply(this,arguments.slice(1));
}
}
//delete all handlers to an event
CustomEvents.prototype.purgeEventType = function(type){
return delete this.handlers_[type];
}
test:
var customEvents = new CustomEvents();
customEvents.addEventListner("event A", function(arg){alert('Event A with arguments' + arg);));
customEvents.triggerEvent("event A", "the args");
EDIT added arguments passing

javascript property change event

I need to fire an event every time a property is updated/changed in order to keep dom elements in sync with the property values on the model (Im using john resig's simple inheritance http://ejohn.org/blog/simple-javascript-inheritance/). Is this possible to do in a cross-browser way? It seems to me that if I could wrap whatever function js uses to set properties and make it fire an event, that it could work, Im just not sure how to do that.
JavaScript doesn't use a function to set properties. They're just variables, and setting them doesn't require any elaborate wrappers.
You could use a function to set the property, though — the same sort of a getter/setter arrangement you might use in a language that supported private data in classes. In that way your function could easily run other functions that have been registered as callbacks. Using jQuery you can even handle those as events.
$(yourObject).bind('some-event-you-made-up', function() {
// This code will run whenever some-event-you-made-up is triggered on yourObject
});
// ...
$(yourObject).trigger('some-event-you-made-up');
Maybe you already solved your problem with jQuery bind/trigger, but I wanted to tell that I'm building a Change Tracking and (in top of that) Entity Modeling Javascript Framework, named "tent" that solves the problem you exposed, without requiring any special syntax on object manipulation, its open source and hosted at:
https://github.com/benjamine/tent
It's documented with JSDoc and unit tested with js-test-driver.
you can use the change tracking module this way:
var myobject = { name: 'john', age: 34 };
// add a change handler that shows changes on alert dialogs
tent.changes.bind(myobject, function(change) {
alert('myobject property '+change.data.propertyName+' changed!');
});
myobject.name = 'charly'; // gets notified on an alert dialog
it works with Array changes too (adds, deletes).
Further you can use "Entity" Contexts to keep a changesets of all detected changes (ADDED, DELETED, MODIFIED items) grouped on collections, cascade adds and deletes, keep reverse properties synced, track 1-to-1, 1-to-N and N-to-M relationships, etc.
Object defineProperty/defineProperties does the trick.
Here goes a simple code. I have built some data binding frameworks based on that, and it can get really complex, but for exercising its like this:
var oScope = {
$privateScope:{},
notify:function(sPropertyPath){
console.log(sPropertyPath,"changed");
}
};
Object.defineProperties(oScope,{
myPropertyA:{
get:function(){
return oScope.$privateScope.myPropertyA
},
set:function(oValue){
oScope.$privateScope.myPropertyA = oValue;
oScope.notify("myPropertyA");
}
}
});
oScope.myPropertyA = "Some Value";
//console will log: myPropertyA changed
You could try Javascript Property Events (jpe.js)
I encountered a similar issue, and ended up writing an overload function for Object.defineProperty that adds event handlers to the properties. It also provides type checking (js-base-types) and stores its value internally, preventing unwanted changes.
Sample of normal defineProperty:
Object.defineProperty(document, "property", {
get:function(){return myProperty},
set:function(value){myProperty = value},
})
var myProperty = false;
Sample of property with onchange event:
Object.defineProperty(document, "property", {
default:false,
get:function(){},
set:function(value){},
onchange:function(event){console.info(event)}
})

Categories

Resources