diff options
| author | Matias Niemelä | 2013-08-01 20:13:36 -0400 | 
|---|---|---|
| committer | Misko Hevery | 2013-08-03 00:46:18 -0700 | 
| commit | f2dfa8916f8ed855d55187f5400c4c2566ce9a1b (patch) | |
| tree | f3fcaa9a956751836da05f0e6eb1383d6421de15 | |
| parent | d45ac7707eb336d47116558e49893f910fdaf1fb (diff) | |
| download | angular.js-f2dfa8916f8ed855d55187f5400c4c2566ce9a1b.tar.bz2 | |
feat($compile): support compile animation hooks classes
| -rw-r--r-- | src/ng/compile.js | 138 | ||||
| -rw-r--r-- | src/ng/directive/ngClass.js | 17 | ||||
| -rwxr-xr-x | test/ng/compileSpec.js | 51 | 
3 files changed, 156 insertions, 50 deletions
| diff --git a/src/ng/compile.js b/src/ng/compile.js index 6aebe537..340263b3 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -274,9 +274,9 @@ function $CompileProvider($provide) {    this.$get = [              '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', -            '$controller', '$rootScope', '$document', '$sce', '$$urlUtils', +            '$controller', '$rootScope', '$document', '$sce', '$$urlUtils', '$animate',      function($injector,   $interpolate,   $exceptionHandler,   $http,   $templateCache,   $parse, -             $controller,   $rootScope,   $document,   $sce,   $$urlUtils) { +             $controller,   $rootScope,   $document,   $sce,   $$urlUtils, $animate) {      var Attributes = function(element, attr) {        this.$$element = element; @@ -288,6 +288,42 @@ function $CompileProvider($provide) {        /** +       * @ngdoc function +       * @name ng.$compile.directive.Attributes#$addClass +       * @methodOf ng.$compile.directive.Attributes +       * @function +       * +       * @description +       * Adds the CSS class value specified by the classVal parameter to the element. If animations +       * are enabled then an animation will be triggered for the class addition.   +       * +       * @param {string} classVal The className value that will be added to the element +       */ +      $addClass : function(classVal) { +        if(classVal && classVal.length > 0) { +          $animate.addClass(this.$$element, classVal); +        } +      }, + +      /** +       * @ngdoc function +       * @name ng.$compile.directive.Attributes#$removeClass +       * @methodOf ng.$compile.directive.Attributes +       * @function +       * +       * @description +       * Removes the CSS class value specified by the classVal parameter from the element. If animations +       * are enabled then an animation will be triggered for the class removal.   +       * +       * @param {string} classVal The className value that will be removed from the element +       */ +      $removeClass : function(classVal) { +        if(classVal && classVal.length > 0) { +          $animate.removeClass(this.$$element, classVal); +        } +      }, + +      /**         * Set a normalized attribute on the element in a way such that all directives         * can share the attribute. This function properly handles boolean attributes.         * @param {string} key Normalized key. (ie ngAttribute) @@ -297,54 +333,64 @@ function $CompileProvider($provide) {         * @param {string=} attrName Optional none normalized name. Defaults to key.         */        $set: function(key, value, writeAttr, attrName) { -        var booleanKey = getBooleanAttrName(this.$$element[0], key), -            $$observers = this.$$observers, -            normalizedVal, -            nodeName; - -        if (booleanKey) { -          this.$$element.prop(key, value); -          attrName = booleanKey; -        } +        //special case for class attribute addition + removal +        //so that class changes can tap into the animation +        //hooks provided by the $animate service +        if(key == 'class') { +          value = value || ''; +          var current = this.$$element.attr('class') || ''; +          this.$removeClass(tokenDifference(current, value).join(' ')); +          this.$addClass(tokenDifference(value, current).join(' ')); +        } else { +          var booleanKey = getBooleanAttrName(this.$$element[0], key), +              normalizedVal, +              nodeName; -        this[key] = value; +          if (booleanKey) { +            this.$$element.prop(key, value); +            attrName = booleanKey; +          } -        // translate normalized key to actual key -        if (attrName) { -          this.$attr[key] = attrName; -        } else { -          attrName = this.$attr[key]; -          if (!attrName) { -            this.$attr[key] = attrName = snake_case(key, '-'); +          this[key] = value; + +          // translate normalized key to actual key +          if (attrName) { +            this.$attr[key] = attrName; +          } else { +            attrName = this.$attr[key]; +            if (!attrName) { +              this.$attr[key] = attrName = snake_case(key, '-'); +            }            } -        } -        nodeName = nodeName_(this.$$element); - -        // sanitize a[href] and img[src] values -        if ((nodeName === 'A' && key === 'href') || -            (nodeName === 'IMG' && key === 'src')) { -          // NOTE: $$urlUtils.resolve() doesn't support IE < 8 so we don't sanitize for that case. -          if (!msie || msie >= 8 ) { -            normalizedVal = $$urlUtils.resolve(value); -            if (normalizedVal !== '') { -              if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) || -                  (key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) { -                this[key] = value = 'unsafe:' + normalizedVal; +          nodeName = nodeName_(this.$$element); + +          // sanitize a[href] and img[src] values +          if ((nodeName === 'A' && key === 'href') || +              (nodeName === 'IMG' && key === 'src')) { +            // NOTE: $$urlUtils.resolve() doesn't support IE < 8 so we don't sanitize for that case. +            if (!msie || msie >= 8 ) { +              normalizedVal = $$urlUtils.resolve(value); +              if (normalizedVal !== '') { +                if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) || +                    (key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) { +                  this[key] = value = 'unsafe:' + normalizedVal; +                }                }              }            } -        } -        if (writeAttr !== false) { -          if (value === null || value === undefined) { -            this.$$element.removeAttr(attrName); -          } else { -            this.$$element.attr(attrName, value); +          if (writeAttr !== false) { +            if (value === null || value === undefined) { +              this.$$element.removeAttr(attrName); +            } else { +              this.$$element.attr(attrName, value); +            }            }          }          // fire observers +        var $$observers = this.$$observers;          $$observers && forEach($$observers[key], function(fn) {            try {              fn(value); @@ -352,6 +398,22 @@ function $CompileProvider($provide) {              $exceptionHandler(e);            }          }); + +        function tokenDifference(str1, str2) { +          var values = [], +              tokens1 = str1.split(/\s+/), +              tokens2 = str2.split(/\s+/); + +          outer: +          for(var i=0;i<tokens1.length;i++) { +            var token = tokens1[i]; +            for(var j=0;j<tokens2.length;j++) { +              if(token == tokens2[j]) continue outer; +            } +            values.push(token); +          } +          return values; +        };        }, diff --git a/src/ng/directive/ngClass.js b/src/ng/directive/ngClass.js index 001f67bb..38b804b5 100644 --- a/src/ng/directive/ngClass.js +++ b/src/ng/directive/ngClass.js @@ -2,7 +2,7 @@  function classDirective(name, selector) {    name = 'ngClass' + name; -  return ['$animate', function($animate) { +  return function() {      return {        restrict: 'AC',        link: function(scope, element, attr) { @@ -11,8 +11,7 @@ function classDirective(name, selector) {          scope.$watch(attr[name], ngClassWatchAction, true);          attr.$observe('class', function(value) { -          var ngClass = scope.$eval(attr[name]); -          ngClassWatchAction(ngClass, ngClass); +          ngClassWatchAction(scope.$eval(attr[name]));          }); @@ -42,18 +41,12 @@ function classDirective(name, selector) {          function removeClass(classVal) { -          classVal = flattenClasses(classVal); -          if(classVal && classVal.length > 0) { -            $animate.removeClass(element, classVal); -          } +          attr.$removeClass(flattenClasses(classVal));          }          function addClass(classVal) { -          classVal = flattenClasses(classVal); -          if(classVal && classVal.length > 0) { -            $animate.addClass(element, classVal); -          } +          attr.$addClass(flattenClasses(classVal));          }          function flattenClasses(classVal) { @@ -73,7 +66,7 @@ function classDirective(name, selector) {          };        }      }; -  }]; +  };  }  /** diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index c1dedd4a..5dfad4be 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -3268,4 +3268,55 @@ describe('$compile', function() {        expect(spans.eq(3)).toBeHidden();      }));    }); + +  describe('$animate animation hooks', function() { + +    beforeEach(module('mock.animate')); + +    it('should automatically fire the addClass and removeClass animation hooks', +      inject(function($compile, $animate, $rootScope) { + +        var data, element = jqLite('<div class="{{val1}} {{val2}} fire"></div>'); +        $compile(element)($rootScope); + +        $rootScope.$digest(); +        data = $animate.flushNext('removeClass'); + +        expect(element.hasClass('fire')).toBe(true); + +        $rootScope.val1 = 'ice'; +        $rootScope.val2 = 'rice'; +        $rootScope.$digest(); + +        data = $animate.flushNext('addClass'); +        expect(data.params[1]).toBe('ice rice'); + +        expect(element.hasClass('ice')).toBe(true); +        expect(element.hasClass('rice')).toBe(true); +        expect(element.hasClass('fire')).toBe(true); + +        $rootScope.val2 = 'dice'; +        $rootScope.$digest(); + +        data = $animate.flushNext('removeClass'); +        expect(data.params[1]).toBe('rice'); +        data = $animate.flushNext('addClass'); +        expect(data.params[1]).toBe('dice'); + +        expect(element.hasClass('ice')).toBe(true); +        expect(element.hasClass('dice')).toBe(true); +        expect(element.hasClass('fire')).toBe(true); + +        $rootScope.val1 = ''; +        $rootScope.val2 = ''; +        $rootScope.$digest(); + +        data = $animate.flushNext('removeClass'); +        expect(data.params[1]).toBe('ice dice'); + +        expect(element.hasClass('ice')).toBe(false); +        expect(element.hasClass('dice')).toBe(false); +        expect(element.hasClass('fire')).toBe(true); +      })); +  });  }); | 
