@@ -672,6 +672,24 @@ function $CompileProvider($provide) {
672
672
}
673
673
} ,
674
674
675
+ /**
676
+ * @ngdoc function
677
+ * @name ng.$compile.directive.Attributes#$updateClass
678
+ * @methodOf ng.$compile.directive.Attributes
679
+ * @function
680
+ *
681
+ * @description
682
+ * Adds and removes the appropriate CSS class values to the element based on the difference
683
+ * between the new and old CSS class values (specified as newClasses and oldClasses).
684
+ *
685
+ * @param {string } newClasses The current CSS className value
686
+ * @param {string } oldClasses The former CSS className value
687
+ */
688
+ $updateClass : function ( newClasses , oldClasses ) {
689
+ this . $removeClass ( tokenDifference ( oldClasses , newClasses ) ) ;
690
+ this . $addClass ( tokenDifference ( newClasses , oldClasses ) ) ;
691
+ } ,
692
+
675
693
/**
676
694
* Set a normalized attribute on the element in a way such that all directives
677
695
* can share the attribute. This function properly handles boolean attributes.
@@ -682,59 +700,53 @@ function $CompileProvider($provide) {
682
700
* @param {string= } attrName Optional none normalized name. Defaults to key.
683
701
*/
684
702
$set : function ( key , value , writeAttr , attrName ) {
685
- //special case for class attribute addition + removal
686
- //so that class changes can tap into the animation
687
- //hooks provided by the $animate service
688
- if ( key == 'class' ) {
689
- value = value || '' ;
690
- var current = this . $$element . attr ( 'class' ) || '' ;
691
- this . $removeClass ( tokenDifference ( current , value ) ) ;
692
- this . $addClass ( tokenDifference ( value , current ) ) ;
693
- } else {
694
- var booleanKey = getBooleanAttrName ( this . $$element [ 0 ] , key ) ,
695
- normalizedVal ,
696
- nodeName ;
703
+ // TODO: decide whether or not to throw an error if "class"
704
+ //is set through this function since it may cause $updateClass to
705
+ //become unstable.
697
706
698
- if ( booleanKey ) {
699
- this . $$element . prop ( key , value ) ;
700
- attrName = booleanKey ;
701
- }
707
+ var booleanKey = getBooleanAttrName ( this . $$element [ 0 ] , key ) ,
708
+ normalizedVal ,
709
+ nodeName ;
702
710
703
- this [ key ] = value ;
711
+ if ( booleanKey ) {
712
+ this . $$element . prop ( key , value ) ;
713
+ attrName = booleanKey ;
714
+ }
704
715
705
- // translate normalized key to actual key
706
- if ( attrName ) {
707
- this . $attr [ key ] = attrName ;
708
- } else {
709
- attrName = this . $attr [ key ] ;
710
- if ( ! attrName ) {
711
- this . $attr [ key ] = attrName = snake_case ( key , '-' ) ;
712
- }
716
+ this [ key ] = value ;
717
+
718
+ // translate normalized key to actual key
719
+ if ( attrName ) {
720
+ this . $attr [ key ] = attrName ;
721
+ } else {
722
+ attrName = this . $attr [ key ] ;
723
+ if ( ! attrName ) {
724
+ this . $attr [ key ] = attrName = snake_case ( key , '-' ) ;
713
725
}
726
+ }
714
727
715
- nodeName = nodeName_ ( this . $$element ) ;
716
-
717
- // sanitize a[href] and img[src] values
718
- if ( ( nodeName === 'A' && key === 'href' ) ||
719
- ( nodeName === 'IMG' && key === 'src' ) ) {
720
- // NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case.
721
- if ( ! msie || msie >= 8 ) {
722
- normalizedVal = urlResolve ( value ) . href ;
723
- if ( normalizedVal !== '' ) {
724
- if ( ( key === 'href' && ! normalizedVal . match ( aHrefSanitizationWhitelist ) ) ||
725
- ( key === 'src' && ! normalizedVal . match ( imgSrcSanitizationWhitelist ) ) ) {
726
- this [ key ] = value = 'unsafe:' + normalizedVal ;
727
- }
728
+ nodeName = nodeName_ ( this . $$element ) ;
729
+
730
+ // sanitize a[href] and img[src] values
731
+ if ( ( nodeName === 'A' && key === 'href' ) ||
732
+ ( nodeName === 'IMG' && key === 'src' ) ) {
733
+ // NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case.
734
+ if ( ! msie || msie >= 8 ) {
735
+ normalizedVal = urlResolve ( value ) . href ;
736
+ if ( normalizedVal !== '' ) {
737
+ if ( ( key === 'href' && ! normalizedVal . match ( aHrefSanitizationWhitelist ) ) ||
738
+ ( key === 'src' && ! normalizedVal . match ( imgSrcSanitizationWhitelist ) ) ) {
739
+ this [ key ] = value = 'unsafe:' + normalizedVal ;
728
740
}
729
741
}
730
742
}
743
+ }
731
744
732
- if ( writeAttr !== false ) {
733
- if ( value === null || value === undefined ) {
734
- this . $$element . removeAttr ( attrName ) ;
735
- } else {
736
- this . $$element . attr ( attrName , value ) ;
737
- }
745
+ if ( writeAttr !== false ) {
746
+ if ( value === null || value === undefined ) {
747
+ this . $$element . removeAttr ( attrName ) ;
748
+ } else {
749
+ this . $$element . attr ( attrName , value ) ;
738
750
}
739
751
}
740
752
@@ -1816,9 +1828,19 @@ function $CompileProvider($provide) {
1816
1828
attr [ name ] = interpolateFn ( scope ) ;
1817
1829
( $$observers [ name ] || ( $$observers [ name ] = [ ] ) ) . $$inter = true ;
1818
1830
( attr . $$observers && attr . $$observers [ name ] . $$scope || scope ) .
1819
- $watch ( interpolateFn , function interpolateFnWatchAction ( value ) {
1820
- attr . $set ( name , value ) ;
1821
- } ) ;
1831
+ $watch ( interpolateFn , function interpolateFnWatchAction ( newValue , oldValue ) {
1832
+ //special case for class attribute addition + removal
1833
+ //so that class changes can tap into the animation
1834
+ //hooks provided by the $animate service. Be sure to
1835
+ //skip animations when the first digest occurs (when
1836
+ //both the new and the old values are the same) since
1837
+ //the CSS classes are the non-interpolated values
1838
+ if ( name === 'class' && newValue != oldValue ) {
1839
+ attr . $updateClass ( newValue , oldValue ) ;
1840
+ } else {
1841
+ attr . $set ( name , newValue ) ;
1842
+ }
1843
+ } ) ;
1822
1844
}
1823
1845
} ;
1824
1846
}
@@ -1958,3 +1980,19 @@ function directiveLinkingFn(
1958
1980
/* Element */ rootElement ,
1959
1981
/* function(Function) */ boundTranscludeFn
1960
1982
) { }
1983
+
1984
+ function tokenDifference ( str1 , str2 ) {
1985
+ var values = '' ,
1986
+ tokens1 = str1 . split ( / \s + / ) ,
1987
+ tokens2 = str2 . split ( / \s + / ) ;
1988
+
1989
+ outer:
1990
+ for ( var i = 0 ; i < tokens1 . length ; i ++ ) {
1991
+ var token = tokens1 [ i ] ;
1992
+ for ( var j = 0 ; j < tokens2 . length ; j ++ ) {
1993
+ if ( token == tokens2 [ j ] ) continue outer;
1994
+ }
1995
+ values += ( values . length > 0 ? ' ' : '' ) + token ;
1996
+ }
1997
+ return values ;
1998
+ }
0 commit comments