mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-08 15:51:16 +08:00
Working on virtual list viewport stuff
This commit is contained in:
90
dist/js/ionic.js
vendored
90
dist/js/ionic.js
vendored
@ -2726,6 +2726,17 @@ window.ionic = {
|
|||||||
initialize: function(opts) {
|
initialize: function(opts) {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
|
|
||||||
|
opts = ionic.extend({
|
||||||
|
virtualRemoveThreshold: -200,
|
||||||
|
virtualAddThreshold: 200
|
||||||
|
}, opts);
|
||||||
|
|
||||||
|
ionic.extend(this, opts);
|
||||||
|
|
||||||
|
if(!this.itemHeight && this.listEl) {
|
||||||
|
this.itemHeight = this.listEl.children[0] && parseInt(this.listEl.children[0].style.height);
|
||||||
|
}
|
||||||
|
|
||||||
ionic.views.ListView.__super__.initialize.call(this, opts);
|
ionic.views.ListView.__super__.initialize.call(this, opts);
|
||||||
|
|
||||||
this.onRefresh = opts.onRefresh || function() {};
|
this.onRefresh = opts.onRefresh || function() {};
|
||||||
@ -2755,21 +2766,43 @@ window.ionic = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
didScroll: function(e) {
|
didScroll: function(e) {
|
||||||
console.log('Scrolling', Date.now());
|
|
||||||
if(this.isVirtual) {
|
if(this.isVirtual) {
|
||||||
var itemHeight = this.itemHeight;
|
var itemHeight = this.itemHeight;
|
||||||
var totalItems = this.listEl.children.length;
|
var totalItems = this.listEl.children.length;
|
||||||
var scrollHeight = e.target.scrollHeight
|
|
||||||
|
var scrollHeight = e.target.scrollHeight;
|
||||||
|
var viewportHeight = this.el.parentNode.offsetHeight;
|
||||||
|
|
||||||
var scrollTop = e.scrollTop;
|
var scrollTop = e.scrollTop;
|
||||||
var height = this.el.parentNode.offsetHeight;
|
|
||||||
console.log('LIST VIEW SCROLLED', e, itemHeight, scrollHeight, height);
|
|
||||||
|
|
||||||
var itemsPerPage = Math.floor(height / itemHeight);
|
var highWater = e.scrollTop + this.virtualRemoveThreshold;
|
||||||
var first = parseInt(scrollTop / itemHeight);
|
var lowWater = Math.min(scrollHeight + viewportHeight, e.scrollTop + viewportHeight + this.virtualAddThreshold);
|
||||||
console.log('FITS', itemsPerPage, 'per page, starting at', first);
|
|
||||||
|
//console.log('LIST VIEW SCROLLED', e, itemHeight, scrollHeight, viewportHeight);
|
||||||
|
var itemsPerViewport = Math.floor((lowWater - highWater) / itemHeight);
|
||||||
|
var first = parseInt(highWater / itemHeight);
|
||||||
|
var last = parseInt(lowWater / itemHeight);
|
||||||
|
|
||||||
|
//console.log('FITS', itemsPerViewport, 'per page, starting at', first);
|
||||||
|
|
||||||
|
this._virtualItemsToRemove = Array.prototype.slice.call(this.listEl.children, 0, first);
|
||||||
|
|
||||||
|
var nodes = Array.prototype.slice.call(this.listEl.children, first, first + itemsPerViewport);
|
||||||
|
|
||||||
|
this.renderViewport && this.renderViewport(highWater, lowWater, first, last);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
didStopScrolling: function(e) {
|
||||||
|
if(this.isVirtual) {
|
||||||
|
//console.log('DONE SCROLLING, Need to remove', this._virtualItemsToRemove);
|
||||||
|
for(var i = 0; i < this._virtualItemsToRemove.length; i++) {
|
||||||
|
var el = this._virtualItemsToRemove[i];
|
||||||
|
//el.parentNode.removeChild(el);
|
||||||
|
this.didHideItem && this.didHideItem(i);
|
||||||
|
}
|
||||||
|
// Once scrolling stops, check if we need to remove old items
|
||||||
|
|
||||||
var nodes = Array.prototype.slice.call(this.listEl.children, first, first + itemsPerPage);
|
|
||||||
console.log('Showing these', nodes.length, 'nodes:', nodes);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -3244,11 +3277,7 @@ window.ionic = {
|
|||||||
// Execute the scrollEnd event after 400ms the wheel stopped scrolling
|
// Execute the scrollEnd event after 400ms the wheel stopped scrolling
|
||||||
clearTimeout(this.wheelTimeout);
|
clearTimeout(this.wheelTimeout);
|
||||||
this.wheelTimeout = setTimeout(function () {
|
this.wheelTimeout = setTimeout(function () {
|
||||||
ionic.trigger(this.scrollEndEventName, {
|
that._doneScrolling();
|
||||||
target: this.el,
|
|
||||||
scrollLeft: this.x,
|
|
||||||
scrollTop: this.y
|
|
||||||
});
|
|
||||||
}, 400);
|
}, 400);
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -3333,18 +3362,10 @@ window.ionic = {
|
|||||||
// Triggered to end scroll, once the final animation has ended
|
// Triggered to end scroll, once the final animation has ended
|
||||||
if(needsWrapping && this._didEndScroll) {
|
if(needsWrapping && this._didEndScroll) {
|
||||||
this._didEndScroll = false;
|
this._didEndScroll = false;
|
||||||
ionic.trigger(this.scrollEndEventName, {
|
this._doneScrolling();
|
||||||
target: this.el,
|
|
||||||
scrollLeft: this.x,
|
|
||||||
scrollTop: this.y
|
|
||||||
});
|
|
||||||
} else if(!needsWrapping) {
|
} else if(!needsWrapping) {
|
||||||
this._didEndScroll = false;
|
this._didEndScroll = false;
|
||||||
ionic.trigger(this.scrollEndEventName, {
|
this._doneScrolling();
|
||||||
target: this.el,
|
|
||||||
scrollLeft: this.x,
|
|
||||||
scrollTop: this.y
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.el.style.webkitTransitionDuration = '0';
|
this.el.style.webkitTransitionDuration = '0';
|
||||||
@ -3587,7 +3608,7 @@ window.ionic = {
|
|||||||
newX = momentumX.destination;
|
newX = momentumX.destination;
|
||||||
newY = momentumY.destination;
|
newY = momentumY.destination;
|
||||||
|
|
||||||
// Calcualte the longest required time for the momentum animation and
|
// Calculate the longest required time for the momentum animation and
|
||||||
// use that.
|
// use that.
|
||||||
time = Math.max(momentumX.duration, momentumY.duration);
|
time = Math.max(momentumX.duration, momentumY.duration);
|
||||||
}
|
}
|
||||||
@ -3603,13 +3624,22 @@ window.ionic = {
|
|||||||
_this.scrollTo(newX, newY, time, easing);
|
_this.scrollTo(newX, newY, time, easing);
|
||||||
} else {
|
} else {
|
||||||
// We are done
|
// We are done
|
||||||
ionic.trigger(_this.scrollEndEventName, {
|
_this._doneScrolling();
|
||||||
target: _this.el,
|
|
||||||
scrollLeft: _this.x,
|
|
||||||
scrollTop: _this.y
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_doneScrolling: function() {
|
||||||
|
this.didStopScrolling && this.didStopScrolling({
|
||||||
|
target: this.el,
|
||||||
|
scrollLeft: this.x,
|
||||||
|
scrollTop: this.y
|
||||||
|
});
|
||||||
|
ionic.trigger(this.scrollEndEventName, {
|
||||||
|
target: this.el,
|
||||||
|
scrollLeft: this.x,
|
||||||
|
scrollTop: this.y
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
DECEL_RATE_NORMAL: 0.998,
|
DECEL_RATE_NORMAL: 0.998,
|
||||||
|
|||||||
@ -364,6 +364,17 @@
|
|||||||
initialize: function(opts) {
|
initialize: function(opts) {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
|
|
||||||
|
opts = ionic.extend({
|
||||||
|
virtualRemoveThreshold: -200,
|
||||||
|
virtualAddThreshold: 200
|
||||||
|
}, opts);
|
||||||
|
|
||||||
|
ionic.extend(this, opts);
|
||||||
|
|
||||||
|
if(!this.itemHeight && this.listEl) {
|
||||||
|
this.itemHeight = this.listEl.children[0] && parseInt(this.listEl.children[0].style.height);
|
||||||
|
}
|
||||||
|
|
||||||
ionic.views.ListView.__super__.initialize.call(this, opts);
|
ionic.views.ListView.__super__.initialize.call(this, opts);
|
||||||
|
|
||||||
this.onRefresh = opts.onRefresh || function() {};
|
this.onRefresh = opts.onRefresh || function() {};
|
||||||
@ -393,21 +404,43 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
didScroll: function(e) {
|
didScroll: function(e) {
|
||||||
console.log('Scrolling', Date.now());
|
|
||||||
if(this.isVirtual) {
|
if(this.isVirtual) {
|
||||||
var itemHeight = this.itemHeight;
|
var itemHeight = this.itemHeight;
|
||||||
var totalItems = this.listEl.children.length;
|
var totalItems = this.listEl.children.length;
|
||||||
var scrollHeight = e.target.scrollHeight
|
|
||||||
|
var scrollHeight = e.target.scrollHeight;
|
||||||
|
var viewportHeight = this.el.parentNode.offsetHeight;
|
||||||
|
|
||||||
var scrollTop = e.scrollTop;
|
var scrollTop = e.scrollTop;
|
||||||
var height = this.el.parentNode.offsetHeight;
|
|
||||||
console.log('LIST VIEW SCROLLED', e, itemHeight, scrollHeight, height);
|
|
||||||
|
|
||||||
var itemsPerPage = Math.floor(height / itemHeight);
|
var highWater = e.scrollTop + this.virtualRemoveThreshold;
|
||||||
var first = parseInt(scrollTop / itemHeight);
|
var lowWater = Math.min(scrollHeight + viewportHeight, e.scrollTop + viewportHeight + this.virtualAddThreshold);
|
||||||
console.log('FITS', itemsPerPage, 'per page, starting at', first);
|
|
||||||
|
//console.log('LIST VIEW SCROLLED', e, itemHeight, scrollHeight, viewportHeight);
|
||||||
|
var itemsPerViewport = Math.floor((lowWater - highWater) / itemHeight);
|
||||||
|
var first = parseInt(highWater / itemHeight);
|
||||||
|
var last = parseInt(lowWater / itemHeight);
|
||||||
|
|
||||||
|
//console.log('FITS', itemsPerViewport, 'per page, starting at', first);
|
||||||
|
|
||||||
|
this._virtualItemsToRemove = Array.prototype.slice.call(this.listEl.children, 0, first);
|
||||||
|
|
||||||
|
var nodes = Array.prototype.slice.call(this.listEl.children, first, first + itemsPerViewport);
|
||||||
|
|
||||||
|
this.renderViewport && this.renderViewport(highWater, lowWater, first, last);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
didStopScrolling: function(e) {
|
||||||
|
if(this.isVirtual) {
|
||||||
|
//console.log('DONE SCROLLING, Need to remove', this._virtualItemsToRemove);
|
||||||
|
for(var i = 0; i < this._virtualItemsToRemove.length; i++) {
|
||||||
|
var el = this._virtualItemsToRemove[i];
|
||||||
|
//el.parentNode.removeChild(el);
|
||||||
|
this.didHideItem && this.didHideItem(i);
|
||||||
|
}
|
||||||
|
// Once scrolling stops, check if we need to remove old items
|
||||||
|
|
||||||
var nodes = Array.prototype.slice.call(this.listEl.children, first, first + itemsPerPage);
|
|
||||||
console.log('Showing these', nodes.length, 'nodes:', nodes);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -214,11 +214,7 @@
|
|||||||
// Execute the scrollEnd event after 400ms the wheel stopped scrolling
|
// Execute the scrollEnd event after 400ms the wheel stopped scrolling
|
||||||
clearTimeout(this.wheelTimeout);
|
clearTimeout(this.wheelTimeout);
|
||||||
this.wheelTimeout = setTimeout(function () {
|
this.wheelTimeout = setTimeout(function () {
|
||||||
ionic.trigger(this.scrollEndEventName, {
|
that._doneScrolling();
|
||||||
target: this.el,
|
|
||||||
scrollLeft: this.x,
|
|
||||||
scrollTop: this.y
|
|
||||||
});
|
|
||||||
}, 400);
|
}, 400);
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -303,18 +299,10 @@
|
|||||||
// Triggered to end scroll, once the final animation has ended
|
// Triggered to end scroll, once the final animation has ended
|
||||||
if(needsWrapping && this._didEndScroll) {
|
if(needsWrapping && this._didEndScroll) {
|
||||||
this._didEndScroll = false;
|
this._didEndScroll = false;
|
||||||
ionic.trigger(this.scrollEndEventName, {
|
this._doneScrolling();
|
||||||
target: this.el,
|
|
||||||
scrollLeft: this.x,
|
|
||||||
scrollTop: this.y
|
|
||||||
});
|
|
||||||
} else if(!needsWrapping) {
|
} else if(!needsWrapping) {
|
||||||
this._didEndScroll = false;
|
this._didEndScroll = false;
|
||||||
ionic.trigger(this.scrollEndEventName, {
|
this._doneScrolling();
|
||||||
target: this.el,
|
|
||||||
scrollLeft: this.x,
|
|
||||||
scrollTop: this.y
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.el.style.webkitTransitionDuration = '0';
|
this.el.style.webkitTransitionDuration = '0';
|
||||||
@ -557,7 +545,7 @@
|
|||||||
newX = momentumX.destination;
|
newX = momentumX.destination;
|
||||||
newY = momentumY.destination;
|
newY = momentumY.destination;
|
||||||
|
|
||||||
// Calcualte the longest required time for the momentum animation and
|
// Calculate the longest required time for the momentum animation and
|
||||||
// use that.
|
// use that.
|
||||||
time = Math.max(momentumX.duration, momentumY.duration);
|
time = Math.max(momentumX.duration, momentumY.duration);
|
||||||
}
|
}
|
||||||
@ -573,13 +561,22 @@
|
|||||||
_this.scrollTo(newX, newY, time, easing);
|
_this.scrollTo(newX, newY, time, easing);
|
||||||
} else {
|
} else {
|
||||||
// We are done
|
// We are done
|
||||||
ionic.trigger(_this.scrollEndEventName, {
|
_this._doneScrolling();
|
||||||
target: _this.el,
|
|
||||||
scrollLeft: _this.x,
|
|
||||||
scrollTop: _this.y
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_doneScrolling: function() {
|
||||||
|
this.didStopScrolling && this.didStopScrolling({
|
||||||
|
target: this.el,
|
||||||
|
scrollLeft: this.x,
|
||||||
|
scrollTop: this.y
|
||||||
|
});
|
||||||
|
ionic.trigger(this.scrollEndEventName, {
|
||||||
|
target: this.el,
|
||||||
|
scrollLeft: this.x,
|
||||||
|
scrollTop: this.y
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
DECEL_RATE_NORMAL: 0.998,
|
DECEL_RATE_NORMAL: 0.998,
|
||||||
|
|||||||
@ -1281,7 +1281,10 @@
|
|||||||
el: list,
|
el: list,
|
||||||
listEl: list.children[0],
|
listEl: list.children[0],
|
||||||
isVirtual: true,
|
isVirtual: true,
|
||||||
itemHeight: 50
|
itemHeight: 50,
|
||||||
|
renderViewport: function(high, low, start, end) {
|
||||||
|
console.log('RENDER VIEWPORT', high, low, start, end);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
50
test/js/views/listView.unit.js
Normal file
50
test/js/views/listView.unit.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
describe('List View', function() {
|
||||||
|
var h, listEl;
|
||||||
|
beforeEach(function() {
|
||||||
|
h = document.createElement('div');
|
||||||
|
h.id = 'content';
|
||||||
|
h.className = 'scroll'
|
||||||
|
var l = document.createElement('ul');
|
||||||
|
l.className = 'list scroll';
|
||||||
|
h.appendChild(l);
|
||||||
|
|
||||||
|
listEl = l;
|
||||||
|
|
||||||
|
for(var i = 0; i < 1000; i++) {
|
||||||
|
var li = document.createElement('li');
|
||||||
|
li.style.height = '50px';
|
||||||
|
l.appendChild(li);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should init', function() {
|
||||||
|
var list = new ionic.views.ListView({
|
||||||
|
el: h,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
iit('Should init item height from CSS', function() {
|
||||||
|
var list = new ionic.views.ListView({
|
||||||
|
el: h,
|
||||||
|
listEl: listEl,
|
||||||
|
isVirtual: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(list.itemHeight).toEqual(50);
|
||||||
|
});
|
||||||
|
|
||||||
|
iit('Should support virtual scrolling', function() {
|
||||||
|
var list = new ionic.views.ListView({
|
||||||
|
el: h,
|
||||||
|
listEl: listEl,
|
||||||
|
isVirtual: true,
|
||||||
|
itemHeight: 50
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(list.itemHeight).toEqual(50);
|
||||||
|
|
||||||
|
list.renderViewport = function(high, low, start, end) {
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user