diff --git a/README.md b/README.md
index 07549c3..9ece628 100644
--- a/README.md
+++ b/README.md
@@ -87,6 +87,35 @@ This index reflects the index of the corresponding object in the source collecti
That's all! Ehm, no. If you run your application now, you will notice that there is only one column. What is missing? Well, we have to define the configuration for the visual representation. And what is the best place for something like this? Yes, for sure! Your CSS file(s).
+### Providing a custom function to build the watch listener
+
+If you are expecting a lot of items or have complex objects you may want to provide a custom function to build a watch expression instead of asking angular to deep compare each object. The function should return a string and will be called for each item in `source`. The result will be a concatenated string from the result of each function.
+
+Example:
+
+```html
+
+```
+
+```javascript
+$scope.items = [
+ { id: 1, name: "Photo 1", properties: {...} },
+ { id: 2, name: "Photo 2", properties: {...} },
+ { id: 3, name: "Photo 3", properties: {...} },
+ { id: 4, name: "Photo 4", properties: {...} },
+];
+
+$scope.getItemId = function (item) {
+ return item.id + "|";
+}
+```
+
+The value used for the watch expression will be: `'1|2|3|4|'`.
+
## The grid configuration
The grid items will be distributed by your configured CSS selectors. An example:
diff --git a/angular-deckgrid.js b/angular-deckgrid.js
index 1bcafc8..6f7c45d 100644
--- a/angular-deckgrid.js
+++ b/angular-deckgrid.js
@@ -60,7 +60,8 @@ angular.module('akoenig.deckgrid').factory('DeckgridDescriptor', [
'';
this.scope = {
- 'model': '=source'
+ 'model': '=source',
+ 'itemIdentifierFn': '='
};
//
@@ -185,7 +186,16 @@ angular.module('akoenig.deckgrid').factory('Deckgrid', [
//
// Register model change.
//
- watcher = this.$$scope.$watch('model', this.$$onModelChange.bind(this), true);
+ var watchExpression = !scope.itemIdentifierFn ?
+ 'model' :
+ function() {
+ var result = '';
+ for (var i = 0; i < scope.model.length; i++) {
+ result += scope.itemIdentifierFn(scope.model[i]);
+ }
+ return result;
+ };
+ watcher = this.$$scope.$watch(watchExpression, this.$$onModelChange.bind(this), true);
this.$$watchers.push(watcher);
//
@@ -368,8 +378,19 @@ angular.module('akoenig.deckgrid').factory('Deckgrid', [
Deckgrid.prototype.$$onModelChange = function $$onModelChange (newModel, oldModel) {
var self = this;
- if (oldModel.length !== newModel.length) {
- self.$$createColumns();
+ if (isString(newModel) || isArray(newModel)) {
+ if (oldModel.length !== newModel.length) {
+ self.$$createColumns();
+ } else {
+ for (var i = newModel.length - 1; i >= 0; i--) {
+ if (oldModel[i] !== newModel[i]) {
+ self.$$createColumns();
+ break;
+ }
+ }
+ }
+ } else {
+ $log.error('DeckGrid watch expression must return an array or string.');
}
};
@@ -386,10 +407,24 @@ angular.module('akoenig.deckgrid').factory('Deckgrid', [
}
};
+ function isString(value) {
+ return typeof value === 'string' ||
+ value && typeof value === 'object' && value.toString() === '[object String]' || false;
+ }
+
+ function isArray(obj) {
+ if (Array.isArray) {
+ return Array.isArray(obj);
+ }
+
+ return obj && typeof obj === 'object' && typeof obj.length === 'number' &&
+ obj.toString() === '[object Array]' || false;
+ }
+
return {
create : function create (scope, element) {
return new Deckgrid(scope, element);
}
};
}
-]);
+]);
\ No newline at end of file
diff --git a/angular-deckgrid.min.js b/angular-deckgrid.min.js
index 3ca836b..95fafb4 100644
--- a/angular-deckgrid.min.js
+++ b/angular-deckgrid.min.js
@@ -1,2 +1,2 @@
/*! angular-deckgrid (v0.4.0) - Copyright: 2013 - 2014, André König (andre.koenig@posteo.de) - MIT */
-angular.module("akoenig.deckgrid",[]),angular.module("akoenig.deckgrid").directive("deckgrid",["DeckgridDescriptor",function(a){"use strict";return a.create()}]),angular.module("akoenig.deckgrid").factory("DeckgridDescriptor",["Deckgrid","$templateCache",function(a,b){"use strict";function c(){this.restrict="AE",this.template='',this.scope={model:"=source"},this.$$deckgrid=null,this.transclude=!0,this.link=this.$$link.bind(this)}return c.prototype.$$destroy=function(){this.$$deckgrid.destroy()},c.prototype.$$link=function(c,d,e,f,g){c.$on("$destroy",this.$$destroy.bind(this)),void 0===e.cardtemplate?(void 0===e.cardtemplatestring?g(c,function(a){var c,d=[],e=0,f=a.length;for(e;f>e;e+=1)c=a[e].outerHTML,void 0!==c&&d.push(c);b.put("innerHtmlTemplate",d.join())}):b.put("innerHtmlTemplate",d.attr("cardtemplatestring")),c.cardTemplate="innerHtmlTemplate"):c.cardTemplate=e.cardtemplate,c.mother=c.$parent,this.$$deckgrid=a.create(c,d[0])},{create:function(){return new c}}}]),angular.module("akoenig.deckgrid").factory("Deckgrid",["$window","$log",function(a,b){"use strict";function c(a,b){var c,d=this;this.$$elem=b,this.$$watchers=[],this.$$scope=a,this.$$scope.columns=[],this.$$scope.layout=this.$$getLayout(),this.$$createColumns(),c=this.$$scope.$watch("model",this.$$onModelChange.bind(this),!0),this.$$watchers.push(c),angular.forEach(d.$$getMediaQueries(),function(a){function b(){a.removeListener(d.$$onMediaQueryChange.bind(d))}a.addListener(d.$$onMediaQueryChange.bind(d)),d.$$watchers.push(b)})}return c.prototype.$$getMediaQueries=function(){function b(a){try{return a.sheet.cssRules||[]}catch(b){return[]}}function c(a){var b=/\[(\w*-)?deckgrid\]::?before/g,c=0,d="";if(!a.media||angular.isUndefined(a.cssRules))return!1;for(c=a.cssRules.length-1;c>=0;c-=1)if(d=a.cssRules[c].selectorText,angular.isDefined(d)&&d.match(b))return!0;return!1}var d=[],e=[];return d=Array.prototype.concat.call(Array.prototype.slice.call(document.querySelectorAll("style[type='text/css']")),Array.prototype.slice.call(document.querySelectorAll("link[rel='stylesheet']"))),angular.forEach(d,function(d){var f=b(d);angular.forEach(f,function(b){c(b)&&e.push(a.matchMedia(b.media.mediaText))})}),e},c.prototype.$$createColumns=function(){var a=this;return this.$$scope.layout?(this.$$scope.columns=[],void angular.forEach(this.$$scope.model,function(b,c){var d=c%a.$$scope.layout.columns|0;a.$$scope.columns[d]||(a.$$scope.columns[d]=[]),b.$index=c,a.$$scope.columns[d].push(b)})):b.error("angular-deckgrid: No CSS configuration found (see https://github.com/akoenig/angular-deckgrid#the-grid-configuration)")},c.prototype.$$getLayout=function(){var b,c=a.getComputedStyle(this.$$elem,":before").content;return c&&(c=c.replace(/'/g,""),c=c.replace(/"/g,""),c=c.split(" "),2===c.length&&(b={},b.columns=0|c[0],b.classList=c[1].replace(/\./g," ").trim())),b},c.prototype.$$onMediaQueryChange=function(){var a=this,b=this.$$getLayout();b.columns!==this.$$scope.layout.columns&&(a.$$scope.layout=b,a.$$scope.$apply(function(){a.$$createColumns()}))},c.prototype.$$onModelChange=function(a,b){var c=this;b.length!==a.length&&c.$$createColumns()},c.prototype.destroy=function(){var a=this.$$watchers.length-1;for(a;a>=0;a-=1)this.$$watchers[a]()},{create:function(a,b){return new c(a,b)}}}]);
\ No newline at end of file
+angular.module("akoenig.deckgrid",[]),angular.module("akoenig.deckgrid").directive("deckgrid",["DeckgridDescriptor",function(a){"use strict";return a.create()}]),angular.module("akoenig.deckgrid").factory("DeckgridDescriptor",["Deckgrid","$templateCache",function(a,b){"use strict";function c(){this.restrict="AE",this.template='',this.scope={model:"=source",itemIdentifierFn:"="},this.$$deckgrid=null,this.transclude=!0,this.link=this.$$link.bind(this)}return c.prototype.$$destroy=function(){this.$$deckgrid.destroy()},c.prototype.$$link=function(c,d,e,f,g){c.$on("$destroy",this.$$destroy.bind(this)),void 0===e.cardtemplate?(void 0===e.cardtemplatestring?g(c,function(a){var c,d=[],e=0,f=a.length;for(e;f>e;e+=1)c=a[e].outerHTML,void 0!==c&&d.push(c);b.put("innerHtmlTemplate",d.join())}):b.put("innerHtmlTemplate",d.attr("cardtemplatestring")),c.cardTemplate="innerHtmlTemplate"):c.cardTemplate=e.cardtemplate,c.mother=c.$parent,this.$$deckgrid=a.create(c,d[0])},{create:function(){return new c}}}]),angular.module("akoenig.deckgrid").factory("Deckgrid",["$window","$log",function(a,b){"use strict";function c(a,b){var c,d=this;this.$$elem=b,this.$$watchers=[],this.$$scope=a,this.$$scope.columns=[],this.$$scope.layout=this.$$getLayout(),this.$$createColumns();var e=a.itemIdentifierFn?function(){for(var b="",c=0;c=0;c-=1)if(d=a.cssRules[c].selectorText,angular.isDefined(d)&&d.match(b))return!0;return!1}var d=[],e=[];return d=Array.prototype.concat.call(Array.prototype.slice.call(document.querySelectorAll("style[type='text/css']")),Array.prototype.slice.call(document.querySelectorAll("link[rel='stylesheet']"))),angular.forEach(d,function(d){var f=b(d);angular.forEach(f,function(b){c(b)&&e.push(a.matchMedia(b.media.mediaText))})}),e},c.prototype.$$createColumns=function(){var a=this;return this.$$scope.layout?(this.$$scope.columns=[],void angular.forEach(this.$$scope.model,function(b,c){var d=c%a.$$scope.layout.columns|0;a.$$scope.columns[d]||(a.$$scope.columns[d]=[]),b.$index=c,a.$$scope.columns[d].push(b)})):b.error("angular-deckgrid: No CSS configuration found (see https://github.com/akoenig/angular-deckgrid#the-grid-configuration)")},c.prototype.$$getLayout=function(){var b,c=a.getComputedStyle(this.$$elem,":before").content;return c&&(c=c.replace(/'/g,""),c=c.replace(/"/g,""),c=c.split(" "),2===c.length&&(b={},b.columns=0|c[0],b.classList=c[1].replace(/\./g," ").trim())),b},c.prototype.$$onMediaQueryChange=function(){var a=this,b=this.$$getLayout();b.columns!==this.$$scope.layout.columns&&(a.$$scope.layout=b,a.$$scope.$apply(function(){a.$$createColumns()}))},c.prototype.$$onModelChange=function(a,c){var f=this;if(d(a)||e(a)){if(c.length!==a.length)f.$$createColumns();else for(var g=a.length-1;g>=0;g--)if(c[g]!==a[g]){f.$$createColumns();break}}else b.error("DeckGrid watch expression must return an array or string.")},c.prototype.destroy=function(){var a=this.$$watchers.length-1;for(a;a>=0;a-=1)this.$$watchers[a]()},{create:function(a,b){return new c(a,b)}}}]);
\ No newline at end of file
diff --git a/src/deckgrid.js b/src/deckgrid.js
index 8cdacff..cb028c2 100644
--- a/src/deckgrid.js
+++ b/src/deckgrid.js
@@ -46,7 +46,16 @@ angular.module('akoenig.deckgrid').factory('Deckgrid', [
//
// Register model change.
//
- watcher = this.$$scope.$watch('model', this.$$onModelChange.bind(this), true);
+ var watchExpression = !scope.itemIdentifierFn ?
+ 'model' :
+ function() {
+ var result = '';
+ for (var i = 0; i < scope.model.length; i++) {
+ result += scope.itemIdentifierFn(scope.model[i]);
+ }
+ return result;
+ };
+ watcher = this.$$scope.$watch(watchExpression, this.$$onModelChange.bind(this), true);
this.$$watchers.push(watcher);
//
@@ -229,8 +238,19 @@ angular.module('akoenig.deckgrid').factory('Deckgrid', [
Deckgrid.prototype.$$onModelChange = function $$onModelChange (newModel, oldModel) {
var self = this;
- if (oldModel.length !== newModel.length) {
- self.$$createColumns();
+ if (isString(newModel) || isArray(newModel)) {
+ if (oldModel.length !== newModel.length) {
+ self.$$createColumns();
+ } else {
+ for (var i = newModel.length - 1; i >= 0; i--) {
+ if (oldModel[i] !== newModel[i]) {
+ self.$$createColumns();
+ break;
+ }
+ }
+ }
+ } else {
+ $log.error('DeckGrid watch expression must return an array or string.');
}
};
@@ -247,6 +267,20 @@ angular.module('akoenig.deckgrid').factory('Deckgrid', [
}
};
+ function isString(value) {
+ return typeof value === 'string' ||
+ value && typeof value === 'object' && value.toString() === '[object String]' || false;
+ }
+
+ function isArray(obj) {
+ if (Array.isArray) {
+ return Array.isArray(obj);
+ }
+
+ return obj && typeof obj === 'object' && typeof obj.length === 'number' &&
+ obj.toString() === '[object Array]' || false;
+ }
+
return {
create : function create (scope, element) {
return new Deckgrid(scope, element);
diff --git a/src/descriptor.js b/src/descriptor.js
index ae10649..624bdd6 100644
--- a/src/descriptor.js
+++ b/src/descriptor.js
@@ -33,7 +33,8 @@ angular.module('akoenig.deckgrid').factory('DeckgridDescriptor', [
'';
this.scope = {
- 'model': '=source'
+ 'model': '=source',
+ 'itemIdentifierFn': '='
};
//