isherwood
isherwood

Reputation: 61114

Nested list is cut off by parent list element after slide animation

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

Answers (3)

Bojan
Bojan

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

CoryDorning
CoryDorning

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

isherwood
isherwood

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');
                });
            }
        }
    }
});

CodePen demo

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

Related Questions