mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-05-12 08:53:10 +00:00
feat(ngAnimate): add support for animation
This commit is contained in:
parent
4bfb66ce0b
commit
0b6f1ce5f8
25 changed files with 1611 additions and 84 deletions
4
angularFiles.js
vendored
4
angularFiles.js
vendored
|
|
@ -9,6 +9,8 @@ angularFiles = {
|
||||||
'src/auto/injector.js',
|
'src/auto/injector.js',
|
||||||
|
|
||||||
'src/ng/anchorScroll.js',
|
'src/ng/anchorScroll.js',
|
||||||
|
'src/ng/animation.js',
|
||||||
|
'src/ng/animator.js',
|
||||||
'src/ng/browser.js',
|
'src/ng/browser.js',
|
||||||
'src/ng/cacheFactory.js',
|
'src/ng/cacheFactory.js',
|
||||||
'src/ng/compile.js',
|
'src/ng/compile.js',
|
||||||
|
|
@ -71,7 +73,6 @@ angularFiles = {
|
||||||
'src/ngMock/angular-mocks.js',
|
'src/ngMock/angular-mocks.js',
|
||||||
'src/ngMobile/mobile.js',
|
'src/ngMobile/mobile.js',
|
||||||
'src/ngMobile/directive/ngClick.js',
|
'src/ngMobile/directive/ngClick.js',
|
||||||
|
|
||||||
'src/bootstrap/bootstrap.js'
|
'src/bootstrap/bootstrap.js'
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
@ -103,6 +104,7 @@ angularFiles = {
|
||||||
'test/ng/*.js',
|
'test/ng/*.js',
|
||||||
'test/ng/directive/*.js',
|
'test/ng/directive/*.js',
|
||||||
'test/ng/filter/*.js',
|
'test/ng/filter/*.js',
|
||||||
|
'test/ngAnimate/*.js',
|
||||||
'test/ngCookies/*.js',
|
'test/ngCookies/*.js',
|
||||||
'test/ngResource/*.js',
|
'test/ngResource/*.js',
|
||||||
'test/ngSanitize/*.js',
|
'test/ngSanitize/*.js',
|
||||||
|
|
|
||||||
|
|
@ -475,6 +475,22 @@ describe('ngdoc', function() {
|
||||||
'<div><p>I am self.</p></div>');
|
'<div><p>I am self.</p></div>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('@animations', function() {
|
||||||
|
it('should render @this', function() {
|
||||||
|
var doc = new Doc('@name a\n@animations\nenter - Add text\nleave - Remove text\n');
|
||||||
|
doc.ngdoc = 'filter';
|
||||||
|
doc.parse();
|
||||||
|
expect(doc.html()).toContain(
|
||||||
|
'<h3 id="Animations">Animations</h3>\n' +
|
||||||
|
'<div class="animations">' +
|
||||||
|
'<ul>' +
|
||||||
|
'<li>enter - Add text</li>' +
|
||||||
|
'<li>leave - Remove text</li>' +
|
||||||
|
'</ul>' +
|
||||||
|
'</div>');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('usage', function() {
|
describe('usage', function() {
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,9 @@ DOM.prototype = {
|
||||||
replace(/-+/gm, '-').
|
replace(/-+/gm, '-').
|
||||||
replace(/-*$/gm, '');
|
replace(/-*$/gm, '');
|
||||||
anchor = {'id': id};
|
anchor = {'id': id};
|
||||||
className = {'class': id.toLowerCase().replace(/[._]/mg, '-')};
|
var classNameValue = id.toLowerCase().replace(/[._]/mg, '-');
|
||||||
|
if(classNameValue == 'hide') classNameValue = '';
|
||||||
|
className = {'class': classNameValue};
|
||||||
}
|
}
|
||||||
this.tag('h' + this.headingDepth, anchor, heading);
|
this.tag('h' + this.headingDepth, anchor, heading);
|
||||||
if (content instanceof Array) {
|
if (content instanceof Array) {
|
||||||
|
|
|
||||||
|
|
@ -328,6 +328,18 @@ Doc.prototype = {
|
||||||
});
|
});
|
||||||
dom.html(param.description);
|
dom.html(param.description);
|
||||||
});
|
});
|
||||||
|
if(this.animations) {
|
||||||
|
dom.h('Animations', this.animations, function(animations){
|
||||||
|
dom.html('<ul>');
|
||||||
|
var animations = animations.split("\n");
|
||||||
|
animations.forEach(function(ani) {
|
||||||
|
dom.html('<li>');
|
||||||
|
dom.text(ani);
|
||||||
|
dom.html('</li>');
|
||||||
|
});
|
||||||
|
dom.html('</ul>');
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
html_usage_returns: function(dom) {
|
html_usage_returns: function(dom) {
|
||||||
|
|
@ -433,6 +445,48 @@ Doc.prototype = {
|
||||||
dom.text('</' + element + '>');
|
dom.text('</' + element + '>');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if(self.animations) {
|
||||||
|
var animations = [], matches = self.animations.split("\n");
|
||||||
|
matches.forEach(function(ani) {
|
||||||
|
var name = ani.match(/^\s*(.+?)\s*-/)[1];
|
||||||
|
animations.push(name);
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.html('with <span id="animations">animations</span>');
|
||||||
|
var comment;
|
||||||
|
if(animations.length == 1) {
|
||||||
|
comment = 'The ' + animations[0] + ' animation is supported';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var rhs = animations[animations.length-1];
|
||||||
|
var lhs = '';
|
||||||
|
for(var i=0;i<animations.length-1;i++) {
|
||||||
|
if(i>0) {
|
||||||
|
lhs += ', ';
|
||||||
|
}
|
||||||
|
lhs += animations[i];
|
||||||
|
}
|
||||||
|
comment = 'The ' + lhs + ' and ' + rhs + ' animations are supported';
|
||||||
|
}
|
||||||
|
var element = self.element || 'ANY';
|
||||||
|
dom.code(function() {
|
||||||
|
dom.text('//' + comment + "\n");
|
||||||
|
dom.text('<' + element + ' ');
|
||||||
|
dom.text(dashCase(self.shortName));
|
||||||
|
renderParams('\n ', '="', '"', true);
|
||||||
|
dom.text(' ng-animate="{');
|
||||||
|
animations.forEach(function(ani, index) {
|
||||||
|
if (index) {
|
||||||
|
dom.text(', ');
|
||||||
|
}
|
||||||
|
dom.text(ani + ': \'' + ani + '-animation\'');
|
||||||
|
});
|
||||||
|
dom.text('}">\n ...\n');
|
||||||
|
dom.text('</' + element + '>');
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.html('<a href="api/ng.$animator#Methods">Click here</a> to learn more about the steps involved in the animation.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.html_usage_directiveInfo(dom);
|
self.html_usage_directiveInfo(dom);
|
||||||
self.html_usage_parameters(dom);
|
self.html_usage_parameters(dom);
|
||||||
|
|
|
||||||
|
|
@ -977,9 +977,9 @@ var _EncodeCode = function(text) {
|
||||||
|
|
||||||
var _DoItalicsAndBold = function(text) {
|
var _DoItalicsAndBold = function(text) {
|
||||||
|
|
||||||
// <strong> must go first:
|
// ** must go first:
|
||||||
text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,
|
text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,
|
||||||
"<strong>$2</strong>");
|
"**$2</strong>");
|
||||||
|
|
||||||
text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,
|
text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,
|
||||||
"<em>$2</em>");
|
"<em>$2</em>");
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,11 @@ function inherit(parent, extra) {
|
||||||
return extend(new (extend(function() {}, {prototype:parent}))(), extra);
|
return extend(new (extend(function() {}, {prototype:parent}))(), extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var START_SPACE = /^\s*/;
|
||||||
|
var END_SPACE = /\s*$/;
|
||||||
|
function stripWhitespace(str) {
|
||||||
|
return isString(str) ? str.replace(START_SPACE, '').replace(END_SPACE, '') : str;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ngdoc function
|
* @ngdoc function
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,8 @@ function publishExternalAPI(angular){
|
||||||
directive(ngEventDirectives);
|
directive(ngEventDirectives);
|
||||||
$provide.provider({
|
$provide.provider({
|
||||||
$anchorScroll: $AnchorScrollProvider,
|
$anchorScroll: $AnchorScrollProvider,
|
||||||
|
$animation: $AnimationProvider,
|
||||||
|
$animator: $AnimatorProvider,
|
||||||
$browser: $BrowserProvider,
|
$browser: $BrowserProvider,
|
||||||
$cacheFactory: $CacheFactoryProvider,
|
$cacheFactory: $CacheFactoryProvider,
|
||||||
$controller: $ControllerProvider,
|
$controller: $ControllerProvider,
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,33 @@ function setupModuleLoader(window) {
|
||||||
*/
|
*/
|
||||||
constant: invokeLater('$provide', 'constant', 'unshift'),
|
constant: invokeLater('$provide', 'constant', 'unshift'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc method
|
||||||
|
* @name angular.Module#animation
|
||||||
|
* @methodOf angular.Module
|
||||||
|
* @param {string} name animation name
|
||||||
|
* @param {Function} animationFactory Factory function for creating new instance of an animation.
|
||||||
|
* @description
|
||||||
|
*
|
||||||
|
* Defines an animation hook that can be later used with {@link ng.directive:ngAnimate ngAnimate}
|
||||||
|
* alongside {@link ng.directive:ngAnimate#Description common ng directives} as well as custom directives.
|
||||||
|
* <pre>
|
||||||
|
* module.animation('animation-name', function($inject1, $inject2) {
|
||||||
|
* return {
|
||||||
|
* //this gets called in preparation to setup an animation
|
||||||
|
* setup : function(element) { ... },
|
||||||
|
*
|
||||||
|
* //this gets called once the animation is run
|
||||||
|
* start : function(element, done, memo) { ... }
|
||||||
|
* }
|
||||||
|
* })
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* See {@link ng.$animationProvider#register $animationProvider.register()} and
|
||||||
|
* {@link ng.directive:ngAnimate ngAnimate} for more information.
|
||||||
|
*/
|
||||||
|
animation: invokeLater('$animationProvider', 'register'),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ngdoc method
|
* @ngdoc method
|
||||||
* @name angular.Module#filter
|
* @name angular.Module#filter
|
||||||
|
|
|
||||||
65
src/ng/animation.js
Normal file
65
src/ng/animation.js
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* @ngdoc object
|
||||||
|
* @name ng.$animationProvider
|
||||||
|
* @description
|
||||||
|
*
|
||||||
|
* The $AnimationProvider provider allows developers to register and access custom JavaScript animations directly inside
|
||||||
|
* of a module.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
$AnimationProvider.$inject = ['$provide'];
|
||||||
|
function $AnimationProvider($provide) {
|
||||||
|
var suffix = 'Animation';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name ng.$animation#register
|
||||||
|
* @methodOf ng.$animationProvider
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* Registers a new injectable animation factory function. The factory function produces the animation object which
|
||||||
|
* has these two properties:
|
||||||
|
*
|
||||||
|
* * `setup`: `function(Element):*` A function which receives the starting state of the element. The purpose
|
||||||
|
* of this function is to get the element ready for animation. Optionally the function returns an memento which
|
||||||
|
* is passed to the `start` function.
|
||||||
|
* * `start`: `function(Element, doneFunction, *)` The element to animate, the `doneFunction` to be called on
|
||||||
|
* element animation completion, and an optional memento from the `setup` function.
|
||||||
|
*
|
||||||
|
* @param {string} name The name of the animation.
|
||||||
|
* @param {function} factory The factory function that will be executed to return the animation object.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
this.register = function(name, factory) {
|
||||||
|
$provide.factory(camelCase(name) + suffix, factory);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$get = ['$injector', function($injector) {
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name ng.$animation
|
||||||
|
* @function
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* The $animation service is used to retrieve any defined animation functions. When executed, the $animation service
|
||||||
|
* will return a object that contains the setup and start functions that were defined for the animation.
|
||||||
|
*
|
||||||
|
* @param {String} name Name of the animation function to retrieve. Animation functions are registered and stored
|
||||||
|
* inside of the AngularJS DI so a call to $animate('custom') is the same as injecting `customAnimation`
|
||||||
|
* via dependency injection.
|
||||||
|
* @return {Object} the animation object which contains the `setup` and `start` functions that perform the animation.
|
||||||
|
*/
|
||||||
|
return function $animation(name) {
|
||||||
|
if (name) {
|
||||||
|
try {
|
||||||
|
return $injector.get(camelCase(name) + suffix);
|
||||||
|
} catch (e) {
|
||||||
|
//TODO(misko): this is a hack! we should have a better way to test if the injector has a given key.
|
||||||
|
// The issue is that the animations are optional, and if not present they should be silently ignored.
|
||||||
|
// The proper way to fix this is to add API onto the injector so that we can ask to see if a given
|
||||||
|
// animation is supported.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
};
|
||||||
312
src/ng/animator.js
Normal file
312
src/ng/animator.js
Normal file
|
|
@ -0,0 +1,312 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// NOTE: this is a pseudo directive.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc directive
|
||||||
|
* @name ng.directive:ngAnimate
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* The `ngAnimate` directive works as an attribute that is attached alongside pre-existing directives.
|
||||||
|
* It effects how the directive will perform DOM manipulation. This allows for complex animations to take place while
|
||||||
|
* without burduning the directive which uses the animation with animation details. The built dn directives
|
||||||
|
* `ngRepeat`, `ngInclude`, `ngSwitch`, `ngShow`, `ngHide` and `ngView` already accept `ngAnimate` directive.
|
||||||
|
* Custom directives can take advantage of animation through {@link ng.$animator $animator service}.
|
||||||
|
*
|
||||||
|
* Below is a more detailed breakdown of the supported callback events provided by pre-exisitng ng directives:
|
||||||
|
*
|
||||||
|
* * {@link ng.directive:ngRepeat#animations ngRepeat} — enter, leave and move
|
||||||
|
* * {@link ng.directive:ngView#animations ngView} — enter and leave
|
||||||
|
* * {@link ng.directive:ngInclude#animations ngInclude} — enter and leave
|
||||||
|
* * {@link ng.directive:ngSwitch#animations ngSwitch} — enter and leave
|
||||||
|
* * {@link ng.directive:ngShow#animations ngShow & ngHide} - show and hide respectively
|
||||||
|
*
|
||||||
|
* You can find out more information about animations upon visiting each directive page.
|
||||||
|
*
|
||||||
|
* Below is an example of a directive that makes use of the ngAnimate attribute:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <!-- you can also use data-ng-animate, ng:animate or x-ng-animate as well -->
|
||||||
|
* <ANY ng-directive ng-animate="{event1: 'animation-name', event2: 'animation-name-2'}"></ANY>
|
||||||
|
*
|
||||||
|
* <!-- you can also use a short hand -->
|
||||||
|
* <ANY ng-directive ng-animate=" 'animation' "></ANY>
|
||||||
|
* <!-- which expands to -->
|
||||||
|
* <ANY ng-directive ng-animate="{ enter: 'animation-enter', leave: 'animation-leave', ...}"></ANY>
|
||||||
|
*
|
||||||
|
* <!-- keep in mind that ng-animate can take expressions -->
|
||||||
|
* <ANY ng-directive ng-animate=" computeCurrentAnimation() "></ANY>
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* The `event1` and `event2` attributes refer to the animation events specific to the directive that has been assigned.
|
||||||
|
*
|
||||||
|
* <h2>CSS-defined Animations</h2>
|
||||||
|
* By default, ngAnimate attaches two CSS3 classes per animation event to the DOM element to achieve the animation.
|
||||||
|
* This is up to you, the developer, to ensure that the animations take place using cross-browser CSS3 transitions.
|
||||||
|
* All that is required is the following CSS code:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <style type="text/css">
|
||||||
|
* /*
|
||||||
|
* The animate-enter prefix is the event name that you
|
||||||
|
* have provided within the ngAnimate attribute.
|
||||||
|
* */
|
||||||
|
* .animate-enter-setup {
|
||||||
|
* -webkit-transition: 1s linear all; /* Safari/Chrome */
|
||||||
|
* -moz-transition: 1s linear all; /* Firefox */
|
||||||
|
* -ms-transition: 1s linear all; /* IE10 */
|
||||||
|
* -o-transition: 1s linear all; /* Opera */
|
||||||
|
* transition: 1s linear all; /* Future Browsers */
|
||||||
|
*
|
||||||
|
* /* The animation preparation code */
|
||||||
|
* opacity: 0;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* /*
|
||||||
|
* Keep in mind that you want to combine both CSS
|
||||||
|
* classes together to avoid any CSS-specificity
|
||||||
|
* conflicts
|
||||||
|
* */
|
||||||
|
* .animate-enter-setup.animate-enter-start {
|
||||||
|
* /* The animation code itself */
|
||||||
|
* opacity: 1;
|
||||||
|
* }
|
||||||
|
* </style>
|
||||||
|
*
|
||||||
|
* <div ng-directive ng-animate="{enter: 'animate-enter'}"></div>
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* Upon DOM mutation, the setup class is added first, then the browser is allowed to reflow the content and then,
|
||||||
|
* the start class is added to trigger the animation. The ngAnimate directive will automatically extract the duration
|
||||||
|
* of the animation to determine when the animation ends. Once the animation is over then both CSS classes will be
|
||||||
|
* removed from the DOM. If a browser does not support CSS transitions then the animation will start and end
|
||||||
|
* immediately resulting in a DOM element that is at it's final state. This final state is when the DOM element
|
||||||
|
* has no CSS animation classes surrounding it.
|
||||||
|
*
|
||||||
|
* <h2>JavaScript-defined Animations</h2>
|
||||||
|
* In the event that you do not want to use CSS3 animations or if you wish to offer animations to browsers that do not
|
||||||
|
* yet support them, then you can make use of JavaScript animations defined inside ngModule.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* var ngModule = angular.module('YourApp', []);
|
||||||
|
* ngModule.animation('animate-enter', function() {
|
||||||
|
* return {
|
||||||
|
* setup : function(element) {
|
||||||
|
* //prepare the element for animation
|
||||||
|
* element.css({ 'opacity': 0 });
|
||||||
|
* var memo = "..."; //this value is passed to the start function
|
||||||
|
* return memo;
|
||||||
|
* },
|
||||||
|
* start : function(element, done, memo) {
|
||||||
|
* //start the animation
|
||||||
|
* element.animate({
|
||||||
|
* 'opacity' : 1
|
||||||
|
* }, function() {
|
||||||
|
* //call when the animation is complete
|
||||||
|
* done()
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* As you can see, the JavaScript code follows a similar template to the CSS3 animations. Once defined, the animation
|
||||||
|
* can be used in the same way with the ngAnimate attribute. Keep in mind that, when using JavaScript-enabled
|
||||||
|
* animations, ngAnimate will also add in the same CSS classes that CSS-enabled animations do (even if you're using
|
||||||
|
* JavaScript animations) to animated the element, but it will not attempt to find any CSS3 transition duration value.
|
||||||
|
* It will instead close off the animation once the provided done function is executed. So it's important that you
|
||||||
|
* make sure your animations remember to fire off the done function once the animations are complete.
|
||||||
|
*
|
||||||
|
* @param {expression} ngAnimate Used to configure the DOM manipulation animations.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name ng.$animator
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* The $animator service provides the DOM manipulation API which is decorated with animations.
|
||||||
|
*
|
||||||
|
* @param {Scope} scope the scope for the ng-animate.
|
||||||
|
* @param {Attributes} attr the attributes object which contains the ngAnimate key / value pair. (The attributes are
|
||||||
|
* passed into the linking function of the directive using the `$animator`.)
|
||||||
|
* @return {object} the animator object which contains the enter, leave, move, show, hide and animate methods.
|
||||||
|
*/
|
||||||
|
var $AnimatorProvider = function() {
|
||||||
|
this.$get = ['$animation', '$window', '$sniffer', function($animation, $window, $sniffer) {
|
||||||
|
return function(scope, attrs) {
|
||||||
|
var ngAnimateAttr = attrs.ngAnimate;
|
||||||
|
var animator = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name ng.animator#enter
|
||||||
|
* @methodOf ng.$animator
|
||||||
|
* @function
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* Injects the element object into the DOM (inside of the parent element) and then runs the enter animation.
|
||||||
|
*
|
||||||
|
* @param {jQuery/jqLite element} element the element that will be the focus of the enter animation
|
||||||
|
* @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the enter animation
|
||||||
|
* @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the enter animation
|
||||||
|
*/
|
||||||
|
animator.enter = animateActionFactory('enter', insert, noop);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name ng.animator#leave
|
||||||
|
* @methodOf ng.$animator
|
||||||
|
* @function
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* Runs the leave animation operation and, upon completion, removes the element from the DOM.
|
||||||
|
*
|
||||||
|
* @param {jQuery/jqLite element} element the element that will be the focus of the leave animation
|
||||||
|
* @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the leave animation
|
||||||
|
*/
|
||||||
|
animator.leave = animateActionFactory('leave', noop, remove);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name ng.animator#move
|
||||||
|
* @methodOf ng.$animator
|
||||||
|
* @function
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* Fires the move DOM operation. Just before the animation starts, the animator will either append it into the parent container or
|
||||||
|
* add the element directly after the after element if present. Then the move animation will be run.
|
||||||
|
*
|
||||||
|
* @param {jQuery/jqLite element} element the element that will be the focus of the move animation
|
||||||
|
* @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the move animation
|
||||||
|
* @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the move animation
|
||||||
|
*/
|
||||||
|
animator.move = animateActionFactory('move', move, noop);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name ng.animator#show
|
||||||
|
* @methodOf ng.$animator
|
||||||
|
* @function
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* Reveals the element by setting the CSS property `display` to `block` and then starts the show animation directly after.
|
||||||
|
*
|
||||||
|
* @param {jQuery/jqLite element} element the element that will be rendered visible or hidden
|
||||||
|
*/
|
||||||
|
animator.show = animateActionFactory('show', show, noop);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name ng.animator#hide
|
||||||
|
* @methodOf ng.$animator
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* Starts the hide animation first and sets the CSS `display` property to `none` upon completion.
|
||||||
|
*
|
||||||
|
* @param {jQuery/jqLite element} element the element that will be rendered visible or hidden
|
||||||
|
*/
|
||||||
|
animator.hide = animateActionFactory('hide', noop, hide);
|
||||||
|
return animator;
|
||||||
|
|
||||||
|
function animateActionFactory(type, beforeFn, afterFn) {
|
||||||
|
var ngAnimateValue = ngAnimateAttr && scope.$eval(ngAnimateAttr);
|
||||||
|
var className = ngAnimateAttr
|
||||||
|
? isObject(ngAnimateValue) ? ngAnimateValue[type] : ngAnimateValue + '-' + type
|
||||||
|
: '';
|
||||||
|
var animationPolyfill = $animation(className);
|
||||||
|
|
||||||
|
var polyfillSetup = animationPolyfill && animationPolyfill.setup;
|
||||||
|
var polyfillStart = animationPolyfill && animationPolyfill.start;
|
||||||
|
|
||||||
|
if (!className) {
|
||||||
|
return function(element, parent, after) {
|
||||||
|
beforeFn(element, parent, after);
|
||||||
|
afterFn(element, parent, after);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var setupClass = className + '-setup';
|
||||||
|
var startClass = className + '-start';
|
||||||
|
|
||||||
|
return function(element, parent, after) {
|
||||||
|
if (!$sniffer.supportsTransitions && !polyfillSetup && !polyfillStart) {
|
||||||
|
beforeFn(element, parent, after);
|
||||||
|
afterFn(element, parent, after);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
element.addClass(setupClass);
|
||||||
|
beforeFn(element, parent, after);
|
||||||
|
if (element.length == 0) return done();
|
||||||
|
|
||||||
|
var memento = (noop || polyfillSetup)(element);
|
||||||
|
|
||||||
|
// $window.setTimeout(beginAnimation, 0); this was causing the element not to animate
|
||||||
|
// keep at 1 for animation dom rerender
|
||||||
|
$window.setTimeout(beginAnimation, 1);
|
||||||
|
|
||||||
|
function beginAnimation() {
|
||||||
|
element.addClass(startClass);
|
||||||
|
if (polyfillStart) {
|
||||||
|
polyfillStart(element, done, memento);
|
||||||
|
} else if (isFunction($window.getComputedStyle)) {
|
||||||
|
var vendorTransitionProp = $sniffer.vendorPrefix + 'Transition';
|
||||||
|
var w3cTransitionProp = 'transition'; //one day all browsers will have this
|
||||||
|
|
||||||
|
var durationKey = 'Duration';
|
||||||
|
var duration = 0;
|
||||||
|
//we want all the styles defined before and after
|
||||||
|
forEach(element, function(element) {
|
||||||
|
var globalStyles = $window.getComputedStyle(element) || {};
|
||||||
|
duration = Math.max(
|
||||||
|
parseFloat(globalStyles[w3cTransitionProp + durationKey]) ||
|
||||||
|
parseFloat(globalStyles[vendorTransitionProp + durationKey]) ||
|
||||||
|
0,
|
||||||
|
duration);
|
||||||
|
});
|
||||||
|
|
||||||
|
$window.setTimeout(done, duration * 1000);
|
||||||
|
} else {
|
||||||
|
dump(3)
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function done() {
|
||||||
|
afterFn(element, parent, after);
|
||||||
|
element.removeClass(setupClass);
|
||||||
|
element.removeClass(startClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function show(element) {
|
||||||
|
element.css('display', 'block');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide(element) {
|
||||||
|
element.css('display', 'none');
|
||||||
|
}
|
||||||
|
|
||||||
|
function insert(element, parent, after) {
|
||||||
|
if (after) {
|
||||||
|
after.after(element);
|
||||||
|
} else {
|
||||||
|
parent.append(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(element) {
|
||||||
|
element.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function move(element, parent, after) {
|
||||||
|
// Do not remove element before insert. Removing will cause data associated with the
|
||||||
|
// element to be dropped. Insert will implicitly do the remove.
|
||||||
|
insert(element, parent, after);
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
@ -12,6 +12,13 @@
|
||||||
* (e.g. ngInclude won't work for cross-domain requests on all browsers and for
|
* (e.g. ngInclude won't work for cross-domain requests on all browsers and for
|
||||||
* file:// access on some browsers).
|
* file:// access on some browsers).
|
||||||
*
|
*
|
||||||
|
* Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter**
|
||||||
|
* and **leave** effects.
|
||||||
|
*
|
||||||
|
* @animations
|
||||||
|
* enter - happens just after the ngInclude contents change and a new DOM element is created and injected into the ngInclude container
|
||||||
|
* leave - happens just after the ngInclude contents change and just before the former contents are removed from the DOM
|
||||||
|
*
|
||||||
* @scope
|
* @scope
|
||||||
*
|
*
|
||||||
* @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
|
* @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
|
||||||
|
|
@ -78,8 +85,8 @@
|
||||||
* @description
|
* @description
|
||||||
* Emitted every time the ngInclude content is reloaded.
|
* Emitted every time the ngInclude content is reloaded.
|
||||||
*/
|
*/
|
||||||
var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile',
|
var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', '$animator',
|
||||||
function($http, $templateCache, $anchorScroll, $compile) {
|
function($http, $templateCache, $anchorScroll, $compile, $animator) {
|
||||||
return {
|
return {
|
||||||
restrict: 'ECA',
|
restrict: 'ECA',
|
||||||
terminal: true,
|
terminal: true,
|
||||||
|
|
@ -88,7 +95,8 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
|
||||||
onloadExp = attr.onload || '',
|
onloadExp = attr.onload || '',
|
||||||
autoScrollExp = attr.autoscroll;
|
autoScrollExp = attr.autoscroll;
|
||||||
|
|
||||||
return function(scope, element) {
|
return function(scope, element, attr) {
|
||||||
|
var animate = $animator(scope, attr);
|
||||||
var changeCounter = 0,
|
var changeCounter = 0,
|
||||||
childScope;
|
childScope;
|
||||||
|
|
||||||
|
|
@ -97,8 +105,7 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
|
||||||
childScope.$destroy();
|
childScope.$destroy();
|
||||||
childScope = null;
|
childScope = null;
|
||||||
}
|
}
|
||||||
|
animate.leave(element.contents(), element);
|
||||||
element.html('');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
scope.$watch(srcExp, function ngIncludeWatchAction(src) {
|
scope.$watch(srcExp, function ngIncludeWatchAction(src) {
|
||||||
|
|
@ -110,9 +117,12 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
|
||||||
|
|
||||||
if (childScope) childScope.$destroy();
|
if (childScope) childScope.$destroy();
|
||||||
childScope = scope.$new();
|
childScope = scope.$new();
|
||||||
|
animate.leave(element.contents(), element);
|
||||||
|
|
||||||
element.html(response);
|
var contents = jqLite('<div/>').html(response).contents();
|
||||||
$compile(element.contents())(childScope);
|
|
||||||
|
animate.enter(contents, element);
|
||||||
|
$compile(contents)(childScope);
|
||||||
|
|
||||||
if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
|
if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
|
||||||
$anchorScroll();
|
$anchorScroll();
|
||||||
|
|
@ -123,7 +133,9 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
|
||||||
}).error(function() {
|
}).error(function() {
|
||||||
if (thisChangeId === changeCounter) clearContent();
|
if (thisChangeId === changeCounter) clearContent();
|
||||||
});
|
});
|
||||||
} else clearContent();
|
} else {
|
||||||
|
clearContent();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,13 @@
|
||||||
* * `$middle` – `{boolean}` – true if the repeated element is between the first and last in the iterator.
|
* * `$middle` – `{boolean}` – true if the repeated element is between the first and last in the iterator.
|
||||||
* * `$last` – `{boolean}` – true if the repeated element is last in the iterator.
|
* * `$last` – `{boolean}` – true if the repeated element is last in the iterator.
|
||||||
*
|
*
|
||||||
|
* Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter**,
|
||||||
|
* **leave** and **move** effects.
|
||||||
|
*
|
||||||
|
* @animations
|
||||||
|
* enter - when a new item is added to the list or when an item is revealed after a filter
|
||||||
|
* leave - when an item is removed from the list or when an item is filtered out
|
||||||
|
* move - when an adjacent item is filtered out causing a reorder or when the item contents are reordered
|
||||||
*
|
*
|
||||||
* @element ANY
|
* @element ANY
|
||||||
* @scope
|
* @scope
|
||||||
|
|
@ -75,13 +82,15 @@
|
||||||
</doc:scenario>
|
</doc:scenario>
|
||||||
</doc:example>
|
</doc:example>
|
||||||
*/
|
*/
|
||||||
var ngRepeatDirective = ['$parse', function($parse) {
|
var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) {
|
||||||
|
var NG_REMOVED = '$$NG_REMOVED';
|
||||||
return {
|
return {
|
||||||
transclude: 'element',
|
transclude: 'element',
|
||||||
priority: 1000,
|
priority: 1000,
|
||||||
terminal: true,
|
terminal: true,
|
||||||
compile: function(element, attr, linker) {
|
compile: function(element, attr, linker) {
|
||||||
return function($scope, $element, $attr){
|
return function($scope, $element, $attr){
|
||||||
|
var animate = $animator($scope, $attr);
|
||||||
var expression = $attr.ngRepeat;
|
var expression = $attr.ngRepeat;
|
||||||
var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),
|
var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),
|
||||||
trackByExp, hashExpFn, trackByIdFn, lhs, rhs, valueIdentifier, keyIdentifier,
|
trackByExp, hashExpFn, trackByIdFn, lhs, rhs, valueIdentifier, keyIdentifier,
|
||||||
|
|
@ -130,6 +139,7 @@ var ngRepeatDirective = ['$parse', function($parse) {
|
||||||
$scope.$watchCollection(rhs, function ngRepeatAction(collection){
|
$scope.$watchCollection(rhs, function ngRepeatAction(collection){
|
||||||
var index, length,
|
var index, length,
|
||||||
cursor = $element, // current position of the node
|
cursor = $element, // current position of the node
|
||||||
|
nextCursor,
|
||||||
// Same as lastBlockMap but it has the current state. It will become the
|
// Same as lastBlockMap but it has the current state. It will become the
|
||||||
// lastBlockMap on the next iteration.
|
// lastBlockMap on the next iteration.
|
||||||
nextBlockMap = {},
|
nextBlockMap = {},
|
||||||
|
|
@ -184,7 +194,8 @@ var ngRepeatDirective = ['$parse', function($parse) {
|
||||||
for (key in lastBlockMap) {
|
for (key in lastBlockMap) {
|
||||||
if (lastBlockMap.hasOwnProperty(key)) {
|
if (lastBlockMap.hasOwnProperty(key)) {
|
||||||
block = lastBlockMap[key];
|
block = lastBlockMap[key];
|
||||||
block.element.remove();
|
animate.leave(block.element);
|
||||||
|
block.element[0][NG_REMOVED] = true;
|
||||||
block.scope.$destroy();
|
block.scope.$destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -200,12 +211,17 @@ var ngRepeatDirective = ['$parse', function($parse) {
|
||||||
// associated scope/element
|
// associated scope/element
|
||||||
childScope = block.scope;
|
childScope = block.scope;
|
||||||
|
|
||||||
if (block.element == cursor) {
|
nextCursor = cursor[0];
|
||||||
|
do {
|
||||||
|
nextCursor = nextCursor.nextSibling;
|
||||||
|
} while(nextCursor && nextCursor[NG_REMOVED]);
|
||||||
|
|
||||||
|
if (block.element[0] == nextCursor) {
|
||||||
// do nothing
|
// do nothing
|
||||||
cursor = block.element;
|
cursor = block.element;
|
||||||
} else {
|
} else {
|
||||||
// existing item which got moved
|
// existing item which got moved
|
||||||
cursor.after(block.element);
|
animate.move(block.element, null, cursor);
|
||||||
cursor = block.element;
|
cursor = block.element;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -221,8 +237,8 @@ var ngRepeatDirective = ['$parse', function($parse) {
|
||||||
childScope.$middle = !(childScope.$first || childScope.$last);
|
childScope.$middle = !(childScope.$first || childScope.$last);
|
||||||
|
|
||||||
if (!block.element) {
|
if (!block.element) {
|
||||||
linker(childScope, function(clone){
|
linker(childScope, function(clone) {
|
||||||
cursor.after(clone);
|
animate.enter(clone, null, cursor);
|
||||||
cursor = clone;
|
cursor = clone;
|
||||||
block.scope = childScope;
|
block.scope = childScope;
|
||||||
block.element = clone;
|
block.element = clone;
|
||||||
|
|
@ -236,3 +252,4 @@ var ngRepeatDirective = ['$parse', function($parse) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,18 @@
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* The `ngShow` and `ngHide` directives show or hide a portion of the DOM tree (HTML)
|
* The `ngShow` and `ngHide` directives show or hide a portion of the DOM tree (HTML)
|
||||||
* conditionally.
|
* conditionally based on **"truthy"** values evaluated within an {expression}. In other
|
||||||
|
* words, if the expression assigned to **ngShow evaluates to a true value** then **the element is set to visible**
|
||||||
|
* (via `display:block` in css) and **if false** then **the element is set to hidden** (so display:none).
|
||||||
|
* With ngHide this is the reverse whereas true values cause the element itself to become
|
||||||
|
* hidden.
|
||||||
|
*
|
||||||
|
* Additionally, you can also provide animations via the ngAnimate attribute to animate the **show**
|
||||||
|
* and **hide** effects.
|
||||||
|
*
|
||||||
|
* @animations
|
||||||
|
* show - happens after the ngShow expression evaluates to a truthy value and the contents are set to visible
|
||||||
|
* hide - happens before the ngShow expression evaluates to a non truthy value and just before the contents are set to hidden
|
||||||
*
|
*
|
||||||
* @element ANY
|
* @element ANY
|
||||||
* @param {expression} ngShow If the {@link guide/expression expression} is truthy
|
* @param {expression} ngShow If the {@link guide/expression expression} is truthy
|
||||||
|
|
@ -33,11 +44,14 @@
|
||||||
</doc:example>
|
</doc:example>
|
||||||
*/
|
*/
|
||||||
//TODO(misko): refactor to remove element from the DOM
|
//TODO(misko): refactor to remove element from the DOM
|
||||||
var ngShowDirective = ngDirective(function(scope, element, attr){
|
var ngShowDirective = ['$animator', function($animator) {
|
||||||
scope.$watch(attr.ngShow, function ngShowWatchAction(value){
|
return function(scope, element, attr) {
|
||||||
element.css('display', toBoolean(value) ? '' : 'none');
|
var animate = $animator(scope, attr);
|
||||||
});
|
scope.$watch(attr.ngShow, function ngShowWatchAction(value){
|
||||||
});
|
animate[toBoolean(value) ? 'show' : 'hide'](element);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -45,8 +59,19 @@ var ngShowDirective = ngDirective(function(scope, element, attr){
|
||||||
* @name ng.directive:ngHide
|
* @name ng.directive:ngHide
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* The `ngHide` and `ngShow` directives hide or show a portion of the DOM tree (HTML)
|
* The `ngShow` and `ngHide` directives show or hide a portion of the DOM tree (HTML)
|
||||||
* conditionally.
|
* conditionally based on **"truthy"** values evaluated within an {expression}. In other
|
||||||
|
* words, if the expression assigned to **ngShow evaluates to a true value** then **the element is set to visible**
|
||||||
|
* (via `display:block` in css) and **if false** then **the element is set to hidden** (so display:none).
|
||||||
|
* With ngHide this is the reverse whereas true values cause the element itself to become
|
||||||
|
* hidden.
|
||||||
|
*
|
||||||
|
* Additionally, you can also provide animations via the ngAnimate attribute to animate the **show**
|
||||||
|
* and **hide** effects.
|
||||||
|
*
|
||||||
|
* @animations
|
||||||
|
* show - happens after the ngHide expression evaluates to a non truthy value and the contents are set to visible
|
||||||
|
* hide - happens after the ngHide expression evaluates to a truthy value and just before the contents are set to hidden
|
||||||
*
|
*
|
||||||
* @element ANY
|
* @element ANY
|
||||||
* @param {expression} ngHide If the {@link guide/expression expression} is truthy then
|
* @param {expression} ngHide If the {@link guide/expression expression} is truthy then
|
||||||
|
|
@ -73,8 +98,11 @@ var ngShowDirective = ngDirective(function(scope, element, attr){
|
||||||
</doc:example>
|
</doc:example>
|
||||||
*/
|
*/
|
||||||
//TODO(misko): refactor to remove element from the DOM
|
//TODO(misko): refactor to remove element from the DOM
|
||||||
var ngHideDirective = ngDirective(function(scope, element, attr){
|
var ngHideDirective = ['$animator', function($animator) {
|
||||||
scope.$watch(attr.ngHide, function ngHideWatchAction(value){
|
return function(scope, element, attr) {
|
||||||
element.css('display', toBoolean(value) ? 'none' : '');
|
var animate = $animator(scope, attr);
|
||||||
});
|
scope.$watch(attr.ngHide, function ngHideWatchAction(value){
|
||||||
});
|
animate[toBoolean(value) ? 'hide' : 'show'](element);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,30 @@
|
||||||
* @restrict EA
|
* @restrict EA
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* Conditionally change the DOM structure. Elements within ngSwitch but without
|
* The ngSwitch directive is used to conditionally swap DOM structure on your template based on a scope expression.
|
||||||
* ngSwitchWhen or ngSwitchDefault directives will be preserved at the location
|
* Elements within ngSwitch but without ngSwitchWhen or ngSwitchDefault directives will be preserved at the location
|
||||||
* as specified in the template
|
* as specified in the template.
|
||||||
|
*
|
||||||
|
* The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
|
||||||
|
* from the template cache), ngSwitch simply choses one of the nested elements and makes it visible based on which element
|
||||||
|
* matches the value obtained from the evaluated expression. In other words, you define a container element
|
||||||
|
* (where you place the directive), place an expression on the **on="..." attribute**
|
||||||
|
* (or the **ng-switch="..." attribute**), define any inner elements inside of the directive and place
|
||||||
|
* a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on
|
||||||
|
* expression is evaluated. If a matching expression is not found via a when attribute then an element with the default
|
||||||
|
* attribute is displayed.
|
||||||
|
*
|
||||||
|
* Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter**
|
||||||
|
* and **leave** effects.
|
||||||
|
*
|
||||||
|
* @animations
|
||||||
|
* enter - happens after the ngSwtich contents change and the matched child element is placed inside the container
|
||||||
|
* leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM
|
||||||
*
|
*
|
||||||
* @usage
|
* @usage
|
||||||
* <ANY ng-switch="expression">
|
* <ANY ng-switch="expression">
|
||||||
* <ANY ng-switch-when="matchValue1">...</ANY>
|
* <ANY ng-switch-when="matchValue1">...</ANY>
|
||||||
* <ANY ng-switch-when="matchValue2">...</ANY>
|
* <ANY ng-switch-when="matchValue2">...</ANY>
|
||||||
* ...
|
|
||||||
* <ANY ng-switch-default>...</ANY>
|
* <ANY ng-switch-default>...</ANY>
|
||||||
* </ANY>
|
* </ANY>
|
||||||
*
|
*
|
||||||
|
|
@ -67,43 +82,48 @@
|
||||||
</doc:scenario>
|
</doc:scenario>
|
||||||
</doc:example>
|
</doc:example>
|
||||||
*/
|
*/
|
||||||
var NG_SWITCH = 'ng-switch';
|
var ngSwitchDirective = ['$animator', function($animator) {
|
||||||
var ngSwitchDirective = valueFn({
|
return {
|
||||||
restrict: 'EA',
|
restrict: 'EA',
|
||||||
require: 'ngSwitch',
|
require: 'ngSwitch',
|
||||||
// asks for $scope to fool the BC controller module
|
|
||||||
controller: ['$scope', function ngSwitchController() {
|
// asks for $scope to fool the BC controller module
|
||||||
this.cases = {};
|
controller: ['$scope', function ngSwitchController() {
|
||||||
}],
|
this.cases = {};
|
||||||
link: function(scope, element, attr, ctrl) {
|
}],
|
||||||
var watchExpr = attr.ngSwitch || attr.on,
|
link: function(scope, element, attr, ngSwitchController) {
|
||||||
selectedTranscludes,
|
var animate = $animator(scope, attr);
|
||||||
selectedElements,
|
var watchExpr = attr.ngSwitch || attr.on,
|
||||||
|
selectedTranscludes,
|
||||||
|
selectedElements,
|
||||||
|
selectedScopes = [];
|
||||||
|
|
||||||
|
scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
|
||||||
|
for (var i= 0, ii=selectedScopes.length; i<ii; i++) {
|
||||||
|
selectedScopes[i].$destroy();
|
||||||
|
animate.leave(selectedElements[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedElements = [];
|
||||||
selectedScopes = [];
|
selectedScopes = [];
|
||||||
|
|
||||||
scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
|
if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) {
|
||||||
for (var i= 0, ii=selectedScopes.length; i<ii; i++) {
|
scope.$eval(attr.change);
|
||||||
selectedScopes[i].$destroy();
|
forEach(selectedTranscludes, function(selectedTransclude) {
|
||||||
selectedElements[i].remove();
|
var selectedScope = scope.$new();
|
||||||
}
|
selectedScopes.push(selectedScope);
|
||||||
|
selectedTransclude.transclude(selectedScope, function(caseElement) {
|
||||||
|
var anchor = selectedTransclude.element;
|
||||||
|
|
||||||
selectedElements = [];
|
selectedElements.push(caseElement);
|
||||||
selectedScopes = [];
|
animate.enter(caseElement, anchor.parent(), anchor);
|
||||||
|
});
|
||||||
if ((selectedTranscludes = ctrl.cases['!' + value] || ctrl.cases['?'])) {
|
|
||||||
scope.$eval(attr.change);
|
|
||||||
forEach(selectedTranscludes, function(selectedTransclude) {
|
|
||||||
var selectedScope = scope.$new();
|
|
||||||
selectedScopes.push(selectedScope);
|
|
||||||
selectedTransclude.transclude(selectedScope, function(caseElement) {
|
|
||||||
selectedElements.push(caseElement);
|
|
||||||
selectedTransclude.element.after(caseElement);
|
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
});
|
}];
|
||||||
|
|
||||||
var ngSwitchWhenDirective = ngDirective({
|
var ngSwitchWhenDirective = ngDirective({
|
||||||
transclude: 'element',
|
transclude: 'element',
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,13 @@
|
||||||
* Every time the current route changes, the included view changes with it according to the
|
* Every time the current route changes, the included view changes with it according to the
|
||||||
* configuration of the `$route` service.
|
* configuration of the `$route` service.
|
||||||
*
|
*
|
||||||
|
* Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter**
|
||||||
|
* and **leave** effects.
|
||||||
|
*
|
||||||
|
* @animations
|
||||||
|
* enter - happens just after the ngView contents are changed (when the new view DOM element is inserted into the DOM)
|
||||||
|
* leave - happens just after the current ngView contents change and just before the former contents are removed from the DOM
|
||||||
|
*
|
||||||
* @scope
|
* @scope
|
||||||
* @example
|
* @example
|
||||||
<example module="ngView">
|
<example module="ngView">
|
||||||
|
|
@ -105,15 +112,16 @@
|
||||||
* Emitted every time the ngView content is reloaded.
|
* Emitted every time the ngView content is reloaded.
|
||||||
*/
|
*/
|
||||||
var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$compile',
|
var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$compile',
|
||||||
'$controller',
|
'$controller', '$animator',
|
||||||
function($http, $templateCache, $route, $anchorScroll, $compile,
|
function($http, $templateCache, $route, $anchorScroll, $compile,
|
||||||
$controller) {
|
$controller, $animator) {
|
||||||
return {
|
return {
|
||||||
restrict: 'ECA',
|
restrict: 'ECA',
|
||||||
terminal: true,
|
terminal: true,
|
||||||
link: function(scope, element, attr) {
|
link: function(scope, element, attr) {
|
||||||
var lastScope,
|
var lastScope,
|
||||||
onloadExp = attr.onload || '';
|
onloadExp = attr.onload || '',
|
||||||
|
animate = $animator(scope, attr);
|
||||||
|
|
||||||
scope.$on('$routeChangeSuccess', update);
|
scope.$on('$routeChangeSuccess', update);
|
||||||
update();
|
update();
|
||||||
|
|
@ -127,7 +135,7 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearContent() {
|
function clearContent() {
|
||||||
element.html('');
|
animate.leave(element.contents(), element);
|
||||||
destroyLastScope();
|
destroyLastScope();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,8 +144,8 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c
|
||||||
template = locals && locals.$template;
|
template = locals && locals.$template;
|
||||||
|
|
||||||
if (template) {
|
if (template) {
|
||||||
element.html(template);
|
clearContent();
|
||||||
destroyLastScope();
|
animate.enter(jqLite('<div></div>').html(template).contents(), element);
|
||||||
|
|
||||||
var link = $compile(element.contents()),
|
var link = $compile(element.contents()),
|
||||||
current = $route.current,
|
current = $route.current,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
*
|
*
|
||||||
* @property {boolean} history Does the browser support html5 history api ?
|
* @property {boolean} history Does the browser support html5 history api ?
|
||||||
* @property {boolean} hashchange Does the browser support hashchange event ?
|
* @property {boolean} hashchange Does the browser support hashchange event ?
|
||||||
|
* @property {boolean} supportsTransitions Does the browser support CSS transition events ?
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* This is very simple implementation of testing browser's features.
|
* This is very simple implementation of testing browser's features.
|
||||||
|
|
@ -16,8 +17,25 @@
|
||||||
function $SnifferProvider() {
|
function $SnifferProvider() {
|
||||||
this.$get = ['$window', '$document', function($window, $document) {
|
this.$get = ['$window', '$document', function($window, $document) {
|
||||||
var eventSupport = {},
|
var eventSupport = {},
|
||||||
android = int((/android (\d+)/.exec(lowercase($window.navigator.userAgent)) || [])[1]),
|
android = int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
|
||||||
document = $document[0];
|
document = $document[0] || {},
|
||||||
|
vendorPrefix,
|
||||||
|
vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/,
|
||||||
|
bodyStyle = document.body && document.body.style,
|
||||||
|
transitions = false,
|
||||||
|
match;
|
||||||
|
|
||||||
|
if (bodyStyle) {
|
||||||
|
for(var prop in bodyStyle) {
|
||||||
|
if(match = vendorRegex.exec(prop)) {
|
||||||
|
vendorPrefix = match[0];
|
||||||
|
vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transitions = !!(vendorPrefix + 'Transition' in bodyStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Android has history.pushState, but it does not update location correctly
|
// Android has history.pushState, but it does not update location correctly
|
||||||
|
|
@ -41,7 +59,9 @@ function $SnifferProvider() {
|
||||||
|
|
||||||
return eventSupport[event];
|
return eventSupport[event];
|
||||||
},
|
},
|
||||||
csp: document.securityPolicy ? document.securityPolicy.isActive : false
|
csp: document.securityPolicy ? document.securityPolicy.isActive : false,
|
||||||
|
vendorPrefix: vendorPrefix,
|
||||||
|
supportsTransitions : transitions
|
||||||
};
|
};
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
51
src/ngMock/angular-mocks.js
vendored
51
src/ngMock/angular-mocks.js
vendored
|
|
@ -587,6 +587,57 @@ angular.mock.$LogProvider = function() {
|
||||||
angular.mock.TzDate.prototype = Date.prototype;
|
angular.mock.TzDate.prototype = Date.prototype;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name angular.mock.createMockWindow
|
||||||
|
* @description
|
||||||
|
*
|
||||||
|
* This function creates a mock window object useful for controlling access ot setTimeout, but mocking out
|
||||||
|
* sufficient window's properties to allow Angular to execute.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
$provide.value('$window', window = angular.mock.createMockWindow());
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should do something', inject(function($window) {
|
||||||
|
var val = null;
|
||||||
|
$window.setTimeout(function() { val = 123; }, 10);
|
||||||
|
expect(val).toEqual(null);
|
||||||
|
window.setTimeout.expect(10).process();
|
||||||
|
expect(val).toEqual(123);
|
||||||
|
});
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
angular.mock.createMockWindow = function() {
|
||||||
|
var mockWindow = {};
|
||||||
|
var setTimeoutQueue = [];
|
||||||
|
|
||||||
|
mockWindow.document = window.document;
|
||||||
|
mockWindow.getComputedStyle = angular.bind(window, window.getComputedStyle);
|
||||||
|
mockWindow.scrollTo = angular.bind(window, window.scrollTo);
|
||||||
|
mockWindow.navigator = window.navigator;
|
||||||
|
mockWindow.setTimeout = function(fn, delay) {
|
||||||
|
setTimeoutQueue.push({fn: fn, delay: delay});
|
||||||
|
};
|
||||||
|
mockWindow.setTimeout.queue = setTimeoutQueue;
|
||||||
|
mockWindow.setTimeout.expect = function(delay) {
|
||||||
|
if (setTimeoutQueue.length > 0) {
|
||||||
|
return {
|
||||||
|
process: function() {
|
||||||
|
setTimeoutQueue.shift().fn();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
expect('SetTimoutQueue empty. Expecting delay of ').toEqual(delay);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return mockWindow;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ngdoc function
|
* @ngdoc function
|
||||||
|
|
|
||||||
15
test/ng/animationSpec.js
Normal file
15
test/ng/animationSpec.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe('$animation', function() {
|
||||||
|
|
||||||
|
it('should allow animation registration', function() {
|
||||||
|
var noopCustom = function(){};
|
||||||
|
module(function($animationProvider) {
|
||||||
|
$animationProvider.register('noop-custom', valueFn(noopCustom));
|
||||||
|
});
|
||||||
|
inject(function($animation) {
|
||||||
|
expect($animation('noop-custom')).toBe(noopCustom);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
195
test/ng/animatorSpec.js
Normal file
195
test/ng/animatorSpec.js
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe("$animator", function() {
|
||||||
|
|
||||||
|
var element;
|
||||||
|
|
||||||
|
afterEach(function(){
|
||||||
|
dealoc(element);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("without animation", function() {
|
||||||
|
var window, animator;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
module(function($animationProvider, $provide) {
|
||||||
|
$provide.value('$window', window = angular.mock.createMockWindow());
|
||||||
|
})
|
||||||
|
inject(function($animator, $compile, $rootScope) {
|
||||||
|
animator = $animator($rootScope, {});
|
||||||
|
element = $compile('<div></div>')($rootScope);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add element at the start of enter animation", inject(function($animator, $compile, $rootScope) {
|
||||||
|
var child = $compile('<div></div>')($rootScope);
|
||||||
|
expect(element.contents().length).toBe(0);
|
||||||
|
animator.enter(child, element);
|
||||||
|
expect(element.contents().length).toBe(1);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should remove the element at the end of leave animation", inject(function($animator, $compile, $rootScope) {
|
||||||
|
var child = $compile('<div></div>')($rootScope);
|
||||||
|
element.append(child);
|
||||||
|
expect(element.contents().length).toBe(1);
|
||||||
|
animator.leave(child, element);
|
||||||
|
expect(element.contents().length).toBe(0);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should reorder the move animation", inject(function($animator, $compile, $rootScope) {
|
||||||
|
var child1 = $compile('<div>1</div>')($rootScope);
|
||||||
|
var child2 = $compile('<div>2</div>')($rootScope);
|
||||||
|
element.append(child1);
|
||||||
|
element.append(child2);
|
||||||
|
expect(element.text()).toBe('12');
|
||||||
|
animator.move(child1, element, child2);
|
||||||
|
expect(element.text()).toBe('21');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should animate the show animation event", inject(function($animator, $compile, $rootScope) {
|
||||||
|
element.css('display','none');
|
||||||
|
expect(element.css('display')).toBe('none');
|
||||||
|
animator.show(element);
|
||||||
|
expect(element.css('display')).toBe('block');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should animate the hide animation event", inject(function($animator, $compile, $rootScope) {
|
||||||
|
element.css('display','block');
|
||||||
|
expect(element.css('display')).toBe('block');
|
||||||
|
animator.hide(element);
|
||||||
|
expect(element.css('display')).toBe('none');
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("with polyfill", function() {
|
||||||
|
|
||||||
|
var child, after, window, animator;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
module(function($animationProvider, $provide) {
|
||||||
|
$provide.value('$window', window = angular.mock.createMockWindow());
|
||||||
|
$animationProvider.register('custom', function() {
|
||||||
|
return {
|
||||||
|
start: function(element, done) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
inject(function($animator, $compile, $rootScope) {
|
||||||
|
element = $compile('<div></div>')($rootScope);
|
||||||
|
child = $compile('<div></div>')($rootScope);
|
||||||
|
after = $compile('<div></div>')($rootScope);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should animate the enter animation event", inject(function($animator, $rootScope) {
|
||||||
|
animator = $animator($rootScope, {
|
||||||
|
ngAnimate : '{enter: \'custom\'}'
|
||||||
|
});
|
||||||
|
expect(element.contents().length).toBe(0);
|
||||||
|
animator.enter(child, element);
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should animate the leave animation event", inject(function($animator, $rootScope) {
|
||||||
|
animator = $animator($rootScope, {
|
||||||
|
ngAnimate : '{leave: \'custom\'}'
|
||||||
|
});
|
||||||
|
element.append(child);
|
||||||
|
expect(element.contents().length).toBe(1);
|
||||||
|
animator.leave(child, element);
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
expect(element.contents().length).toBe(0);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should animate the move animation event", inject(function($animator, $compile, $rootScope) {
|
||||||
|
animator = $animator($rootScope, {
|
||||||
|
ngAnimate : '{move: \'custom\'}'
|
||||||
|
});
|
||||||
|
var child1 = $compile('<div>1</div>')($rootScope);
|
||||||
|
var child2 = $compile('<div>2</div>')($rootScope);
|
||||||
|
element.append(child1);
|
||||||
|
element.append(child2);
|
||||||
|
expect(element.text()).toBe('12');
|
||||||
|
animator.move(child1, element, child2);
|
||||||
|
expect(element.text()).toBe('21');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should animate the show animation event", inject(function($animator, $rootScope) {
|
||||||
|
animator = $animator($rootScope, {
|
||||||
|
ngAnimate : '{show: \'custom\'}'
|
||||||
|
});
|
||||||
|
element.css('display','none');
|
||||||
|
expect(element.css('display')).toBe('none');
|
||||||
|
animator.show(element);
|
||||||
|
expect(element.css('display')).toBe('block');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
expect(element.css('display')).toBe('block');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should animate the hide animation event", inject(function($animator, $rootScope) {
|
||||||
|
animator = $animator($rootScope, {
|
||||||
|
ngAnimate : '{hide: \'custom\'}'
|
||||||
|
});
|
||||||
|
element.css('display','block');
|
||||||
|
expect(element.css('display')).toBe('block');
|
||||||
|
animator.hide(element);
|
||||||
|
expect(element.css('display')).toBe('block');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
expect(element.css('display')).toBe('none');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should assign the ngAnimate string to all events if a string is given",
|
||||||
|
inject(function($animator, $sniffer, $rootScope) {
|
||||||
|
if (!$sniffer.supportsTransitions) return;
|
||||||
|
animator = $animator($rootScope, {
|
||||||
|
ngAnimate : '"custom"'
|
||||||
|
});
|
||||||
|
|
||||||
|
//enter
|
||||||
|
animator.enter(child, element);
|
||||||
|
expect(child.attr('class')).toContain('custom-enter-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
expect(child.attr('class')).toContain('custom-enter-start');
|
||||||
|
window.setTimeout.expect(0).process();
|
||||||
|
|
||||||
|
//leave
|
||||||
|
element.append(after);
|
||||||
|
animator.move(child, element, after);
|
||||||
|
expect(child.attr('class')).toContain('custom-move-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
expect(child.attr('class')).toContain('custom-move-start');
|
||||||
|
window.setTimeout.expect(0).process();
|
||||||
|
|
||||||
|
//hide
|
||||||
|
animator.hide(child);
|
||||||
|
expect(child.attr('class')).toContain('custom-hide-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
expect(child.attr('class')).toContain('custom-hide-start');
|
||||||
|
window.setTimeout.expect(0).process();
|
||||||
|
|
||||||
|
//show
|
||||||
|
animator.show(child);
|
||||||
|
expect(child.attr('class')).toContain('custom-show-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
expect(child.attr('class')).toContain('custom-show-start');
|
||||||
|
window.setTimeout.expect(0).process();
|
||||||
|
|
||||||
|
//leave
|
||||||
|
animator.leave(child);
|
||||||
|
expect(child.attr('class')).toContain('custom-leave-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
expect(child.attr('class')).toContain('custom-leave-start');
|
||||||
|
window.setTimeout.expect(0).process();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error when an invalid ng-animate syntax is provided", inject(function($compile, $rootScope) {
|
||||||
|
expect(function() {
|
||||||
|
element = $compile('<div ng-repeat="i in is" ng-animate=":"></div>')($rootScope);
|
||||||
|
}).toThrow("Syntax Error: Token ':' not a primary expression at column 1 of the expression [:] starting at [:].");
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
@ -280,3 +280,116 @@ describe('ngInclude', function() {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ngInclude ngAnimate', function() {
|
||||||
|
var element, vendorPrefix, window;
|
||||||
|
|
||||||
|
beforeEach(module(function($animationProvider, $provide) {
|
||||||
|
$provide.value('$window', window = angular.mock.createMockWindow());
|
||||||
|
return function($sniffer) {
|
||||||
|
vendorPrefix = '-' + $sniffer.vendorPrefix + '-';
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterEach(function(){
|
||||||
|
dealoc(element);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire off the enter animation + add and remove the css classes',
|
||||||
|
inject(function($compile, $rootScope, $templateCache, $sniffer) {
|
||||||
|
|
||||||
|
$templateCache.put('enter', [200, '<div>data</div>', {}]);
|
||||||
|
$rootScope.tpl = 'enter';
|
||||||
|
element = $compile(
|
||||||
|
'<div ' +
|
||||||
|
'ng-include="tpl" ' +
|
||||||
|
'ng-animate="{enter: \'custom-enter\'}">' +
|
||||||
|
'</div>'
|
||||||
|
)($rootScope);
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
//if we add the custom css stuff here then it will get picked up before the animation takes place
|
||||||
|
var child = jqLite(element.children()[0]);
|
||||||
|
var cssProp = vendorPrefix + 'transition';
|
||||||
|
var cssValue = '1s linear all';
|
||||||
|
child.css(cssProp, cssValue);
|
||||||
|
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
expect(child.attr('class')).toContain('custom-enter-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
|
||||||
|
expect(child.attr('class')).toContain('custom-enter-start');
|
||||||
|
window.setTimeout.expect(1000).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(child.attr('class')).not.toContain('custom-enter-setup');
|
||||||
|
expect(child.attr('class')).not.toContain('custom-enter-start');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should fire off the leave animation + add and remove the css classes',
|
||||||
|
inject(function($compile, $rootScope, $templateCache, $sniffer) {
|
||||||
|
$templateCache.put('enter', [200, '<div>data</div>', {}]);
|
||||||
|
$rootScope.tpl = 'enter';
|
||||||
|
element = $compile(
|
||||||
|
'<div ' +
|
||||||
|
'ng-include="tpl" ' +
|
||||||
|
'ng-animate="{leave: \'custom-leave\'}">' +
|
||||||
|
'</div>'
|
||||||
|
)($rootScope);
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
//if we add the custom css stuff here then it will get picked up before the animation takes place
|
||||||
|
var child = jqLite(element.children()[0]);
|
||||||
|
var cssProp = vendorPrefix + 'transition';
|
||||||
|
var cssValue = '1s linear all';
|
||||||
|
child.css(cssProp, cssValue);
|
||||||
|
|
||||||
|
$rootScope.tpl = '';
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
expect(child.attr('class')).toContain('custom-leave-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
|
||||||
|
expect(child.attr('class')).toContain('custom-leave-start');
|
||||||
|
window.setTimeout.expect(1000).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(child.attr('class')).not.toContain('custom-leave-setup');
|
||||||
|
expect(child.attr('class')).not.toContain('custom-leave-start');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should catch and use the correct duration for animation',
|
||||||
|
inject(function($compile, $rootScope, $templateCache, $sniffer) {
|
||||||
|
$templateCache.put('enter', [200, '<div>data</div>', {}]);
|
||||||
|
$rootScope.tpl = 'enter';
|
||||||
|
element = $compile(
|
||||||
|
'<div ' +
|
||||||
|
'ng-include="tpl" ' +
|
||||||
|
'ng-animate="{enter: \'custom-enter\'}">' +
|
||||||
|
'</div>'
|
||||||
|
)($rootScope);
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
//if we add the custom css stuff here then it will get picked up before the animation takes place
|
||||||
|
var child = jqLite(element.children()[0]);
|
||||||
|
var cssProp = vendorPrefix + 'transition';
|
||||||
|
var cssValue = '0.5s linear all';
|
||||||
|
child.css(cssProp, cssValue);
|
||||||
|
|
||||||
|
$rootScope.tpl = 'enter';
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
window.setTimeout.expect(500).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -189,11 +189,11 @@ describe('ngRepeat', function() {
|
||||||
|
|
||||||
element = $compile(
|
element = $compile(
|
||||||
'<ul>' +
|
'<ul>' +
|
||||||
'<li ng-repeat="(key, value) in items track by $index">' +
|
'<li ng-repeat="(key, value) in items track by $index">' +
|
||||||
'{{key}}:{{value}};' +
|
'{{key}}:{{value}};' +
|
||||||
'<input type="checkbox" ng-model="items[key]">' +
|
'<input type="checkbox" ng-model="items[key]">' +
|
||||||
'</li>' +
|
'</li>' +
|
||||||
'</ul>')(scope);
|
'</ul>')(scope);
|
||||||
|
|
||||||
scope.items = {misko: true, shyam: true, zhenbo:true};
|
scope.items = {misko: true, shyam: true, zhenbo:true};
|
||||||
scope.$digest();
|
scope.$digest();
|
||||||
|
|
@ -410,6 +410,24 @@ describe('ngRepeat', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should preserve data on move of elements', function() {
|
||||||
|
element = $compile('<ul><li ng-repeat="item in array">{{item}}|</li></ul>')(scope);
|
||||||
|
scope.array = ['a', 'b'];
|
||||||
|
scope.$digest();
|
||||||
|
|
||||||
|
var lis = element.find('li');
|
||||||
|
lis.eq(0).data('mark', 'a');
|
||||||
|
lis.eq(1).data('mark', 'b');
|
||||||
|
|
||||||
|
scope.array = ['b', 'a'];
|
||||||
|
scope.$digest();
|
||||||
|
|
||||||
|
var lis = element.find('li');
|
||||||
|
expect(lis.eq(0).data('mark')).toEqual('b');
|
||||||
|
expect(lis.eq(1).data('mark')).toEqual('a');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('stability', function() {
|
describe('stability', function() {
|
||||||
var a, b, c, d, lis;
|
var a, b, c, d, lis;
|
||||||
|
|
||||||
|
|
@ -481,6 +499,7 @@ describe('ngRepeat', function() {
|
||||||
scope.items = ['hello', 'cau', 'ahoj'];
|
scope.items = ['hello', 'cau', 'ahoj'];
|
||||||
scope.$digest();
|
scope.$digest();
|
||||||
lis = element.find('li');
|
lis = element.find('li');
|
||||||
|
lis[2].id = 'yes';
|
||||||
|
|
||||||
scope.items = ['ahoj', 'hello', 'cau'];
|
scope.items = ['ahoj', 'hello', 'cau'];
|
||||||
scope.$digest();
|
scope.$digest();
|
||||||
|
|
@ -492,3 +511,189 @@ describe('ngRepeat', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ngRepeat ngAnimate', function() {
|
||||||
|
var element, vendorPrefix, window;
|
||||||
|
|
||||||
|
beforeEach(module(function($animationProvider, $provide) {
|
||||||
|
$provide.value('$window', window = angular.mock.createMockWindow());
|
||||||
|
return function($sniffer) {
|
||||||
|
vendorPrefix = '-' + $sniffer.vendorPrefix + '-';
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterEach(function(){
|
||||||
|
dealoc(element);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire off the enter animation + add and remove the css classes',
|
||||||
|
inject(function($compile, $rootScope, $sniffer) {
|
||||||
|
|
||||||
|
element = $compile(
|
||||||
|
'<div><div ' +
|
||||||
|
'ng-repeat="item in items" ' +
|
||||||
|
'ng-animate="{enter: \'custom-enter\'}">' +
|
||||||
|
'{{ item }}' +
|
||||||
|
'</div></div>'
|
||||||
|
)($rootScope);
|
||||||
|
|
||||||
|
$rootScope.items = ['1','2','3'];
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
//if we add the custom css stuff here then it will get picked up before the animation takes place
|
||||||
|
var cssProp = vendorPrefix + 'transition';
|
||||||
|
var cssValue = '1s linear all';
|
||||||
|
var kids = element.children();
|
||||||
|
for(var i=0;i<kids.length;i++) {
|
||||||
|
kids[i] = jqLite(kids[i]);
|
||||||
|
kids[i].css(cssProp, cssValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
angular.forEach(kids, function(kid) {
|
||||||
|
expect(kid.attr('class')).toContain('custom-enter-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
});
|
||||||
|
|
||||||
|
angular.forEach(kids, function(kid) {
|
||||||
|
expect(kid.attr('class')).toContain('custom-enter-start');
|
||||||
|
window.setTimeout.expect(1000).process();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.forEach(kids, function(kid) {
|
||||||
|
expect(kid.attr('class')).not.toContain('custom-enter-setup');
|
||||||
|
expect(kid.attr('class')).not.toContain('custom-enter-start');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should fire off the leave animation + add and remove the css classes',
|
||||||
|
inject(function($compile, $rootScope, $sniffer) {
|
||||||
|
|
||||||
|
element = $compile(
|
||||||
|
'<div><div ' +
|
||||||
|
'ng-repeat="item in items" ' +
|
||||||
|
'ng-animate="{leave: \'custom-leave\'}">' +
|
||||||
|
'{{ item }}' +
|
||||||
|
'</div></div>'
|
||||||
|
)($rootScope);
|
||||||
|
|
||||||
|
$rootScope.items = ['1','2','3'];
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
//if we add the custom css stuff here then it will get picked up before the animation takes place
|
||||||
|
var cssProp = vendorPrefix + 'transition';
|
||||||
|
var cssValue = '1s linear all';
|
||||||
|
var kids = element.children();
|
||||||
|
for(var i=0;i<kids.length;i++) {
|
||||||
|
kids[i] = jqLite(kids[i]);
|
||||||
|
kids[i].css(cssProp, cssValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
$rootScope.items = ['1','3'];
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
//the last element gets pushed down when it animates
|
||||||
|
var kid = jqLite(element.children()[1]);
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
expect(kid.attr('class')).toContain('custom-leave-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
expect(kid.attr('class')).toContain('custom-leave-start');
|
||||||
|
window.setTimeout.expect(1000).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(kid.attr('class')).not.toContain('custom-leave-setup');
|
||||||
|
expect(kid.attr('class')).not.toContain('custom-leave-start');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should fire off the move animation + add and remove the css classes',
|
||||||
|
inject(function($compile, $rootScope, $sniffer) {
|
||||||
|
element = $compile(
|
||||||
|
'<div>' +
|
||||||
|
'<div ng-repeat="item in items" ng-animate="{move: \'custom-move\'}">' +
|
||||||
|
'{{ item }}' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>'
|
||||||
|
)($rootScope);
|
||||||
|
|
||||||
|
$rootScope.items = ['1','2','3'];
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
//if we add the custom css stuff here then it will get picked up before the animation takes place
|
||||||
|
var cssProp = '-' + $sniffer.vendorPrefix + '-transition';
|
||||||
|
var cssValue = '1s linear all';
|
||||||
|
var kids = element.children();
|
||||||
|
for(var i=0;i<kids.length;i++) {
|
||||||
|
kids[i] = jqLite(kids[i]);
|
||||||
|
kids[i].css(cssProp, cssValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
$rootScope.items = ['2','3','1'];
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
//the last element gets pushed down when it animates
|
||||||
|
var kids = element.children();
|
||||||
|
var first = jqLite(kids[0]);
|
||||||
|
var left = jqLite(kids[1]);
|
||||||
|
var right = jqLite(kids[2]);
|
||||||
|
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
expect(first.attr('class')).toContain('custom-move-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
expect(left.attr('class')).toContain('custom-move-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
|
||||||
|
expect(first.attr('class')).toContain('custom-move-start');
|
||||||
|
window.setTimeout.expect(1000).process();
|
||||||
|
expect(left.attr('class')).toContain('custom-move-start');
|
||||||
|
window.setTimeout.expect(1000).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(first.attr('class')).not.toContain('custom-move-setup');
|
||||||
|
expect(first.attr('class')).not.toContain('custom-move-start');
|
||||||
|
expect(left.attr('class')).not.toContain('custom-move-setup');
|
||||||
|
expect(left.attr('class')).not.toContain('custom-move-start');
|
||||||
|
expect(right.attr('class')).not.toContain('custom-move-setup');
|
||||||
|
expect(right.attr('class')).not.toContain('custom-move-start');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should catch and use the correct duration for animation',
|
||||||
|
inject(function($compile, $rootScope, $sniffer) {
|
||||||
|
|
||||||
|
element = $compile(
|
||||||
|
'<div><div ' +
|
||||||
|
'ng-repeat="item in items" ' +
|
||||||
|
'ng-animate="{enter: \'custom-enter\'}">' +
|
||||||
|
'{{ item }}' +
|
||||||
|
'</div></div>'
|
||||||
|
)($rootScope);
|
||||||
|
|
||||||
|
$rootScope.items = ['a','b'];
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
//if we add the custom css stuff here then it will get picked up before the animation takes place
|
||||||
|
var kids = element.children();
|
||||||
|
var first = jqLite(kids[0]);
|
||||||
|
var second = jqLite(kids[1]);
|
||||||
|
var cssProp = '-' + $sniffer.vendorPrefix + '-transition';
|
||||||
|
var cssValue = '0.5s linear all';
|
||||||
|
first.css(cssProp, cssValue);
|
||||||
|
second.css(cssProp, cssValue);
|
||||||
|
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
window.setTimeout.expect(500).process();
|
||||||
|
window.setTimeout.expect(500).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -41,3 +41,104 @@ describe('ngShow / ngHide', function() {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ngShow / ngHide - ngAnimate', function() {
|
||||||
|
var element, window;
|
||||||
|
var vendorPrefix;
|
||||||
|
|
||||||
|
beforeEach(module(function($animationProvider, $provide) {
|
||||||
|
$provide.value('$window', window = angular.mock.createMockWindow());
|
||||||
|
return function($sniffer) {
|
||||||
|
vendorPrefix = '-' + $sniffer.vendorPrefix + '-';
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
dealoc(element);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ngShow', function() {
|
||||||
|
it('should fire off the animator.show and animator.hide animation', inject(function($compile, $rootScope, $sniffer) {
|
||||||
|
var $scope = $rootScope.$new();
|
||||||
|
$scope.on = true;
|
||||||
|
element = $compile(
|
||||||
|
'<div ' +
|
||||||
|
'style="'+vendorPrefix+'transition: 1s linear all"' +
|
||||||
|
'ng-show="on" ' +
|
||||||
|
'ng-animate="{show: \'custom-show\', hide: \'custom-hide\'}">' +
|
||||||
|
'</div>'
|
||||||
|
)($scope);
|
||||||
|
$scope.$digest();
|
||||||
|
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
expect(element.attr('class')).toContain('custom-show-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
|
||||||
|
expect(element.attr('class')).toContain('custom-show-start');
|
||||||
|
window.setTimeout.expect(1000).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(element.attr('class')).not.toContain('custom-show-start');
|
||||||
|
expect(element.attr('class')).not.toContain('custom-show-setup');
|
||||||
|
|
||||||
|
$scope.on = false;
|
||||||
|
$scope.$digest();
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
expect(element.attr('class')).toContain('custom-hide-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
expect(element.attr('class')).toContain('custom-hide-start');
|
||||||
|
window.setTimeout.expect(1000).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(element.attr('class')).not.toContain('custom-hide-start');
|
||||||
|
expect(element.attr('class')).not.toContain('custom-hide-setup');
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ngHide', function() {
|
||||||
|
it('should fire off the animator.show and animator.hide animation', inject(function($compile, $rootScope, $sniffer) {
|
||||||
|
var $scope = $rootScope.$new();
|
||||||
|
$scope.off = true;
|
||||||
|
element = $compile(
|
||||||
|
'<div ' +
|
||||||
|
'style="'+vendorPrefix+'transition: 1s linear all"' +
|
||||||
|
'ng-hide="off" ' +
|
||||||
|
'ng-animate="{show: \'custom-show\', hide: \'custom-hide\'}">' +
|
||||||
|
'</div>'
|
||||||
|
)($scope);
|
||||||
|
$scope.$digest();
|
||||||
|
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
expect(element.attr('class')).toContain('custom-hide-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
|
||||||
|
expect(element.attr('class')).toContain('custom-hide-start');
|
||||||
|
window.setTimeout.expect(1000).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(element.attr('class')).not.toContain('custom-hide-start');
|
||||||
|
expect(element.attr('class')).not.toContain('custom-hide-setup');
|
||||||
|
|
||||||
|
$scope.off = false;
|
||||||
|
$scope.$digest();
|
||||||
|
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
expect(element.attr('class')).toContain('custom-show-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
expect(element.attr('class')).toContain('custom-show-start');
|
||||||
|
window.setTimeout.expect(1000).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(element.attr('class')).not.toContain('custom-show-start');
|
||||||
|
expect(element.attr('class')).not.toContain('custom-show-setup');
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -213,3 +213,121 @@ describe('ngSwitch', function() {
|
||||||
// afterwards a global afterEach will check for leaks in jq data cache object
|
// afterwards a global afterEach will check for leaks in jq data cache object
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ngSwitch ngAnimate', function() {
|
||||||
|
var element, vendorPrefix, window;
|
||||||
|
|
||||||
|
beforeEach(module(function($animationProvider, $provide) {
|
||||||
|
$provide.value('$window', window = angular.mock.createMockWindow());
|
||||||
|
return function($sniffer) {
|
||||||
|
vendorPrefix = '-' + $sniffer.vendorPrefix + '-';
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterEach(function(){
|
||||||
|
dealoc(element);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire off the enter animation + set and remove the classes',
|
||||||
|
inject(function($compile, $rootScope, $sniffer) {
|
||||||
|
var $scope = $rootScope.$new();
|
||||||
|
var style = vendorPrefix + 'transition: 1s linear all';
|
||||||
|
element = $compile(
|
||||||
|
'<div ng-switch on="val" ng-animate="{enter: \'cool-enter\', leave: \'cool-leave\'}">' +
|
||||||
|
'<div ng-switch-when="one" style="' + style + '">one</div>' +
|
||||||
|
'<div ng-switch-when="two" style="' + style + '">two</div>' +
|
||||||
|
'<div ng-switch-when="three" style="' + style + '">three</div>' +
|
||||||
|
'</div>'
|
||||||
|
)($scope);
|
||||||
|
|
||||||
|
$scope.val = 'one';
|
||||||
|
$scope.$digest();
|
||||||
|
|
||||||
|
expect(element.children().length).toBe(1);
|
||||||
|
var first = element.children()[0];
|
||||||
|
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
expect(first.className).toContain('cool-enter-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
|
||||||
|
expect(first.className).toContain('cool-enter-start');
|
||||||
|
window.setTimeout.expect(1000).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(first.className).not.toContain('cool-enter-setup');
|
||||||
|
expect(first.className).not.toContain('cool-enter-start');
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should fire off the leave animation + set and remove the classes',
|
||||||
|
inject(function($compile, $rootScope, $sniffer) {
|
||||||
|
var $scope = $rootScope.$new();
|
||||||
|
var style = vendorPrefix + 'transition: 1s linear all';
|
||||||
|
element = $compile(
|
||||||
|
'<div ng-switch on="val" ng-animate="{enter: \'cool-enter\', leave: \'cool-leave\'}">' +
|
||||||
|
'<div ng-switch-when="one" style="' + style + '">one</div>' +
|
||||||
|
'<div ng-switch-when="two" style="' + style + '">two</div>' +
|
||||||
|
'<div ng-switch-when="three" style="' + style + '">three</div>' +
|
||||||
|
'</div>'
|
||||||
|
)($scope);
|
||||||
|
|
||||||
|
$scope.val = 'two';
|
||||||
|
$scope.$digest();
|
||||||
|
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
window.setTimeout.expect(1000).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.val = 'three';
|
||||||
|
$scope.$digest();
|
||||||
|
|
||||||
|
expect(element.children().length).toBe($sniffer.supportsTransitions ? 2 : 1);
|
||||||
|
var first = element.children()[0];
|
||||||
|
|
||||||
|
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
expect(first.className).toContain('cool-leave-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
expect(first.className).toContain('cool-leave-start');
|
||||||
|
window.setTimeout.expect(1000).process();
|
||||||
|
window.setTimeout.expect(1000).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(first.className).not.toContain('cool-leave-setup');
|
||||||
|
expect(first.className).not.toContain('cool-leave-start');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should catch and use the correct duration for animation',
|
||||||
|
inject(function($compile, $rootScope, $sniffer) {
|
||||||
|
element = $compile(
|
||||||
|
'<div ng-switch on="val" ng-animate="{enter: \'cool-enter\', leave: \'cool-leave\'}">' +
|
||||||
|
'<div ng-switch-when="one" style="' + vendorPrefix + 'transition: 0.5s linear all">one</div>' +
|
||||||
|
'</div>'
|
||||||
|
)($rootScope);
|
||||||
|
|
||||||
|
$rootScope.val = 'one';
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
window.setTimeout.expect(500).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -473,7 +473,7 @@ describe('ngView', function() {
|
||||||
$rootScope.$digest();
|
$rootScope.$digest();
|
||||||
|
|
||||||
forEach(element.contents(), function(node) {
|
forEach(element.contents(), function(node) {
|
||||||
if ( node.nodeType == 3 ) {
|
if ( node.nodeType == 3 /* text node */) {
|
||||||
expect(jqLite(node).scope()).not.toBe($route.current.scope);
|
expect(jqLite(node).scope()).not.toBe($route.current.scope);
|
||||||
expect(jqLite(node).controller()).not.toBeDefined();
|
expect(jqLite(node).controller()).not.toBeDefined();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -484,3 +484,105 @@ describe('ngView', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ngAnimate', function() {
|
||||||
|
var element, window;
|
||||||
|
|
||||||
|
beforeEach(module(function($provide, $routeProvider) {
|
||||||
|
$provide.value('$window', window = angular.mock.createMockWindow());
|
||||||
|
$routeProvider.when('/foo', {controller: noop, templateUrl: '/foo.html'});
|
||||||
|
return function($templateCache) {
|
||||||
|
$templateCache.put('/foo.html', [200, '<div>data</div>', {}]);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterEach(function(){
|
||||||
|
dealoc(element);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire off the enter animation + add and remove the css classes',
|
||||||
|
inject(function($compile, $rootScope, $sniffer, $location, $templateCache) {
|
||||||
|
element = $compile('<div ng-view ng-animate="{enter: \'custom-enter\'}"></div>')($rootScope);
|
||||||
|
|
||||||
|
$location.path('/foo');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
//if we add the custom css stuff here then it will get picked up before the animation takes place
|
||||||
|
var child = jqLite(element.children()[0]);
|
||||||
|
var cssProp = '-' + $sniffer.vendorPrefix + '-transition';
|
||||||
|
var cssValue = '1s linear all';
|
||||||
|
child.css(cssProp, cssValue);
|
||||||
|
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
expect(child.attr('class')).toContain('custom-enter-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
|
||||||
|
expect(child.attr('class')).toContain('custom-enter-start');
|
||||||
|
window.setTimeout.expect(1000).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(child.attr('class')).not.toContain('custom-enter-setup');
|
||||||
|
expect(child.attr('class')).not.toContain('custom-enter-start');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should fire off the leave animation + add and remove the css classes',
|
||||||
|
inject(function($compile, $rootScope, $sniffer, $location, $templateCache) {
|
||||||
|
$templateCache.put('/foo.html', [200, '<div>foo</div>', {}]);
|
||||||
|
element = $compile('<div ng-view ng-animate="{leave: \'custom-leave\'}"></div>')($rootScope);
|
||||||
|
|
||||||
|
$location.path('/foo');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
//if we add the custom css stuff here then it will get picked up before the animation takes place
|
||||||
|
var child = jqLite(element.children()[0]);
|
||||||
|
var cssProp = '-' + $sniffer.vendorPrefix + '-transition';
|
||||||
|
var cssValue = '1s linear all';
|
||||||
|
child.css(cssProp, cssValue);
|
||||||
|
|
||||||
|
$location.path('/');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
if ($sniffer.supportsTransitions) {
|
||||||
|
expect(child.attr('class')).toContain('custom-leave-setup');
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
|
||||||
|
expect(child.attr('class')).toContain('custom-leave-start');
|
||||||
|
window.setTimeout.expect(1000).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(child.attr('class')).not.toContain('custom-leave-setup');
|
||||||
|
expect(child.attr('class')).not.toContain('custom-leave-start');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should catch and use the correct duration for animations',
|
||||||
|
inject(function($compile, $rootScope, $sniffer, $location, $templateCache) {
|
||||||
|
$templateCache.put('/foo.html', [200, '<div>foo</div>', {}]);
|
||||||
|
element = $compile(
|
||||||
|
'<div ' +
|
||||||
|
'ng-view ' +
|
||||||
|
'ng-animate="{enter: \'customEnter\'}">' +
|
||||||
|
'</div>'
|
||||||
|
)($rootScope);
|
||||||
|
|
||||||
|
$location.path('/foo');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
//if we add the custom css stuff here then it will get picked up before the animation takes place
|
||||||
|
var child = jqLite(element.children()[0]);
|
||||||
|
var cssProp = '-' + $sniffer.vendorPrefix + '-transition';
|
||||||
|
var cssValue = '0.5s linear all';
|
||||||
|
child.css(cssProp, cssValue);
|
||||||
|
|
||||||
|
if($sniffer.supportsTransitions) {
|
||||||
|
window.setTimeout.expect(1).process();
|
||||||
|
window.setTimeout.expect($sniffer.supportsTransitions ? 500 : 0).process();
|
||||||
|
} else {
|
||||||
|
expect(window.setTimeout.queue).toEqual([]);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ describe('$sniffer', function() {
|
||||||
function sniffer($window, $document) {
|
function sniffer($window, $document) {
|
||||||
$window.navigator = {};
|
$window.navigator = {};
|
||||||
$document = jqLite($document || {});
|
$document = jqLite($document || {});
|
||||||
|
if (!$document[0].body) {
|
||||||
|
$document[0].body = window.document.body;
|
||||||
|
}
|
||||||
return new $SnifferProvider().$get[2]($window, $document);
|
return new $SnifferProvider().$get[2]($window, $document);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,11 +24,11 @@ describe('$sniffer', function() {
|
||||||
|
|
||||||
describe('hashchange', function() {
|
describe('hashchange', function() {
|
||||||
it('should be true if onhashchange property defined', function() {
|
it('should be true if onhashchange property defined', function() {
|
||||||
expect(sniffer({onhashchange: true}, {}).hashchange).toBe(true);
|
expect(sniffer({onhashchange: true}).hashchange).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be false if onhashchange property not defined', function() {
|
it('should be false if onhashchange property not defined', function() {
|
||||||
expect(sniffer({}, {}).hashchange).toBe(false);
|
expect(sniffer({}).hashchange).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be false if documentMode is 7 (IE8 comp mode)', function() {
|
it('should be false if documentMode is 7 (IE8 comp mode)', function() {
|
||||||
|
|
@ -83,7 +86,7 @@ describe('$sniffer', function() {
|
||||||
|
|
||||||
describe('csp', function() {
|
describe('csp', function() {
|
||||||
it('should be false if document.securityPolicy.isActive not available', function() {
|
it('should be false if document.securityPolicy.isActive not available', function() {
|
||||||
expect(sniffer({}, {}).csp).toBe(false);
|
expect(sniffer({}).csp).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -96,4 +99,38 @@ describe('$sniffer', function() {
|
||||||
expect(sniffer({}, createDocumentWithCSP(true)).csp).toBe(true);
|
expect(sniffer({}, createDocumentWithCSP(true)).csp).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('vendorPrefix', function() {
|
||||||
|
|
||||||
|
it('should return the correct vendor prefix based on the browser', function() {
|
||||||
|
inject(function($sniffer, $window) {
|
||||||
|
var expectedPrefix;
|
||||||
|
var ua = $window.navigator.userAgent.toLowerCase();
|
||||||
|
if(/chrome/i.test(ua) || /safari/i.test(ua) || /webkit/i.test(ua)) {
|
||||||
|
expectedPrefix = 'Webkit';
|
||||||
|
}
|
||||||
|
else if(/firefox/i.test(ua)) {
|
||||||
|
expectedPrefix = 'Moz';
|
||||||
|
}
|
||||||
|
else if(/ie/i.test(ua)) {
|
||||||
|
expectedPrefix = 'Ms';
|
||||||
|
}
|
||||||
|
else if(/opera/i.test(ua)) {
|
||||||
|
expectedPrefix = 'O';
|
||||||
|
}
|
||||||
|
expect($sniffer.vendorPrefix).toBe(expectedPrefix);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('supportsTransitions', function() {
|
||||||
|
|
||||||
|
it('should be either true or false', function() {
|
||||||
|
inject(function($sniffer) {
|
||||||
|
expect($sniffer.supportsTransitions).not.toBe(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue