diff --git a/dist/js/ionic-angular.js b/dist/js/ionic-angular.js
index 6bd7090824..b1801fe36a 100644
--- a/dist/js/ionic-angular.js
+++ b/dist/js/ionic-angular.js
@@ -443,7 +443,6 @@ angular.module('ionic.ui.list', ['ngAnimate'])
reorderIcon: '@'
},
- // So we can require being under this
controller: function($scope) {
var _this = this;
@@ -482,12 +481,23 @@ angular.module('ionic.ui.list', ['ngAnimate'])
itemHeight: '@'
},
- // So we can require being under this
- controller: function($scope) {
+ controller: function($scope, $element) {
var _this = this;
this.scope = $scope;
+ this.element = $element;
+
+ var lv = new ionic.views.ListView({
+ el: $element[0],
+ listEl: $element[0].children[0],
+ isVirtual: true,
+ itemHeight: $scope.itemHeight,
+ });
+
+ this.listView = lv;
+
+
$scope.$watch('isEditing', function(v) {
_this.isEditing = true;
});
@@ -498,23 +508,13 @@ angular.module('ionic.ui.list', ['ngAnimate'])
compile: function(element, attr, transclude) {
return function($scope, $element, $attr) {
- var lv = new ionic.views.ListView({
- el: $element[0],
- listEl: $element[0].children[0],
- isVirtual: true,
- itemHeight: $scope.itemHeight,
- renderViewport: function(high, low, start, end) {
- console.log('RENDER VIEWPORT', high, low, start, end);
- }
- });
-
if(attr.animation) {
$element.addClass(attr.animation);
}
};
}
};
-});
+})
})();
;
@@ -1136,3 +1136,353 @@ angular.module('ionic.ui.toggle', [])
}
};
});
+;
+(function() {
+'use strict';
+
+angular.module('ionic.ui.virtRepeat', [])
+
+.directive('virtRepeat', function() {
+ return {
+ require: ['?ngModel', '^virtualList'],
+ transclude: 'element',
+ priority: 1000,
+ terminal: true,
+ compile: function(element, attr, transclude) {
+ return function($scope, $element, $attr, ctrls) {
+ var virtualList = ctrls[1];
+ var _this = this;
+
+ virtualList.listView.renderViewport = function(high, low, start, end) {
+ console.log('RENDER VIEWPORT', high, low, start, end);
+ }
+ }
+ }
+ }
+});
+})(ionic);
+;
+
+(function() {
+'use strict';
+
+// Turn the expression supplied to the directive:
+//
+// a in b
+//
+// into `{ value: "a", collection: "b" }`
+function parseRepeatExpression(expression){
+ var match = expression.match(/^\s*([\$\w]+)\s+in\s+(\S*)\s*$/);
+ if (! match) {
+ throw new Error("Expected sfVirtualRepeat in form of '_item_ in _collection_' but got '" +
+ expression + "'.");
+ }
+ return {
+ value: match[1],
+ collection: match[2]
+ };
+}
+
+// Utility to filter out elements by tag name
+function isTagNameInList(element, list){
+ var t, tag = element.tagName.toUpperCase();
+ for( t = 0; t < list.length; t++ ){
+ if( list[t] === tag ){
+ return true;
+ }
+ }
+ return false;
+}
+
+
+// Utility to find the viewport/content elements given the start element:
+function findViewportAndContent(startElement){
+ /*jshint eqeqeq:false, curly:false */
+ var root = $rootElement[0];
+ var e, n;
+ // Somewhere between the grandparent and the root node
+ for( e = startElement.parent().parent()[0]; e !== root; e = e.parentNode ){
+ // is an element
+ if( e.nodeType != 1 ) break;
+ // that isn't in the blacklist (tables etc.),
+ if( isTagNameInList(e, DONT_WORK_AS_VIEWPORTS) ) continue;
+ // has a single child element (the content),
+ if( e.childElementCount != 1 ) continue;
+ // which is not in the blacklist
+ if( isTagNameInList(e.firstElementChild, DONT_WORK_AS_CONTENT) ) continue;
+ // and no text.
+ for( n = e.firstChild; n; n = n.nextSibling ){
+ if( n.nodeType == 3 && /\S/g.test(n.textContent) ){
+ break;
+ }
+ }
+ if( n == null ){
+ // That element should work as a viewport.
+ return {
+ viewport: angular.element(e),
+ content: angular.element(e.firstElementChild)
+ };
+ }
+ }
+ throw new Error("No suitable viewport element");
+}
+
+// Apply explicit height and overflow styles to the viewport element.
+//
+// If the viewport has a max-height (inherited or otherwise), set max-height.
+// Otherwise, set height from the current computed value or use
+// window.innerHeight as a fallback
+//
+function setViewportCss(viewport){
+ var viewportCss = {'overflow': 'auto'},
+ style = window.getComputedStyle ?
+ window.getComputedStyle(viewport[0]) :
+ viewport[0].currentStyle,
+ maxHeight = style && style.getPropertyValue('max-height'),
+ height = style && style.getPropertyValue('height');
+
+ if( maxHeight && maxHeight !== '0px' ){
+ viewportCss.maxHeight = maxHeight;
+ }else if( height && height !== '0px' ){
+ viewportCss.height = height;
+ }else{
+ viewportCss.height = window.innerHeight;
+ }
+ viewport.css(viewportCss);
+}
+
+// Apply explicit styles to the content element to prevent pesky padding
+// or borders messing with our calculations:
+function setContentCss(content){
+ var contentCss = {
+ margin: 0,
+ padding: 0,
+ border: 0,
+ 'box-sizing': 'border-box'
+ };
+ content.css(contentCss);
+}
+
+// TODO: compute outerHeight (padding + border unless box-sizing is border)
+function computeRowHeight(element){
+ var style = window.getComputedStyle ? window.getComputedStyle(element)
+ : element.currentStyle,
+ maxHeight = style && style.getPropertyValue('max-height'),
+ height = style && style.getPropertyValue('height');
+
+ if( height && height !== '0px' && height !== 'auto' ){
+ $log.info('Row height is "%s" from css height', height);
+ }else if( maxHeight && maxHeight !== '0px' && maxHeight !== 'none' ){
+ height = maxHeight;
+ $log.info('Row height is "%s" from css max-height', height);
+ }else if( element.clientHeight ){
+ height = element.clientHeight+'px';
+ $log.info('Row height is "%s" from client height', height);
+ }else{
+ throw new Error("Unable to compute height of row");
+ }
+ angular.element(element).css('height', height);
+ return parseInt(height, 10);
+}
+
+angular.module('ionic.ui.virtualRepeat', [])
+
+/**
+ * A replacement for ng-repeat that supports virtual lists.
+ * This is not a 1 to 1 replacement for ng-repeat. However, in situations
+ * where you have huge lists, this repeater will work with our virtual
+ * scrolling to only render items that are showing or will be showing
+ * if a scroll is made.
+ */
+.directive('virtualRepeat', ['$log', function($log) {
+ return {
+ require: ['?ngModel, ^virtualList'],
+ transclude: 'element',
+ priority: 1000,
+ terminal: true,
+ compile: function(element, attr, transclude) {
+ var ident = parseRepeatExpression(attr.sfVirtualRepeat);
+
+ return function(scope, iterStartElement, attrs, ctrls, b) {
+ var virtualList = ctrls[1];
+
+ var rendered = [];
+ var rowHeight = 0;
+ var sticky = false;
+
+ var dom = virtualList.element;
+ //var dom = findViewportAndContent(iterStartElement);
+
+ // The list structure is controlled by a few simple (visible) variables:
+ var state = 'ngModel' in attrs ? scope.$eval(attrs.ngModel) : {};
+
+ function makeNewScope (idx, collection, containerScope) {
+ var childScope = containerScope.$new();
+ childScope[ident.value] = collection[idx];
+ childScope.$index = idx;
+ childScope.$first = (idx === 0);
+ childScope.$last = (idx === (collection.length - 1));
+ childScope.$middle = !(childScope.$first || childScope.$last);
+ childScope.$watch(function updateChildScopeItem(){
+ childScope[ident.value] = collection[idx];
+ });
+ return childScope;
+ }
+
+ // Given the collection and a start and end point, add the current
+ function addElements (start, end, collection, containerScope, insPoint) {
+ var frag = document.createDocumentFragment();
+ var newElements = [], element, idx, childScope;
+ for( idx = start; idx !== end; idx ++ ){
+ childScope = makeNewScope(idx, collection, containerScope);
+ element = linker(childScope, angular.noop);
+ //setElementCss(element);
+ newElements.push(element);
+ frag.appendChild(element[0]);
+ }
+ insPoint.after(frag);
+ return newElements;
+ }
+
+ function recomputeActive() {
+ // We want to set the start to the low water mark unless the current
+ // start is already between the low and high water marks.
+ var start = clip(state.firstActive, state.firstVisible - state.lowWater, state.firstVisible - state.highWater);
+ // Similarly for the end
+ var end = clip(state.firstActive + state.active,
+ state.firstVisible + state.visible + state.lowWater,
+ state.firstVisible + state.visible + state.highWater );
+ state.firstActive = Math.max(0, start);
+ state.active = Math.min(end, state.total) - state.firstActive;
+ }
+
+ function sfVirtualRepeatOnScroll(evt){
+ if( !rowHeight ){
+ return;
+ }
+ // Enter the angular world for the state change to take effect.
+ scope.$apply(function(){
+ state.firstVisible = Math.floor(evt.target.scrollTop / rowHeight);
+ state.visible = Math.ceil(dom.viewport[0].clientHeight / rowHeight);
+ $log.log('scroll to row %o', state.firstVisible);
+ sticky = evt.target.scrollTop + evt.target.clientHeight >= evt.target.scrollHeight;
+ recomputeActive();
+ $log.log(' state is now %o', state);
+ $log.log(' sticky = %o', sticky);
+ });
+ }
+
+ function sfVirtualRepeatWatchExpression(scope){
+ var coll = scope.$eval(ident.collection);
+ if( coll.length !== state.total ){
+ state.total = coll.length;
+ recomputeActive();
+ }
+ return {
+ start: state.firstActive,
+ active: state.active,
+ len: coll.length
+ };
+ }
+
+ function destroyActiveElements (action, count) {
+ var dead, ii, remover = Array.prototype[action];
+ for( ii = 0; ii < count; ii++ ){
+ dead = remover.call(rendered);
+ dead.scope().$destroy();
+ dead.remove();
+ }
+ }
+
+ // When the watch expression for the repeat changes, we may need to add
+ // and remove scopes and elements
+ function sfVirtualRepeatListener(newValue, oldValue, scope){
+ var oldEnd = oldValue.start + oldValue.active,
+ collection = scope.$eval(ident.collection),
+ newElements;
+ if(newValue === oldValue) {
+ $log.info('initial listen');
+ newElements = addElements(newValue.start, oldEnd, collection, scope, iterStartElement);
+ rendered = newElements;
+ if(rendered.length) {
+ rowHeight = computeRowHeight(newElements[0][0]);
+ }
+ } else {
+ var newEnd = newValue.start + newValue.active;
+ var forward = newValue.start >= oldValue.start;
+ var delta = forward ? newValue.start - oldValue.start
+ : oldValue.start - newValue.start;
+ var endDelta = newEnd >= oldEnd ? newEnd - oldEnd : oldEnd - newEnd;
+ var contiguous = delta < (forward ? oldValue.active : newValue.active);
+ $log.info('change by %o,%o rows %s', delta, endDelta, forward ? 'forward' : 'backward');
+ if(!contiguous) {
+ $log.info('non-contiguous change');
+ destroyActiveElements('pop', rendered.length);
+ rendered = addElements(newValue.start, newEnd, collection, scope, iterStartElement);
+ } else {
+ if(forward) {
+ $log.info('need to remove from the top');
+ destroyActiveElements('shift', delta);
+ } else if(delta) {
+ $log.info('need to add at the top');
+ newElements = addElements(
+ newValue.start,
+ oldValue.start,
+ collection, scope, iterStartElement);
+ rendered = newElements.concat(rendered);
+ }
+
+ if(newEnd < oldEnd) {
+ $log.info('need to remove from the bottom');
+ destroyActiveElements('pop', oldEnd - newEnd);
+ } else if(endDelta) {
+ var lastElement = rendered[rendered.length-1];
+ $log.info('need to add to the bottom');
+ newElements = addElements(
+ oldEnd,
+ newEnd,
+ collection, scope, lastElement);
+ rendered = rendered.concat(newElements);
+ }
+ }
+ if(!rowHeight && rendered.length) {
+ rowHeight = computeRowHeight(rendered[0][0]);
+ }
+ dom.content.css({'padding-top': newValue.start * rowHeight + 'px'});
+ }
+ dom.content.css({'height': newValue.len * rowHeight + 'px'});
+ if(sticky) {
+ dom.viewport[0].scrollTop = dom.viewport[0].clientHeight + dom.viewport[0].scrollHeight;
+ }
+ }
+
+ // - The index of the first active element
+ state.firstActive = 0;
+ // - The index of the first visible element
+ state.firstVisible = 0;
+ // - The number of elements visible in the viewport.
+ state.visible = 0;
+ // - The number of active elements
+ state.active = 0;
+ // - The total number of elements
+ state.total = 0;
+ // - The point at which we add new elements
+ state.lowWater = state.lowWater || 100;
+ // - The point at which we remove old elements
+ state.highWater = state.highWater || 300;
+ // TODO: now watch the water marks
+
+ setContentCss(dom.content);
+ setViewportCss(dom.viewport);
+ // When the user scrolls, we move the `state.firstActive`
+ dom.bind('momentumScrolled', sfVirtualRepeatOnScroll);
+
+ // The watch on the collection is just a watch on the length of the
+ // collection. We don't care if the content changes.
+ scope.$watch(sfVirtualRepeatWatchExpression, sfVirtualRepeatListener, true);
+ }
+ }
+ };
+ }]);
+
+})(ionic);
diff --git a/js/ext/angular/src/directive/ionicList.js b/js/ext/angular/src/directive/ionicList.js
index a2d69197a8..d2c43438d8 100644
--- a/js/ext/angular/src/directive/ionicList.js
+++ b/js/ext/angular/src/directive/ionicList.js
@@ -75,7 +75,6 @@ angular.module('ionic.ui.list', ['ngAnimate'])
reorderIcon: '@'
},
- // So we can require being under this
controller: function($scope) {
var _this = this;
@@ -114,12 +113,23 @@ angular.module('ionic.ui.list', ['ngAnimate'])
itemHeight: '@'
},
- // So we can require being under this
- controller: function($scope) {
+ controller: function($scope, $element) {
var _this = this;
this.scope = $scope;
+ this.element = $element;
+
+ var lv = new ionic.views.ListView({
+ el: $element[0],
+ listEl: $element[0].children[0],
+ isVirtual: true,
+ itemHeight: $scope.itemHeight,
+ });
+
+ this.listView = lv;
+
+
$scope.$watch('isEditing', function(v) {
_this.isEditing = true;
});
@@ -130,22 +140,12 @@ angular.module('ionic.ui.list', ['ngAnimate'])
compile: function(element, attr, transclude) {
return function($scope, $element, $attr) {
- var lv = new ionic.views.ListView({
- el: $element[0],
- listEl: $element[0].children[0],
- isVirtual: true,
- itemHeight: $scope.itemHeight,
- renderViewport: function(high, low, start, end) {
- console.log('RENDER VIEWPORT', high, low, start, end);
- }
- });
-
if(attr.animation) {
$element.addClass(attr.animation);
}
};
}
};
-});
+})
})();
diff --git a/js/ext/angular/src/directive/ionicVirtRepeat.js b/js/ext/angular/src/directive/ionicVirtRepeat.js
new file mode 100644
index 0000000000..61b9ab6f46
--- /dev/null
+++ b/js/ext/angular/src/directive/ionicVirtRepeat.js
@@ -0,0 +1,24 @@
+(function() {
+'use strict';
+
+angular.module('ionic.ui.virtRepeat', [])
+
+.directive('virtRepeat', function() {
+ return {
+ require: ['?ngModel', '^virtualList'],
+ transclude: 'element',
+ priority: 1000,
+ terminal: true,
+ compile: function(element, attr, transclude) {
+ return function($scope, $element, $attr, ctrls) {
+ var virtualList = ctrls[1];
+ var _this = this;
+
+ virtualList.listView.renderViewport = function(high, low, start, end) {
+ console.log('RENDER VIEWPORT', high, low, start, end);
+ }
+ }
+ }
+ }
+});
+})(ionic);
diff --git a/js/ext/angular/src/directive/ionicVirtualRepeat.js b/js/ext/angular/src/directive/ionicVirtualRepeat.js
new file mode 100644
index 0000000000..f012fef329
--- /dev/null
+++ b/js/ext/angular/src/directive/ionicVirtualRepeat.js
@@ -0,0 +1,324 @@
+
+(function() {
+'use strict';
+
+// Turn the expression supplied to the directive:
+//
+// a in b
+//
+// into `{ value: "a", collection: "b" }`
+function parseRepeatExpression(expression){
+ var match = expression.match(/^\s*([\$\w]+)\s+in\s+(\S*)\s*$/);
+ if (! match) {
+ throw new Error("Expected sfVirtualRepeat in form of '_item_ in _collection_' but got '" +
+ expression + "'.");
+ }
+ return {
+ value: match[1],
+ collection: match[2]
+ };
+}
+
+// Utility to filter out elements by tag name
+function isTagNameInList(element, list){
+ var t, tag = element.tagName.toUpperCase();
+ for( t = 0; t < list.length; t++ ){
+ if( list[t] === tag ){
+ return true;
+ }
+ }
+ return false;
+}
+
+
+// Utility to find the viewport/content elements given the start element:
+function findViewportAndContent(startElement){
+ /*jshint eqeqeq:false, curly:false */
+ var root = $rootElement[0];
+ var e, n;
+ // Somewhere between the grandparent and the root node
+ for( e = startElement.parent().parent()[0]; e !== root; e = e.parentNode ){
+ // is an element
+ if( e.nodeType != 1 ) break;
+ // that isn't in the blacklist (tables etc.),
+ if( isTagNameInList(e, DONT_WORK_AS_VIEWPORTS) ) continue;
+ // has a single child element (the content),
+ if( e.childElementCount != 1 ) continue;
+ // which is not in the blacklist
+ if( isTagNameInList(e.firstElementChild, DONT_WORK_AS_CONTENT) ) continue;
+ // and no text.
+ for( n = e.firstChild; n; n = n.nextSibling ){
+ if( n.nodeType == 3 && /\S/g.test(n.textContent) ){
+ break;
+ }
+ }
+ if( n == null ){
+ // That element should work as a viewport.
+ return {
+ viewport: angular.element(e),
+ content: angular.element(e.firstElementChild)
+ };
+ }
+ }
+ throw new Error("No suitable viewport element");
+}
+
+// Apply explicit height and overflow styles to the viewport element.
+//
+// If the viewport has a max-height (inherited or otherwise), set max-height.
+// Otherwise, set height from the current computed value or use
+// window.innerHeight as a fallback
+//
+function setViewportCss(viewport){
+ var viewportCss = {'overflow': 'auto'},
+ style = window.getComputedStyle ?
+ window.getComputedStyle(viewport[0]) :
+ viewport[0].currentStyle,
+ maxHeight = style && style.getPropertyValue('max-height'),
+ height = style && style.getPropertyValue('height');
+
+ if( maxHeight && maxHeight !== '0px' ){
+ viewportCss.maxHeight = maxHeight;
+ }else if( height && height !== '0px' ){
+ viewportCss.height = height;
+ }else{
+ viewportCss.height = window.innerHeight;
+ }
+ viewport.css(viewportCss);
+}
+
+// Apply explicit styles to the content element to prevent pesky padding
+// or borders messing with our calculations:
+function setContentCss(content){
+ var contentCss = {
+ margin: 0,
+ padding: 0,
+ border: 0,
+ 'box-sizing': 'border-box'
+ };
+ content.css(contentCss);
+}
+
+// TODO: compute outerHeight (padding + border unless box-sizing is border)
+function computeRowHeight(element){
+ var style = window.getComputedStyle ? window.getComputedStyle(element)
+ : element.currentStyle,
+ maxHeight = style && style.getPropertyValue('max-height'),
+ height = style && style.getPropertyValue('height');
+
+ if( height && height !== '0px' && height !== 'auto' ){
+ $log.info('Row height is "%s" from css height', height);
+ }else if( maxHeight && maxHeight !== '0px' && maxHeight !== 'none' ){
+ height = maxHeight;
+ $log.info('Row height is "%s" from css max-height', height);
+ }else if( element.clientHeight ){
+ height = element.clientHeight+'px';
+ $log.info('Row height is "%s" from client height', height);
+ }else{
+ throw new Error("Unable to compute height of row");
+ }
+ angular.element(element).css('height', height);
+ return parseInt(height, 10);
+}
+
+angular.module('ionic.ui.virtualRepeat', [])
+
+/**
+ * A replacement for ng-repeat that supports virtual lists.
+ * This is not a 1 to 1 replacement for ng-repeat. However, in situations
+ * where you have huge lists, this repeater will work with our virtual
+ * scrolling to only render items that are showing or will be showing
+ * if a scroll is made.
+ */
+.directive('virtualRepeat', ['$log', function($log) {
+ return {
+ require: ['?ngModel, ^virtualList'],
+ transclude: 'element',
+ priority: 1000,
+ terminal: true,
+ compile: function(element, attr, transclude) {
+ var ident = parseRepeatExpression(attr.sfVirtualRepeat);
+
+ return function(scope, iterStartElement, attrs, ctrls, b) {
+ var virtualList = ctrls[1];
+
+ var rendered = [];
+ var rowHeight = 0;
+ var sticky = false;
+
+ var dom = virtualList.element;
+ //var dom = findViewportAndContent(iterStartElement);
+
+ // The list structure is controlled by a few simple (visible) variables:
+ var state = 'ngModel' in attrs ? scope.$eval(attrs.ngModel) : {};
+
+ function makeNewScope (idx, collection, containerScope) {
+ var childScope = containerScope.$new();
+ childScope[ident.value] = collection[idx];
+ childScope.$index = idx;
+ childScope.$first = (idx === 0);
+ childScope.$last = (idx === (collection.length - 1));
+ childScope.$middle = !(childScope.$first || childScope.$last);
+ childScope.$watch(function updateChildScopeItem(){
+ childScope[ident.value] = collection[idx];
+ });
+ return childScope;
+ }
+
+ // Given the collection and a start and end point, add the current
+ function addElements (start, end, collection, containerScope, insPoint) {
+ var frag = document.createDocumentFragment();
+ var newElements = [], element, idx, childScope;
+ for( idx = start; idx !== end; idx ++ ){
+ childScope = makeNewScope(idx, collection, containerScope);
+ element = linker(childScope, angular.noop);
+ //setElementCss(element);
+ newElements.push(element);
+ frag.appendChild(element[0]);
+ }
+ insPoint.after(frag);
+ return newElements;
+ }
+
+ function recomputeActive() {
+ // We want to set the start to the low water mark unless the current
+ // start is already between the low and high water marks.
+ var start = clip(state.firstActive, state.firstVisible - state.lowWater, state.firstVisible - state.highWater);
+ // Similarly for the end
+ var end = clip(state.firstActive + state.active,
+ state.firstVisible + state.visible + state.lowWater,
+ state.firstVisible + state.visible + state.highWater );
+ state.firstActive = Math.max(0, start);
+ state.active = Math.min(end, state.total) - state.firstActive;
+ }
+
+ function sfVirtualRepeatOnScroll(evt){
+ if( !rowHeight ){
+ return;
+ }
+ // Enter the angular world for the state change to take effect.
+ scope.$apply(function(){
+ state.firstVisible = Math.floor(evt.target.scrollTop / rowHeight);
+ state.visible = Math.ceil(dom.viewport[0].clientHeight / rowHeight);
+ $log.log('scroll to row %o', state.firstVisible);
+ sticky = evt.target.scrollTop + evt.target.clientHeight >= evt.target.scrollHeight;
+ recomputeActive();
+ $log.log(' state is now %o', state);
+ $log.log(' sticky = %o', sticky);
+ });
+ }
+
+ function sfVirtualRepeatWatchExpression(scope){
+ var coll = scope.$eval(ident.collection);
+ if( coll.length !== state.total ){
+ state.total = coll.length;
+ recomputeActive();
+ }
+ return {
+ start: state.firstActive,
+ active: state.active,
+ len: coll.length
+ };
+ }
+
+ function destroyActiveElements (action, count) {
+ var dead, ii, remover = Array.prototype[action];
+ for( ii = 0; ii < count; ii++ ){
+ dead = remover.call(rendered);
+ dead.scope().$destroy();
+ dead.remove();
+ }
+ }
+
+ // When the watch expression for the repeat changes, we may need to add
+ // and remove scopes and elements
+ function sfVirtualRepeatListener(newValue, oldValue, scope){
+ var oldEnd = oldValue.start + oldValue.active,
+ collection = scope.$eval(ident.collection),
+ newElements;
+ if(newValue === oldValue) {
+ $log.info('initial listen');
+ newElements = addElements(newValue.start, oldEnd, collection, scope, iterStartElement);
+ rendered = newElements;
+ if(rendered.length) {
+ rowHeight = computeRowHeight(newElements[0][0]);
+ }
+ } else {
+ var newEnd = newValue.start + newValue.active;
+ var forward = newValue.start >= oldValue.start;
+ var delta = forward ? newValue.start - oldValue.start
+ : oldValue.start - newValue.start;
+ var endDelta = newEnd >= oldEnd ? newEnd - oldEnd : oldEnd - newEnd;
+ var contiguous = delta < (forward ? oldValue.active : newValue.active);
+ $log.info('change by %o,%o rows %s', delta, endDelta, forward ? 'forward' : 'backward');
+ if(!contiguous) {
+ $log.info('non-contiguous change');
+ destroyActiveElements('pop', rendered.length);
+ rendered = addElements(newValue.start, newEnd, collection, scope, iterStartElement);
+ } else {
+ if(forward) {
+ $log.info('need to remove from the top');
+ destroyActiveElements('shift', delta);
+ } else if(delta) {
+ $log.info('need to add at the top');
+ newElements = addElements(
+ newValue.start,
+ oldValue.start,
+ collection, scope, iterStartElement);
+ rendered = newElements.concat(rendered);
+ }
+
+ if(newEnd < oldEnd) {
+ $log.info('need to remove from the bottom');
+ destroyActiveElements('pop', oldEnd - newEnd);
+ } else if(endDelta) {
+ var lastElement = rendered[rendered.length-1];
+ $log.info('need to add to the bottom');
+ newElements = addElements(
+ oldEnd,
+ newEnd,
+ collection, scope, lastElement);
+ rendered = rendered.concat(newElements);
+ }
+ }
+ if(!rowHeight && rendered.length) {
+ rowHeight = computeRowHeight(rendered[0][0]);
+ }
+ dom.content.css({'padding-top': newValue.start * rowHeight + 'px'});
+ }
+ dom.content.css({'height': newValue.len * rowHeight + 'px'});
+ if(sticky) {
+ dom.viewport[0].scrollTop = dom.viewport[0].clientHeight + dom.viewport[0].scrollHeight;
+ }
+ }
+
+ // - The index of the first active element
+ state.firstActive = 0;
+ // - The index of the first visible element
+ state.firstVisible = 0;
+ // - The number of elements visible in the viewport.
+ state.visible = 0;
+ // - The number of active elements
+ state.active = 0;
+ // - The total number of elements
+ state.total = 0;
+ // - The point at which we add new elements
+ state.lowWater = state.lowWater || 100;
+ // - The point at which we remove old elements
+ state.highWater = state.highWater || 300;
+ // TODO: now watch the water marks
+
+ setContentCss(dom.content);
+ setViewportCss(dom.viewport);
+ // When the user scrolls, we move the `state.firstActive`
+ dom.bind('momentumScrolled', sfVirtualRepeatOnScroll);
+
+ // The watch on the collection is just a watch on the length of the
+ // collection. We don't care if the content changes.
+ scope.$watch(sfVirtualRepeatWatchExpression, sfVirtualRepeatListener, true);
+ }
+ }
+ };
+ }]);
+
+})(ionic);
diff --git a/js/ext/angular/test/biglist.html b/js/ext/angular/test/biglist.html
index 23dbdac6c0..eda6c3d77f 100644
--- a/js/ext/angular/test/biglist.html
+++ b/js/ext/angular/test/biglist.html
@@ -67,7 +67,7 @@
-