diff --git a/List.js b/List.js index 77a5726c9..61d3e4d7a 100644 --- a/List.js +++ b/List.js @@ -399,6 +399,61 @@ function(arrayUtil, kernel, declare, listen, has, miscUtil, TouchScroll, hasClas }while((next = next.nextSibling) && next.rowIndex != rowIndex); } }, + _observeResults: function(results, rows, options){ + // add an observer to results + var self = this; + var observerIndex = this.observers.push(results.observe(function(object, from, to){ + var firstRow, nextNode; + // a change in the data took place + if(from > -1 && rows[from]){ + // remove from old slot + row = rows.splice(from, 1)[0]; + // check to make the sure the node is still there before we try to remove it, (in case it was moved to a different place in the DOM) + if(row.parentNode == options.container){ + firstRow = row.nextSibling; + if(firstRow){ // it's possible for this to have been already removed if it is in overlapping query results + if(from != to){ // if from and to are identical, it is an in-place update and we don't want to alter the rowIndex at all + firstRow.rowIndex--; // adjust the rowIndex so adjustRowIndices has the right starting point + } + } + self.removeRow(row); // now remove + } + // the removal of rows could cause us to need to page in more items + if(self._processScroll){ + self._processScroll(); + } + } + if(to > -1){ + // Add to new slot (either before an existing row, or at the end) + // First determine the DOM node that this should be placed before. + nextNode = rows[to]; + if(!nextNode){ + nextNode = rows[to - 1]; + if(nextNode){ + // Make sure to skip connected nodes, so we don't accidentally + // insert a row in between a parent and its children. + nextNode = (nextNode.connected || nextNode).nextSibling; + } + } + row = self.newRow(object, nextNode, to, options); + + if(row){ + row.observerIndex = observerIndex; + rows.splice(to, 0, row); + if(!firstRow || to < from){ + // the inserted row is first, so we update firstRow to point to it + var previous = row.previousSibling; + // if we are not in sync with the previous row, roll the firstRow back one so adjustRowIndices can sync everything back up. + firstRow = !previous || previous.rowIndex + 1 == row.rowIndex || row.rowIndex == 0 ? + row : previous; + } + } + options.count++; + } + from != to && firstRow && self.adjustRowIndices(firstRow); + }, true)) - 1; + return observerIndex; + }, renderArray: function(results, beforeNode, options){ // summary: // This renders an array or collection of objects as rows in the grid, before the @@ -412,59 +467,6 @@ function(arrayUtil, kernel, declare, listen, has, miscUtil, TouchScroll, hasClas if(!beforeNode){ this._lastCollection = results; } - if(results.observe){ - // observe the results for changes - var observerIndex = this.observers.push(results.observe(function(object, from, to){ - var firstRow, nextNode; - // a change in the data took place - if(from > -1 && rows[from]){ - // remove from old slot - row = rows.splice(from, 1)[0]; - // check to make the sure the node is still there before we try to remove it, (in case it was moved to a different place in the DOM) - if(row.parentNode == container){ - firstRow = row.nextSibling; - if(firstRow){ // it's possible for this to have been already removed if it is in overlapping query results - if(from != to){ // if from and to are identical, it is an in-place update and we don't want to alter the rowIndex at all - firstRow.rowIndex--; // adjust the rowIndex so adjustRowIndices has the right starting point - } - } - self.removeRow(row); // now remove - } - // the removal of rows could cause us to need to page in more items - if(self._processScroll){ - self._processScroll(); - } - } - if(to > -1){ - // Add to new slot (either before an existing row, or at the end) - // First determine the DOM node that this should be placed before. - nextNode = rows[to]; - if(!nextNode){ - nextNode = rows[to - 1]; - if(nextNode){ - // Make sure to skip connected nodes, so we don't accidentally - // insert a row in between a parent and its children. - nextNode = (nextNode.connected || nextNode).nextSibling; - } - } - row = self.newRow(object, nextNode, to, options); - - if(row){ - row.observerIndex = observerIndex; - rows.splice(to, 0, row); - if(!firstRow || to < from){ - // the inserted row is first, so we update firstRow to point to it - var previous = row.previousSibling; - // if we are not in sync with the previous row, roll the firstRow back one so adjustRowIndices can sync everything back up. - firstRow = !previous || previous.rowIndex + 1 == row.rowIndex || row.rowIndex == 0 ? - row : previous; - } - } - options.count++; - } - from != to && firstRow && self.adjustRowIndices(firstRow); - }, true)) - 1; - } var rowsFragment = document.createDocumentFragment(); // now render the results if(results.map){ @@ -478,6 +480,10 @@ function(arrayUtil, kernel, declare, listen, has, miscUtil, TouchScroll, hasClas rows[i] = mapEach(results[i]); } } + if(results.observe && !options.dontObserve){ + // observe the results for changes + var observerIndex = this._observeResults(results, rows, options); + } var lastRow; function mapEach(object){ lastRow = self.insertRow(object, rowsFragment, null, start++, options); @@ -485,7 +491,7 @@ function(arrayUtil, kernel, declare, listen, has, miscUtil, TouchScroll, hasClas return lastRow; } function whenDone(resolvedRows){ - container = beforeNode ? beforeNode.parentNode : self.contentNode; + container = options.container = beforeNode ? beforeNode.parentNode : self.contentNode; if(container){ container.insertBefore(rowsFragment, beforeNode || null); lastRow = resolvedRows[resolvedRows.length - 1]; diff --git a/OnDemandList.js b/OnDemandList.js index af3067d38..cc0e0439e 100644 --- a/OnDemandList.js +++ b/OnDemandList.js @@ -54,10 +54,12 @@ return declare([List, _StoreMixin], { // Creates a preload node for rendering a query into, and executes the query // for the first page of data. Subsequent data will be downloaded as it comes // into view. + var rows = []; var preload = { query: query, count: 0, node: preloadNode, + rows: rows, options: options }; if(!preloadNode){ @@ -70,6 +72,7 @@ return declare([List, _StoreMixin], { //topPreloadNode.preload = true; query: query, next: preload, + rows: rows, options: options }; preload.node = preloadNode = put(this.contentNode, "div.dgrid-preload"); @@ -106,12 +109,28 @@ return declare([List, _StoreMixin], { // Establish query options, mixing in our own. // (The getter returns a delegated object, so simply using mixin is safe.) options = lang.mixin(this.get("queryOptions"), options, - {start: 0, count: this.minRowsPerPage, query: query}); + {query: query}); // execute the query - var results = query(options); + if(this.store.sliceable){ + // sliceable query, so do the query first and then slice for each page + var results = query(options); + this._observeResults(results, rows, options); + preload.queryResults = results; + if(topPreload){ + topPreload.queryResults = results; + } + options.dontObserve = true; + options.queryResults = results; + results = results.slice(0, this.minRowsPerPage); + }else{ + options.start = 0; + options.count = this.minRowsPerPage; + var results = query(options); + } var self = this; // render the result set Deferred.when(this.renderArray(results, preloadNode, options), function(trs){ + rows.push.apply(rows, trs); return Deferred.when(results.total || results.length, function(total){ // remove loading node put(loadingNode, "!"); @@ -152,10 +171,11 @@ return declare([List, _StoreMixin], { this.inherited(arguments); if(this.store){ // render the query - var self = this; + var self = this, + store = self.store; this._trackError(function(){ return self.renderQuery(function(queryOptions){ - return self.store.query(self.query, queryOptions); + return store.query(self.query, queryOptions); }); }); } @@ -231,6 +251,10 @@ return declare([List, _StoreMixin], { lastObserverIndex = currentObserverIndex; // we just do cleanup here, as we will do a more efficient node destruction in the setTimeout below grid.removeRow(row, true); + if(preload.rows[row.rowIndex] == row){ + // delete it from our array of rows to free memory + delete preload.rows[row.rowIndex]; + } toDelete.push(row); } // now adjust the preloadNode based on the reclaimed space @@ -365,15 +389,24 @@ return declare([List, _StoreMixin], { // Query now to fill in these rows. // Keep _trackError-wrapped results separate, since if results is a // promise, it will lose QueryResults functions when chained by `when` - var results = preload.query(options), + var results = preload.queryResults ? + // try to use slice() if we query results that are sliceable + preload.queryResults.slice(options.start, options.start + options.count) : + preload.query(options), trackedResults = grid._trackError(function(){ return results; }); if(trackedResults === undefined){ return; } // sync query failed // Isolate the variables in case we make multiple requests // (which can happen if we need to render on both sides of an island of already-rendered rows) - (function(loadingNode, scrollNode, below, keepScrollTo, results){ - Deferred.when(grid.renderArray(results, loadingNode, options), function(){ + (function(loadingNode, scrollNode, below, keepScrollTo, results, rows){ + Deferred.when(grid.renderArray(results, loadingNode, options), function(trs){ + if(rows){ + // if we are tracking all the rows for the query, splice it in + for(var i = 0, l = trs.length; i < l; i++){ + rows[i + options.start] = trs[i]; + } + } // can remove the loading node now beforeNode = loadingNode.nextSibling; put(loadingNode, "!"); @@ -405,7 +438,7 @@ return declare([List, _StoreMixin], { // make sure we have covered the visible area grid._processScroll(); }); - }).call(this, loadingNode, scrollNode, below, keepScrollTo, results); + }).call(this, loadingNode, scrollNode, below, keepScrollTo, results, preload.rows); preload = preload.previous; } }