Using JavaScript and JQuery, How Can I Maintain the Selected State of a Dynamically-Updated HTML Select Element?

StackOverflow https://stackoverflow.com/questions/8370534

Question

I have a form UI whereby several sections require duplicate HTML select list to be updated dynamically from a single, dynamically-updatable select list.

The dynamically-updatable list works just fine, in that new options can be added and removed on-the-fly. I can then get this update to propagate through each of the duplicate lists using JQuery .find(). I have even added a bit of logic to maintain the currently selected index of the original select list.

What I'm not able to do is maintain the selected state of each of the duplicate select lists as new options are added and removed from the original select list. As each update to the original select list iterates through each duplicate select list, they lose their currently selected option index.

Here is an example of my conundrum--*EDIT--I would encourage you to try and execute the code I've provided below and apply your theories before suggesting a solution, as none of the suggestions so far have worked. I believe you will find this problem a good deal trickier than you might assume at first:

<form>
<div id="duplicates">
<!--// I need for each of these duplicates to maintain their currently selected option index as the original updates dynamically //-->
  <select>
  </select>
  <select>
  </select>
  <select>
  </select>
</div>
<div>
  <input type="button" value="add/copy" onclick="var original_select = document.getElementById('original'); var new_option = document.createElement('option'); new_option.text = 'Option #' + original_select.length; new_option.value = new_option.text; document.getElementById('original').add(new_option); original_select.options[original_select.options.length-1].selected = 'selected'; updateDuplicates();" />
  <input type="button" value="remove" onclick="var original_select = document.getElementById('original'); var current_selected = original_select.selectedIndex; original_select.remove(original_select[current_selected]); if(original_select.options.length){original_select.options[current_selected < original_select.options.length?current_selected:current_selected - 1].selected = 'selected';} updateDuplicates();" />
  <select id="original">
  </select>
</div>
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript">
    function updateDuplicates(){
        $("#duplicates").find("select").html($("#original").html());
     }
</script>
</form>

It is important to note that the duplicate HTML select lists should remain somewhat arbitrary, if at all possible (i.e.; no ID's) as this method needs to apply generically to other dynamically-created select lists throughout the document.

Thanks in advance!

Was it helpful?

Solution

Still not 100% sure what you're asking but it seems like this should do what you're looking for and is a few less lines of code.

(function () {
        function updateDuplicates() {
            $("#duplicates").find("select").html($("#original").html());

            $('#duplicates select').each(function () {
                var lastSelectedValue = $(this).data('lastSelectedValue');
                $(this).val(lastSelectedValue || $(this).val());
            });
        }

        $(document).ready(function () {
            $('button:contains(remove)').bind('click', function (e) {
                e.preventDefault();
                var original_select = document.getElementById('original'),
                current_selected = original_select.selectedIndex;

                original_select.remove(original_select[current_selected]);
                if (original_select.options.length) {
                    original_select.options[current_selected < original_select.options.length ? current_selected : current_selected - 1].selected = 'selected';
                }
                updateDuplicates();
            });

            $('button:contains(add/copy)').bind('click', function (e) {
                e.preventDefault();
                var original_select = document.getElementById('original'),
                new_option = document.createElement('option');

                new_option.text = 'Option #' + original_select.length;
                new_option.value = new_option.text;
                document.getElementById('original').add(new_option);
                original_select.options[original_select.options.length - 1].selected = 'selected';
                updateDuplicates();
            });

            $('#duplicates select').bind('change', function () {
                $(this).data('lastSelectedValue', $(this).val());
            });
        } ());
    } ());

EDIT: I changed your markup to be

<button>add/copy</button>
<button>remove</button>

OTHER TIPS

just set the currently selected item/value of select to some variable, then do your operation, finally reselect the value to the select.

Okay, I think I have a workable approach to a solution, if not a clumsy one. The tricky part isn't adding a value to the original list, because the added option is always at the end of the list. The problem comes in removing a select option because doing so changes the index of the currently selectedIndex. I've tested using Google Chrome on a Mac with no errors. I have commented the code to demonstrate how I approached my solution:

<form>
<div id="duplicates">
  <!--// Each of these select lists should maintain their currently selected index //-->
  <select>
  </select>
  <select>
  </select>
  <select>
  </select>
</div>
<div>
  <!--// Using a generic function to capture each event //-->
  <input type="button" value="add/copy" onClick="updateDuplicates('add');" />
  <input type="button" value="remove" onClick="updateDuplicates('remove');" />
  <select id="original">
  </select>
</div>
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript">
    function updateDuplicates(editMode){
        ///* Capture the selectedIndex of each select list and store that value in an Array *///
        var original_select = document.getElementById('original');
        var current_selected = new Array();
        $("#duplicates").find("select").each(function(index, element) {
            current_selected[index] = element.selectedIndex;
        });
        switch(editMode){
            case "add":
                var new_option = document.createElement('option');
                new_option.text = 'Option #' + original_select.length;
                new_option.value = new_option.text;
                original_select.add(new_option);
                original_select.options[original_select.options.length-1].selected = 'selected';
                ///* Traverse each select element and copy the original into it, then set the defaultSelected attribute for each *///
                $("#duplicates").find("select").each(function(index, element){
                    $(element).html($("#original").html());
                    ///* Retrieve the currently selected state stored in the array from before, making sure it is a non -1 value, then set the defaultSelected attribute of the currently indexed element... *///
                    if(current_selected[index] > -1){
                        element.options[current_selected[index]].defaultSelected = true;
                    }
                });
                break;
            case "remove":
                var current_index = original_select.selectedIndex;
                original_select.remove(original_select[current_index]);
                ///* Thou shalt not remove from thine empty list *///
                if(original_select.options.length){
                    original_select.options[current_index > 0?current_index - 1:0].selected = 'selected';
                }
                ///* Traverse each select element and copy the original into it... *///
                $("#duplicates").find("select").each(function(index, element){
                    $(element).html($("#original").html());
                    ///* Avoid operating on empty lists... *///
                    if(original_select.options.length){
                        ///* Retrieve the currently selected state stored in the array from before, making sure it is a non -1 value... *///
                        if(current_selected[index] > -1){
                            ///* If the stored index state is less or equal to the currently selected index of the original... *///
                            if(current_selected[index] <= current_index){
                                element.options[current_selected[index]].defaultSelected = true;
                            ///* ...otherwise, the stored index state must be greater than the currently selected index of the original, and therefore we want to select the index after the stored state *///
                            }else{
                                element.options[current_selected[index] -  1].defaultSelected = true;
                            }
                        }
                    }
                });
            }           
     }
</script>
</form>

There is plenty of room to modify my code so that options can be inserted after the currently selectedIndex rather than appended to the end of the original select list. Theoretically, a multi-select list/menu should work as well. Have at thee.

I'm sure one of the geniuses here will be able to do this same thing with cleaner, prettier code than mine. Thanks to everyone who reviewed and commented on my original question! Cheers.

If you can reset a little, I think that the problem is you are setting your select list's HTML to another list's HTML. The browser probably doesn't try to preserve the currently selected item if all the of underlying html is being changed.

So, I think what you might try doing is explicitly adding the option elements to the target lists.

Try this jsfiddle. If you select an item other than the default first item and click "add", notice that the selected item is maintained. So you need to be a little more surgical in your managing of the target list items.

Maybe that'll help or maybe I missed the point.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top