Jump to content

Unobtrusive Events Misbehaving


geomagnet
 Share

Recommended Posts

Hello,I'm not sure how to ask this so I hope it makes sense.I've written a script to handle onchange events unobtrusively, and it works fine until I reload the select box values with AJAX.I'm totally stumped. However, if I add inline with with select tag - onchange="func();" it works.Here's a simplified version of the unobtrusive script...problem remains.////////////////////////////////////////////////window.onload = init;function init(){ objID("hide_project_name").onchange = function (){ var ele = window.event.srcElement.id.split("_"); hide(ele); }}function hide(ele){ // to handle dropdowns with normal event onchange handler - hides the menu once selected.alert(ele[0]); // this is to test is the function is being called - the actual function is meaningless at this point as the alert doesn't fire. }///////// HTML snippet ////////<select class="textFields" id="hide_project_name" style="width:145px" > <option> project 1 </option> <option> project 2</option> <option> project 3</option> </select>////////////////////////////////////As mentioned, this will run fine if called directly by the browser. Butif the Select menu is repopulated via ajax call (via php) - like this:///////////////////////////////////// PHP AJAX response //////////////function getJobList($parms){$job = explode("^", $parms); $query="SELECT job_id,job_name FROM authority WHERE user_id={$job[0]}"; $result=mysql_query($query); while($row=mysql_fetch_row($result)){ static $i=0; // for setting the selected index $options.="var opt = document.createElement('option'); "; $options.="opt.text = '".$row[1]."'; "; $options.="opt.value = '".$row[0]."'; "; $options.="objID('hide_project_name').add(opt); "; print $options; if($row[0]==$job[1]) { // sets the selected project in title and on dropdown print "objID('hide_project_name').options[{$i}].selected = true; "; print "objID('show_project_name_text').innerHTML='{$row[1]}'; "; } $options = ''; $i++; }}////////////////////////////////////The select box is repopulated as expected, but the unobtrusive onchange function fails and produces no error...it simply doesn't work.Unless I add to the select menu the onchange event - like this:///////// HTML snippet ////////<select class="textFields" id="hide_project_name" style="width:145px" onchange = "splitter();"> <option> project 1 </option> <option> project 2</option> <option> project 3</option> </select>////////////////////////////////////splitter function is simply to mimic the original nested init function(); Just for clearity, here is the splitter function://////// Splitter function /////////function splitter(){var ele = window.event.srcElement.id.split("_");if(ele[0] == "hide") hide(ele);}///////////////////////////////////So my question is...what causes the failure of the event handler of the initial window.onload vs. ajax repopulate? Or put another way, why does the event handler work statically, but not once ajax rebuilds the element. And whatdo I need to do to get this to work unobtrusively.

Link to comment
Share on other sites

I think the biggest issue here isn't that it doesn't work, but that you shouldn't expect it to work. Changing content by javascript really shouldn't fire an event, and in Firefox, it doesn't, not by any of the methods you mention.You're also locking yourself into some IE-only syntax. E.g., window.event.srcElement. Standards-compliant browsers just refer to event, not window event, so you need to do a little sniffing there. Also, standards-compliant browsers don't have a srcElement; they have a target. Again, you could do some sniffing. Or just use 'this' syntax, which should work anywhere:

objID("hide_project_name").onchange = function (){   var ele = this.id.split("_");   hide(ele);}

Link to comment
Share on other sites

I think the biggest issue here isn't that it doesn't work, but that you shouldn't expect it to work. Changing content by javascript really shouldn't fire an event, and in Firefox, it doesn't, not by any of the methods you mention.You're also locking yourself into some IE-only syntax. E.g., window.event.srcElement. Standards-compliant browsers just refer to event, not window event, so you need to do a little sniffing there. Also, standards-compliant browsers don't have a srcElement; they have a target. Again, you could do some sniffing. Or just use 'this' syntax, which should work anywhere:
objID("hide_project_name").onchange = function (){   var ele = this.id.split("_");   hide(ele);}

Thanks for the feedback. I've been struggling for months trying to catch up to Web2.0 standards (not that I mastered Web1.0). I find myself stabbing at the problem with every possible combination of syntax only to find a handful of statements that work. Originally I was using "target"but it yeilded no results so I resorted back to "srcElement" which works on modern browsers. I'm not expecting the content change to fire anything. It was more the problem with once changed, the behaviors no longer were recognized. For instance, showing a select menu which is populated by ajax and then never closes as the hide function fails...but if I don't make the call to ajax the select box shows and hides as anticipated.Anyhow, I'll take your advice and investigate these nuances. Hopefully, I can get a grip on this.
Link to comment
Share on other sites

Okay, I misunderstood.Maybe you could show the parts of your javascript where you unpopulate and repopulate the select element. I assume you're removing all the original option elements and replacing them by running eval() with the responseText. Maybe there's something in the way they get removed.

Link to comment
Share on other sites

Okay, I misunderstood.Maybe you could show the parts of your javascript where you unpopulate and repopulate the select element. I assume you're removing all the original option elements and replacing them by running eval() with the responseText. Maybe there's something in the way they get removed.
This is the closest I can provide without loading up a half dozen files. It more or less is the problem (forced). The idea was to use unobtrusive code to capture the events and fire a function. I had to revert back to inline events for the onchange as they simply don't work globally, but the onclick event works perfect. This is a lack of experience and knowledge on my part. Run this from a browser to see what I mean.<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>Untitled Document</title><script language="javascript" type="text/javascript">// JavaScript Document UnObtrusive handler// Create Object identifier based on various browsersfunction objID(obj){ return (document.getElementById) ? document.getElementById(obj) : document.all[obj];}// initialize an event handler function init(){ //objID("hide_select_2").onchange = function () //uncomment this line, comment the next line for it to work. // and uncomment the block under for loop. document.onchange = function () { var ele = window.event.srcElement.id.split("_"); hide(ele); } document.onclick = function () { var ele = window.event.srcElement.id.split("_"); if(ele[0] == "show") show(ele); } // More or less is simulates an ajax population function...just for testing objID("select_2").innerHTML = '<select id="hide_select_2"></select>';for(i=1; i<4; i++){ var opt = document.createElement('option'); opt.text = 'test_'+i; opt.value = i; objID('hide_select_2').add(opt); } /* objID("hide_select_2").onchange = function () { var ele = window.event.srcElement.id.split("_"); hide(ele); } */ }// window loading the event handler //window.onload = init;/////////////////////// Functions for operating the control panel ///////////////////function show(ele){ // changes css display mode of element objID(ele[1] + "_" + ele[2]).style.display=""; }function hide(ele){ // to handle dropdowns with normal event onchange handler//alert(ele[0]); objID(ele[1]+"_"+ele[2]).style.display="none"; }// This is for reuse with other functions not shown.function splitter(){var ele = window.event.srcElement.id.split("_");if(ele[0] == "hide") hide(ele);}</script></head><body><div id="select_1" style="display:none"><select id="hide_select_1" onchange="splitter();"><option>1</option><option>2</option><option>3</option></select></div><input id ="show_select_1" type="button" value="test_1"/><br /><div id="select_2" style="display:none"></div><input id ="show_select_2" type="button" value="test_2"/></body></html>
Link to comment
Share on other sites

As I suspected, you're creating the <select> element during the AJAX response, but attaching the onchange handler in your init() function. Once that select object gets overwritten, the handler needs to be attached again. So I moved that part into the AJAX response.I'm posting some simplified and commented code below. Some of the ways you were doing things were old or limited to IE. And unless there's something I'm missing, you don't need to mess with that srcElement stuff at all. As long as your elements contain some way of indexing things, you're good. I took the liberty of suggesting a new id schema that does that.Since I wasn't sure if you needed this hide mechanism to run on one element or many, I created two different init() functions. init_a() is probably the one you want.Anyway, play around with this and see if it gives you ideas. :)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml">	<head>		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />		<title>Untitled Document</title>		<script language="javascript" type="text/javascript">			function objID(id){				return document.getElementById(id);				// document.all() IS TOO OUTDATED TO BOTHER WITH			}			function populate_div (index){				var div_id = "container_" + index;				var sel_id = "sel_" + index;				objID(div_id).innerHTML = '<select id="' + sel_id + '"></select>';				for(i = 1; i < 4; i++) {					var opt = document.createElement('option');					opt.text = 'test_' + i;					opt.value = i;					insertOption (objID(sel_id), opt);				}				// BECAUSE populate_div() CREATES THE SELECT OBJECT, IT MUST ALSO ASSIGN THE EVENT HANDLER				objID(sel_id).onchange = function () {					alert(this.options[this.selectedIndex].value);				}			}			// WE NEED THIS FUNCTION BECAUSE IE ADDS OPTIONS INCORRECTLY			function insertOption(sel, opt) {				try {					sel.add (opt, null); // W3 STANDARD				} catch (ex) {					sel.add (opt); // IE				}			}			// GIVES YOU THE OPTION OF SETTING THIS UP FOR UNLIMITED ITEMS			function init_a () {				var buttons = document.getElementsByTagName("button");				for (var i = 0; buttons[i]; i++) {					buttons[i].onclick = function () {						var index = this.id.match(/\d+/); // EXTRACTS THE NUMBER PART OF THE ID						var div_id = "container_" + index;						populate_div (index)						objID(div_id).style.display = "block";					}				}			}			// IF YOU JUST NEED ONE			function init_b () {				objID("show_1").onclick = function () {					populate_div ("1")					objID("container_1").style.display = "block";				}			}			window.onload = init_a;		</script>	</head>	<body>		<button id="show_1" type="button">Show 1</button>		<div id="container_1" style="display:none"></div>	</body></html>

Link to comment
Share on other sites

As I suspected, you're creating the <select> element during the AJAX response, but attaching the onchange handler in your init() function. Once that select object gets overwritten, the handler needs to be attached again. So I moved that part into the AJAX response.I'm posting some simplified and commented code below. Some of the ways you were doing things were old or limited to IE. And unless there's something I'm missing, you don't need to mess with that srcElement stuff at all. As long as your elements contain some way of indexing things, you're good. I took the liberty of suggesting a new id schema that does that.Since I wasn't sure if you needed this hide mechanism to run on one element or many, I created two different init() functions. init_a() is probably the one you want.Anyway, play around with this and see if it gives you ideas. :)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml">	<head>		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />		<title>Untitled Document</title>		<script language="javascript" type="text/javascript">			function objID(id){				return document.getElementById(id);				// document.all() IS TOO OUTDATED TO BOTHER WITH			}			function populate_div (index){				var div_id = "container_" + index;				var sel_id = "sel_" + index;				objID(div_id).innerHTML = '<select id="' + sel_id + '"></select>';				for(i = 1; i < 4; i++) {					var opt = document.createElement('option');					opt.text = 'test_' + i;					opt.value = i;					insertOption (objID(sel_id), opt);				}				// BECAUSE populate_div() CREATES THE SELECT OBJECT, IT MUST ALSO ASSIGN THE EVENT HANDLER				objID(sel_id).onchange = function () {					alert(this.options[this.selectedIndex].value);				}			}			// WE NEED THIS FUNCTION BECAUSE IE ADDS OPTIONS INCORRECTLY			function insertOption(sel, opt) {				try {					sel.add (opt, null); // W3 STANDARD				} catch (ex) {					sel.add (opt); // IE				}			}			// GIVES YOU THE OPTION OF SETTING THIS UP FOR UNLIMITED ITEMS			function init_a () {				var buttons = document.getElementsByTagName("button");				for (var i = 0; buttons[i]; i++) {					buttons[i].onclick = function () {						var index = this.id.match(/\d+/); // EXTRACTS THE NUMBER PART OF THE ID						var div_id = "container_" + index;						populate_div (index)						objID(div_id).style.display = "block";					}				}			}			// IF YOU JUST NEED ONE			function init_b () {				objID("show_1").onclick = function () {					populate_div ("1")					objID("container_1").style.display = "block";				}			}			window.onload = init_a;		</script>	</head>	<body>		<button id="show_1" type="button">Show 1</button>		<div id="container_1" style="display:none"></div>	</body></html>

Thanks Heaps!I had a feeling the new object wasn't being recognized. This adds a whole new level of functionality/possibilities. I've been wrestling with a mix-match of code to get these features to work. This is so much more straight forward...not to mention reusable. My project is more or less a spread sheet with interactive cells so there will be rows*columns of events to accomodate. Fortunately, I don't haveany select menus in the cells, just the control panel (three to be exact). But I can be certain, that the rows will require your solution as they are all generated by AJAX. It's taken months and a couple of rewrites to get this far.As they say...Third times' a charm.
Link to comment
Share on other sites

Some advice? Even though you're setting up a grid, there's no need for a table, with all that extra code. You can do it with inputs styled to arrange themselves correctly. Obviously you'll generate this (and IDs and stuff) in PHP, but this is how the skeleton might look when outputted:

<style type="text/css">	* {		margin: 0;		padding: 0;	}	div.grid {		border-top: solid 1px #000000;		border-left: solid 1px #000000;		width: 500px;		margin: 20px auto;	}	div.grid input[type="text"] {		border: 0;		border-bottom: solid 1px #000000;		border-right: solid 1px #000000;		width: 19px;		height: 19px;		display: block;		float: left;	}	input.first {		clear: both;	}	div.clear {		clear: both;	}</style>------------------<div class="grid">	<input type="text" class="first">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="first">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="first">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="first">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<input type="text" class="normal">	<div class="clear"></div></div>

Link to comment
Share on other sites

That's a little beyond my comprehension. I'm from the old school and will probably continue to use tables for tabular layout and div/span for content.No reason to discard tables because its the fashionable thing to do. But thanks for the example. If I need to use divs as a table I'll know where to start.Cheers,James

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...