PDA

View Full Version : dynamically building optgroup using javascript



rama1977
02-11-2013, 07:24 PM
Hi,

I am trying to group options using <optgroup> tag and below is the html which I want to apply the javascript code and add optgroup tag.


<html>
<body>
<select id="vendorCode" name="vendorCode">

<option value="05-South">Chennai</option>
<option value="06-South">Bangalore</option>

<option value="01-North">Delhi</option>
<option value="02-North">Punjab</option>
<option value="03-North">Gujarat</option>

</select>

</body>
</html>

The output I needs is as below after applying javascript to add the optgroup tags.


<select id="vendorCode" name="vendorCode">

<optgroup label="South">
<option value="05-South">Chennai</option>
<option value="06-South">Bangalore</option>
</optgroup>

<optgroup label="North">
<option value="01-North">Delhi</option>
<option value="02-North">Punjab</option>
<option value="03-North">Gujarat</option>
</optgroup>


</select>

Please let me know the javascript code. I have tried few things but the label is getting mixed up as it is having like 05-South/01-North etc.


Thanks in advance for your time. I really appreciate.

Thanks
Ram

jscheuer1
02-12-2013, 12:16 PM
For me the real trick was getting all of the options for each optgroup to migrate to their optgroup. Here's the code of a working page for this:


<!DOCTYPE html>
<html>
<head>
<title>Optgroup - Demo</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript">
function makegroups(id, groups){
var parentselect = document.getElementById(id), opts = parentselect.options,
i = opts.length, j = groups.length, groupobj = {};
while(--j > -1){
groupobj[groups[j]] = [document.createElement('optgroup'), []];
groupobj[groups[j]][0].setAttribute('label', groups[j]);
parentselect.insertBefore(groupobj[groups[j]][0], parentselect.firstChild);
}
while(--i > -1){
j = groups.length;
while(--j > -1){
if(opts[i].value.indexOf(groups[j]) > -1){
groupobj[groups[j]][1].push(opts[i]);
}
}
}
j = groups.length;
while(--j > -1){
i = groupobj[groups[j]][1].length;
while(--i > -1){
groupobj[groups[j]][0].appendChild(groupobj[groups[j]][1][i]);
}
}
}
</script>
</head>
<body>
<select id="vendorCode" name="vendorCode">

<option value="05-South">Chennai</option>
<option value="06-South">Bangalore</option>

<option value="01-North">Delhi</option>
<option value="02-North">Punjab</option>
<option value="03-North">Gujarat</option>

</select>
<script type="text/javascript">
makegroups('vendorCode', ['South', 'North']);
</script>
</body>
</html>

Live Demo:

http://home.comcast.net/~jscheuer1/side/tidbits/optgroup.htm

I also worked out a version where the groups can be determined via regular expression from the option values:

http://home.comcast.net/~jscheuer1/side/tidbits/optgroup2.htm

Use your browser's "view source" to get its code if you're interested.

javacrypter
06-19-2014, 05:51 PM
Hello John,

I was able to use your regular expression solution to fix a problem for me. Thank you for coming up with that. That is an ingenious script. I was curious if there would be a way to alphanumerically sort the option groups? I understand that it creates the option groups by the order that it sees them first in the values of the options. I could move around the options so that it could display the option groups in the preferred order Please let me know if you need an example of the working page I am dealing with. Thank you in advance if you could point me in the right direction.

Thomas

jscheuer1
06-19-2014, 06:57 PM
As far as I know, unless you are willing to have 10 come before 2 and similar odd treatment of other numbers, there is no simple alpha-numeric sort in javascript. I did a search and found:

http://sourceforge.net/projects/js-natsort/

which in very limited testing appears to do the job.

Now, do you want to sort the groups, so that in the examples in this thread North would come before South, or do you want to sort within the groups, so that Gujarat would come before Punjab?

javacrypter
06-19-2014, 07:07 PM
North before South

jscheuer1
06-19-2014, 07:59 PM
http://home.comcast.net/~jscheuer1/side/demos/tidbits/optgroup3.htm

jscheuer1
06-21-2014, 01:38 PM
Actually, after more testing, I've decided that the sorting algorithm provided by that script is inferior as it will put something like:

bob

after:

bob1

No other sorting routine does that. I discovered this because I wanted to make up my own functions. I think they work better. But obviously they could do with more testing and scrutiny.

Here's a demo which uses them:

http://home.comcast.net/~jscheuer1/side/demos/tidbits/optgroup4.htm

javacrypter
06-23-2014, 11:50 PM
Thank you for all of your help with this. I am trying to apply it to the values that I am working with. Maybe you could help. The values I am working with have spaces in them which throws the sort function off :(


<select id="vendorCode" name="vendorCode">

<option value="1x">one</option>
<option value="Android Device">two</option>
<option value="4G LTE Android Device">three</option>
<option value="iPhone">four</option>
<option value="Smart Device">five</option>
<option value="4G LTE Smartphone Device">six</option>

</select>

Honestly I am not really worried about how it sorts it, but rather that I can have those option groups spelled out. It has been mashing together the 4G LTE groups, and it doesn't like that some start with a digit and others don't. I changed my function call to:

makegroups('vendorCode', /(\w+)/);

but to no avail. If you could help with this I would greatly appreciate it.

Thomas

jscheuer1
06-24-2014, 01:15 AM
How's this:

http://home.comcast.net/~jscheuer1/side/demos/tidbits/optgroup5.htm

javacrypter
06-24-2014, 08:17 PM
Again, amazing solution. Thank you very much John. I finally was able to port to my actual application and it is all working like a dream, except I am getting a option group at the bottom called 'undifined' with the options, 'natsort' and 'natcasesort'. The script is running on an internal site and only used by a handful of people, so if it is an inevitable problem, it is fine. I am more curious as to where it is getting those values to actually put into the options. I of course can see that is what you are calling the functions and whatnot. If you need to see how it is all actually working on my page, just let me know. Thank you again.

Thomas

jscheuer1
06-25-2014, 12:12 AM
If you're not seeing this issue on my demo, I would need to at least see a page where it happens. Either way I would also want to know which browser and which version of that browser is doing that.

javacrypter
06-25-2014, 02:48 AM
In Firefox 24, Chrome Version 35.0.1916.153 m, and IE 11. I tried recreating it with your example and it doesn't happen, so I attached a stripped version of my page where it is happening. Thank you again for your help.

Thomas

jscheuer1
06-25-2014, 04:23 PM
The variable phonelist is created as an Array, but is used as an Object. That wouldn't matter except that the sorting algorithms I've added have been added as prototypes of the Array object. They should not be seen as properties of an array. They are properties of the prototype of Array. But browsers apparently are ignoring that distinction. I should probably rewrite my sorting algorithms as standalone functions to avoid this potential problem. However, there is an easy fix for the code in your post and for code in general around this issue. When constructing an Object for use as an Object, make it an actual Object. (Use an Object instead of an Array.) Change (the formal Array constructor):


var phonelist = new Array();

to (the literal Object constructor):


var phonelist = {};

This problem could also have been avoided by using the hasOwnProperty() function when iterating the phonelist array as an object, but simply defining it as an object to begin with is easier, and more efficient here. But in javascript in general (where the overall environment might have additional Object properties) the hasOwnProperty() function technically should be used whenever iteration of an Object (Array or otherwise) is being done in order to avoid prototypical properties and other properties of the the Global Object Constructor from being included (this is good practice, but not required for the particular demo you provided once the above change is made):


for (var phone in phonelist)
{
if(phonelist.hasOwnProperty && phonelist.hasOwnProperty(phone) || !phonelist.hasOwnProperty && phonelist[phone] && !phonelist.constructor.prototype[phone]){
var option = document.createElement("option");
option.text = phone;
option.value = i + "," + phone + "," + phonelist[phone][0] + "," + phonelist[phone][1] + "," + phonelist[phone][2] + "," + phonelist[phone][3] + "," + phonelist[phone][4] + "," + phonelist[phone][5] + "," + phonelist[phone][6] + "," + phonelist[phone][7] + "," + phonelist[phone][8];
curlistbox.options.add(option);
}
}

BTW, you should use the literal Array constructor when constructing the member arrays of what is now the phonelist Object. But this is only an efficiency move, not required to avoid problems. It would mean changing (and similar):


phonelist["Blackberry Curve (9530)"] = new Array( "349.99", "0.00" , "" , "99.99" , "16.67" , "0.00", "20.84", "phone" , "End of Life" );

to fit this template (slide code to the end to also see change from closing ) to closing ]):


phonelist["Blackberry Curve (9530)"] = [ "349.99", "0.00" , "" , "99.99" , "16.67" , "0.00", "20.84", "phone" , "End of Life" ];

javacrypter
06-28-2014, 03:33 PM
Hello John,

Thank you again for all your help. I was able to use your script to do what I wanted to do. Do you know if there would be a way to go back to the originally selected option after it is sorted. I think I have figured out how to store the actual object that is the selected option and be able to pass it back and call the function that is normally called when you originally select the option. But it always go back to the first option in the select list. Can you changed the selectedness by name and not index?

Thomas

jscheuer1
06-28-2014, 03:47 PM
That's easy, just remove or comment out this line (at the end of the code I wrote):


parentselect.selectedIndex = 0;

But as far as I can see, there is no originally selected option, at least not in the demo code you provided.

javacrypter
06-28-2014, 08:43 PM
Hello again John,

You were right, i was thinking way too much into it. You can do one sort on the list and it will just keep everything selected how it was. Now I have thrown a wrench into the whole thing. I want to be able to sort it two different ways. First way is by the type that we just figured out how to do. The second way is by price. So first it was keeping the old option groups and adding the new ones. I was able to make it so that it would clear out the old option groups and then add in the new ones. But now if I sort one way, then try to sort a different way it just leaves an empty select. I was trying to do an attach like I did before but it won't let me. So here is the full page I am working with.


<!--<!DOCTYPE html>-->
<html>

<head>
<meta http-equiv="Content-Language" content="en-us">
<meta name="GENERATOR" content="Microsoft FrontPage 5.0">
<meta name="ProgId" content="FrontPage.Editor.Document">
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<title>Compare Plans</title>
<script type="text/javascript" src="natsortjs/natsortjs.js">
<script Language="JavaScript" type="text/javascript">

var phonelist = {};
phonelist["Choose One"] = [ " ", " " , "" , " " , " " , " ", " ", "phone" , "" ];
phonelist["Option 1"] = [ " ", "50" , "0" , " " , " " , " ", " ", "phone" , "Smart Devices" ];
phonelist["Option 2"] = [ " ", "100" , "0" , " " , " " , " ", " ", "phone" , "End of Life" ];
phonelist["Option 3"] = [ " ", "150" , "50" , " " , " " , " ", " ", "phone" , "4G LTE Smartphone Devices" ];
phonelist["Option 4"] = [ " ", "200" , "0" , " " , " " , " ", " ", "tablet", "Tablet" ];
phonelist["Option 5"] = [ " ", "250" , "0" , " " , " " , " ", " ", "phone" , "iPhone" ];
phonelist["Option 6"] = [ " ", "300" , "0" , " " , " " , " ", " ", "phone" , "1x" ];
phonelist["Option 7"] = [ " ", "350" , "50" , " " , " " , " ", " ", "phone" , "Android Devices" ];
phonelist["Option 8"] = [ " ", "400" , "50" , " " , " " , " ", " ", "phone" , "4G LTE Smartphone Devices" ];

function init()
{
for (i = 0; i < 10; i++)
{
var curlistbox = document.getElementById("Phone".concat(i));

for (var phone in phonelist)
{
if(phonelist.hasOwnProperty && phonelist.hasOwnProperty(phone) || !phonelist.hasOwnProperty && phonelist[phone] && !phonelist.constructor.prototype[phone])
{
var option = document.createElement("option");
option.text = phone;
option.value = i + "," + phone + "," +
phonelist[phone][0] + "," +
phonelist[phone][1] + "," +
phonelist[phone][2] + "," +
phonelist[phone][3] + "," +
phonelist[phone][4] + "," +
phonelist[phone][5] + "," +
phonelist[phone][6] + "," +
phonelist[phone][7] + "," +
phonelist[phone][8] + ",";
if (phone == "Choose One")
{
option.value += "";
option.selected = "true";
}
else {option.value += (Number(phonelist[phone][1]).toFixed(0) - phonelist[phone][2]);}
curlistbox.options.add(option);
}
}
}
}

function sortphone(sel)
{
if (sel.options.value == "1")
{
for (i = 0; i < 10; i++)
{
var curlistbox = document.getElementById("Phone".concat(i));
removeOptGrp(curlistbox);
curlistbox.focus();
}
document.getElementById("Phone9").blur();
}
else if (sel.options.value == "2")
{
for (i = 0; i < 10; i++)
{
var curlistbox = document.getElementById("Phone".concat(i));
removeOptGrp(curlistbox);
sorttype("Phone".concat(i), /(\w+ *\w* *\w* *\w*)/);
curlistbox.focus();
}
document.getElementById("Phone9").blur();
}
else if (sel.options.value == "3")
{
for (i = 0; i < 10; i++)
{
var curlistbox = document.getElementById("Phone".concat(i));
removeOptGrp(curlistbox);
sortprice("Phone".concat(i), /(\d+ *\w* *\w* *\w*)/);
curlistbox.focus();
}
document.getElementById("Phone9").blur();
}
}

function removeOptGrp(sel)
{
var len, groups, par;

groups = sel.getElementsByTagName('optgroup');
len = groups.length;
for (var i=len; i; i--)
{
sel.removeChild( groups[i-1] );
}
}


function sorttype(id, groupre){
var parentselect = document.getElementById(id), opts = parentselect.options,
groupobj = {}, opt = -1, grp, groups = (function(){
var groups = [], groupname, name, limit = opts.length;
while(++opt < limit)
{
groupname = groupre.exec(opts[opt].value.split(",")[10]);
if(groupname && groupname[1])
{
groupobj[groupname[1]] = true;
}
}
for(name in groupobj)
{
groups.push(name);
groupobj[name] = {optgroup: document.createElement('optgroup'), options: []};
groupobj[name].optgroup.setAttribute('label', name);
}
groups.natcasesort().reverse();
grp = groups.length;
while(--grp > -1)
{
parentselect.appendChild(groupobj[groups[grp]].optgroup);
}
return groups;
})();
opt = opts.length;
while(--opt > -1){
grp = groups.length;
while(--grp > -1
){
if(opts[opt].value.split(",")[10] === groups[grp])
{
groupobj[groups[grp]].options.push(opts[opt]);
}
}
}
grp = groups.length;
while(--grp > -1)
{
opt = groupobj[groups[grp]].options.length;
while(--opt > -1)
{
groupobj[groups[grp]].optgroup.appendChild(groupobj[groups[grp]].options[opt]);
}
}
// parentselect.selectedIndex = 0;
}

function sortprice(id, groupre)
{
var parentselect = document.getElementById(id), opts = parentselect.options,
groupobj = {}, opt = -1, grp, groups = (function(){
var groups = [], groupname, name, limit = opts.length;
while(++opt < limit)
{
groupname = groupre.exec(opts[opt].value.split(",")[11]);
if(groupname && groupname[1])
{
groupobj[groupname[1]] = true;
}
}
for(name in groupobj)
{
groups.push(name);
groupobj[name] = {optgroup: document.createElement('optgroup'), options: []};
groupobj[name].optgroup.setAttribute('label', name);
}
groups.natcasesort().reverse();
grp = groups.length;
while(--grp > -1)
{
parentselect.appendChild(groupobj[groups[grp]].optgroup);
}
return groups;
})();
opt = opts.length;
while(--opt > -1)
{
grp = groups.length;
while(--grp > -1)
{
if(opts[opt].value.split(",")[11] === groups[grp])
{
groupobj[groups[grp]].options.push(opts[opt]);
}
}
}
grp = groups.length;
while(--grp > -1)
{
opt = groupobj[groups[grp]].options.length;
while(--opt > -1)
{
groupobj[groups[grp]].optgroup.appendChild(groupobj[groups[grp]].options[opt]);
}
}
// parentselect.selectedIndex = 0;
}
</script>
</head>

<body onload="init()" style="border-collapse: collapse; padding-right:4; padding-left:4" text="#000000" bgcolor="#FEE101">

<form name="plan">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111" width="100%">
<tr>
<td width="7%" align="left">Phone</td>
<td width="8%" align="center" colspan="2">
<select size="1" name="Phone0" id="Phone0" style="width: 100%">
</select></td>
<td width="8%" align="center" colspan="2">
<select size="1" name="Phone1" id="Phone1" style="width: 100%">
</select></td>
<td width="8%" align="center" colspan="2">
<select size="1" name="Phone2" id="Phone2" style="width: 100%">
</select></td>
<td width="8%" align="center" colspan="2">
<select size="1" name="Phone3" id="Phone3" style="width: 100%">
</select></td>
<td width="8%" align="center" colspan="2">
<select size="1" name="Phone4" id="Phone4" style="width: 100%">
</select></td>
<td width="8%" align="center" colspan="2">
<select size="1" name="Phone5" id="Phone5" style="width: 100%">
</select></td>
<td width="8%" align="center" colspan="2">
<select size="1" name="Phone6" id="Phone6" style="width: 100%">
</select></td>
<td width="8%" align="center" colspan="2">
<select size="1" name="Phone7" id="Phone7" style="width: 100%">
</select></td>
<td width="8%" align="center" colspan="2">
<select size="1" name="Phone8" id="Phone8" style="width: 100%">
</select></td>
<td width="8%" align="center" colspan="2">
<select size="1" name="Phone9" id="Phone9" style="width: 100%">
</select></td>
<td width="6%" align="center">
<p align="right">Sort:&nbsp;&nbsp; </td>
<td width="7%" align="center">
<select size="1" name="phonesort" onchange="sortphone(this)" style="width: 100%">
<option value="1" selected>Alphabet</option>
<option value="2">Type</option>
<option value="3">Price</option>
</select>
</td>
</tr>
</table>
</form>

</body>

</html>