@@ -672,6 +672,24 @@ function $CompileProvider($provide) {
672672 }
673673 } ,
674674
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+
675693 /**
676694 * Set a normalized attribute on the element in a way such that all directives
677695 * can share the attribute. This function properly handles boolean attributes.
@@ -682,59 +700,53 @@ function $CompileProvider($provide) {
682700 * @param {string= } attrName Optional none normalized name. Defaults to key.
683701 */
684702 $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.
697706
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 ;
702710
703- this [ key ] = value ;
711+ if ( booleanKey ) {
712+ this . $$element . prop ( key , value ) ;
713+ attrName = booleanKey ;
714+ }
704715
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 , '-' ) ;
713725 }
726+ }
714727
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 ;
728740 }
729741 }
730742 }
743+ }
731744
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 ) ;
738750 }
739751 }
740752
@@ -1816,9 +1828,19 @@ function $CompileProvider($provide) {
18161828 attr [ name ] = interpolateFn ( scope ) ;
18171829 ( $$observers [ name ] || ( $$observers [ name ] = [ ] ) ) . $$inter = true ;
18181830 ( 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+ } ) ;
18221844 }
18231845 } ;
18241846 }
@@ -1958,3 +1980,19 @@ function directiveLinkingFn(
19581980 /* Element */ rootElement ,
19591981 /* function(Function) */ boundTranscludeFn
19601982) { }
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