Reputation: 61114
I'm building an Ionic app with nested lists of comments. I need to animate replies as child elements and retain state. Currently I'm using a simple directive with jQuery slideToggle, but this doesn't retain state (and isn't "the Angular way").
This example of slide animations by Shlomi Assaf is a great start to what I need, but it doesn't handle nested elements. I've created a nested version of his CodePen project to demonstrate the problem.
I'm not sure whether the animation function should be modified to handle nested elements, or whether my controller should call the animation on ancestor elements when the child element is animated (or after it has completed).
Assistance is appreciated. Here's the basis of the HTML using native AngularJS directives:
<button ng-click="slideToggle1=!slideToggle1">Click Me</button>
<div class="slide-toggle" ng-show="slideToggle1"> ... </div>
Here's the original animation function:
app.animation('.slide-toggle', ['$animateCss', function($animateCss) {
var lastId = 0;
var _cache = {};
function getId(el) {
var id = el[0].getAttribute("data-slide-toggle");
if (!id) {
id = ++lastId;
el[0].setAttribute("data-slide-toggle", id);
}
return id;
}
function getState(id) {
var state = _cache[id];
if (!state) {
state = {};
_cache[id] = state;
}
return state;
}
function generateRunner(closing, state, animator, element, doneFn) {
return function() {
state.animating = true;
state.animator = animator;
state.doneFn = doneFn;
animator.start().finally(function() {
if (closing && state.doneFn === doneFn) {
element[0].style.height = '';
}
state.animating = false;
state.animator = undefined;
state.doneFn();
});
}
}
return {
addClass: function(element, className, doneFn) {
if (className == 'ng-hide') {
var state = getState(getId(element));
var height = (state.animating && state.height) ?
state.height : element[0].offsetHeight;
var animator = $animateCss(element, {
from: {
height: height + 'px',
opacity: 1
},
to: {
height: '0px',
opacity: 0
}
});
if (animator) {
if (state.animating) {
state.doneFn =
generateRunner(true,
state,
animator,
element,
doneFn);
return state.animator.end();
} else {
state.height = height;
return generateRunner(true,
state,
animator,
element,
doneFn)();
}
}
}
doneFn();
},
removeClass: function(element, className, doneFn) {
if (className == 'ng-hide') {
var state = getState(getId(element));
var height = (state.animating && state.height) ?
state.height : element[0].offsetHeight;
var animator = $animateCss(element, {
from: {
height: '0px',
opacity: 0
},
to: {
height: height + 'px',
opacity: 1
}
});
if (animator) {
if (state.animating) {
state.doneFn = generateRunner(false,
state,
animator,
element,
doneFn);
return state.animator.end();
} else {
state.height = height;
return generateRunner(false,
state,
animator,
element,
doneFn)();
}
}
}
doneFn();
}
};
}]);
Upvotes: 1
Views: 415
Reputation: 186
You only need to add else statement here:
if (closing && state.doneFn === doneFn) {
element[0].style.height = '';
} else {
element[0].style.height = 'auto';
}
This way after the animation is done, height is set to auto and that solves everything on as many levels as you want.
Upvotes: 0
Reputation: 1914
if you want to do more than one level deep, you can use this:
jQuery
app.directive('toggleParents', function () {
return {
compile: function (element, attr) {
return function (scope, element) {
element.on('click', function (event) {
$(this).parents('.slide-toggle').css('height', 'auto');
});
}
}
}
});
Native JS
app.directive('toggleParents', function() {
return {
compile: function(element, attr) {
var parents = function(el, cls) {
var els = [];
while (el = el.parentElement) {
var hasClass = el.classList.contains(cls);
if (hasClass) {
els.push(el);
}
}
return els;
};
return function(scope, element) {
element.on('click', function(event) {
angular.element(parents(element[0], 'slide-toggle')).css('height', 'auto');
});
};
}
};
});
Upvotes: 0
Reputation: 61114
Here's what I'm using for now. It's a directive to set the parent element's height to auto
so it expands or contracts with the child list height as it's toggled. If the parent list is toggled the height gets recalculated as normal for its animation.
app.directive('toggleParent', function () {
return {
restrict: 'C',
compile: function (element, attr) {
return function (scope, element) {
element.on('click', function (event) {
$(this).closest('.slide-toggle').css('height', 'auto');
});
}
}
}
});
I'm sure that this same functionality can be implemented with the animation instead. That's what I'd really like help with.
Upvotes: 0