From ab896be99453fdbdc296044a33b8bbecd2f5ed11 Mon Sep 17 00:00:00 2001 From: Max Lynch Date: Wed, 30 Oct 2013 12:32:25 -0500 Subject: [PATCH] Early virtual directive stuff Don't use any of this yet. --- dist/js/ionic-angular.js | 378 +++++++++++++++++- js/ext/angular/src/directive/ionicList.js | 28 +- .../angular/src/directive/ionicVirtRepeat.js | 24 ++ .../src/directive/ionicVirtualRepeat.js | 324 +++++++++++++++ js/ext/angular/test/biglist.html | 4 +- 5 files changed, 728 insertions(+), 30 deletions(-) create mode 100644 js/ext/angular/src/directive/ionicVirtRepeat.js create mode 100644 js/ext/angular/src/directive/ionicVirtualRepeat.js 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 @@ -