geomagnet Posted December 23, 2008 Share Posted December 23, 2008 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 More sharing options...
jeffman Posted December 23, 2008 Share Posted December 23, 2008 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 More sharing options...
geomagnet Posted December 24, 2008 Author Share Posted December 24, 2008 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 More sharing options...
jeffman Posted December 24, 2008 Share Posted December 24, 2008 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 More sharing options...
geomagnet Posted December 26, 2008 Author Share Posted December 26, 2008 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 More sharing options...
jeffman Posted December 26, 2008 Share Posted December 26, 2008 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 More sharing options...
geomagnet Posted December 27, 2008 Author Share Posted December 27, 2008 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 More sharing options...
jeffman Posted December 27, 2008 Share Posted December 27, 2008 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 More sharing options...
geomagnet Posted December 30, 2008 Author Share Posted December 30, 2008 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 More sharing options...
Recommended Posts
Archived
This topic is now archived and is closed to further replies.