Forum Moderators: open
I want to add in a middle menu (filtermenu) which will append a variable to the url value of the first menu thereby filtering the list in the ajaxloadedmenu, so the submitted value becomes "spot.php?LatestArticles&genre=Romance". Ideally without a page refresh.
My Javascript skills are nonexistant, so any help with this will be very much appreciated.
<form>
<select name="firstmenu" onchange="loadDoc(event)">
<option value="spot.php?LatestArticles">Latest Articles</option>
<option value="spot.php?AllArticles">All Articles</option>
<option value="spot.php?ArticlesAlphabetically">Articles Alphabetically</option>
</select>
<select name="filtermenu">
<option value="&genre=Thrillers">Thrillers</option>
<option value="&genre=Romance"">Romance</option>
<option value="&genre=Comedy"">Comedy</option>
</select>
<select name="ajaxloadedmenu" id="topics" onchange="showDetail(event)">
<option value="">Choose a Category First</option>
</select>
</form>
[edited by: SteveXi7 at 5:51 pm (utc) on April 2, 2007]
If I then select Comedy from the filtermenu, I want the existing chosen value from the firstmenu (the url) appended with the value from the filter menu "&genre=Comedy" so the resulting filtered list in the third menu is called by the amended url "spot.php?LatestArticles&genre=Comedy".
The ajaxloadedmenu works well with selection from the first list ("spot.php?LatestArticles") and also by manually changing the url ("spot.php?LatestArticles&genre=Comedy") I am just not sure what I need to do to cause the filtermenu to change the url and pass it on to ajaxloadedmenu.
Thanks for helping!
<select id="menu1" name="firstmenu" onchange="loadDoc(event)">
<select id="menu2" name="filtermenu" onChange="applyFilter()">
<option value="">View All</option>
ok, now a bit of script, put this inside your HEAD tag, I've commented the code so you can see what it's doing:
function applyFilter() {
// Find select elements
var menu1 = document.getElementById( "menu1");
var menu2 = document.getElementById( "menu2");
// Get selected value from menu1
// menu1.options is a list of your OPTION tags
// menu1.selectedIndex tells you which one is selected
var listURL = menu1.options[ menu1.selectedIndex].value;
// Use a regular expression to remove any genre setting previously added
// Not going to explain regular expressions, they're a nightmare
listURL = listURL.replace( /&genre.*/, "");
// Get selected value from menu2
var URLfilter = menu2.options[ menu2.selectedIndex].value;
// Add URL filter to URL
listURL += URLfilter;
// Put value back to menu1's option
menu1.options[ menu1.selectedIndex].value = listURL;
}
That should do it, but I don't know if it will trigger menu1's onChange event, so you may need to call loadDoc manually from the applyFilter function.
Let me know how it goes!
I choose "All Articles" from firstmenu, the ajaxmenu loads the list of all articles.
I then choose "Comedy" from filtermenu, nothing changes in ajaxmenu
I change my choice to "Latest Articles" from firstmenu, still no change,
But then when I select "All Articles" again in firstmenu the correctly filtered list of Comedy Articles appears in ajaxmenu.
How would I go about trying what you suggest in calling loadDoc manually from the applyFilter function?
Many Thanks!
This is the loadDoc() function:
function loadDoc(evt) {
evt = (evt)? evt : ((window.event)? window.event : null);
if (evt) {
var elem = (evt.target)? evt.target : ((evt.srcElement)? evt.srcElement : null);
if (elem) {
try {
if (elem.selectedIndex > 0) {
loadXMLDoc(elem.options[elem.selectedIndex].value);
}
}
catch(e) {
var msg = (typeof e == "string")? e : ((e.message)? e.message : "Unknown Error");
alert("Unable to get XML data:\n" + msg);
return;
}
}
}
}
Ultimately, it's a wrapper for the loadXMLDoc function, which takes the url as a parameter, in this case from the SELECT OPTION value.
So, replace loadDoc in your function to loadXMLDoc( menu1.options[ menu1.selectedIndex].value);
But I think there's a better way we can do this bypassing the loadDoc function. See if that works first and I'll change it around a bit if it does.
That is much closer to how it should work.
firstmenu maintains a memory of the choice made in the second menu which breaks the user experience if they return to their original firstmenu choice.
Reading that back it sounds completely unclear to me, so here is my test page which explains things better than me:
<url removed>
[edited by: encyclo at 1:36 am (utc) on April 5, 2007]
[edit reason] no URLs please, see TOS [webmasterworld.com] [/edit]
I stand corrected and will not post any url whatsoever again.
So I will try to explain better.
When I select from firstmenu (Latest Uploads) all is well - the ajaxmenu loads its list of all Latest Uploads.
I then select from the filtermenu (Comedy), again all is well, the ajax menu is filtered and shows all Comedy Latest Uploads.
I change the selection in the firstmenu to Articles Listed Alphabetically, the unfiltered options show in the ajaxmenu (all articles A to Z).
I select a different option (Romance) in the filtermenu, the ajaxmenu changes appropriately, showing Romance A to Z.
But then, if I choose my original selection (Latest Uploads) from the firstmenu, the filtermenu selected item does not change from it's previous selection (Romance), whilst the ajaxmenu still shows the list of Latest Uploads Comedy.
I hope this explains better, and apologies once again for any possible slight breach of etiquette.
And crucially, thankyou so much for your help Dabrowski, I really do appreciate it very much. I am sorry I could not provide you (and anyone eslse interested) with a link to reduce your reading time!
[edited by: SteveXi7 at 2:17 am (utc) on April 5, 2007]
No problem, now that's working we'll replace the loadDoc function with one of our own, try this. Change both your select tags to call the applyFilter function:
<select id="menu1" name="firstmenu" onchange="applyFilter()">
<select id="menu2" name="filtermenu" onChange="applyFilter()">
function applyFilter() {
var menu1 = document.getElementById( "menu1");
var menu2 = document.getElementById( "menu2");
var url = "";url += menu1.options[ menu1.selectedIndex].value;
url += menu2.options[ menu2.selectedIndex].value;loadXMLDoc( url);
}
This way is actually shorter and easier - all we do is put the 2 OPTION value strings together, and pass it to loadXMLDoc. This way we don't need to worry about modifying values of menu OPTIONs.
I have had some trouble with the XML and ampersands, but have worked around it within my xml templates and php. However, I am unable to reach the option label displaying in the third menu to clean it up. I think is being built by this code:
function appendToSelect(select, value, content) {
var opt;
opt = document.createElement("option");
opt.value = value;
opt.appendChild(content);
select.appendChild(opt);
}
Is there a Javascript equivalent to url_decode which I can add to this function? My title in the last menu is currently showing as Drum+%26+Bass, which would need to be decoded to Drum & Bass.
Thanks
[edited by: SteveXi7 at 1:55 pm (utc) on April 5, 2007]
To convert it back to a normal string you can use the unescape function:
unescape( string);
In this case it would give you 'Drum+&+Bass'. If the space character was also escaped (%20) this would be easier. Use a regexp to sort the '+'s out:
string = string.replace( /\+/, " ");
So then you should get 'Drum & Bass'.
So to put that in your function you'd modify it as:
function appendToSelect(select, value, content) {
var opt;
var realVal = unescape( value);
opt = document.createElement("option");
opt.value = realVal.replace( /\+/, " ");
opt.appendChild(content);
select.appendChild(opt);
}
That should work.
Whould it be the function containing appendToSelect which needs the change?
function buildTopicList() {
var select = document.getElementById("topics");
var items = req.responseXML.getElementsByTagName("item");
for (var i = 0; i < items.length; i++) {
appendToSelect(select, i,
document.createTextNode(getElementTextNS("", "title", items[i], 0)));
}
document.getElementById("ultraspot").innerHTML = "";
}
In this function, you have select, value, content as parameters; 'select' is the SELECT tag to append (DOM Object), 'value' is the value='?' parameter of the OPTION, and 'content' is the actual text in the OPTION.
So instead of modifying 'value' we should have modified 'content'. Try that.
function appendToSelect(select, value, content) {
var opt;
opt = document.createElement("option");
opt.value = value;
var realCont = unescape(content);
opt.content = realCont.replace( /\+/, " ");
opt.appendChild(content);
select.appendChild(opt);
}
I have tried using unescape() all over, but always either no change, or the whole section breaks down.
ok, modify your function quoted above to:
function appendToSelect(select, value, content) {
var opt;
opt = document.createElement("option");
opt.value = value;
var realCont = unescape(content);
opt.content = realCont.replace( /\+/, " ");
opt.appendChild(content);
select.appendChild(opt);
}
See you were creating a new variable, opt.content which would contain the correct string, but then opt.appendChild( content) is called to add the text to the OPTION.
content there referrs to the function's content parameter. You need to remove the opt.content= line as it's not needed, and change the opt.appendChild( content) to opt.appendChild( realCont.replace.....);
You almost had it right!
Where you say to modify my function to: the function in your code box is identical to the one above!
I have tried your instructions below, but the debugger shows that it fails at this line (opt.appendChild(realCont.replace( /\+/, " "));) so I guess there is something missing from the code box?
function appendToSelect(select, value, content) {
var opt;
opt = document.createElement("option");
opt.value = value;
var realCont = unescape(content);
opt.appendChild(realCont.replace( /\+/, " "));
select.appendChild(opt);
}
I forgot how hard it was trying to work though code somebody else has written. A couple of things, sorry I forgot to modify that function after I copied from your post, you did the mod ok though.
I checked your test page again, it appears you have accidentally duplicated the appendToSelect function, but that's not the problem. I checked the buildtopiclist function and it's passing a textNode object, rather than actual text, so another slight modification as follows:
function appendToSelect(select, value, content) {content.data = unescape( content.data);
content.data = content.data.replace( /\+/, " ");var opt = document.createElement("option");
opt.value = value;
opt.appendChild( content);select.appendChild(opt);
}
You see the textNode object contains the string in a variable called data (content.data) and because it's already stored here it's easier not to create another var. By saying content.data = (do something with content.data) we're diverting the output back into itself.
Apart from that I've just rearranged the code slightly to tidy it up. 1st rule of coding is tidy code is easier to work with!
I found that the regex needed changing slightly as it only removed the first +, so that line became
content.data = content.data.replace( /\+/g, " ");
When selecting a menu for the first time I have noticed an error reported in the Console (although I don't see any problem with the way that it is working):
uncaught exception: [Exception... "Index or size is negative or greater than the allowed amount" code: "1" nsresult: "0x80530001 (NS_ERROR_DOM_INDEX_SIZE_ERR)" location: "http://example.com/jambbb/community.php Line: 833"]
which is referring to one of these lines in the applyFilter function, depending on if the firstmenu or secondmenu is selected first.
832: url += menu1.options[ menu1.selectedIndex].value;
833: url += menu2.options[ menu2.selectedIndex].value;
Is there a means of testing whether both selections have been made to avoid the error?
Thanks again!
[edited by: jatar_k at 10:00 pm (utc) on April 5, 2007]
[edit reason] please use example.com [/edit]
As for the selectedIndex error, there's 2 ways we can solve it, either specify the default in the HTML:
...
<option value="http://bristol.fm/jambbb/ultraspot.php?LatestUploads" SELECTED>Latest Uploads</option>
...
<option value="" SELECTED>All Genres</option>
...
Notice we've added the 'SELECTED' flag on the OPTION tags. Or we can specify a default in the JS:
function applyGenreFilter() {
var menu1 = document.getElementById( "menu1");
var menu2 = document.getElementById( "menu2");
var idx1 = menu1.selectedIndex ¦¦ 0;
var idx2 = menu2.selectedIndex ¦¦ 0;
var url = "";
url += menu1.options[ idx1].value;
url += menu2.options[ idx2].value;
loadXMLDoc(url);
}
The expression 'menu1.selectedIndex ¦¦ 0' means try menu1.selectedIndex, if there's nothing there then use 0. The ¦¦ means OR.
***NOTE VERY IMPORTANT*** THE ¦ CHARACTER HAS BEEN INCORRECTLY INTERPRETED ON THIS PAGE, IT'S THE SYMBOL ABOVE '\'.
You can shorten this further by saying
menu1.options[ (menu1.selectedIndex ¦¦ 0)].value, but sometimes code shortened too much can be difficult to read back. This is ok though.
I do have a further question though (hope I'm not being too pushy!).
Looking at the result now, it does exactly what I wanted it to do. But seeing it working reveals something that I hadn't thought of - when the final selection is made (in the ajaxloadedmenu) the article is loaded below. If the visitor starts out on making another another selection, the article disappears immediately, which doesnt seem right - it should persist until another ajaxloadedmenu choice is made, which will load the new article. The content should persist until fresh content is selected to load.
Many, many thanks for your assistance during my first Javascript foray. I won't be a noob forever, I know this because of the increase in understanding I have made today. For me, your explanations work jsut as well as your solutions!
The function showDetail fills in the info window. This line:
if (select && select.options.length > 1) { Says if it has found the SELECT tag, and it has more than one option (the first is always 'Choose a Category First' or 'Refine Choice'). When the list resets I think the onChange event is being fired, and because then nothing is selected in that box the list clears.
If that is the case, all we have to do is change that IF statement to also check something has been selected.
if (select && select.options.length > 1 && select.selectedIndex) { The only thing is I'm not sure if selectedIndex will be reset, so it may remember the old value. Try it and see.
As for explanations, I have spent many years learning. I find that someone giving me the solution is no good as it teaches me nothing, so I always explain. That way you'll know for next time.