30 votes

Maçonnerie avec AngularJS

Je suis en train d'élaborer une "galerie d'art" app.

Hésitez pas à tirer vers le bas la source sur github et de jouer avec elle.

Plunker avec la totalité du code source.

Les travaux en cours autour de l'obtention de Maçonnerie à jouer gentil avec Angulaires:

.directive("masonry", function($parse) {
  return {
    restrict: 'AC',
    link: function (scope, elem, attrs) {
      elem.masonry({ itemSelector: '.masonry-brick'});
    }
  };     
})
.directive('masonryBrick', function ($compile) {
  return {
    restrict: 'AC',
    link: function (scope, elem, attrs) {
      scope.$watch('$index',function(v){
        elem.imagesLoaded(function () {
          elem.parents('.masonry').masonry('reload');
        });
      });
    }
  };    
});

Cela ne fonctionne pas bien parce que:

  • Comme le contenu grandit, la surcharge de tiggering recharger sur l'ensemble du conteneur.

L' reload fonction de:

  • Ne pas "ajouterdes éléments", plutôt ré-organise chaque élément dans le conteneur.
  • N' travail pour le déclenchement d'un rechargement lorsque les articles sont filtrés par un jeu de résultats.

Dans le contexte de l'application que je vous ai donné les liens ci-dessus, ce problème devient très facile à reproduire.

Je suis à la recherche d'une solution qui vous permettra d'utiliser les directives à l'effet de levier:

.masonry('appended', elem) et .masonry('prepended', elem)

Plutôt que de l'exécuter .masonry('reload') à chaque fois.

.masonry('reload') lorsque les éléments sont supprimés de jeu de résultats.


MODIFIER

Le projet a été mis à jour pour utiliser la solution de travail ci-dessous.

Prenez les sources sur GitHub

Voir une version de travail sur Plunker

19voto

James Sharp Points 576

J'ai été jouer avec cela un peu plus et @ganaraj la réponse est très soigné. Si vous vous en tenez à un $element.masonry('resize'); de son contrôleur appendBrick méthode et compte pour les images de chargement puis on dirait qu'il travaille.

Voici un plunker fourche avec elle: http://plnkr.co/edit/8t41rRnLYfhOF9oAfSUA

La raison de ce qui est nécessaire, c'est parce que le nombre de colonnes est calculée uniquement lorsque la maçonnerie est initialisé sur l'élément ou le conteneur est redimensionné et à ce stade, nous n'avons pas toutes les briques pour la valeur par défaut d'une colonne unique.

Si vous ne souhaitez pas utiliser le 'redimensionner' méthode (je ne pense pas que c'est documenté) ensuite, vous pouvez simplement appeler $element.maçonnerie (), mais qui provoque une re-mise en page de sorte que vous voulez les appeler uniquement lorsque la première brique est ajouté.

Edit: j'ai mis à jour le plunker au-dessus de n'appeler qu' resize lorsque la liste se développe au-dessus de 0 longueur et de ne faire qu'un "reload" lorsque plusieurs briques sont supprimés dans le même $digest cycle.

La Directive de code est:

angular.module('myApp.directives', [])
  .directive("masonry", function($parse, $timeout) {
    return {
      restrict: 'AC',
      link: function (scope, elem, attrs) {
        elem.masonry({ itemSelector: '.masonry-brick'});
        // Opitonal Params, delimited in class name like:
        // class="masonry:70;"
        //elem.masonry({ itemSelector: '.masonry-item', columnWidth: 140, gutterWidth: $parse(attrs.masonry)(scope) });
      },
      controller : function($scope,$element){
          var bricks = [];
          this.appendBrick = function(child, brickId, waitForImage){
            function addBrick() {
              $element.masonry('appended', child, true);

              // If we don't have any bricks then we're going to want to 
              // resize when we add one.
              if (bricks.length === 0) {
                // Timeout here to allow for a potential
                // masonary timeout when appending (when animating
                // from the bottom)
                $timeout(function(){
                  $element.masonry('resize');  
                }, 2);  
              }

              // Store the brick id
              var index = bricks.indexOf(brickId);
              if (index === -1) {
                bricks.push(brickId);
              }
            }

            if (waitForImage) {
              child.imagesLoaded(addBrick);      
            } else {
              addBrick();
            }
          };

          // Removed bricks - we only want to call masonry.reload() once
          // if a whole batch of bricks have been removed though so push this
          // async.
          var willReload = false;
          function hasRemovedBrick() {
            if (!willReload) {
              willReload = true;
              $scope.$evalAsync(function(){
                willReload = false;
                $element.masonry("reload");
              });
            }
          }

          this.removeBrick = function(brickId){
              hasRemovedBrick();
              var index = bricks.indexOf(brickId);
              if (index != -1) {
                bricks.splice(index,1);
              }
          };
      }
    };     
  })
  .directive('masonryBrick', function ($compile) {
    return {
      restrict: 'AC',
      require : '^masonry',
      link: function (scope, elem, attrs, MasonryCtrl) {

      elem.imagesLoaded(function () {
        MasonryCtrl.appendBrick(elem, scope.$id, true);
      });

      scope.$on("$destroy",function(){
          MasonryCtrl.removeBrick(scope.$id);
      }); 
    }
  };
});

7voto

g00fy Points 1777

Ce n'est pas exactement ce que vous cherchez (prepend et append), mais doit être juste ce que vous cherchez:

http://plnkr.co/edit/dmuGHCNTCBBuYpjyKQ8E?p=preview

Votre version de la directive déclenche reload pour chaque brick. Cette version ne se déclenche que recharger seulement une fois pour l'ensemble de la liste de changement.

L'approche est très simple:

  1. Inscrire de nouveaux bricks parents masonry controller
  2. $watch pour les changements dans le régime enregistré d' bricks et le feu masonry('reload')
  3. Retirez brick de bricks de registre lorsque vous retirez l'élément - $on('$destroy')
  4. ?
  5. Résultat

Vous pouvez étendre cette approche pour faire ce que tu voulais (utiliser prepend et append) mais je ne vois pas pourquoi vous voulez le faire. Cette mesure permettrait également d'être beaucoup plus compliqué, parce que vous auriez à suivre manuellement l'ordre des éléments. Je ne pense qu'il serait plus rapide, au contraire, il peut être plus lent, puisque vous avez à déclencher de multiples append/prepend si vos modifications beaucoup de briques.

Je ne suis pas tout à fait sûr, mais je suppose que vous pourriez utiliser ng-animate (la JavaScript animation version)

Nous avons mis en place quelque chose de similaire pour tiling événements dans notre agenda. Cette solution s'est avéré pour être le plus rapide. Si quelqu'un a une meilleure solution, je serais ravi de voir que.

Pour ceux qui veulent voir le code:

angular.module('myApp.directives', [])
  .directive("masonry", function($parse) {
    return {
      restrict: 'AC',
      controller:function($scope,$element){
        // register and unregister bricks
        var bricks = [];
        this.addBrick = function(brick){
          bricks.push(brick)
        }
        this.removeBrick = function(brick){
          var index = bricks.indexOf(brick);
          if(index!=-1)bricks.splice(index,1);
        }
        $scope.$watch(function(){
          return bricks
        },function(){
          // triggers only once per list change (not for each brick)
          console.log('reload');
          $element.masonry('reload');
        },true);
      },
      link: function (scope, elem, attrs) {
        elem.masonry({ itemSelector: '.masonry-brick'});
      }
    };     
  })
  .directive('masonryBrick', function ($compile) {
    return {
      restrict: 'AC',
      require:'^masonry',
      link: function (scope, elem, attrs,ctrl) {
        ctrl.addBrick(scope.$id);

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

Edit: il y a une chose que j'ai oublié (le chargement des images) et appeler à "recharger" lorsque toutes les images ont été chargées. Malade, essayez de modifier le code plus tard.

3voto

CMCDragonkai Points 544

Hey je viens de faire de la maçonnerie directive pour AngularJS qui est beaucoup plus simple que la plupart des implémentations que j'ai vu. Vérifiez le résumé ici: https://gist.github.com/CMCDragonkai/6191419

Il est compatible avec les AMD. Nécessite jQuery, imagesLoaded et lodash. Fonctionne avec la quantité dynamique des objets, AJAX chargé éléments (même avec les points initiaux), redimensionnement de la fenêtre, et les options de personnalisation. Ajouté des éléments, pièces jointes, reloaded éléments... etc. 73 lignes!

Voici un plunkr de montrer ce travail: http://plnkr.co/edit/ZuSrSh?p=preview (sans AMD, mais le même code).

1voto

ganaraj Points 14228

L'un des moins fonctionnalité documentée Angulaire est sa Directive Contrôleurs ( même si elle est sur la première page de www.angularjs.org - Onglets ).

Ici est une version modifiée de plunker qui rend l'utilisation de ce mécanisme.

http://plnkr.co/edit/NmV3m6DZFSpIkQOAjRRE

Les gens utilisent les Contrôleurs de la Directive, mais il a été utilisé ( et abusé ) pour les choses qu'il n'a probablement pas fait pour.

Dans le plunker ci-dessus, j'ai seulement modifié la directives.js fichier. La Directive contrôleurs sont un mécanisme de communication entre les directives. Parfois , il ne suffit pas / facile de tout faire en une seule directive. Dans ce cas, vous avez déjà créé deux directives, mais la bonne façon de les faire interagir par l'intermédiaire d'un contrôleur de la directive.

Je n'étais pas en mesure de comprendre quand vous vouliez ajouter et lorsque vous voulez ajouter. J'ai seulement mis en œuvre "ajouter" à l'heure actuelle.

Également sur une note de côté : Si les ressources ne mettent déjà en œuvre des promesses, vous pouvez mettre en œuvre vous-même. Il n'est pas vraiment difficile à faire. J'ai remarqué que vous êtes en utilisant un mécanisme de rappel (que je n'est pas recommander ). Vous avez déjà mis dans les promesses de là, mais encore vous utilisez des rappels dont je n'étais pas en mesure de comprendre pourquoi.

Est-ce à fournir une solution adéquate à votre problème ?

Pour voir la documentation http://docs.angularjs.org/guide/directive > la Directive Définition de l'Objet > contrôleur.

0voto

Conrad Points 16

Je crois que j'ai eu exactement le même problème:

De nombreuses images dans un ng-repeat boucle, et que vous voulez appliquer maçonnerie/isotope à eux lorsqu'ils sont chargé et prêt.

Le problème est que, même après imagesLoaded est appelé en arrière, il y a une période de temps lorsque les images ne sont pas "complète", et ne peut donc pas être mesuré et mis en œuvre adéquatement.

Je suis venu avec la solution suivante qui fonctionne pour moi et ne nécessite qu'une seule passe de mise en page. Il se produit en trois étapes

  1. D'attente pour les images à charger (lorsque le dernier est ajouté à partir de la boucle - utilise les images jQuery plugin chargé).
  2. Attendre que toutes les images pour "compléter"
  3. La mise en images.

angularApp.directive('checkLast', function () {
    return {
        restrict: 'A',
        compile: function (element, attributes) {
            return function postLink(scope, element) {
                if (scope.$last === true) {
                    $('#imagesHolder').imagesLoaded(function () {
                        waitForRender();
                    });
                }
            }
        }
    }
});

function waitForRender() {
    //
    // We have to wait for every image to be ready before we can lay them out
    //
    var ready = true;
    var images = $('#imagesHolder').find('img');
    $.each(images,function(index,img) {
        if ( !img.complete ) {
            setTimeout(waitForRender);
            ready = false;
            return false;
        }
    });
    if (ready) {
        layoutImages();
    }
}

function layoutImages() {
    $('#imagesHolder').isotope({
        itemSelector: '.imageHolder',
        layoutMode: 'fitRows'
    });
}

Cela fonctionne avec mise en page comme ceci

<div id="imagesHolder">
    <div class="imageHolder"
         check-last
         ng-repeat="image in images.image"
        <img ng-src="{{image.url}}"/>
    </div>
</div>

J'espère que cette aide.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X