From c4fa1a2e0271a58c0390c0b1665a5facc8bac411 Mon Sep 17 00:00:00 2001 From: Dominic Watson Date: Sat, 3 Oct 2015 19:02:50 +0200 Subject: [PATCH 01/11] Moved source files to src/js --- src/{ => js}/as.filter.js | 0 src/{ => js}/custom-filter.filter.js | 0 src/{ => js}/custom-ranker.filter.js | 0 src/{ => js}/dynamic-layout.directive.js | 0 src/{ => js}/filter.service.js | 0 src/{ => js}/layout-on-load.directive.js | 0 src/{ => js}/module.js | 0 src/{ => js}/position.service.js | 0 src/{ => js}/ranker.service.js | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename src/{ => js}/as.filter.js (100%) rename src/{ => js}/custom-filter.filter.js (100%) rename src/{ => js}/custom-ranker.filter.js (100%) rename src/{ => js}/dynamic-layout.directive.js (100%) rename src/{ => js}/filter.service.js (100%) rename src/{ => js}/layout-on-load.directive.js (100%) rename src/{ => js}/module.js (100%) rename src/{ => js}/position.service.js (100%) rename src/{ => js}/ranker.service.js (100%) diff --git a/src/as.filter.js b/src/js/as.filter.js similarity index 100% rename from src/as.filter.js rename to src/js/as.filter.js diff --git a/src/custom-filter.filter.js b/src/js/custom-filter.filter.js similarity index 100% rename from src/custom-filter.filter.js rename to src/js/custom-filter.filter.js diff --git a/src/custom-ranker.filter.js b/src/js/custom-ranker.filter.js similarity index 100% rename from src/custom-ranker.filter.js rename to src/js/custom-ranker.filter.js diff --git a/src/dynamic-layout.directive.js b/src/js/dynamic-layout.directive.js similarity index 100% rename from src/dynamic-layout.directive.js rename to src/js/dynamic-layout.directive.js diff --git a/src/filter.service.js b/src/js/filter.service.js similarity index 100% rename from src/filter.service.js rename to src/js/filter.service.js diff --git a/src/layout-on-load.directive.js b/src/js/layout-on-load.directive.js similarity index 100% rename from src/layout-on-load.directive.js rename to src/js/layout-on-load.directive.js diff --git a/src/module.js b/src/js/module.js similarity index 100% rename from src/module.js rename to src/js/module.js diff --git a/src/position.service.js b/src/js/position.service.js similarity index 100% rename from src/position.service.js rename to src/js/position.service.js diff --git a/src/ranker.service.js b/src/js/ranker.service.js similarity index 100% rename from src/ranker.service.js rename to src/js/ranker.service.js From 1c2c505d03ee368fbd24071a4fd402401ac2b993 Mon Sep 17 00:00:00 2001 From: Dominic Watson Date: Mon, 5 Oct 2015 10:19:52 +0200 Subject: [PATCH 02/11] Refactored dynamic-layout to be more angular and easier to implement --- src/index.html | 329 ++++++++++++++++++++++++ src/js/as.filter.js | 19 -- src/js/custom-filter.filter.js | 21 -- src/js/custom-ranker.filter.js | 21 -- src/js/dynamic-layout-item.directive.js | 80 ++++++ src/js/dynamic-layout.directive.js | 146 +++-------- src/js/filter.service.js | 155 ----------- src/js/layout-on-click.directive.js | 26 ++ src/js/layout-on-load.directive.js | 17 +- src/js/position.service.js | 283 +++++--------------- src/js/ranker.service.js | 122 --------- src/partials/aboutMe.html | 19 ++ src/partials/businessCard.html | 27 ++ src/partials/education.html | 31 +++ src/partials/home.html | 19 ++ src/partials/work1.html | 37 +++ src/partials/work2.html | 22 ++ src/partials/work3.html | 22 ++ src/partials/work4.html | 19 ++ 19 files changed, 740 insertions(+), 675 deletions(-) create mode 100644 src/index.html delete mode 100644 src/js/as.filter.js delete mode 100644 src/js/custom-filter.filter.js delete mode 100644 src/js/custom-ranker.filter.js create mode 100644 src/js/dynamic-layout-item.directive.js delete mode 100644 src/js/filter.service.js create mode 100644 src/js/layout-on-click.directive.js delete mode 100644 src/js/ranker.service.js create mode 100755 src/partials/aboutMe.html create mode 100755 src/partials/businessCard.html create mode 100755 src/partials/education.html create mode 100755 src/partials/home.html create mode 100755 src/partials/work1.html create mode 100755 src/partials/work2.html create mode 100755 src/partials/work3.html create mode 100755 src/partials/work4.html diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..98cb225 --- /dev/null +++ b/src/index.html @@ -0,0 +1,329 @@ + + + + + + + + + + + + + + + + + + + + angular-dynamic-layout demo + + +
+

angular-dynamic-layout +

A lightweight AngularJS dynamic grid layout

+

+
+
+
+
Masonry layout
+

+ The items provided are displayed by placing elements in optimal position based on available vertical space. +

+
+
+
Templates
+ Each item needs to have a template that will be loaded and displayed. You can provide data to fill in the templates. +
+
+
Animations
+ All the animations are controlled from the CSS. You can add an animation when a card enters, leaves or move. +
+
+
Responsiveness
+ The layout can be easily made responsive with media queries, a layout is triggered when the screen changes size, try it! +
+
+
+ +
+
Filtering
+
+ + + +
+
+
+
Sorting
+
+ + +
+
+
+
Adding and removing items
+
+ + +
+
+
+
+ +
+
+
+ +
+ + Fork me on GitHub + + + diff --git a/src/js/as.filter.js b/src/js/as.filter.js deleted file mode 100644 index 15669a9..0000000 --- a/src/js/as.filter.js +++ /dev/null @@ -1,19 +0,0 @@ -(function() { - 'use strict'; - - angular - .module('dynamicLayout') - .filter('as', ['$parse', as]); - - /* - * This allowed the result of the filters to be assigned to the scope - */ - function as($parse) { - - return function(value, context, path) { - $parse(path).assign(context, value); - return value; - }; - } - -})(); diff --git a/src/js/custom-filter.filter.js b/src/js/custom-filter.filter.js deleted file mode 100644 index d541511..0000000 --- a/src/js/custom-filter.filter.js +++ /dev/null @@ -1,21 +0,0 @@ -(function() { - 'use strict'; - - angular - .module('dynamicLayout') - .filter('customFilter', ['FilterService', customFilter]); - - /* - * The filter to be applied on the ng-repeat directive - */ - function customFilter(FilterService) { - - return function(items, filters) { - if (filters) { - return FilterService.applyFilters(items, filters); - } - return items; - }; - } - -})(); diff --git a/src/js/custom-ranker.filter.js b/src/js/custom-ranker.filter.js deleted file mode 100644 index 8298094..0000000 --- a/src/js/custom-ranker.filter.js +++ /dev/null @@ -1,21 +0,0 @@ -(function() { - 'use strict'; - - angular - .module('dynamicLayout') - .filter('customRanker', ['RankerService', customRanker]); - - /* - * The ranker to be applied on the ng-repeat directive - */ - function customRanker(RankerService) { - - return function(items, rankers) { - if (rankers) { - return RankerService.applyRankers(items, rankers); - } - return items; - }; - } - -})(); diff --git a/src/js/dynamic-layout-item.directive.js b/src/js/dynamic-layout-item.directive.js new file mode 100644 index 0000000..b9da304 --- /dev/null +++ b/src/js/dynamic-layout-item.directive.js @@ -0,0 +1,80 @@ +(function() { + 'use strict'; + + angular + .module('dynamicLayout') + .directive('dynamicLayoutItem', dynamicLayoutItem); + + /* + * + */ + function dynamicLayoutItem($window, $animate) { + return { + restrict: 'A', + require: '^dynamicLayout', + link: link + }; + + function link(scope, element, attrs, ctrl) { + + var animation; + + scope.dimensions = { + columnSpan: 0, + width: 0, + height: 0 + }; + scope.pos = { + x: 0, + y: 0 + }; + scope.calculateDimensions = calculateDimensions; + + ctrl.subscribe(scope); + + scope.$watch('$index', ctrl.layout); + scope.$watchCollection('pos', position); + + // Cleanup + scope.$on('$destroy', function() { + ctrl.unsubscribe(scope); + }); + + function calculateDimensions() { + var rect = element[0].children[0].getBoundingClientRect(); + var width; + var height; + + if (rect.width) { + width = rect.width; + height = rect.height; + } else { + width = rect.right - rect.left; + height = rect.top - rect.bottom; + } + + scope.dimensions.width = width + parseFloat($window.getComputedStyle(element[0].children[0]).marginLeft); + scope.dimensions.height = height + parseFloat($window.getComputedStyle(element[0]).marginTop); + } + + function position() { + if (animation) { + $animate.cancel(animation); + } + animation = $animate.addClass(element, 'move-items-animation', { + from: { + position: 'absolute' + }, + to: { + left: scope.pos.x + 'px', + top: scope.pos.y + 'px' + } + }).then(function() { + element.removeClass('move-items-animation'); + }); + } + + } + } + +})(); diff --git a/src/js/dynamic-layout.directive.js b/src/js/dynamic-layout.directive.js index 5e13695..1d8ed18 100644 --- a/src/js/dynamic-layout.directive.js +++ b/src/js/dynamic-layout.directive.js @@ -3,145 +3,65 @@ angular .module('dynamicLayout') - .directive('dynamicLayout', ['$timeout', '$window', '$q', '$animate', 'PositionService', dynamicLayout]); + .directive('dynamicLayout', dynamicLayout); /* * The isotope directive that renders the templates based on the array of items * passed - * @scope items: the list of items to be rendered - * @scope rankers: the rankers to be applied on the list of items - * @scope filters: the filters to be applied on the list of items - * @scope defaulttemplate: (optional) the deafult template to be applied on each item if no item template is defined */ - function dynamicLayout($timeout, $window, $q, $animate, PositionService) { + function dynamicLayout($window, $timeout, PositionService) { return { restrict: 'A', - scope: { - items: '=', - rankers: '=', - filters: '=', - defaulttemplate: '=?' - }, - template: '
', - link: link + controller: controller }; - function link(scope, element) { + function controller($scope, $element) { - // Keep count of the number of templates left to load - scope.templatesToLoad = 0; - scope.externalScope = externalScope; + var vm = this; + var timeoutId; + var items = []; - // Fires when a template is requested through the ng-include directive - scope.$on('$includeContentRequested', function() { - scope.templatesToLoad++; - }); - - // Fires when a template has been loaded through the ng-include - // directive - scope.$on('$includeContentLoaded', function() { - scope.templatesToLoad--; - }); - - /* - * Triggers a layout every time the items are changed - */ - scope.$watch('filteredItems', function(newValue, oldValue) { - // We want the filteredItems to be available to the controller - // This feels hacky, there must be a better way to do this - scope.$parent.filteredItems = scope.filteredItems; - - if (!angular.equals(newValue, oldValue)) { - itemsLoaded().then(function() { - layout(); - }); - } - }, true); + vm.subscribe = subscribe; + vm.unsubscribe = unsubscribe; + vm.layout = layout; /* * Triggers a layout every time the window is resized */ - angular.element($window).on('resize', onResize); + angular.element($window).on('resize', layout); - /* - * Triggers a layout whenever requested by an external source - * Allows a callback to be fired after the layout animation is - * completed - */ - scope.$on('dynamicLayout.layout', function(event, callback) { - layout().then(function() { - if (angular.isFunction('function')) { - callback(); - } - }); + $scope.$on('$destroy', function() { + angular.element($window).off('resize', layout); }); - /* - * Triggers the initial layout once all the templates are loaded - */ - itemsLoaded().then(function() { + function subscribe(item) { + items.push(item); layout(); - }); - - // Cleanup - scope.$on('$destroy', function() { - angular.element($window).off('resize', onResize); - }); - - function onResize() { - // We need to apply the scope - scope.$apply(function() { - layout(); - }); } - /* - * Use the PositionService to layout the items - * @return the promise of the cards being animated - */ - function layout() { - return PositionService.layout(element[0].offsetWidth); + function unsubscribe(item) { + items.splice(items.indexOf(item), 1); + layout(); } - /* - * Check when all the items have been loaded by the ng-include - * directive - */ - function itemsLoaded() { - var def = $q.defer(); - - // $timeout : We need to wait for the includeContentRequested to - // be called before we can assume there is no templates to be loaded - $timeout(function() { - if (scope.templatesToLoad === 0) { - def.resolve(); - } - }); + function layout() { + $timeout.cancel(timeoutId); + timeoutId = $timeout(function() { + + items.sort(function(a, b) { + if (a.$index < b.$index) { + return -1; + } else if (a.$index > b.$index) { + return 1; + } + return 0; + }); - scope.$watch('templatesToLoad', function(newValue, oldValue) { - if (newValue !== oldValue && scope.templatesToLoad === 0) { - def.resolve(); - } + var lastItem = items[items.length - 1]; + PositionService.layout($element, items); + $element[0].style.height = lastItem.pos.y + lastItem.dimensions.height + 'px'; }); - - return def.promise; - } - - /* - * This allows the external scope, that is the scope of - * dynamic-layout's container to be called from the templates - * @return the given scope - */ - function externalScope() { - return scope.$parent; } } diff --git a/src/js/filter.service.js b/src/js/filter.service.js deleted file mode 100644 index dd04b6e..0000000 --- a/src/js/filter.service.js +++ /dev/null @@ -1,155 +0,0 @@ -(function() { - 'use strict'; - - angular - .module('dynamicLayout') - .factory('FilterService', FilterService); - - /* - * The filter service - * - * COMPARATORS = ['=', '<', '>', '<=', '>=', '!=', 'in', 'not in', 'contains'] - * - * Allows filters in Conjuctive Normal Form using the item's property or any - * custom operation on the items - * - * For example: - * var filters = [ // an AND goup compose of OR groups - * [ // an OR group compose of statements - * ['color', '=', 'grey'], // A statement - * ['color', '=', 'black'] - * ], - * [ // a second OR goup composed of statements - * ['atomicNumber', '<', 3] - * ] - * ]; - * Or - * var myCustomFilter = function(item){ - * if(item.color != 'red') - * return true; - * else - * return false; - * }; - * - * filters = [ - * [myCustomFilter] - * ]; - * - */ - function FilterService() { - - return { - applyFilters: applyFilters - }; - - /* - * Check which of the items passes the filters - * @param items: the items being probed - * @param filters: the array of and groups use to probe the item - * @return the list of items that passes the filters - */ - function applyFilters(items, filters) { - var retItems = []; - var i; - for (i in items) { - if (checkAndGroup(items[i], filters)) { - retItems.push(items[i]); - } - } - return retItems; - } - - /* - * Check if a single item passes the single statement criteria - * @param item: the item being probed - * @param statement: the criteria being use to test the item - * @return true if the item passed the statement, false otherwise - */ - function checkStatement(item, statement) { - // If the statement is a custom filter, we give the item as a parameter - if (angular.isFunction(statement)) { - return statement(item); - } - - // If the statement is a regular filter, it has to be with the form - // [propertyName, comparator, value] - - var STATEMENT_LENGTH = 3; - if (statement.length < STATEMENT_LENGTH) { - throw 'Incorrect statement'; - } - - var property = statement[0]; - var comparator = statement[1]; - var value = statement[2]; - - // If the property is not found in the item then we consider the - // statement to be false - if (!item[property]) { - return false; - } - - switch (comparator) { - case '=': - return item[property] === value; - case '<': - return item[property] < value; - case '<=': - return item[property] <= value; - case '>': - return item[property] > value; - case '>=': - return item[property] >= value; - case '!=': - return item[property] !== value; - case 'in': - return item[property] in value; - case 'not in': - return !(item[property] in value); - case 'contains': - if (!(item[property] instanceof Array)) { - throw 'contains statement has to be applied on array'; - } - return item[property].indexOf(value) > -1; - default: - throw 'Incorrect statement comparator: ' + comparator; - } - } - - /* - * Check a sub (or) group - * @param item: the item being probed - * @param orGroup: the array of statement use to probe the item - * @return true if the item passed at least one of the statements, - * false otherwise - */ - function checkOrGroup(item, orGroup) { - var j; - for (j in orGroup) { - if (checkStatement(item, orGroup[j])) { - return true; - } - } - return false; - } - - /* - * Check the main group - * @param item: the item being probed - * @param orGroup: the array of or groups use to probe the item - * @return true if the item passed all of of the or groups, - * false otherwise - */ - function checkAndGroup(item, andGroup) { - var i; - for (i in andGroup) { - if (!checkOrGroup(item, andGroup[i])) { - return false; - } - } - return true; - } - - } - -})(); diff --git a/src/js/layout-on-click.directive.js b/src/js/layout-on-click.directive.js new file mode 100644 index 0000000..2ce6b74 --- /dev/null +++ b/src/js/layout-on-click.directive.js @@ -0,0 +1,26 @@ +(function() { + 'use strict'; + + angular + .module('dynamicLayout') + .directive('layoutOnClick', layoutOnClick); + + /* + * Directive on images to layout after each load + */ + function layoutOnClick() { + + return { + restrict: 'A', + require: '^dynamicLayout', + link: link + }; + + function link(scope, element, attrs, ctrl) { + element.on('click', function() { + ctrl.layout(); + }); + } + } + +})(); diff --git a/src/js/layout-on-load.directive.js b/src/js/layout-on-load.directive.js index d734050..f221b2e 100644 --- a/src/js/layout-on-load.directive.js +++ b/src/js/layout-on-load.directive.js @@ -3,21 +3,24 @@ angular .module('dynamicLayout') - .directive('layoutOnLoad', ['$rootScope', layoutOnLoad]); + .directive('layoutOnLoad', layoutOnLoad); /* * Directive on images to layout after each load */ - function layoutOnLoad($rootScope) { + function layoutOnLoad() { return { restrict: 'A', - link: function(scope, element) { - element.bind('load error', function() { - $rootScope.$broadcast('dynamicLayout.layout'); - }); - } + require: '^dynamicLayout', + link: link }; + + function link(scope, element, attrs, ctrl) { + element.on('load error', function() { + ctrl.layout(); + }); + } } })(); diff --git a/src/js/position.service.js b/src/js/position.service.js index 5625ac9..8660ddc 100644 --- a/src/js/position.service.js +++ b/src/js/position.service.js @@ -3,7 +3,7 @@ angular .module('dynamicLayout') - .factory('PositionService', ['$window', '$document', '$animate', '$timeout', '$q', PositionService]); + .factory('PositionService', PositionService); /* * The position service @@ -15,164 +15,66 @@ * personalized animations * */ - function PositionService($window, $document, $animate, $timeout, $q) { + function PositionService() { - // The list of ongoing animations - var ongoingAnimations = {}; - // The list of items related to the DOM elements - var items = []; - // The list of the DOM elements - var elements = []; - // The columns that contains the items - var columns = []; - - var self = { - getItemsDimensionFromDOM: getItemsDimensionFromDOM, - applyToDOM: applyToDOM, - layout: layout, - getColumns: getColumns + return { + layout: layout }; - return self; - - /* - * Get the items heights and width from the DOM - * @return: the list of items with their sizes - */ - function getItemsDimensionFromDOM() { - // not(.ng-leave) : we don't want to select elements that have been - // removed but are still in the DOM - elements = $document[0].querySelectorAll( - '.dynamic-layout-item-parent:not(.ng-leave)' - ); - items = []; - for (var i = 0; i < elements.length; ++i) { - // Note: we need to get the children element width because that's - // where the style is applied - var rect = elements[i].children[0].getBoundingClientRect(); - var width; - var height; - if (rect.width) { - width = rect.width; - height = rect.height; - } else { - width = rect.right - rect.left; - height = rect.top - rect.bottom; - } - - items.push({ - height: height + - parseFloat($window.getComputedStyle(elements[i]).marginTop), - width: width + - parseFloat( - $window.getComputedStyle(elements[i].children[0]).marginLeft - ) - }); - } - return items; - } - /* - * Apply positions to the DOM with an animation - * @return: the promise of the position animations being completed - */ - function applyToDOM() { + function layout(element, items) { - var ret = $q.defer(); - - /* - * Launch an animation on a specific element - * Once the animation is complete remove it from the ongoing animation - * @param element: the element being moved - * @param i: the index of the current animation - * @return: the promise of the animation being completed - */ - function launchAnimation(element, i) { - var animationPromise = $animate.addClass(element, - 'move-items-animation', - { - from: { - position: 'absolute' - }, - to: { - left: items[i].x + 'px', - top: items[i].y + 'px' - } - } - ); + // Calculate dimensions + angular.forEach(items, function(item) { + item.calculateDimensions(); + }); - animationPromise.then(function() { - // We remove the class so that the animation can be ran again - element.classList.remove('move-items-animation'); - delete ongoingAnimations[i]; - }); + // 2) Calculate amount of columns using total width and item width + var colWidth = getColWidth(items); - return animationPromise; - } + // Apply columnSpan to each item + angular.forEach(items, function(item) { + item.dimensions.columnSpan = Math.ceil(item.dimensions.width / colWidth); + }); - /* - * Launch the animations on all the elements - * @return: the promise of the animations being completed - */ - function launchAnimations() { - var i; - for (i = 0; i < items.length; ++i) { - // We need to pass the specific element we're dealing with - // because at the next iteration elements[i] might point to - // something else - ongoingAnimations[i] = launchAnimation(elements[i], i); - } - $q.all(ongoingAnimations).then(function() { - ret.resolve(); - }); - } + // We set what should be their absolute position in the DOM + return setItemsPosition(element[0].offsetWidth, colWidth, items); + } - // We need to cancel all ongoing animations before we start the new - // ones - if (Object.keys(ongoingAnimations).length) { - for (var j in ongoingAnimations) { - $animate.cancel(ongoingAnimations[j]); - delete ongoingAnimations[j]; + /* + * Get the column size based on the minimum width of the items + * @return: column size + */ + function getColWidth(items) { + var colWidth; + angular.forEach(items, function(item) { + if (!colWidth || item.dimensions.width < colWidth) { + colWidth = item.dimensions.width; } - } - - // For some reason we need to launch the new animations at the next - // digest - $timeout(function() { - launchAnimations(ret); }); - - return ret.promise; + return colWidth; } /* - * Apply the position service on the elements in the DOM - * @param containerWidth: the width of the dynamic-layout container - * @return: the promise of the position animations being completed + * Set the items' absolute position + * @param columns: the empty columns + * @param colWidth: the column size */ - function layout(containerWidth) { - // We first gather the items dimension based on the DOM elements - items = self.getItemsDimensionFromDOM(); + function setItemsPosition(containerWidth, colWidth, items) { - // Then we get the column size base the elements minimum width - var colSize = getColSize(); - var nbColumns = Math.floor(containerWidth / colSize); - // We create empty columns to be filled with the items - initColumns(nbColumns); - - // We determine what is the column size of each of the items based on - // their width and the column size - setItemsColumnSpan(colSize); - - // We set what should be their absolute position in the DOM - setItemsPosition(columns, colSize); + var columns = initColumns(containerWidth, colWidth); - // We apply those positions to the DOM with an animation - return self.applyToDOM(); - } + angular.forEach(items, function(item) { + var columnHeights = getColumnHeights(columns); + var colPos = getItemColumnsAndPosition(item, columnHeights, colWidth); + var j; + + for (j in colPos.columns) { + columns[colPos.columns[j]].push(item); + } - // Make the columns public - function getColumns() { - return columns; + item.pos.x = colPos.position.x; + item.pos.y = colPos.position.y; + }); } /* @@ -180,10 +82,11 @@ * @param nb: the number of columns to be initialized * @return: the empty columns */ - function initColumns(nb) { - columns = []; + function initColumns(containerWidth, colWidth) { + var amount = Math.floor(containerWidth / colWidth); + var columns = []; var i; - for (i = 0; i < nb; ++i) { + for (i = 0; i < amount; ++i) { columns.push([]); } return columns; @@ -194,32 +97,30 @@ * @param columns: the columns with the items they contain * @return: an array of columns heights */ - function getColumnsHeights(cols) { - var columnsHeights = []; + function getColumnHeights(columns) { + var columnHeights = []; var i; - for (i in cols) { - var h; - if (cols[i].length) { - var lastItem = cols[i][cols[i].length - 1]; - h = lastItem.y + lastItem.height; - } else { - h = 0; + for (i in columns) { + var h = 0; + if (columns[i].length) { + var lastItem = columns[i][columns[i].length - 1]; + h = lastItem.pos.y + lastItem.dimensions.height; } - columnsHeights.push(h); + columnHeights.push(h); } - return columnsHeights; + return columnHeights; } /* * Find the item absolute position and what columns it belongs too * @param item: the item to place - * @param colHeights: the current heigh of the column when all items prior to this - * one were places - * @param colSize: the column size + * @param colHeights: the current height of the column when all items prior + * to this one were placed + * @param colWidth: the column size * @return the item's columms and coordinates */ - function getItemColumnsAndPosition(item, colHeights, colSize) { - if (item.columnSpan > colHeights.length) { + function getItemColumnsAndPosition(item, colHeights, colWidth) { + if (item.dimensions.columnSpan > colHeights.length) { throw 'Item too large'; } @@ -228,9 +129,9 @@ var i; // We look at what set of columns have the minimum height - for (i = 0; i <= colHeights.length - item.columnSpan; ++i) { + for (i = 0; i <= colHeights.length - item.dimensions.columnSpan; ++i) { var startingColumn = i; - var endingColumn = i + item.columnSpan; + var endingColumn = i + item.dimensions.columnSpan; var maxHeightInPart = Math.max.apply( Math, colHeights.slice(startingColumn, endingColumn) ); @@ -242,12 +143,12 @@ } var itemColumns = []; - for (i = indexOfMin; i < indexOfMin + item.columnSpan; ++i) { + for (i = indexOfMin; i < indexOfMin + item.dimensions.columnSpan; ++i) { itemColumns.push(i); } var position = { - x: itemColumns[0] * colSize, + x: itemColumns[0] * colWidth, y: minFound }; @@ -257,58 +158,6 @@ }; } - /* - * Set the items' absolute position - * @param columns: the empty columns - * @param colSize: the column size - */ - function setItemsPosition(cols, colSize) { - var i; - var j; - for (i = 0; i < items.length; ++i) { - var columnsHeights = getColumnsHeights(cols); - - var itemColumnsAndPosition = getItemColumnsAndPosition(items[i], - columnsHeights, - colSize); - - // We place the item in the found columns - for (j in itemColumnsAndPosition.columns) { - columns[itemColumnsAndPosition.columns[j]].push(items[i]); - } - - items[i].x = itemColumnsAndPosition.position.x; - items[i].y = itemColumnsAndPosition.position.y; - } - } - - /* - * Get the column size based on the minimum width of the items - * @return: column size - */ - function getColSize() { - var colSize; - var i; - for (i = 0; i < items.length; ++i) { - if (!colSize || items[i].width < colSize) { - colSize = items[i].width; - } - } - return colSize; - } - - /* - * Set the column span for each of the items based on their width and the - * column size - * @param: column size - */ - function setItemsColumnSpan(colSize) { - var i; - for (i = 0; i < items.length; ++i) { - items[i].columnSpan = Math.ceil(items[i].width / colSize); - } - } - } })(); diff --git a/src/js/ranker.service.js b/src/js/ranker.service.js deleted file mode 100644 index 5356a87..0000000 --- a/src/js/ranker.service.js +++ /dev/null @@ -1,122 +0,0 @@ -(function() { - 'use strict'; - - angular - .module('dynamicLayout') - .factory('RankerService', RankerService); - - /* - * The rankers service - * - * Allows a list of rankers to sort the items. - * If two items are the same regarding the first ranker, the second one is used - * to part them, etc. - * - * Rankers can be either a property name or a custom operation on the item. - * They all need to specify the order chosen (asc' or 'desc') - * - * var rankers = [ - * ['color', 'asc'], - * ['atomicNumber', 'desc'] - * ]; - * Or - * var rankers = [ - * [myCustomGetter, 'asc'] - * ]; - * - */ - function RankerService() { - - return { - applyRankers: applyRankers - }; - - /* - * Order the items with the given rankers - * @param items: the items being ranked - * @param rankers: the array of rankers used to rank the items - * @return the ordered list of items - */ - function applyRankers(items, rankers) { - // The ranker counter - var i = 0; - - if (rankers) { - items.sort(sorter); - } - - /* - * The custom sorting function using the built comparison function - * @param a, b: the items to be compared - * @return -1, 0 or 1 - */ - function sorter(a, b) { - i = 0; - return recursiveRanker(a, b); - } - - /* - * Compare recursively two items - * It first compare the items with the first ranker, if no conclusion - * can be drawn it uses the second ranker and so on until it finds a - * winner or there are no more rankers - * @param a, b: the items to be compared - * @return -1, 0 or 1 - */ - function recursiveRanker(a, b) { - var ranker = rankers[i][0]; - var ascDesc = rankers[i][1]; - var valueA; - var valueB; - // If it is a custom ranker, give the item as input and gather the - // ouput - if (angular.isFunction(ranker)) { - valueA = ranker(a); - valueB = ranker(b); - } else { - // Otherwise use the item's properties - if (!(ranker in a) && !(ranker in b)) { - valueA = 0; - valueB = 0; - } else if (!(ranker in a)) { - return ascDesc === 'asc' ? -1 : 1; - } else if (!(ranker in b)) { - return ascDesc === 'asc' ? 1 : -1; - } - valueA = a[ranker]; - valueB = b[ranker]; - } - - if (typeof valueA === typeof valueB) { - - if (angular.isString(valueA)) { - var comp = valueA.localeCompare(valueB); - if (comp === 1) { - return ascDesc === 'asc' ? 1 : -1; - } else if (comp === -1) { - return ascDesc === 'asc' ? -1 : 1; - } - } else { - if (valueA > valueB) { - return ascDesc === 'asc' ? 1 : -1; - } else if (valueA < valueB) { - return ascDesc === 'asc' ? -1 : 1; - } - } - } - - ++i; - - if (rankers.length > i) { - return recursiveRanker(a, b); - } - - return 0; - } - - return items; - } - - } - -})(); diff --git a/src/partials/aboutMe.html b/src/partials/aboutMe.html new file mode 100755 index 0000000..06460fb --- /dev/null +++ b/src/partials/aboutMe.html @@ -0,0 +1,19 @@ +
+ +
+
+

About Me

+ + + +
+
+ Media card description lorem ipsum dolor est compendium +
+ + + +
+
diff --git a/src/partials/businessCard.html b/src/partials/businessCard.html new file mode 100755 index 0000000..088fe03 --- /dev/null +++ b/src/partials/businessCard.html @@ -0,0 +1,27 @@ +
+ +
+
+

Business Card

+ + + +
+
+
+

Name

+

John Smith

+
+
+

Occupation

+

Web Developer

+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam mauris tellus, vehicula ut tellus id, suscipit dapibus tortor. Integer viverra turpis ac fringilla hendrerit. Sed faucibus posuere felis et pellentesque. Cras varius tortor vitae molestie tempor. Proin ut viverra elit, ac gravida tortor.

+
+
+ +
+
diff --git a/src/partials/education.html b/src/partials/education.html new file mode 100755 index 0000000..59bbd14 --- /dev/null +++ b/src/partials/education.html @@ -0,0 +1,31 @@ +
+ +
+
+

Education

+ + + +
+
+
+

Degree

+

{{card.data.degree}}

+
+
+

Field of Study

+

{{card.data.field}}

+
+
+

School

+

{{card.data.school}}

+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam mauris tellus, vehicula ut tellus id, suscipit dapibus tortor. Integer viverra turpis ac fringilla hendrerit. Sed faucibus posuere felis et pellentesque. Cras varius tortor vitae molestie tempor. Proin ut viverra elit, ac gravida tortor.

+
+
+ +
+
diff --git a/src/partials/home.html b/src/partials/home.html new file mode 100755 index 0000000..54ee920 --- /dev/null +++ b/src/partials/home.html @@ -0,0 +1,19 @@ +
+ +
+
+

My Hometown

+ + + +
+
+ Media card description lorem ipsum dolor est compendium +
+ + + +
+
diff --git a/src/partials/work1.html b/src/partials/work1.html new file mode 100755 index 0000000..dd27c5e --- /dev/null +++ b/src/partials/work1.html @@ -0,0 +1,37 @@ +
+ +
+
+

Work Experience

+ + + +
+
+
+

Position

+

{{card.data.position}}

+
+
+

Company

+

{{card.data.company}}

+
+
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam mauris tellus, vehicula ut tellus id, suscipit dapibus tortor. Integer viverra turpis ac fringilla hendrerit. Sed faucibus posuere felis et pellentes ... + + que let me illustrate how an item expands and how other items rearrange around it. + Cras varius tortor vitae molestie tempor. Proin ut viverra elit, ac gravida tortor. + +

+
+
+ + +
+
diff --git a/src/partials/work2.html b/src/partials/work2.html new file mode 100755 index 0000000..0e01056 --- /dev/null +++ b/src/partials/work2.html @@ -0,0 +1,22 @@ +
+ +
+
+

Internship

+ + + +
+
+
+

International Office

+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam mauris tellus, vehicula ut tellus id, suscipit dapibus tortor. Integer viverra turpis ac fringilla hendrerit. Sed faucibus posuere felis et pellentesque. Cras varius tortor vitae molestie tempor. Proin ut viverra elit, ac gravida tortor.

+
+
+ +
+
diff --git a/src/partials/work3.html b/src/partials/work3.html new file mode 100755 index 0000000..95833e4 --- /dev/null +++ b/src/partials/work3.html @@ -0,0 +1,22 @@ +
+ +
+
+

Work Experience

+ + + +
+
+
+

Another example of card to add to your profile

+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam mauris tellus, vehicula ut tellus id, suscipit dapibus tortor. Integer viverra turpis ac fringilla hendrerit. Sed faucibus posuere felis et pellentesque. Cras varius tortor vitae molestie tempor. Proin ut viverra elit, ac gravida tortor.

+
+
+ +
+
diff --git a/src/partials/work4.html b/src/partials/work4.html new file mode 100755 index 0000000..7246210 --- /dev/null +++ b/src/partials/work4.html @@ -0,0 +1,19 @@ +
+ +
+
+

Work Experience

+ + + +
+
+ Media card description lorem ipsum dolor est compendium +
+ + + +
+
From 2eefb3544f41bcd2be3677bd56a2d4881e6942e9 Mon Sep 17 00:00:00 2001 From: Dominic Watson Date: Tue, 6 Oct 2015 09:50:49 +0200 Subject: [PATCH 03/11] Replaced jshint with eslint and added ngAnnotate to automatically inject deps during build process --- Gruntfile.js | 45 +- dist/angular-dynamic-layout.js | 825 ------------------------ dist/angular-dynamic-layout.min.js | 4 - dist/js/angular-dynamic-layout.js | 273 ++++++++ dist/js/angular-dynamic-layout.min.js | 4 + package.json | 7 +- src/js/dynamic-layout-item.directive.js | 4 +- src/js/dynamic-layout.directive.js | 2 + src/js/layout-on-click.directive.js | 2 + src/js/layout-on-load.directive.js | 2 + src/js/position.service.js | 3 +- 11 files changed, 319 insertions(+), 852 deletions(-) delete mode 100644 dist/angular-dynamic-layout.js delete mode 100644 dist/angular-dynamic-layout.min.js create mode 100644 dist/js/angular-dynamic-layout.js create mode 100644 dist/js/angular-dynamic-layout.min.js diff --git a/Gruntfile.js b/Gruntfile.js index 33980c3..34ab096 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -2,7 +2,7 @@ // our wrapper function (required by grunt and its plugins) // all configuration goes inside this function -module.exports = function (grunt) { +module.exports = function(grunt) { // =========================================================================== // CONFIGURE GRUNT =========================================================== @@ -17,16 +17,26 @@ module.exports = function (grunt) { separator: ';' }, dist: { - src: ['src/module.js', - 'src/dynamic-layout.directive.js', - 'src/layout-on-load.directive.js', - 'src/filter.service.js', - 'src/position.service.js', - 'src/ranker.service.js', - 'src/as.filter.js', - 'src/custom-filter.filter.js', - 'src/custom-ranker.filter.js'], - dest: 'dist/<%= pkg.name %>.js' + src: ['src/js/module.js', + 'src/js/dynamic-layout.directive.js', + 'src/js/layout-on-load.directive.js', + 'src/js/filter.service.js', + 'src/js/position.service.js', + 'src/js/ranker.service.js', + 'src/js/as.filter.js', + 'src/js/custom-filter.filter.js', + 'src/js/custom-ranker.filter.js'], + dest: 'dist/js/<%= pkg.name %>.js' + } + }, + ngAnnotate: { + options: { + singleQuotes: true, + }, + dist: { + files: { + 'dist/js/<%= pkg.name %>.js': 'dist/js/<%= pkg.name %>.js' + } } }, karma: { @@ -35,12 +45,12 @@ module.exports = function (grunt) { singleRun: true } }, - jshint: { + eslint: { // when this task is run, lint the Gruntfile and all js files in src options: { - multistr: true, + configFile: '.eslintrc', }, - build: ['Grunfile.js', 'src/**/*.js', 'tests/**/*.js'] + build: ['Gruntfile.js', 'src/**/*.js', 'tests/**/*.js'] }, uglify: { options: { @@ -49,7 +59,7 @@ module.exports = function (grunt) { }, build: { files: { - 'dist/<%= pkg.name %>.min.js': ['dist/<%= pkg.name %>.js'], + 'dist/js/<%= pkg.name %>.min.js': ['dist/js/<%= pkg.name %>.js'], } } } @@ -61,8 +71,9 @@ module.exports = function (grunt) { // we can only load these if they are in our package.json // make sure you have run npm install so our app can find these grunt.loadNpmTasks('grunt-karma'); - grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('gruntify-eslint'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.registerTask('default', ['karma', 'jshint', 'concat', 'uglify']); + grunt.loadNpmTasks('grunt-ng-annotate'); + grunt.registerTask('default', ['eslint', 'karma', 'concat', 'ngAnnotate', 'uglify']); }; diff --git a/dist/angular-dynamic-layout.js b/dist/angular-dynamic-layout.js deleted file mode 100644 index 1739105..0000000 --- a/dist/angular-dynamic-layout.js +++ /dev/null @@ -1,825 +0,0 @@ -(function() { - 'use strict'; - - angular - .module('dynamicLayout', [ 'ngAnimate' ]); - -})(); -;(function() { - 'use strict'; - - angular - .module('dynamicLayout') - .directive('dynamicLayout', ['$timeout', '$window', '$q', '$animate', 'PositionService', dynamicLayout]); - - /* - * The isotope directive that renders the templates based on the array of items - * passed - * @scope items: the list of items to be rendered - * @scope rankers: the rankers to be applied on the list of items - * @scope filters: the filters to be applied on the list of items - * @scope defaulttemplate: (optional) the deafult template to be applied on each item if no item template is defined - */ - function dynamicLayout($timeout, $window, $q, $animate, PositionService) { - - return { - restrict: 'A', - scope: { - items: '=', - rankers: '=', - filters: '=', - defaulttemplate: '=?' - }, - template: '
', - link: link - }; - - function link(scope, element) { - - // Keep count of the number of templates left to load - scope.templatesToLoad = 0; - scope.externalScope = externalScope; - - // Fires when a template is requested through the ng-include directive - scope.$on('$includeContentRequested', function() { - scope.templatesToLoad++; - }); - - // Fires when a template has been loaded through the ng-include - // directive - scope.$on('$includeContentLoaded', function() { - scope.templatesToLoad--; - }); - - /* - * Triggers a layout every time the items are changed - */ - scope.$watch('filteredItems', function(newValue, oldValue) { - // We want the filteredItems to be available to the controller - // This feels hacky, there must be a better way to do this - scope.$parent.filteredItems = scope.filteredItems; - - if (!angular.equals(newValue, oldValue)) { - itemsLoaded().then(function() { - layout(); - }); - } - }, true); - - /* - * Triggers a layout every time the window is resized - */ - angular.element($window).bind('resize', function() { - // We need to apply the scope - scope.$apply(function() { - layout(); - }); - }); - - /* - * Triggers a layout whenever requested by an external source - * Allows a callback to be fired after the layout animation is - * completed - */ - scope.$on('layout', function(event, callback) { - layout().then(function() { - if (angular.isFunction('function')) { - callback(); - } - }); - }); - - /* - * Triggers the initial layout once all the templates are loaded - */ - itemsLoaded().then(function() { - layout(); - }); - - /* - * Use the PositionService to layout the items - * @return the promise of the cards being animated - */ - function layout() { - return PositionService.layout(element[0].offsetWidth); - } - - /* - * Check when all the items have been loaded by the ng-include - * directive - */ - function itemsLoaded() { - var def = $q.defer(); - - // $timeout : We need to wait for the includeContentRequested to - // be called before we can assume there is no templates to be loaded - $timeout(function() { - if (scope.templatesToLoad === 0) { - def.resolve(); - } - }); - - scope.$watch('templatesToLoad', function(newValue, oldValue) { - if (newValue !== oldValue && scope.templatesToLoad === 0) { - def.resolve(); - } - }); - - return def.promise; - } - - /* - * This allows the external scope, that is the scope of - * dynamic-layout's container to be called from the templates - * @return the given scope - */ - function externalScope() { - return scope.$parent; - } - - } - } - -})(); -;(function() { - 'use strict'; - - angular - .module('dynamicLayout') - .directive('layoutOnLoad', ['$rootScope', layoutOnLoad]); - - /* - * Directive on images to layout after each load - */ - function layoutOnLoad($rootScope) { - - return { - restrict: 'A', - link: function(scope, element) { - element.bind('load error', function() { - $rootScope.$broadcast('layout'); - }); - } - }; - } - -})(); -;(function() { - 'use strict'; - - angular - .module('dynamicLayout') - .factory('FilterService', FilterService); - - /* - * The filter service - * - * COMPARATORS = ['=', '<', '>', '<=', '>=', '!=', 'in', 'not in', 'contains'] - * - * Allows filters in Conjuctive Normal Form using the item's property or any - * custom operation on the items - * - * For example: - * var filters = [ // an AND goup compose of OR groups - * [ // an OR group compose of statements - * ['color', '=', 'grey'], // A statement - * ['color', '=', 'black'] - * ], - * [ // a second OR goup composed of statements - * ['atomicNumber', '<', 3] - * ] - * ]; - * Or - * var myCustomFilter = function(item){ - * if(item.color != 'red') - * return true; - * else - * return false; - * }; - * - * filters = [ - * [myCustomFilter] - * ]; - * - */ - function FilterService() { - - return { - applyFilters: applyFilters - }; - - /* - * Check which of the items passes the filters - * @param items: the items being probed - * @param filters: the array of and groups use to probe the item - * @return the list of items that passes the filters - */ - function applyFilters(items, filters) { - var retItems = []; - var i; - for (i in items) { - if (checkAndGroup(items[i], filters)) { - retItems.push(items[i]); - } - } - return retItems; - } - - /* - * Check if a single item passes the single statement criteria - * @param item: the item being probed - * @param statement: the criteria being use to test the item - * @return true if the item passed the statement, false otherwise - */ - function checkStatement(item, statement) { - // If the statement is a custom filter, we give the item as a parameter - if (angular.isFunction(statement)) { - return statement(item); - } - - // If the statement is a regular filter, it has to be with the form - // [propertyName, comparator, value] - - var STATEMENT_LENGTH = 3; - if (statement.length < STATEMENT_LENGTH) { - throw 'Incorrect statement'; - } - - var property = statement[0]; - var comparator = statement[1]; - var value = statement[2]; - - // If the property is not found in the item then we consider the - // statement to be false - if (!item[property]) { - return false; - } - - switch (comparator) { - case '=': - return item[property] === value; - case '<': - return item[property] < value; - case '<=': - return item[property] <= value; - case '>': - return item[property] > value; - case '>=': - return item[property] >= value; - case '!=': - return item[property] !== value; - case 'in': - return item[property] in value; - case 'not in': - return !(item[property] in value); - case 'contains': - if (!(item[property] instanceof Array)) { - throw 'contains statement has to be applied on array'; - } - return item[property].indexOf(value) > -1; - default: - throw 'Incorrect statement comparator: ' + comparator; - } - } - - /* - * Check a sub (or) group - * @param item: the item being probed - * @param orGroup: the array of statement use to probe the item - * @return true if the item passed at least one of the statements, - * false otherwise - */ - function checkOrGroup(item, orGroup) { - var j; - for (j in orGroup) { - if (checkStatement(item, orGroup[j])) { - return true; - } - } - return false; - } - - /* - * Check the main group - * @param item: the item being probed - * @param orGroup: the array of or groups use to probe the item - * @return true if the item passed all of of the or groups, - * false otherwise - */ - function checkAndGroup(item, andGroup) { - var i; - for (i in andGroup) { - if (!checkOrGroup(item, andGroup[i])) { - return false; - } - } - return true; - } - - } - -})(); -;(function() { - 'use strict'; - - angular - .module('dynamicLayout') - .factory('PositionService', ['$window', '$document', '$animate', '$timeout', '$q', PositionService]); - - /* - * The position service - * - * Find the best adjustements of the elemnts in the DOM according the their - * order, height and width - * - * Fix their absolute position in the DOM while adding a ng-animate class for - * personalized animations - * - */ - function PositionService($window, $document, $animate, $timeout, $q) { - - // The list of ongoing animations - var ongoingAnimations = {}; - // The list of items related to the DOM elements - var items = []; - // The list of the DOM elements - var elements = []; - // The columns that contains the items - var columns = []; - - var self = { - getItemsDimensionFromDOM: getItemsDimensionFromDOM, - applyToDOM: applyToDOM, - layout: layout, - getColumns: getColumns - }; - return self; - - /* - * Get the items heights and width from the DOM - * @return: the list of items with their sizes - */ - function getItemsDimensionFromDOM() { - // not(.ng-leave) : we don't want to select elements that have been - // removed but are still in the DOM - elements = $document[0].querySelectorAll( - '.dynamic-layout-item-parent:not(.ng-leave)' - ); - items = []; - for (var i = 0; i < elements.length; ++i) { - // Note: we need to get the children element width because that's - // where the style is applied - var rect = elements[i].children[0].getBoundingClientRect(); - var width; - var height; - if (rect.width) { - width = rect.width; - height = rect.height; - } else { - width = rect.right - rect.left; - height = rect.top - rect.bottom; - } - - items.push({ - height: height + - parseFloat($window.getComputedStyle(elements[i]).marginTop), - width: width + - parseFloat( - $window.getComputedStyle(elements[i].children[0]).marginLeft - ) - }); - } - return items; - } - - /* - * Apply positions to the DOM with an animation - * @return: the promise of the position animations being completed - */ - function applyToDOM() { - - var ret = $q.defer(); - - /* - * Launch an animation on a specific element - * Once the animation is complete remove it from the ongoing animation - * @param element: the element being moved - * @param i: the index of the current animation - * @return: the promise of the animation being completed - */ - function launchAnimation(element, i) { - var animationPromise = $animate.addClass(element, - 'move-items-animation', - { - from: { - position: 'absolute' - }, - to: { - left: items[i].x + 'px', - top: items[i].y + 'px' - } - } - ); - - animationPromise.then(function() { - // We remove the class so that the animation can be ran again - element.classList.remove('move-items-animation'); - delete ongoingAnimations[i]; - }); - - return animationPromise; - } - - /* - * Launch the animations on all the elements - * @return: the promise of the animations being completed - */ - function launchAnimations() { - var i; - for (i = 0; i < items.length; ++i) { - // We need to pass the specific element we're dealing with - // because at the next iteration elements[i] might point to - // something else - ongoingAnimations[i] = launchAnimation(elements[i], i); - } - $q.all(ongoingAnimations).then(function() { - ret.resolve(); - }); - } - - // We need to cancel all ongoing animations before we start the new - // ones - if (Object.keys(ongoingAnimations).length) { - for (var j in ongoingAnimations) { - $animate.cancel(ongoingAnimations[j]); - delete ongoingAnimations[j]; - } - } - - // For some reason we need to launch the new animations at the next - // digest - $timeout(function() { - launchAnimations(ret); - }); - - return ret.promise; - } - - /* - * Apply the position service on the elements in the DOM - * @param containerWidth: the width of the dynamic-layout container - * @return: the promise of the position animations being completed - */ - function layout(containerWidth) { - // We first gather the items dimension based on the DOM elements - items = self.getItemsDimensionFromDOM(); - - // Then we get the column size base the elements minimum width - var colSize = getColSize(); - var nbColumns = Math.floor(containerWidth / colSize); - // We create empty columns to be filled with the items - initColumns(nbColumns); - - // We determine what is the column size of each of the items based on - // their width and the column size - setItemsColumnSpan(colSize); - - // We set what should be their absolute position in the DOM - setItemsPosition(columns, colSize); - - // We apply those positions to the DOM with an animation - return self.applyToDOM(); - } - - // Make the columns public - function getColumns() { - return columns; - } - - /* - * Intialize the columns - * @param nb: the number of columns to be initialized - * @return: the empty columns - */ - function initColumns(nb) { - columns = []; - var i; - for (i = 0; i < nb; ++i) { - columns.push([]); - } - return columns; - } - - /* - * Get the columns heights - * @param columns: the columns with the items they contain - * @return: an array of columns heights - */ - function getColumnsHeights(cols) { - var columnsHeights = []; - var i; - for (i in cols) { - var h; - if (cols[i].length) { - var lastItem = cols[i][cols[i].length - 1]; - h = lastItem.y + lastItem.height; - } else { - h = 0; - } - columnsHeights.push(h); - } - return columnsHeights; - } - - /* - * Find the item absolute position and what columns it belongs too - * @param item: the item to place - * @param colHeights: the current heigh of the column when all items prior to this - * one were places - * @param colSize: the column size - * @return the item's columms and coordinates - */ - function getItemColumnsAndPosition(item, colHeights, colSize) { - if (item.columnSpan > colHeights.length) { - throw 'Item too large'; - } - - var indexOfMin = 0; - var minFound = 0; - var i; - - // We look at what set of columns have the minimum height - for (i = 0; i <= colHeights.length - item.columnSpan; ++i) { - var startingColumn = i; - var endingColumn = i + item.columnSpan; - var maxHeightInPart = Math.max.apply( - Math, colHeights.slice(startingColumn, endingColumn) - ); - - if (i === 0 || maxHeightInPart < minFound) { - minFound = maxHeightInPart; - indexOfMin = i; - } - } - - var itemColumns = []; - for (i = indexOfMin; i < indexOfMin + item.columnSpan; ++i) { - itemColumns.push(i); - } - - var position = { - x: itemColumns[0] * colSize, - y: minFound - }; - - return { - columns: itemColumns, - position: position - }; - } - - /* - * Set the items' absolute position - * @param columns: the empty columns - * @param colSize: the column size - */ - function setItemsPosition(cols, colSize) { - var i; - var j; - for (i = 0; i < items.length; ++i) { - var columnsHeights = getColumnsHeights(cols); - - var itemColumnsAndPosition = getItemColumnsAndPosition(items[i], - columnsHeights, - colSize); - - // We place the item in the found columns - for (j in itemColumnsAndPosition.columns) { - columns[itemColumnsAndPosition.columns[j]].push(items[i]); - } - - items[i].x = itemColumnsAndPosition.position.x; - items[i].y = itemColumnsAndPosition.position.y; - } - } - - /* - * Get the column size based on the minimum width of the items - * @return: column size - */ - function getColSize() { - var colSize; - var i; - for (i = 0; i < items.length; ++i) { - if (!colSize || items[i].width < colSize) { - colSize = items[i].width; - } - } - return colSize; - } - - /* - * Set the column span for each of the items based on their width and the - * column size - * @param: column size - */ - function setItemsColumnSpan(colSize) { - var i; - for (i = 0; i < items.length; ++i) { - items[i].columnSpan = Math.ceil(items[i].width / colSize); - } - } - - } - -})(); -;(function() { - 'use strict'; - - angular - .module('dynamicLayout') - .factory('RankerService', RankerService); - - /* - * The rankers service - * - * Allows a list of rankers to sort the items. - * If two items are the same regarding the first ranker, the second one is used - * to part them, etc. - * - * Rankers can be either a property name or a custom operation on the item. - * They all need to specify the order chosen (asc' or 'desc') - * - * var rankers = [ - * ['color', 'asc'], - * ['atomicNumber', 'desc'] - * ]; - * Or - * var rankers = [ - * [myCustomGetter, 'asc'] - * ]; - * - */ - function RankerService() { - - return { - applyRankers: applyRankers - }; - - /* - * Order the items with the given rankers - * @param items: the items being ranked - * @param rankers: the array of rankers used to rank the items - * @return the ordered list of items - */ - function applyRankers(items, rankers) { - // The ranker counter - var i = 0; - - if (rankers) { - items.sort(sorter); - } - - /* - * The custom sorting function using the built comparison function - * @param a, b: the items to be compared - * @return -1, 0 or 1 - */ - function sorter(a, b) { - i = 0; - return recursiveRanker(a, b); - } - - /* - * Compare recursively two items - * It first compare the items with the first ranker, if no conclusion - * can be drawn it uses the second ranker and so on until it finds a - * winner or there are no more rankers - * @param a, b: the items to be compared - * @return -1, 0 or 1 - */ - function recursiveRanker(a, b) { - var ranker = rankers[i][0]; - var ascDesc = rankers[i][1]; - var valueA; - var valueB; - // If it is a custom ranker, give the item as input and gather the - // ouput - if (angular.isFunction(ranker)) { - valueA = ranker(a); - valueB = ranker(b); - } else { - // Otherwise use the item's properties - if (!(ranker in a) && !(ranker in b)) { - valueA = 0; - valueB = 0; - } else if (!(ranker in a)) { - return ascDesc === 'asc' ? -1 : 1; - } else if (!(ranker in b)) { - return ascDesc === 'asc' ? 1 : -1; - } - valueA = a[ranker]; - valueB = b[ranker]; - } - - if (typeof valueA === typeof valueB) { - - if (angular.isString(valueA)) { - var comp = valueA.localeCompare(valueB); - if (comp === 1) { - return ascDesc === 'asc' ? 1 : -1; - } else if (comp === -1) { - return ascDesc === 'asc' ? -1 : 1; - } - } else { - if (valueA > valueB) { - return ascDesc === 'asc' ? 1 : -1; - } else if (valueA < valueB) { - return ascDesc === 'asc' ? -1 : 1; - } - } - } - - ++i; - - if (rankers.length > i) { - return recursiveRanker(a, b); - } - - return 0; - } - - return items; - } - - } - -})(); -;(function() { - 'use strict'; - - angular - .module('dynamicLayout') - .filter('as', ['$parse', as]); - - /* - * This allowed the result of the filters to be assigned to the scope - */ - function as($parse) { - - return function(value, context, path) { - $parse(path).assign(context, value); - return value; - }; - } - -})(); -;(function() { - 'use strict'; - - angular - .module('dynamicLayout') - .filter('customFilter', ['FilterService', customFilter]); - - /* - * The filter to be applied on the ng-repeat directive - */ - function customFilter(FilterService) { - - return function(items, filters) { - if (filters) { - return FilterService.applyFilters(items, filters); - } - return items; - }; - } - -})(); -;(function() { - 'use strict'; - - angular - .module('dynamicLayout') - .filter('customRanker', ['RankerService', customRanker]); - - /* - * The ranker to be applied on the ng-repeat directive - */ - function customRanker(RankerService) { - - return function(items, rankers) { - if (rankers) { - return RankerService.applyRankers(items, rankers); - } - return items; - }; - } - -})(); diff --git a/dist/angular-dynamic-layout.min.js b/dist/angular-dynamic-layout.min.js deleted file mode 100644 index bcef0de..0000000 --- a/dist/angular-dynamic-layout.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/* - angular-dynamic-layout 2015-09-13 -*/ -!function(){"use strict";angular.module("dynamicLayout",["ngAnimate"])}(),function(){"use strict";function dynamicLayout($timeout,$window,$q,$animate,PositionService){function link(scope,element){function layout(){return PositionService.layout(element[0].offsetWidth)}function itemsLoaded(){var def=$q.defer();return $timeout(function(){0===scope.templatesToLoad&&def.resolve()}),scope.$watch("templatesToLoad",function(newValue,oldValue){newValue!==oldValue&&0===scope.templatesToLoad&&def.resolve()}),def.promise}function externalScope(){return scope.$parent}scope.templatesToLoad=0,scope.externalScope=externalScope,scope.$on("$includeContentRequested",function(){scope.templatesToLoad++}),scope.$on("$includeContentLoaded",function(){scope.templatesToLoad--}),scope.$watch("filteredItems",function(newValue,oldValue){scope.$parent.filteredItems=scope.filteredItems,angular.equals(newValue,oldValue)||itemsLoaded().then(function(){layout()})},!0),angular.element($window).bind("resize",function(){scope.$apply(function(){layout()})}),scope.$on("layout",function(event,callback){layout().then(function(){angular.isFunction("function")&&callback()})}),itemsLoaded().then(function(){layout()})}return{restrict:"A",scope:{items:"=",rankers:"=",filters:"=",defaulttemplate:"=?"},template:'
',link:link}}angular.module("dynamicLayout").directive("dynamicLayout",["$timeout","$window","$q","$animate","PositionService",dynamicLayout])}(),function(){"use strict";function layoutOnLoad($rootScope){return{restrict:"A",link:function(scope,element){element.bind("load error",function(){$rootScope.$broadcast("layout")})}}}angular.module("dynamicLayout").directive("layoutOnLoad",["$rootScope",layoutOnLoad])}(),function(){"use strict";function FilterService(){function applyFilters(items,filters){var i,retItems=[];for(i in items)checkAndGroup(items[i],filters)&&retItems.push(items[i]);return retItems}function checkStatement(item,statement){if(angular.isFunction(statement))return statement(item);var STATEMENT_LENGTH=3;if(statement.length":return item[property]>value;case">=":return item[property]>=value;case"!=":return item[property]!==value;case"in":return item[property]in value;case"not in":return!(item[property]in value);case"contains":if(!(item[property]instanceof Array))throw"contains statement has to be applied on array";return item[property].indexOf(value)>-1;default:throw"Incorrect statement comparator: "+comparator}}function checkOrGroup(item,orGroup){var j;for(j in orGroup)if(checkStatement(item,orGroup[j]))return!0;return!1}function checkAndGroup(item,andGroup){var i;for(i in andGroup)if(!checkOrGroup(item,andGroup[i]))return!1;return!0}return{applyFilters:applyFilters}}angular.module("dynamicLayout").factory("FilterService",FilterService)}(),function(){"use strict";function PositionService($window,$document,$animate,$timeout,$q){function getItemsDimensionFromDOM(){elements=$document[0].querySelectorAll(".dynamic-layout-item-parent:not(.ng-leave)"),items=[];for(var i=0;ii;++i)columns.push([]);return columns}function getColumnsHeights(cols){var i,columnsHeights=[];for(i in cols){var h;if(cols[i].length){var lastItem=cols[i][cols[i].length-1];h=lastItem.y+lastItem.height}else h=0;columnsHeights.push(h)}return columnsHeights}function getItemColumnsAndPosition(item,colHeights,colSize){if(item.columnSpan>colHeights.length)throw"Item too large";var i,indexOfMin=0,minFound=0;for(i=0;i<=colHeights.length-item.columnSpan;++i){var startingColumn=i,endingColumn=i+item.columnSpan,maxHeightInPart=Math.max.apply(Math,colHeights.slice(startingColumn,endingColumn));(0===i||minFound>maxHeightInPart)&&(minFound=maxHeightInPart,indexOfMin=i)}var itemColumns=[];for(i=indexOfMin;ivalueB)return"asc"===ascDesc?1:-1;if(valueB>valueA)return"asc"===ascDesc?-1:1}return++i,rankers.length>i?recursiveRanker(a,b):0}var i=0;return rankers&&items.sort(sorter),items}return{applyRankers:applyRankers}}angular.module("dynamicLayout").factory("RankerService",RankerService)}(),function(){"use strict";function as($parse){return function(value,context,path){return $parse(path).assign(context,value),value}}angular.module("dynamicLayout").filter("as",["$parse",as])}(),function(){"use strict";function customFilter(FilterService){return function(items,filters){return filters?FilterService.applyFilters(items,filters):items}}angular.module("dynamicLayout").filter("customFilter",["FilterService",customFilter])}(),function(){"use strict";function customRanker(RankerService){return function(items,rankers){return rankers?RankerService.applyRankers(items,rankers):items}}angular.module("dynamicLayout").filter("customRanker",["RankerService",customRanker])}(); \ No newline at end of file diff --git a/dist/js/angular-dynamic-layout.js b/dist/js/angular-dynamic-layout.js new file mode 100644 index 0000000..15bf041 --- /dev/null +++ b/dist/js/angular-dynamic-layout.js @@ -0,0 +1,273 @@ +(function() { + 'use strict'; + + angular + .module('dynamicLayout', [ 'ngAnimate' ]); + +})(); +;(function() { + 'use strict'; + + angular + .module('dynamicLayout') + .directive('dynamicLayout', dynamicLayout); + + /* + * The isotope directive that renders the templates based on the array of items + * passed + * + * @ngInject + */ + function dynamicLayout($window, $timeout, PositionService) { + + controller.$inject = ['$scope', '$element']; + return { + restrict: 'A', + controller: controller + }; + + function controller($scope, $element) { + + var vm = this; + var timeoutId; + var items = []; + + vm.subscribe = subscribe; + vm.unsubscribe = unsubscribe; + vm.layout = layout; + + /* + * Triggers a layout every time the window is resized + */ + angular.element($window).on('resize', layout); + + $scope.$on('$destroy', function() { + angular.element($window).off('resize', layout); + }); + + function subscribe(item) { + items.push(item); + layout(); + } + + function unsubscribe(item) { + items.splice(items.indexOf(item), 1); + layout(); + } + + function layout() { + $timeout.cancel(timeoutId); + timeoutId = $timeout(function() { + + items.sort(function(a, b) { + if (a.$index < b.$index) { + return -1; + } else if (a.$index > b.$index) { + return 1; + } + return 0; + }); + + var lastItem = items[items.length - 1]; + PositionService.layout($element, items); + $element[0].style.height = lastItem.pos.y + lastItem.dimensions.height + 'px'; + }); + } + + } + } + dynamicLayout.$inject = ['$window', '$timeout', 'PositionService']; + +})(); +;(function() { + 'use strict'; + + angular + .module('dynamicLayout') + .directive('layoutOnLoad', layoutOnLoad); + + /* + * Directive on images to layout after each load + * + * @ngInject + */ + function layoutOnLoad() { + + return { + restrict: 'A', + require: '^dynamicLayout', + link: link + }; + + function link(scope, element, attrs, ctrl) { + element.on('load error', function() { + ctrl.layout(); + }); + } + } + +})(); +;(function() { + 'use strict'; + + angular + .module('dynamicLayout') + .factory('PositionService', PositionService); + + /* + * The position service + * + * Find the best adjustements of the elemnts in the DOM according the their + * order, height and width + * + * Fix their absolute position in the DOM while adding a ng-animate class for + * personalized animations + * + * @ngInject + */ + function PositionService() { + + return { + layout: layout + }; + + function layout(element, items) { + + // Calculate dimensions + angular.forEach(items, function(item) { + item.calculateDimensions(); + }); + + // 2) Calculate amount of columns using total width and item width + var colWidth = getColWidth(items); + + // Apply columnSpan to each item + angular.forEach(items, function(item) { + item.dimensions.columnSpan = Math.ceil(item.dimensions.width / colWidth); + }); + + // We set what should be their absolute position in the DOM + return setItemsPosition(element[0].offsetWidth, colWidth, items); + } + + /* + * Get the column size based on the minimum width of the items + * @return: column size + */ + function getColWidth(items) { + var colWidth; + angular.forEach(items, function(item) { + if (!colWidth || item.dimensions.width < colWidth) { + colWidth = item.dimensions.width; + } + }); + return colWidth; + } + + /* + * Set the items' absolute position + * @param columns: the empty columns + * @param colWidth: the column size + */ + function setItemsPosition(containerWidth, colWidth, items) { + + var columns = initColumns(containerWidth, colWidth); + + angular.forEach(items, function(item) { + var columnHeights = getColumnHeights(columns); + var colPos = getItemColumnsAndPosition(item, columnHeights, colWidth); + var j; + + for (j in colPos.columns) { + columns[colPos.columns[j]].push(item); + } + + item.pos.x = colPos.position.x; + item.pos.y = colPos.position.y; + }); + } + + /* + * Intialize the columns + * @param nb: the number of columns to be initialized + * @return: the empty columns + */ + function initColumns(containerWidth, colWidth) { + var amount = Math.floor(containerWidth / colWidth); + var columns = []; + var i; + for (i = 0; i < amount; ++i) { + columns.push([]); + } + return columns; + } + + /* + * Get the columns heights + * @param columns: the columns with the items they contain + * @return: an array of columns heights + */ + function getColumnHeights(columns) { + var columnHeights = []; + var i; + for (i in columns) { + var h = 0; + if (columns[i].length) { + var lastItem = columns[i][columns[i].length - 1]; + h = lastItem.pos.y + lastItem.dimensions.height; + } + columnHeights.push(h); + } + return columnHeights; + } + + /* + * Find the item absolute position and what columns it belongs too + * @param item: the item to place + * @param colHeights: the current height of the column when all items prior + * to this one were placed + * @param colWidth: the column size + * @return the item's columms and coordinates + */ + function getItemColumnsAndPosition(item, colHeights, colWidth) { + if (item.dimensions.columnSpan > colHeights.length) { + throw 'Item too large'; + } + + var indexOfMin = 0; + var minFound = 0; + var i; + + // We look at what set of columns have the minimum height + for (i = 0; i <= colHeights.length - item.dimensions.columnSpan; ++i) { + var startingColumn = i; + var endingColumn = i + item.dimensions.columnSpan; + var maxHeightInPart = Math.max.apply( + Math, colHeights.slice(startingColumn, endingColumn) + ); + + if (i === 0 || maxHeightInPart < minFound) { + minFound = maxHeightInPart; + indexOfMin = i; + } + } + + var itemColumns = []; + for (i = indexOfMin; i < indexOfMin + item.dimensions.columnSpan; ++i) { + itemColumns.push(i); + } + + var position = { + x: itemColumns[0] * colWidth, + y: minFound + }; + + return { + columns: itemColumns, + position: position + }; + } + + } + +})(); diff --git a/dist/js/angular-dynamic-layout.min.js b/dist/js/angular-dynamic-layout.min.js new file mode 100644 index 0000000..96557cd --- /dev/null +++ b/dist/js/angular-dynamic-layout.min.js @@ -0,0 +1,4 @@ +/* + angular-dynamic-layout 2015-10-06 +*/ +!function(){"use strict";angular.module("dynamicLayout",["ngAnimate"])}(),function(){"use strict";function dynamicLayout($window,$timeout,PositionService){function controller($scope,$element){function subscribe(item){items.push(item),layout()}function unsubscribe(item){items.splice(items.indexOf(item),1),layout()}function layout(){$timeout.cancel(timeoutId),timeoutId=$timeout(function(){items.sort(function(a,b){return a.$indexb.$index?1:0});var lastItem=items[items.length-1];PositionService.layout($element,items),$element[0].style.height=lastItem.pos.y+lastItem.dimensions.height+"px"})}var timeoutId,vm=this,items=[];vm.subscribe=subscribe,vm.unsubscribe=unsubscribe,vm.layout=layout,angular.element($window).on("resize",layout),$scope.$on("$destroy",function(){angular.element($window).off("resize",layout)})}return controller.$inject=["$scope","$element"],{restrict:"A",controller:controller}}angular.module("dynamicLayout").directive("dynamicLayout",dynamicLayout),dynamicLayout.$inject=["$window","$timeout","PositionService"]}(),function(){"use strict";function layoutOnLoad(){function link(scope,element,attrs,ctrl){element.on("load error",function(){ctrl.layout()})}return{restrict:"A",require:"^dynamicLayout",link:link}}angular.module("dynamicLayout").directive("layoutOnLoad",layoutOnLoad)}(),function(){"use strict";function PositionService(){function layout(element,items){angular.forEach(items,function(item){item.calculateDimensions()});var colWidth=getColWidth(items);return angular.forEach(items,function(item){item.dimensions.columnSpan=Math.ceil(item.dimensions.width/colWidth)}),setItemsPosition(element[0].offsetWidth,colWidth,items)}function getColWidth(items){var colWidth;return angular.forEach(items,function(item){(!colWidth||item.dimensions.widthi;++i)columns.push([]);return columns}function getColumnHeights(columns){var i,columnHeights=[];for(i in columns){var h=0;if(columns[i].length){var lastItem=columns[i][columns[i].length-1];h=lastItem.pos.y+lastItem.dimensions.height}columnHeights.push(h)}return columnHeights}function getItemColumnsAndPosition(item,colHeights,colWidth){if(item.dimensions.columnSpan>colHeights.length)throw"Item too large";var i,indexOfMin=0,minFound=0;for(i=0;i<=colHeights.length-item.dimensions.columnSpan;++i){var startingColumn=i,endingColumn=i+item.dimensions.columnSpan,maxHeightInPart=Math.max.apply(Math,colHeights.slice(startingColumn,endingColumn));(0===i||minFound>maxHeightInPart)&&(minFound=maxHeightInPart,indexOfMin=i)}var itemColumns=[];for(i=indexOfMin;i Date: Tue, 6 Oct 2015 18:24:07 +0200 Subject: [PATCH 04/11] Smoother animation by animating from old -> new position and seeding with offsetTop/Left --- src/js/dynamic-layout-item.directive.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/js/dynamic-layout-item.directive.js b/src/js/dynamic-layout-item.directive.js index 7c58c4f..d47c72f 100644 --- a/src/js/dynamic-layout-item.directive.js +++ b/src/js/dynamic-layout-item.directive.js @@ -23,15 +23,17 @@ height: 0 }; scope.pos = { - x: 0, - y: 0 + x: element[0].offsetLeft, + y: element[0].offsetTop }; scope.calculateDimensions = calculateDimensions; ctrl.subscribe(scope); scope.$watch('$index', ctrl.layout); - scope.$watchCollection('pos', position); + scope.$watchCollection('pos', function(newPos, oldPos) { + position(newPos, oldPos); + }); // Cleanup scope.$on('$destroy', function() { @@ -55,17 +57,19 @@ scope.dimensions.height = height + parseFloat($window.getComputedStyle(element[0]).marginTop); } - function position() { + function position(newPos, oldPos) { if (animation) { $animate.cancel(animation); } animation = $animate.addClass(element, 'move-items-animation', { from: { - position: 'absolute' + position: 'absolute', + left: oldPos.x + 'px', + top: oldPos.y + 'px' }, to: { - left: scope.pos.x + 'px', - top: scope.pos.y + 'px' + left: newPos.x + 'px', + top: newPos.y + 'px' } }).then(function() { element.removeClass('move-items-animation'); From b8b9d83619d275a29cec75b6dfa52e2d62e00af4 Mon Sep 17 00:00:00 2001 From: Eliza Krajewska Date: Wed, 18 Nov 2015 10:39:50 +0100 Subject: [PATCH 05/11] Handling error if no items exist in layout --- src/js/dynamic-layout.directive.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/js/dynamic-layout.directive.js b/src/js/dynamic-layout.directive.js index 771ceea..0c9a422 100644 --- a/src/js/dynamic-layout.directive.js +++ b/src/js/dynamic-layout.directive.js @@ -51,6 +51,10 @@ $timeout.cancel(timeoutId); timeoutId = $timeout(function() { + if (!items.length) { + return; + } + items.sort(function(a, b) { if (a.$index < b.$index) { return -1; From 4126b9fb52a65df5655c8909b7b5e3e783c01a55 Mon Sep 17 00:00:00 2001 From: Eliza Krajewska Date: Wed, 18 Nov 2015 18:13:22 +0100 Subject: [PATCH 06/11] fix calculation problem for items in layout due to differences in browsers --- src/js/position.service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/position.service.js b/src/js/position.service.js index ee5b606..cade493 100644 --- a/src/js/position.service.js +++ b/src/js/position.service.js @@ -34,7 +34,7 @@ // Apply columnSpan to each item angular.forEach(items, function(item) { - item.dimensions.columnSpan = Math.ceil(item.dimensions.width / colWidth); + item.dimensions.columnSpan = Math.round(item.dimensions.width / colWidth); }); // We set what should be their absolute position in the DOM @@ -84,7 +84,7 @@ * @return: the empty columns */ function initColumns(containerWidth, colWidth) { - var amount = Math.floor(containerWidth / colWidth); + var amount = Math.round(containerWidth / colWidth); var columns = []; var i; for (i = 0; i < amount; ++i) { From e580aa4ce814a808d9f5d0f060f393c8d1c7430b Mon Sep 17 00:00:00 2001 From: Dominic Watson Date: Fri, 27 Nov 2015 13:52:20 +0100 Subject: [PATCH 07/11] Fix width/height calculation and added right/bottom margin checks --- src/js/dynamic-layout-item.directive.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/dynamic-layout-item.directive.js b/src/js/dynamic-layout-item.directive.js index d47c72f..58a0295 100644 --- a/src/js/dynamic-layout-item.directive.js +++ b/src/js/dynamic-layout-item.directive.js @@ -53,8 +53,8 @@ height = rect.top - rect.bottom; } - scope.dimensions.width = width + parseFloat($window.getComputedStyle(element[0].children[0]).marginLeft); - scope.dimensions.height = height + parseFloat($window.getComputedStyle(element[0]).marginTop); + scope.dimensions.width = width + parseFloat($window.getComputedStyle(element[0]).marginLeft) + parseFloat($window.getComputedStyle(element[0]).marginRight); + scope.dimensions.height = height + parseFloat($window.getComputedStyle(element[0]).marginTop) + parseFloat($window.getComputedStyle(element[0]).marginBottom); } function position(newPos, oldPos) { From 4d7f6829701100f827d3c8323e18f1b223e04497 Mon Sep 17 00:00:00 2001 From: Dominic Watson Date: Sat, 28 Nov 2015 10:59:06 +0100 Subject: [PATCH 08/11] Getting width/height from element and not first child --- src/js/dynamic-layout-item.directive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/dynamic-layout-item.directive.js b/src/js/dynamic-layout-item.directive.js index 58a0295..cc3d431 100644 --- a/src/js/dynamic-layout-item.directive.js +++ b/src/js/dynamic-layout-item.directive.js @@ -41,7 +41,7 @@ }); function calculateDimensions() { - var rect = element[0].children[0].getBoundingClientRect(); + var rect = element[0].getBoundingClientRect(); var width; var height; From 5bd0bc5c3c3f2e3532125ba4f0d54465a41f0ae1 Mon Sep 17 00:00:00 2001 From: Dominic Watson Date: Sat, 28 Nov 2015 14:10:17 +0100 Subject: [PATCH 09/11] Cache computed style --- src/js/dynamic-layout-item.directive.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/js/dynamic-layout-item.directive.js b/src/js/dynamic-layout-item.directive.js index cc3d431..2ba87d9 100644 --- a/src/js/dynamic-layout-item.directive.js +++ b/src/js/dynamic-layout-item.directive.js @@ -42,6 +42,7 @@ function calculateDimensions() { var rect = element[0].getBoundingClientRect(); + var style = $window.getComputedStyle(element[0]); var width; var height; @@ -53,8 +54,8 @@ height = rect.top - rect.bottom; } - scope.dimensions.width = width + parseFloat($window.getComputedStyle(element[0]).marginLeft) + parseFloat($window.getComputedStyle(element[0]).marginRight); - scope.dimensions.height = height + parseFloat($window.getComputedStyle(element[0]).marginTop) + parseFloat($window.getComputedStyle(element[0]).marginBottom); + scope.dimensions.width = width + parseFloat(style.marginLeft) + parseFloat(style.marginRight); + scope.dimensions.height = height + parseFloat(style.marginTop) + parseFloat(style.marginBottom); } function position(newPos, oldPos) { From c57c320d250cc5ef871b5b6b146361ab359202a2 Mon Sep 17 00:00:00 2001 From: Dominic Watson Date: Sat, 28 Nov 2015 16:48:17 +0100 Subject: [PATCH 10/11] Dont trigger layout when $index hasnt changed --- src/js/dynamic-layout-item.directive.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/js/dynamic-layout-item.directive.js b/src/js/dynamic-layout-item.directive.js index 2ba87d9..06830ec 100644 --- a/src/js/dynamic-layout-item.directive.js +++ b/src/js/dynamic-layout-item.directive.js @@ -30,7 +30,11 @@ ctrl.subscribe(scope); - scope.$watch('$index', ctrl.layout); + scope.$watch('$index', function(newVal, oldVal) { + if (newVal !== oldVal) { + ctrl.layout(); + } + }); scope.$watchCollection('pos', function(newPos, oldPos) { position(newPos, oldPos); }); From 4f9dba02f32578a7803a1502a5d8a9fed6c635bd Mon Sep 17 00:00:00 2001 From: Dominic Watson Date: Sun, 29 Nov 2015 21:41:37 +0100 Subject: [PATCH 11/11] Updated dependencies --- bower.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index 4b21f46..c270d85 100644 --- a/bower.json +++ b/bower.json @@ -3,11 +3,13 @@ "version": "0.1.6", "main": "dist/angular-dynamic-layout.min.js", "dependencies": { - "angular": "1.3.5", - "angular-animate": "1.3.5" + "angular": "~1.4.0", + "angular-animate": "~1.4.0" }, "devDependencies": { - "angular-mocks": "1.3.5" + "angular-mocks": "~1.4.0", + "bootcards": "latest", + "bootstrap": "latest" }, "homepage": "https://github.com/tristanguigue/angular-dynamic-layout", "authors": [