aboutsummaryrefslogtreecommitdiffstats
path: root/src/ng/directive
diff options
context:
space:
mode:
authorMisko Hevery2013-03-20 16:24:23 -0700
committerMisko Hevery2013-04-02 14:05:06 -0700
commit0b6f1ce5f89f47f9302ff1e8cd8f4b92f837c413 (patch)
tree8cbc0c86024dd4f97d0aa54e0c9b7df9b0d56b86 /src/ng/directive
parent4bfb66ce0be46d3a0e9da2f80f3e1d0c2b559828 (diff)
downloadangular.js-0b6f1ce5f89f47f9302ff1e8cd8f4b92f837c413.tar.bz2
feat(ngAnimate): add support for animation
Diffstat (limited to 'src/ng/directive')
-rw-r--r--src/ng/directive/ngInclude.js28
-rw-r--r--src/ng/directive/ngRepeat.js29
-rw-r--r--src/ng/directive/ngShowHide.js54
-rw-r--r--src/ng/directive/ngSwitch.js92
-rw-r--r--src/ng/directive/ngView.js20
5 files changed, 154 insertions, 69 deletions
diff --git a/src/ng/directive/ngInclude.js b/src/ng/directive/ngInclude.js
index d4eacbe3..a385d00b 100644
--- a/src/ng/directive/ngInclude.js
+++ b/src/ng/directive/ngInclude.js
@@ -12,6 +12,13 @@
* (e.g. ngInclude won't work for cross-domain requests on all browsers and for
* 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
*
* @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
@@ -78,8 +85,8 @@
* @description
* Emitted every time the ngInclude content is reloaded.
*/
-var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile',
- function($http, $templateCache, $anchorScroll, $compile) {
+var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', '$animator',
+ function($http, $templateCache, $anchorScroll, $compile, $animator) {
return {
restrict: 'ECA',
terminal: true,
@@ -88,7 +95,8 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
onloadExp = attr.onload || '',
autoScrollExp = attr.autoscroll;
- return function(scope, element) {
+ return function(scope, element, attr) {
+ var animate = $animator(scope, attr);
var changeCounter = 0,
childScope;
@@ -97,8 +105,7 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
childScope.$destroy();
childScope = null;
}
-
- element.html('');
+ animate.leave(element.contents(), element);
};
scope.$watch(srcExp, function ngIncludeWatchAction(src) {
@@ -110,9 +117,12 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
if (childScope) childScope.$destroy();
childScope = scope.$new();
+ animate.leave(element.contents(), element);
- element.html(response);
- $compile(element.contents())(childScope);
+ var contents = jqLite('<div/>').html(response).contents();
+
+ animate.enter(contents, element);
+ $compile(contents)(childScope);
if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
@@ -123,7 +133,9 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
}).error(function() {
if (thisChangeId === changeCounter) clearContent();
});
- } else clearContent();
+ } else {
+ clearContent();
+ }
});
};
}
diff --git a/src/ng/directive/ngRepeat.js b/src/ng/directive/ngRepeat.js
index a00bc9e4..fada0696 100644
--- a/src/ng/directive/ngRepeat.js
+++ b/src/ng/directive/ngRepeat.js
@@ -16,6 +16,13 @@
* * `$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.
*
+ * 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
* @scope
@@ -75,13 +82,15 @@
</doc:scenario>
</doc:example>
*/
-var ngRepeatDirective = ['$parse', function($parse) {
+var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) {
+ var NG_REMOVED = '$$NG_REMOVED';
return {
transclude: 'element',
priority: 1000,
terminal: true,
compile: function(element, attr, linker) {
return function($scope, $element, $attr){
+ var animate = $animator($scope, $attr);
var expression = $attr.ngRepeat;
var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),
trackByExp, hashExpFn, trackByIdFn, lhs, rhs, valueIdentifier, keyIdentifier,
@@ -130,6 +139,7 @@ var ngRepeatDirective = ['$parse', function($parse) {
$scope.$watchCollection(rhs, function ngRepeatAction(collection){
var index, length,
cursor = $element, // current position of the node
+ nextCursor,
// Same as lastBlockMap but it has the current state. It will become the
// lastBlockMap on the next iteration.
nextBlockMap = {},
@@ -184,7 +194,8 @@ var ngRepeatDirective = ['$parse', function($parse) {
for (key in lastBlockMap) {
if (lastBlockMap.hasOwnProperty(key)) {
block = lastBlockMap[key];
- block.element.remove();
+ animate.leave(block.element);
+ block.element[0][NG_REMOVED] = true;
block.scope.$destroy();
}
}
@@ -200,12 +211,17 @@ var ngRepeatDirective = ['$parse', function($parse) {
// associated scope/element
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
cursor = block.element;
} else {
// existing item which got moved
- cursor.after(block.element);
+ animate.move(block.element, null, cursor);
cursor = block.element;
}
} else {
@@ -221,8 +237,8 @@ var ngRepeatDirective = ['$parse', function($parse) {
childScope.$middle = !(childScope.$first || childScope.$last);
if (!block.element) {
- linker(childScope, function(clone){
- cursor.after(clone);
+ linker(childScope, function(clone) {
+ animate.enter(clone, null, cursor);
cursor = clone;
block.scope = childScope;
block.element = clone;
@@ -236,3 +252,4 @@ var ngRepeatDirective = ['$parse', function($parse) {
}
};
}];
+
diff --git a/src/ng/directive/ngShowHide.js b/src/ng/directive/ngShowHide.js
index 74195468..418a43ff 100644
--- a/src/ng/directive/ngShowHide.js
+++ b/src/ng/directive/ngShowHide.js
@@ -6,7 +6,18 @@
*
* @description
* 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
* @param {expression} ngShow If the {@link guide/expression expression} is truthy
@@ -33,11 +44,14 @@
</doc:example>
*/
//TODO(misko): refactor to remove element from the DOM
-var ngShowDirective = ngDirective(function(scope, element, attr){
- scope.$watch(attr.ngShow, function ngShowWatchAction(value){
- element.css('display', toBoolean(value) ? '' : 'none');
- });
-});
+var ngShowDirective = ['$animator', function($animator) {
+ return function(scope, element, attr) {
+ 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
*
* @description
- * The `ngHide` and `ngShow` directives hide or show a portion of the DOM tree (HTML)
- * conditionally.
+ * The `ngShow` and `ngHide` directives show or hide a portion of the DOM tree (HTML)
+ * 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
* @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>
*/
//TODO(misko): refactor to remove element from the DOM
-var ngHideDirective = ngDirective(function(scope, element, attr){
- scope.$watch(attr.ngHide, function ngHideWatchAction(value){
- element.css('display', toBoolean(value) ? 'none' : '');
- });
-});
+var ngHideDirective = ['$animator', function($animator) {
+ return function(scope, element, attr) {
+ var animate = $animator(scope, attr);
+ scope.$watch(attr.ngHide, function ngHideWatchAction(value){
+ animate[toBoolean(value) ? 'hide' : 'show'](element);
+ });
+ };
+}];
diff --git a/src/ng/directive/ngSwitch.js b/src/ng/directive/ngSwitch.js
index f22e634d..8b0dab31 100644
--- a/src/ng/directive/ngSwitch.js
+++ b/src/ng/directive/ngSwitch.js
@@ -6,15 +6,30 @@
* @restrict EA
*
* @description
- * Conditionally change the DOM structure. Elements within ngSwitch but without
- * ngSwitchWhen or ngSwitchDefault directives will be preserved at the location
- * as specified in the template
+ * The ngSwitch directive is used to conditionally swap DOM structure on your template based on a scope expression.
+ * Elements within ngSwitch but without ngSwitchWhen or ngSwitchDefault directives will be preserved at the location
+ * 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
* <ANY ng-switch="expression">
* <ANY ng-switch-when="matchValue1">...</ANY>
* <ANY ng-switch-when="matchValue2">...</ANY>
- * ...
* <ANY ng-switch-default>...</ANY>
* </ANY>
*
@@ -67,43 +82,48 @@
</doc:scenario>
</doc:example>
*/
-var NG_SWITCH = 'ng-switch';
-var ngSwitchDirective = valueFn({
- restrict: 'EA',
- require: 'ngSwitch',
- // asks for $scope to fool the BC controller module
- controller: ['$scope', function ngSwitchController() {
- this.cases = {};
- }],
- link: function(scope, element, attr, ctrl) {
- var watchExpr = attr.ngSwitch || attr.on,
- selectedTranscludes,
- selectedElements,
- selectedScopes = [];
+var ngSwitchDirective = ['$animator', function($animator) {
+ return {
+ restrict: 'EA',
+ require: 'ngSwitch',
- scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
- for (var i= 0, ii=selectedScopes.length; i<ii; i++) {
- selectedScopes[i].$destroy();
- selectedElements[i].remove();
- }
+ // asks for $scope to fool the BC controller module
+ controller: ['$scope', function ngSwitchController() {
+ this.cases = {};
+ }],
+ link: function(scope, element, attr, ngSwitchController) {
+ var animate = $animator(scope, attr);
+ var watchExpr = attr.ngSwitch || attr.on,
+ selectedTranscludes,
+ selectedElements,
+ selectedScopes = [];
- 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]);
+ }
- 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);
+ selectedElements = [];
+ selectedScopes = [];
+
+ if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) {
+ scope.$eval(attr.change);
+ forEach(selectedTranscludes, function(selectedTransclude) {
+ var selectedScope = scope.$new();
+ selectedScopes.push(selectedScope);
+ selectedTransclude.transclude(selectedScope, function(caseElement) {
+ var anchor = selectedTransclude.element;
+
+ selectedElements.push(caseElement);
+ animate.enter(caseElement, anchor.parent(), anchor);
+ });
});
- });
- }
- });
+ }
+ });
+ }
}
-});
+}];
var ngSwitchWhenDirective = ngDirective({
transclude: 'element',
diff --git a/src/ng/directive/ngView.js b/src/ng/directive/ngView.js
index 6e92c2d8..2ffd64da 100644
--- a/src/ng/directive/ngView.js
+++ b/src/ng/directive/ngView.js
@@ -12,6 +12,13 @@
* Every time the current route changes, the included view changes with it according to the
* 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
* @example
<example module="ngView">
@@ -105,15 +112,16 @@
* Emitted every time the ngView content is reloaded.
*/
var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$compile',
- '$controller',
+ '$controller', '$animator',
function($http, $templateCache, $route, $anchorScroll, $compile,
- $controller) {
+ $controller, $animator) {
return {
restrict: 'ECA',
terminal: true,
link: function(scope, element, attr) {
var lastScope,
- onloadExp = attr.onload || '';
+ onloadExp = attr.onload || '',
+ animate = $animator(scope, attr);
scope.$on('$routeChangeSuccess', update);
update();
@@ -127,7 +135,7 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c
}
function clearContent() {
- element.html('');
+ animate.leave(element.contents(), element);
destroyLastScope();
}
@@ -136,8 +144,8 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c
template = locals && locals.$template;
if (template) {
- element.html(template);
- destroyLastScope();
+ clearContent();
+ animate.enter(jqLite('<div></div>').html(template).contents(), element);
var link = $compile(element.contents()),
current = $route.current,