Angular with Masonry Sorting
by ian_smithz
HTML
<script src="//cdnjs.cloudflare.com/ajax/libs/masonry/2.1.08/jquery.masonry.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.5/angular.min.js"></script>
<div class="container">
<div ng-controller="MainCtrl">
<div class="row">
<div class="col-xs-1"></div>
<div class="col-xs-5">
<input type="text" class="form-control col-sm-6" placeholder="filter by title" ng-model="nameFilter" />
</div>
<!-- Single button -->
<div class="btn-group col-xs-5">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Sort by .. <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li>
<li> <a href="#" ng-click="changeSort('created')">Sort by last edited</a>
</li>
<li> <a href="#" ng-click="changeSort('title')">Sort by title</a>
</li>
<li><a href="#" ng-click="changeSort('url')">Sort by url</a>
</li>
</ul>
</div>
</div>
<br />
<hr />
<ul class="grid" masonry="true">
<li ng-repeat="item in items | filter: { title: nameFilter } | orderBy:sort:reverse: order"><span>Fiddle Title: </span>
<br />
<h5> <a href="http:{{item.url}}" target="_blank"><span>{{item.title}}</span></a></h5>
<span>Framework: <strong>{{item.framework}}</strong></span>
<br />
<br />
<!--br />...
CSS
body{padding:20px}
.grid {
border: solid 1px #ccc padding: 10px 10px 0 0;
}
.grid li {
display: inline-block;
width: 200px;
height: 100px;
padding:5px;
border: solid 1px #ccc;
margin: 0 0 10px 10px;
background: #f6f6f6;
}
.grid li img {
margin-left:35%;
margin-top:5px
}
/**** Transitions ****/
.masonry, .masonry li {
/* apply before masonry add class */
position: absolute;
/* important to add it before enter animation start */
-webkit-transition-duration: .7s;
transition-duration: .7s;
-webkit-transition-property: all;
transition-property: all;
}
.masonry .masonry-enter {
/* limit animated properties for entering elements */
-webkit-transition-property: -webkit-transform, opacity;
transition-property: transform, opacity;
}
.masonry-enter, .masonry-leave.masonry-leave-active {
opacity: 0;
-webkit-transform: scale(0.1);
transform: scale(0.1);
}
.masonry-leave, .masonry-enter.masonry-enter-active {
opacity: 1;
-webkit-transform: scale(1);
transform: scale(1);
}
JavaScript
angular.module('app', [])
.directive("masonry", function () {
var NGREPEAT_SOURCE_RE = '<!-- ngRepeat: ((.*) in ((.*?)( track by (.*))?)) -->';
return {
compile: function (element, attrs) {
// auto add animation to brick element
var animation = attrs.ngAnimate || "'masonry'";
var $brick = element.children();
$brick.attr("ng-animate", animation);
// generate item selector (exclude leaving items)
var type = $brick.prop('tagName');
var itemSelector = type + ":not([class$='-leave-active'])";
return function (scope, element, attrs) {
var options = angular.extend({
itemSelector: itemSelector
}, scope.$eval(attrs.masonry));
// try to infer model from ngRepeat
if (!options.model) {
var ngRepeatMatch = element.html().match(NGREPEAT_SOURCE_RE);
if (ngRepeatMatch) {
options.model = ngRepeatMatch[4];
}
}
// initial animation
element.addClass('masonry');
// Wait inside directives to render
setTimeout(function () {
element.masonry(options);
element.on("$destroy", function () {
element.masonry('destroy')
});
if (options.model) {
scope.$apply(function () {
scope.$watchCollection(options.model, function (_new, _old) {
if (_new == _old) return;
// Wait inside directives to render
setTimeout(function () {
element.masonry("reload");
});
});
});
...