Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS Masonry for Dynamically changing heights

I have divs that expand and contract when clicked on. The Masonry library has worked great for initializing the page. The problem I am experiencing is that with the absolute positioning in place from Masonry and the directive below, when divs expand they overlap with the divs below. I need to have the divs below the expanding div move down to deal with the expansion.

My sources are: http://masonry.desandro.com/

and

https://github.com/passy/angular-masonry/blob/master/src/angular-masonry.js

/*!
* angular-masonry <%= pkg.version %>
* Pascal Hartig, weluse GmbH, http://weluse.de/
* License: MIT
*/
(function () {
  'use strict';

angular.module('wu.masonry', [])
.controller('MasonryCtrl', function controller($scope, $element, $timeout) {
  var bricks = {};
  var schedule = [];
  var destroyed = false;
  var self = this;
  var timeout = null;

  this.preserveOrder = false;
  this.loadImages = true;

  this.scheduleMasonryOnce = function scheduleMasonryOnce() {
    var args = arguments;
    var found = schedule.filter(function filterFn(item) {
      return item[0] === args[0];
    }).length > 0;

    if (!found) {
      this.scheduleMasonry.apply(null, arguments);
    }
  };

  // Make sure it's only executed once within a reasonable time-frame in
  // case multiple elements are removed or added at once.
  this.scheduleMasonry = function scheduleMasonry() {
    if (timeout) {
      $timeout.cancel(timeout);
    }

    schedule.push([].slice.call(arguments));

    timeout = $timeout(function runMasonry() {
      if (destroyed) {
        return;
      }
      schedule.forEach(function scheduleForEach(args) {
        $element.masonry.apply($element, args);
      });
      schedule = [];
    }, 30);
  };

  function defaultLoaded($element) {
    $element.addClass('loaded');
  }

  this.appendBrick = function appendBrick(element, id) {
    if (destroyed) {
      return;
    }

    function _append() {
      if (Object.keys(bricks).length === 0) {
        $element.masonry('resize');
      }
      if (bricks[id] === undefined) {
        // Keep track of added elements.
        bricks[id] = true;
        defaultLoaded(element);
        $element.masonry('appended', element, true);
      }
    }

    function _layout() {
      // I wanted to make this dynamic but ran into huuuge memory leaks
      // that I couldn't fix. If you know how to dynamically add a
      // callback so one could say <masonry loaded="callback($element)">
      // please submit a pull request!
      self.scheduleMasonryOnce('layout');
    }

    if (!self.loadImages){
      _append();
      _layout();
    } else if (self.preserveOrder) {
      _append();
      element.imagesLoaded(_layout);
    } else {
      element.imagesLoaded(function imagesLoaded() {
        _append();
        _layout();
      });
    }
  };

  this.removeBrick = function removeBrick(id, element) {
    if (destroyed) {
      return;
    }

    delete bricks[id];
    $element.masonry('remove', element);
    this.scheduleMasonryOnce('layout');
  };

  this.destroy = function destroy() {
    destroyed = true;

    if ($element.data('masonry')) {
      // Gently uninitialize if still present
      $element.masonry('destroy');
    }
    $scope.$emit('masonry.destroyed');

    bricks = [];
  };

  this.reload = function reload() {
    $element.masonry();
    $scope.$emit('masonry.reloaded');
  };


}).directive('masonry', function masonryDirective() {
  return {
    restrict: 'AE',
    controller: 'MasonryCtrl',
    link: {
      pre: function preLink(scope, element, attrs, ctrl) {
        var attrOptions = scope.$eval(attrs.masonry || attrs.masonryOptions);
        var options = angular.extend({
          itemSelector: attrs.itemSelector || '.masonry-brick',
          columnWidth: parseInt(attrs.columnWidth, 10) || attrs.columnWidth
        }, attrOptions || {});
        element.masonry(options);
        var loadImages = scope.$eval(attrs.loadImages);
        ctrl.loadImages = loadImages !== false;
        var preserveOrder = scope.$eval(attrs.preserveOrder);
        ctrl.preserveOrder = (preserveOrder !== false && attrs.preserveOrder !== undefined);

        scope.$emit('masonry.created', element);
        scope.$on('$destroy', ctrl.destroy);
      }
    }
  };
}).directive('masonryBrick', function masonryBrickDirective() {
  return {
    restrict: 'AC',
    require: '^masonry',
    scope: true,
    link: {
      pre: function preLink(scope, element, attrs, ctrl) {
        var id = scope.$id, index;

        ctrl.appendBrick(element, id);
        element.on('$destroy', function () {
          ctrl.removeBrick(id, element);
        });

        scope.$on('masonry.reload', function () {
          ctrl.scheduleMasonryOnce('reloadItems');
          ctrl.scheduleMasonryOnce('layout');
        });

        scope.$watch('$index', function () {
          if (index !== undefined && index !== scope.$index) {
            ctrl.scheduleMasonryOnce('reloadItems');
            ctrl.scheduleMasonryOnce('layout');
          }
          index = scope.$index;
        });
      }
    }
  };
});
}());
like image 365
Yahiko Kikikoto Avatar asked Nov 01 '22 23:11

Yahiko Kikikoto


2 Answers

Like with many non-Angular libraries, it appears the answer lies in wrapping the library in an Angular directive.

I haven't tried it out but it appears that is what this person did

like image 163
Garrett McCullough Avatar answered Nov 11 '22 02:11

Garrett McCullough


You can use angular's $emit, $broadcast, and $on functionality.

Inside your masonry directive link function:

scope.$on('$resizeMasonry', ctrl.scheduleMasonryOnce('layout'));

Inside your masonryBrick directive link function or any other child element:

scope.$emit('$resizeMasonry');

Use $emit to send an event up the scope tree and $broadcast to send an event down the scope tree.

like image 27
Travis Avatar answered Nov 11 '22 03:11

Travis