javascript property change event - javascript

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)}
})

Related

Do I need to unsubscribe of jQuery events? [duplicate]

jQuery holds references to DOM nodes in its internal cache until I explicitly call $.remove(). If I use a framework such as React which removes DOM nodes on its own (using native DOM element APIs), how do I clean up jQuery's mem cache?
I'm designing a fairly large app using React. For those unfamiliar, React will tear down the DOM and rebuild as needed based on its own "shadow" DOM representation. The part works great with no memory leaks.
Flash forward, we decided to use a jQuery plugin. After React runs through its render loop and builds the DOM, we initialize the plugin which causes jQuery to hold a reference to the corresponding DOM nodes. Later, the user changes tabs on the page and React removes those DOM elements. Unfortunately, because React doesn't use jQuery's $.remove() method, jQuery maintains the reference to those DOM elements and the garbage collector never clears them.
Is there a way I can tell jQuery to flush its cache, or better yet, to not cache at all? I would love to still be able to leverage jQuery for its plugins and cross-browser goodness.
jQuery keeps track of the events and other kind of data via the internal API jQuery._data() however due to this method is internal, it has no official support.
The internal method have the following signature:
jQuery._data( DOMElement, data)
Thus, for example we are going to retrieve all event handlers attached to an Element (via jQuery):
var allEvents = jQuery._data( document, 'events');
This returns and Object containing the event type as key, and an array of event handlers as the value.
Now if you want to get all event handlers of a specific type, we can write as follow:
var clickHandlers = (jQuery._data(document, 'events') || {}).click;
This returns an Array of the "click" event handlers or undefined if the specified event is not bound to the Element.
And why I speak about this method? Because it allow us tracking down the event delegation and the event listeners attached directly, so that we can find out if an event handler is bound several times to the same Element, resulting in memory leaks.
But if you also want a similar functionality without jQuery, you can achieve it with the method getEventHandlers
Take a look at this useful articles:
getEventHandlers
getEventListeners - chrome
getEventListeners - firebug
Debugging
We are going to write a simple function that prints the event handlers and its namespace (if it was specified)
function writeEventHandlers (dom, event) {
jQuery._data(dom, 'events')[event].forEach(function (item) {
console.info(new Array(40).join("-"));
console.log("%cnamespace: " + item.namespace, "color:orangered");
console.log(item.handler.toString());
});
}
Using this function is quite easy:
writeEventHandlers(window, "resize");
I wrote some utilities that allow us keep tracking of the events bound to DOM Elements
Gist: Get all event handlers of an Element
And if you care about performance, you will find useful the following links:
Leaking Memory in Single Page Apps
Writing Fast, Memory-Efficient JavaScript
JavaScript Memory Profiling
I encourage anybody who reads this post, to pay attention to memory allocation in our code, I learn the performance problems ocurrs because of three important things:
Memory
Memory
And yes, Memory.
Events: good practices
It is a good idea create named functions in order to bind and unbind event handlers from DOM elements.
If you are creating DOM elements dynamically, and for example, adding handlers to some events, you could consider using event delegation instead of keep bounding event listeners directly to each element, that way, a parent of dynamically added elements will handle the event. Also if you are using jQuery, you can namespace the events ;)
//the worse!
$(".my-elements").click(function(){});
//not good, anonymous function can not be unbinded
$(".my-element").on("click", function(){});
//better, named function can be unbinded
$(".my-element").on("click", onClickHandler);
$(".my-element").off("click", onClickHandler);
//delegate! it is bound just one time to a parent element
$("#wrapper").on("click.nsFeature", ".my-elements", onClickMyElement);
//ensure the event handler is not bound several times
$("#wrapper")
.off(".nsFeature1 .nsFeature2") //unbind event handlers by namespace
.on("click.nsFeature1", ".show-popup", onShowPopup)
.on("click.nsFeature2", ".show-tooltip", onShowTooltip);
Circular references
Although circular references are not a problem anymore for those browsers that implement the Mark-and-sweep algorithm in their Garbage Collector, it is not a wise practice using that kind of objects if we are interchanging data, because is not possible (for now) serialize to JSON, but in future releases, it will be possible due to a new algorithm that handles that kind of objects. Let's see an example:
var o1 = {};
o2 = {};
o1.a = o2; // o1 references o2
o2.a = o1; // o2 references o1
//now we try to serialize to JSON
var json = JSON.stringify(o1);
//we get:"Uncaught TypeError: Converting circular structure to JSON"
Now let's try with this other example
var freeman = {
name: "Gordon Freeman",
friends: ["Barney Calhoun"]
};
var david = {
name: "David Rivera",
friends: ["John Carmack"]
};
//we create a circular reference
freeman.friends.push(david); //freeman references david
david.friends.push(freeman); //david references freeman
//now we try to serialize to JSON
var json = JSON.stringify(freeman);
//we get:"Uncaught TypeError: Converting circular structure to JSON"
PD: This article is about Cloning Objects in JavaScript. Also this gist contain demos about cloning objects with circular references: clone.js
Reusing objects
Let's follow some of the programming principles, DRY (Don't Repeat Yourself) and instead of creating new objects with similar functionality, we can abstract them in a fancy way. In this example I will going to reuse an event handler (again with events)
//the usual way
function onShowContainer(e) {
$("#container").show();
}
function onHideContainer(e) {
$("#container").hide();
}
$("#btn1").on("click.btn1", onShowContainer);
$("#btn2").on("click.btn2", onHideContainer);
//the good way, passing data to events
function onToggleContainer(e) {
$("#container").toggle(e.data.show);
}
$("#btn1").on("click.btn1", { show: true }, onToggleContainer);
$("#btn2").on("click.btn2", { show: false }, onToggleContainer);
And there are a lot of ways to improve our code, having an impact on performance, and preventing memory leaks. In this post I spoke mainly about events, but there are other ways that can produce memory leaks. I suggest read the articles posted before.
Happy reading and happy coding!
If your plugin exposes a method to programatically destroy one of its instances (i.e. $(element).plugin('destroy')), you should be calling that in the componentWillUnmount lifecycle of your component.
componentWillUnmount is called right before your component is unmounted from the DOM, it's the right place to clean up all external references / event listeners / dom elements your component might have created during its lifetime.
var MyComponent = React.createClass({
componentDidMount() {
$(React.findDOMNode(this.refs.jqueryPluginContainer)).plugin();
},
componentWillUnmount() {
$(React.findDOMNode(this.refs.jqueryPluginContainer)).plugin('destroy');
},
render() {
return <div ref="jqueryPluginContainer" />;
},
});
If your plugin doesn't expose a way to clean up after itself, this article lists a few ways in which you can try to dereference a poorly thought out plugin.
However, if you are creating DOM elements with jQuery from within your React component, then you are doing something seriously wrong: you should almost never need jQuery when working with React, since it already abstracts away all the pain points of working with the DOM.
I'd also be wary of using refs. There are only few use cases where refs are really needed, and those usually involve integration with third-party libraries that manipulate/read from the DOM.
If your component conditionally renders the element affected by your jQuery plugin, you can use callback refs to listen to its mount/unmount events.
The previous code would become:
var MyComponent = React.createClass({
handlePluginContainerLifecycle(component) {
if (component) {
// plugin container mounted
this.pluginContainerNode = React.findDOMNode(component);
$(this.pluginContainerNode).plugin();
} else {
// plugin container unmounted
$(this.pluginContainerNode).plugin('destroy');
}
},
render() {
return (
<div>
{Math.random() > 0.5 &&
// conditionally render the element
<div ref={this.handlePluginContainerLifecycle} />
}
</div>
);
},
});
How about do this when the user exits the tab:
for (x in window) {
delete x;
}
This is much better to do, though:
for (i in $) {
delete i;
}

Adding an event handler inside a knockoutjs custom binding

I'm a fairly experienced knockout user, so I understand quite a bit of the under the hood stuff, I have however been battling now for a few days trying to figure out how to achieve a given scenario.
I have to create a system that allows observable's within a given knockout component to be able to translate themselves to different languages.
to facilitate this, I've created a custom binding, which is applied to a given element in the following way.
<p data-bind="translatedText: {observable: translatedStringFour, translationToken: 'testUiTransFour'}"></p>
This is in turn attached to a property in my knockout component with a simple standard observable
private translatedStringFour: KnockoutObservable<string> = ko.observable<string>("I'm an untranslated string four....");
(YES, I am using typescript for the project, but TS/JS either I can work with.....)
With my custom binding I can still do 'translatedStringFour("foo")' and it will still update in exactly the same way as the normal text binding.
Where storing the translations in the HTML5 localStorage key/value store, and right at the beginning when our app is launched, there is another component that's responsible, for taking a list of translation ID's and requesting the translated strings from our app, based on the users chosen language.
These strings are then stored in localStorage using the translationToken (seen in the binding) as the key.
This means that when the page loads, and our custom bind fires, we can grab the translationToken off the binding, and interrogate localStorage to ask for the value to replace the untranslated string with, the code for our custom binding follows:
ko.bindingHandlers.translatedText = {
init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
// Get our custom binding values
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
},
update: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
// Get our custom binding values
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
// Ask local storage if we have a token by that name
var translatedText = sessionStorage[translationToken];
// Check if our translated text is defined, if it's not then substitute it for a fixed string that will
// be seen in the UI (We should really not change this but this is for dev purposes so we can see whats missing)
if (undefined === translatedText) {
translatedText = "No Translation ID";
}
associatedObservable(translatedText);
ko.utils.setTextContent(element, associatedObservable());
}
}
Now, thus far this works brilliantly, as long as the full cache of translations has been loaded into localStorage, the observables will self translate with the correct strings as needed.
HOWEVER......
Because this translation loader may take more than a few seconds, and the initial page that it's loading on also needs to have some elements translated, the first time the page is loaded it is very possible that the translations the UI is asking for have not yet been loaded into into localStorage, or may be in the process of still loading.
Handling this is not a big deal, I'm performing the load using a promise, so the load takes place, my then clause fires, and I do something like
window.postMessage(...);
or
someElement.dispatchEvent(...);
or even (my favorite)
ko.postbox.publish(...)
The point here is I have no shortage of ways to raise an event/message of some description to notify the page and/or it's components that the translations have finished loading, and you are free to retry requesting them if you so wish.
HERE IN.... Lies my problem.
I need the event/message handler that receives this message to live inside the binding handler, so that the very act of me "binding" using our custom binding, will add the ability for this element to receive this event/message, and be able to retry.
This is not a problem for other pages in the application, because by the time the user has logged in, and all that jazz the translations will have loaded and be safely stored in local storage.
I'm more than happy to use post box (Absolutely awesome job by the way Ryan -- if your reading this.... it's an amazingly useful plugin, and should be built into the core IMHO) but, I intend to wrap this binding in a stand alone class which I'll then just load with requireJs as needed, by those components that need it. I cannot however guarantee that postbox will be loaded before or even at the same instant the binding is loaded.
Every other approach i've tried to get an event listener working in the binding have just gotten ignored, no errors or anything, they just don't fire.
I've tried using the postmessage api, I've tried using a custom event, I've even tried abusing JQuery, and all to no avail.
I've scoured the KO source code, specifically the event binding, and the closest I've come to attaching an event in the init handler is as follows:
init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
// Get our custom binding values
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
// Set up an event handler that will respond to events on session storage, by doing this
// the custom binding will instantly update when a key matching it's translation ID is loaded into the
// local session store
//ko.utils.registerEventHandler(element, 'storage', (event) => {
// console.log("Storage event");
// console.log(event);
//});
ko.utils.registerEventHandler(element, 'customEvent', (event) => {
console.log("HTML5 custom event recieved in the binding handler.");
console.log(event);
});
},
None of this has worked, so folks of the Knockout community.....
How do I add an event handler inside of a custom binding, that I can then trigger from outside that binding, but without depending on anything other than Knockout core and my binding being loaded.
Shawty
Update (About an hour later)
I wanted to add this part, beacuse it's not 100% clear why Regis's answer solves my problem.
Effectively, I was using exactly the same method, BUT (and this is the crucial part) I was targeting the "element" that came in as part of the binding.
This is my mind was the correct approach, as I wanted the event to stick specifically with the element the binding was applied too, as it was said element that I wanted to re-try it's translation once it knew it had the go-ahead.
However, after looking at Regis's code, and comparing it to mine, I noticed he was attaching his event handlers to the "Window" object, and not the "Element".
Following up on this, I too changed my code to use the window object, and everything I'd been attempting started to work.
More's the point, the element specific targeting works too, so I get the actual event, on the actual element, in the actual binding that needs to re-try it's translation.
[EDIT: trying to better answer the question]
I don't really get the whole point of the question, since I don't see how sessionStorage load can be asynchronous.
I supposed therefore sessionStorage is populated from som asynchronous functions like an ajax call to a translation API.
But I don't see what blocks you here, since you already have all the code in your question:
var sessionStorageMock = { // mandatory to mock in code snippets: initially empty
};
var counter = 0;
var attemptTranslation = function() {
setInterval(function() { // let's say it performs some AJAX calls which result is cached in the sessionStorage
var token = "token"; // that should be a collection
sessionStorageMock[token] = "after translation " + (counter++); // we're done, notifying event handlers
window.dispatchEvent(new Event("translation-" + token));
}, 500);
};
ko.bindingHandlers.translated = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var val = valueAccessor();
var token = val.token;
console.log("init");
window.addEventListener("translation-" + token, function() {
if (token && sessionStorageMock[token]) {
val.observable(sessionStorageMock[token]);
}
});
}
};
var vm = function() {
this.aftertranslation = ko.observable("before translation");
};
ko.applyBindings(new vm());
attemptTranslation();
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="translated: { observable: aftertranslation, token: 'token' }, text: aftertranslation" />

How do I bind a function to a change in an object's data member in Javascript?

I'm working on a project in JavaScript where we're building a Greasemonkey plugin to an organizational site we're using in our office. We're having trouble getting our changes to stay rendered, since we can't simply inject our changes into the existing render function.
As a result, we need to find every event where rendering happens and inject our own render function there. However, there are some events that we can see happening, but we can't hook into them. What I'd like to know is how to bind a function to an object's data member, so that the function is called whenever that member changes. One of our team members seemed to think it was possible, but the method he told us to use didn't seem to work.
What we tried was something along the lines of
window.Controller.bind("change:idBoardCurrent", OMGITWORKED);
where idBoardCurrent is a member of window.Controller and OMGITWORKED is the function we'd like to be called when window.Controller.idBoardCurrent is changed.
I'm not very familiar with JavaScript or data binding, so I have no idea if this is right or wrong, or what is correct or incorrect about it. If someone could point out what to change in this snippet, or if they could suggest another way to go about this, I would be very appreciative.
You can use Object.defineProperty to define a setter and getter for the Objects property
Object.defineProperty(window.Controller,"idBoardCurrent",{
get : function() { return this.val; },
set : function(value) {this.val = value;OMGITWORKED(value); }
});
function OMGITWORKED(param) {
console.log("idBoardCurrent has been Changed to " + param);
}
window.Controller.idBoardCurrent = "Test";
window.Controller.idBoardCurrent = "Test2";
console.log(window.Controller.idBoardCurrent)
Edit: changed the code according to the contexts object
JSBin
As this is specifically Firefox, you can use the mutation events it provides. But note the caveats on them from that page:
The W3C specification for them was never widely implemented and is now deprecated
Using DOM mutation events "significantly degrades" the performance of DOM modifications
If you're able to restrict yourselves to Firefox 14 and higher, you can use the new mutation observers stuff instead.
This is, when I am not totally wrong, more a question of javascript.
I found some information about that topic
Listening for variable changes in JavaScript or jQuery
jQuery trigger on variable change
Javascript Track Variable Change
Sorry when I didn't understand the topic.
All the best

Make Backbone.js Model change "partially" silent?

When my "chartModel" changes I want to update the "globalModel".
chartModel.bind("change", updateGlobalModel);
updateGlobalModel(){
globalModel.set(obj)
}
And vice versa, I want my chartModel to update when the globalModel changes.
globalModel.bind("change", updateChartModel);
updateChartModel(){
chartModel.set(obj)
}
This results in a feedback loop when setting the globalModel. I could prevent this by setting {silent:true}.
But here comes the problem. I have another Model that is dependent on the change event:
globalModel.bind("change", updateOtherModel);
How can I alert this model of the change but not the former one (to avoid the feedback loop)?
UPDATE:
For now, I decided to generate a specific ID for each set call:
set : function(attrs, options) {
if(!("setID" in attrs)){
attrs.setID = myApp.utils.uniqueID(); //newDate.getTime();
}
Backbone.Model.prototype.set.call(this, attrs, options);
},
This way, I can always generate a "setID" attribute from anywhere in my application. If the setID is still the same when fetching something from the model, I know there could be risk for a feedback loop.
Better late than never..
The easiest way to do this is by using a flag. For example, when setting something in globalModel, you could also change a property on the model to indicate that you've changed something. You can then verify the value of this flag in updateChartModel. For example:
chartModel.bind("change", updateGlobalModel);
function updateGlobalModel() {
if (!flag) {
globalModel.set(obj);
flag = true;
}
}
Probably very similar to what you've ended up doing with your setID. As an aside, underscore has a uniqueId function built in.
Another thing that you can do, which is much cleaner, is to pass an option with your sets calls.
chartModel.set(obj, { notify : false });
Yes, you can pass any options you want, you're not just limited to { silent : true }. See this discussion on github for more. Then, you check for the existence of this property where you handle change events like so:
function updateGlobalModel(model, options){
// explicitly check for false since it will otherwise be undefined and falsy
// you could reverse it.. but I find this simpler
if (options.notify !== false) {
globalModel.set(obj)
}
}
and in your third (and other models), you can just forego this check.
The final option is of course to look at your design. If these two models are so closely related that they must be kept in sync with each other, maybe it makes sense to merge their functionality. Alternatively, you could split the common functionality out. This all depends heavily on your particular situation.
My knowledge is limited, so maybe I shouldn't be answering, but I would try to pass a reference to chartModel when it's created that refers to the "other" model that you want updated. Then trigger an event on updateChartModel() and make sure your "other" model is bound on that event.
The question I have is: does the silent object mute all events? Or just model related ones? This obviously wouldn't work if all events are muted.

How to expand an onchange event with 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>) { ... }
}

Categories

Resources