@@ -245,7 +245,7 @@ var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s
245245// jshint maxlen: 100
246246
247247
248- var ngOptionsDirective = [ '$compile' , '$parse' , function ( $compile , $parse ) {
248+ var ngOptionsDirective = [ '$compile' , '$document' , '$ parse', function ( $compile , $document , $parse ) {
249249
250250 function parseOptionsExpression ( optionsExp , selectElement , scope ) {
251251
@@ -432,7 +432,10 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
432432
433433 var options ;
434434 var ngOptions = parseOptionsExpression ( attr . ngOptions , selectElement , scope ) ;
435-
435+ // This stores the newly created options before they are appended to the select.
436+ // Since the contents are removed from the fragment when it is appended,
437+ // we only need to create it once.
438+ var listFragment = $document [ 0 ] . createDocumentFragment ( ) ;
436439
437440 var renderEmptyOption = function ( ) {
438441 if ( ! providedEmptyOption ) {
@@ -581,6 +584,8 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
581584 emptyOption = jqLite ( optionTemplate . cloneNode ( false ) ) ;
582585 }
583586
587+ selectElement . empty ( ) ;
588+
584589 // We need to do this here to ensure that the options object is defined
585590 // when we first hit it in writeNgOptionsValue
586591 updateOptions ( ) ;
@@ -590,6 +595,12 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
590595
591596 // ------------------------------------------------------------------ //
592597
598+ function addOptionElement ( option , parent ) {
599+ var optionElement = optionTemplate . cloneNode ( false ) ;
600+ parent . appendChild ( optionElement ) ;
601+ updateOptionElement ( option , optionElement ) ;
602+ }
603+
593604
594605 function updateOptionElement ( option , element ) {
595606 option . element = element ;
@@ -606,133 +617,66 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
606617 if ( option . value !== element . value ) element . value = option . selectValue ;
607618 }
608619
609- function addOrReuseElement ( parent , current , type , templateElement ) {
610- var element ;
611- // Check whether we can reuse the next element
612- if ( current && lowercase ( current . nodeName ) === type ) {
613- // The next element is the right type so reuse it
614- element = current ;
615- } else {
616- // The next element is not the right type so create a new one
617- element = templateElement . cloneNode ( false ) ;
618- if ( ! current ) {
619- // There are no more elements so just append it to the select
620- parent . appendChild ( element ) ;
621- } else {
622- // The next element is not a group so insert the new one
623- parent . insertBefore ( element , current ) ;
624- }
625- }
626- return element ;
627- }
628-
629-
630- function removeExcessElements ( current ) {
631- var next ;
632- while ( current ) {
633- next = current . nextSibling ;
634- jqLiteRemove ( current ) ;
635- current = next ;
636- }
637- }
638-
620+ function updateOptions ( ) {
621+ var previousValue = options && selectCtrl . readValue ( ) ;
639622
640- function skipEmptyAndUnknownOptions ( current ) {
641- var emptyOption_ = emptyOption && emptyOption [ 0 ] ;
642- var unknownOption_ = unknownOption && unknownOption [ 0 ] ;
643-
644- // We cannot rely on the extracted empty option being the same as the compiled empty option,
645- // because the compiled empty option might have been replaced by a comment because
646- // it had an "element" transclusion directive on it (such as ngIf)
647- if ( emptyOption_ || unknownOption_ ) {
648- while ( current &&
649- ( current === emptyOption_ ||
650- current === unknownOption_ ||
651- current . nodeType === NODE_TYPE_COMMENT ||
652- ( nodeName_ ( current ) === ' option' && current . value === '' ) ) ) {
653- current = current . nextSibling ;
623+ // We must remove all current options, but cannot simply set innerHTML = null
624+ // since the providedEmptyOption might have an ngIf on it that inserts comments which we
625+ // must preserve.
626+ // Instead, iterate over the current option elements and remove them or their optgroup
627+ // parents
628+ if ( options ) {
629+
630+ for ( var i = options . items . length - 1 ; i >= 0 ; i -- ) {
631+ var option = options . items [ i ] ;
632+ if ( option . group ) {
633+ jqLiteRemove ( option . element . parentNode ) ;
634+ } else {
635+ jqLiteRemove ( option . element ) ;
636+ }
654637 }
655638 }
656- return current ;
657- }
658-
659-
660- function updateOptions ( ) {
661-
662- var previousValue = options && selectCtrl . readValue ( ) ;
663639
664640 options = ngOptions . getOptions ( ) ;
665641
666- var groupMap = { } ;
667- var currentElement = selectElement [ 0 ] . firstChild ;
642+ var groupElementMap = { } ;
668643
669644 // Ensure that the empty option is always there if it was explicitly provided
670645 if ( providedEmptyOption ) {
671646 selectElement . prepend ( emptyOption ) ;
672647 }
673648
674- currentElement = skipEmptyAndUnknownOptions ( currentElement ) ;
675-
676- options . items . forEach ( function updateOption ( option ) {
677- var group ;
649+ options . items . forEach ( function addOption ( option ) {
678650 var groupElement ;
679- var optionElement ;
680651
681652 if ( isDefined ( option . group ) ) {
682653
683654 // This option is to live in a group
684655 // See if we have already created this group
685- group = groupMap [ option . group ] ;
656+ groupElement = groupElementMap [ option . group ] ;
686657
687- if ( ! group ) {
658+ if ( ! groupElement ) {
688659
689- // We have not already created this group
690- groupElement = addOrReuseElement ( selectElement [ 0 ] ,
691- currentElement ,
692- 'optgroup' ,
693- optGroupTemplate ) ;
694- // Move to the next element
695- currentElement = groupElement . nextSibling ;
660+ groupElement = optGroupTemplate . cloneNode ( false ) ;
661+ listFragment . appendChild ( groupElement ) ;
696662
697663 // Update the label on the group element
698664 groupElement . label = option . group ;
699665
700666 // Store it for use later
701- group = groupMap [ option . group ] = {
702- groupElement : groupElement ,
703- currentOptionElement : groupElement . firstChild
704- } ;
705-
667+ groupElementMap [ option . group ] = groupElement ;
706668 }
707669
708- // So now we have a group for this option we add the option to the group
709- optionElement = addOrReuseElement ( group . groupElement ,
710- group . currentOptionElement ,
711- 'option' ,
712- optionTemplate ) ;
713- updateOptionElement ( option , optionElement ) ;
714- // Move to the next element
715- group . currentOptionElement = optionElement . nextSibling ;
670+ addOptionElement ( option , groupElement ) ;
716671
717672 } else {
718673
719674 // This option is not in a group
720- optionElement = addOrReuseElement ( selectElement [ 0 ] ,
721- currentElement ,
722- 'option' ,
723- optionTemplate ) ;
724- updateOptionElement ( option , optionElement ) ;
725- // Move to the next element
726- currentElement = optionElement . nextSibling ;
675+ addOptionElement ( option , listFragment ) ;
727676 }
728677 } ) ;
729678
730-
731- // Now remove all excess options and group
732- Object . keys ( groupMap ) . forEach ( function ( key ) {
733- removeExcessElements ( groupMap [ key ] . currentOptionElement ) ;
734- } ) ;
735- removeExcessElements ( currentElement ) ;
679+ selectElement [ 0 ] . appendChild ( listFragment ) ;
736680
737681 ngModelCtrl . $render ( ) ;
738682
0 commit comments