1 // MODEL
  2 
  3 /*
  4  * For a JSON array that gets too large to load in one go, this class
  5  * helps break it up into chunks and provides an
  6  * async API for using the information in the array.
  7  */
  8 
  9 /**
 10  * Construct a new LazyArray, which partially loads large JSON arrays.
 11  * @class
 12  * @constructor
 13  * @param lazyArrayParams {Object} as:
 14  * <ul>
 15  * <li><code>urlTemplate</code> - for each lazily-loaded array chunk, the chunk number will get substituted for {chunk} in this template, and the result will beused as the URL of the JSON for that array chunk</li>
 16  * <li><code>length</code> - length of the overall array</li>
 17  * <li><code>chunkSize</code> - the size of each array chunk</li>
 18  * </ul>
 19  */
 20 function LazyArray(lazyArrayParams, baseUrl) {
 21     this.urlTemplate = lazyArrayParams.urlTemplate;
 22     this.chunkSize = lazyArrayParams.chunkSize;
 23     this.length = lazyArrayParams.length;
 24     this.baseUrl = (baseUrl === undefined ? "" : baseUrl);
 25     // Once a range gets loaded, it goes into the "chunks" array.
 26     // this.chunks[n] contains data for indices in the range
 27     // [n * chunkSize, Math.min(length - 1, (n * (chunkSize + 1)) - 1)]
 28     this.chunks = [];
 29     // If a range is currently loading, this will contain a property
 30     // "chunk number": [{start, end, callback, param}, ...]
 31     this.toProcess = {};
 32 }
 33 
 34 /**
 35  * call the callback on one element of the array
 36  * @param i index
 37  * @param callback callback, gets called with (i, value, param)
 38  * @param param (optional) callback will get this as its last parameter
 39  */
 40 LazyArray.prototype.index = function(i, callback, param) {
 41     this.range(i, i, callback, undefined, param);
 42 };
 43 
 44 /**
 45  * call the callback on each element in the range [start, end]
 46  * @param start index of first element to call the callback on
 47  * @param end index of last element to call the callback on
 48  * @param callback callback, gets called with (i, value, param)
 49  * @param postFun (optional) callback that gets called when <code>callback</code> has been run on every element in the range
 50  * @param param (optional) callback will get this as its last parameter
 51  */
 52 LazyArray.prototype.range = function(start, end, callback, postFun, param) {
 53     start = Math.max(0, start);
 54     end = Math.min(end, this.length - 1);
 55 
 56     var firstChunk = Math.floor(start / this.chunkSize);
 57     var lastChunk = Math.floor(end / this.chunkSize);
 58 
 59     if (postFun === undefined) /** @inner */ postFun = function() {};
 60     var finish = new Finisher(postFun);
 61 
 62     for (var chunk = firstChunk; chunk <= lastChunk; chunk++) {
 63         if (this.chunks[chunk]) {
 64             // chunk is loaded
 65             this._processChunk(start, end, chunk, callback, param);
 66         } else {
 67             var toProcessInfo = {
 68                 start: start,
 69                 end: end,
 70                 callback: callback,
 71                 param: param,
 72                 finish: finish
 73             };
 74 
 75             finish.inc();
 76             if (this.toProcess[chunk]) {
 77                 // chunk is currently being loaded
 78                 this.toProcess[chunk].push(toProcessInfo);
 79             } else {
 80                 // start loading chunk
 81                 this.toProcess[chunk] = [toProcessInfo];
 82                 var url = this.urlTemplate.replace(/\{Chunk\}/g, chunk);
 83                 var thisObj = this;
 84                 dojo.xhrGet(
 85                     {
 86                         url: Util.resolveUrl(this.baseUrl, url),
 87                         handleAs: "json",
 88                         load: this._makeLoadFun(chunk),
 89                         error: function() { finish.dec(); }
 90                     });
 91             }
 92         }
 93     }
 94     finish.finish();
 95 };
 96 
 97 LazyArray.prototype._makeLoadFun = function(chunk) {
 98     var thisObj = this;
 99     return function(data) {
100         thisObj.chunks[chunk] = data;
101         var toProcess = thisObj.toProcess[chunk];
102         delete thisObj.toProcess[chunk];
103         for (var i = 0; i < toProcess.length; i++) {
104             thisObj._processChunk(toProcess[i].start,
105                                   toProcess[i].end,
106                                   chunk,
107                                   toProcess[i].callback,
108                                   toProcess[i].param);
109             toProcess[i].finish.dec();
110         }
111     };
112 };
113 
114 LazyArray.prototype._processChunk = function(start, end, chunk,
115                                              callback, param) {
116     // index (in the overall lazy array) of the first position in this chunk
117     var firstIndex = chunk * this.chunkSize;
118 
119     var chunkStart = start - firstIndex;
120     var chunkEnd = end - firstIndex;
121     chunkStart = Math.max(0, chunkStart);
122     chunkEnd = Math.min(chunkEnd, this.chunkSize - 1);
123 
124     for (var i = chunkStart; i <= chunkEnd; i++) {
125         callback(i + firstIndex, this.chunks[chunk][i], param);
126     }
127 };
128