Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to fix IE select issue when dynamically changing options

I have a set of selects that all have the same options. Then I run those options through a filter so that any options that are selected in a different select don't show up in the select. See this jsFiddle (in non-IE browser) to see what I mean. Basically I'm preventing the same option from being selected multiple times among the selects

Now, what I've done has an issue in IE. Open that fiddle in IE (I've only tried it in IE9, but I'm guessing previous versions have the same issue). Change the last select to AAA. Notice how the 3 other selects all changed what they display. The model for them did not change, but IE somehow chokes when the options are changed.

My questions is first, am I doing something wrong with this functionality in general? This same code does exactly what I want in Chrome and FF, but am I doing something that I just shouldn't be? Second, how can I get around this in IE? I tried some timeouts that would clear and re-set the model, but things noticeable jumped around. I'm wondering if there's a good, clean, low impact workaround for this.

Any help would be much appreciated. Thanks.

--UPDATE--

This has been fixed in Angular itself with version 1.3.3 using A. S. Ranjan's solution below. See new fiddle with 1.3.3: http://jsfiddle.net/m2ytyapv/

//dummy code so I can post the edit
like image 217
dnc253 Avatar asked Oct 17 '12 20:10

dnc253


7 Answers

I experienced the same issue the other night and after throwing everything I could think of at it I've come to the conclusion that IE just doesn't want to handle updating filters when using selects.

My solution is to change your selects to look like this:

 <select class="selectList" ng-repeat="currId in selectedIds" ng-model="selectedIds[$index]"  ng-options="currOption.id as currOption.value for currOption in myObj | myfilter:selectedIds:$index" data-ng-change="fixIE()"></select>

They now have a class and an ng-change on them. Then in your controller do this fun little bit of code:

$scope.fixIE = function(){
    //code to check if IE so the other browsers don't get this ugly hack.
    var selectLists = document.querySelectorAll(".selectList");
    for(var x = 0;x  < selectLists.length; x++){
        selectLists[x].parentNode.insertBefore(selectLists[x], selectLists[x]);
    }       
};

What it does is rip the elements out of the DOM and replace them into the same location. Here's a working fiddle jsFiddle

Some of the other solutions I tried that didn't involve javascript were things like toggling the display/visibility of the select. Having their zIndex's moved. The only thing that for sure fixed it was this piece of code.

like image 190
Mathew Berg Avatar answered Oct 05 '22 01:10

Mathew Berg


I've finally come up with a solution that works for my needs. Basically what appears to be happening is that the text for the option at the selected index is pointing to the old string that used to be in that place. I believe changing this text updates the strings and/or references. I did something like this:

angular.forEach($("select"), function (currSelect) {
     currSelect.options[currSelect.selectedIndex].text += " ";
});

Here is the updated fiddle: http://jsfiddle.net/H48sP/35/

In my app, I have a directive where these selects are, and so I do element.find("select") instead of $("select") to limit the scope of the element selecting. The text is forced to refresh and so displays correctly after all the digest cycles run.

If you have run into this same issue, you may need to add a $timeout like in the fiddle, and/or you may need to later remove the extra space that was added to the option text if that becomes a problem.

like image 32
dnc253 Avatar answered Oct 05 '22 01:10

dnc253


I have the fix.

We have to add and remove options list to trigger the rendering in IE8.

http://kkurni.blogspot.com.au/2013/10/angularjs-ng-option-with-ie8.html


/**
 * Fix for IE select menus getting stuck when their underlying list changes.
 * Original code: http://kkurni.blogspot.com.au/2013/10/angularjs-ng-option-with-ie8.html
 * 
 * Set the `ie-select-fix` attribute to the model expression that should trigger the list to re-render.
 * 
 * @example <select ng-model="modelValue" ie-select-fix="itemList" ng-options="item.label for item in itemList">
 */
app.directive('ieSelectFix', ['$document',
        function($document) {

            return {
                restrict: 'A',
                require: 'ngModel',
                link: function(scope, element, attributes, ngModelCtrl) {
                    var isIE = $document[0] && $document[0].attachEvent;
                    if (!isIE) return;

                    var control = element[0];
                    //to fix IE8 issue with parent and detail controller, we need to depend on the parent controller
                    scope.$watch(attributes.ieSelectFix, function() {
                        // setTimeout is needed starting from angular 1.3+
                        setTimeout(function() {
                            //this will add and remove the options to trigger the rendering in IE8
                            var option = document.createElement("option");
                            control.add(option,null);
                            control.remove(control.options.length-1);
                        }, 0);
                    });
                }
            }
        }
    ]);
like image 38
kkurni Avatar answered Oct 05 '22 02:10

kkurni


Adding couple of lines the following places (marked in bold as **) in render function of the selectDirective in angular.js worked fine for me. I am looking if there is any other possible solution other than patching angularJS or the forEach given below?

            if (existingOption.label !== option.label) {
              lastElement.text(existingOption.label = option.label);
              **lastElement.attr('label', existingOption.label);**
            }

and

              (element = optionTemplate.clone())
                  .val(option.id)
                  .attr('selected', option.selected)
                  .text(option.label);
              **element.attr('label', option.label);**

The issue was the label attribute of HTMLOptionElement is not the same as the text attribute if label is blank in IE.

This can be seen verified by adding the following code after the screen has loaded and looking at the web console of FF and IE to see the difference. If you uncomment the last line where the label is set to text it works fine. Alternatively patch angular.js as above.

// This is an IE fix for not updating the section of dropdowns which has ng-options with filters
angular.forEach($("select"), function (currSelect) {
    console.log("1.text ", currSelect.options[currSelect.selectedIndex].text);
    console.log("1.label ", currSelect.options[currSelect.selectedIndex].label);
    //console.log("1.innerHTML ", currSelect.options[currSelect.selectedIndex].innerHTML);
    //console.log("1.textContent ", currSelect.options[currSelect.selectedIndex].textContent);
    //console.log("1.cN.data ", currSelect.options[currSelect.selectedIndex].childNodes[0].data);
    //console.log("1.cN.nodeValue ", currSelect.options[currSelect.selectedIndex].childNodes[0].nodeValue);
    //console.log("1.cN.textContent ", currSelect.options[currSelect.selectedIndex].childNodes[0].textContent);
    //console.log("1.cN.wholeText ", currSelect.options[currSelect.selectedIndex].childNodes[0].wholeText);
    //console.log("1. ", currSelect.options[currSelect.selectedIndex], "\n");

    //currSelect.options[currSelect.selectedIndex].label = "xyz";
    //currSelect.options[currSelect.selectedIndex].label = currSelect.options[currSelect.selectedIndex].text;
});
like image 20
A. S. Ranjan Avatar answered Oct 05 '22 03:10

A. S. Ranjan


The problem seems to be related to the order of the options returned by the filter. When you change the last option to A, the other select options changes. What seems to cause a problem for IE is that the selected option changes place. In the first select box C is selected from the options in the following order: A, B, C, D. The selected option is the third option. When you change the forth select box from G to A, the filter changes the options in the first box to B, C, D, G. The selected option is now the second option, and this causes a problem with IE. This might be a bug in Angular, or it might be to some strange behavior in IE. I've created a fork that works around this by making sure that selected element is always the first option from the filtered options:

   var newOptions = [],selected;
    angular.forEach(allOptions, function (currentOption) {
        if (!isIdInUse(selectedIds, currentOption.id)){
            newOptions.push(currentOption);
        }else if(currentOption.id == selectedIds[parseInt(index)]){
            selected = currentOption;
        }
    });
    if(selected){newOptions.unshift(selected);}

http://jsfiddle.net/XhxSD/ (old)

Update:

I did some debugging and found the line that causes problems in IE, but I don't understand why. It does seem like a rendering bug or something. I've created another workaround that doesn't require any rearranging of the options - it's a directive that watches for changes on the select element. If a change is detected it appends an option and removes it immediately:

.directive('ieSelectFix',function($timeout){
  return {
    require:'select',
    link: function (scope, element) {
      var isIE = document.attachEvent;

      if(isIE){
        $timeout(function(){
          var index = element.prop('selectedIndex'), children = element.children().length;
          scope.$watch(function(){
            if(index !== element.prop('selectedIndex') || children !== element.children().length){
              index = element.prop('selectedIndex');
              children = element.children().length;
              var tmp =angular.element('<option></option>');
              element.append(tmp);
              tmp.remove();
            }
          })

        });
      }
    }
  }
});

Just add ie-select-fix to select elements inside ng-repeats:

<div ng-app="myApp" ng-controller="MyCtrl">
  <select ie-select-fix ng-repeat="currId in selectedIds" ng-model="selectedIds[$index]"  ng-options="currOption.id as currOption.value for currOption in myObj | myfilter:selectedIds:$index"></select><br>
  {{selectedIds}}
</div>

http://jsfiddle.net/VgpyZ/ (new)

like image 25
joakimbl Avatar answered Oct 05 '22 01:10

joakimbl


i've found the same bug in IE with select's.. this bug is the result of cloning DOM nodes..

if you instantiating a SELECT like (jQuery style):

$select = $template.clone();

and then doing:

$select.html('<option>111</option>');

you'll get the bug, described above..

BUT, if you instantiate

$select = $('< div />').html( $template ).html();

no bugs occurred :)

like image 20
enslaved Avatar answered Oct 05 '22 03:10

enslaved


Oh, I'm going to hell for the following advice...!

I tried these suggestions, but none worked for me.

I was actually using Angular to populate select controls with multiple options in each.

<select class="cssMultipleSelect" multiple="multiple" ...>

Sometimes, Angular would populate these controls, the new data would appear, but in IE, you couldn't scroll up and down to view all of the options.

But, if you hit F12, modified the width, and put it back to its original width, then IE would burst back into life again, and you could scroll up and down in the list of values.

So, my solution was to call this, a second or so after Angular had finished populating the controls:

function RefreshMultipleSelectControls()
{
    //  A dodgy fix to an IE11 issue.
    setTimeout(function () {
        $(".cssMultipleSelect").width("");
    }, 1500);
    setTimeout(function () {
        $(".cssMultipleSelect").width(298);
    }, 1600);
}

(I told you this was a dodgy fix..)

One other thing: remember that in IE11, navigator.appName will now return NETSCAPE (rather than MSIE or Microsoft Internet Explorer)... so be careful when you're testing if your code is running on IE, or on a decent browser.

You've been warned..!!

like image 40
Mike Gledhill Avatar answered Oct 05 '22 02:10

Mike Gledhill