1 /**
  2  * @fileOverview Manages background loading of data for jMatrixBrowse.
  3  * 
  4  * When initiated, it loads the data for the complete matrix in the DOM. 
  5  * Other components should make requests to this to obtain data to be loaded
  6  * in the matrix rather than directly speaking with the API as this module
  7  * will manage the data already cached form the API and make additional requests 
  8  * if necessary. 
  9  * 
 10  * @version 0.1
 11  * @author Pulkit Goyal <pulkit110@gmail.com>
 12 */
 13 
 14 var jMatrixBrowseNs = jMatrixBrowseNs || {};
 15 
 16 (function (jQuery, jMatrixBrowseNs) {
 17 
 18   /**
 19    * Manages backgorund loading for jMatrixBrowse.
 20    *
 21    * @param {jQuery Object} elem - element that initiated jMatrixBrowse.
 22    * @param {Object} api - api manager for making requests to api.
 23    * @class BackgroundDataManager
 24    * @memberOf jMatrixBrowseNs
 25    */
 26   jMatrixBrowseNs.BackgroundDataManager = function(elem, api, config) {
 27     var that = this;
 28 
 29     var _backgroundDataContainer; // Container for background data.
 30     
 31     var _windowLoaded = {
 32       row1: 0,
 33       col1: 0,
 34       row2: 0,
 35       col2: 0
 36     };
 37 
 38     var backgroundLoadingWindowSize = {height: 20, width: 20}; // TODO: Move to config
 39     
 40     var _elem = elem;     // Element that triggered jMatrixBrowse.
 41     var _api = api;       // api manager
 42     var _config = config; // jMatrixBrowse configuration.
 43 
 44     beginLoadingData();
 45 
 46     // Public methods
 47     this.getCellsForRequest = function(request, callback) {
 48       // Create a request dynamically to load only the cells which have not already been loaded.
 49       var requests = getRequiredRequests(request);
 50 
 51       if (requests.length > 0) {
 52         // Make requests for required data and merge the responses in one matrix of cells
 53         var numberOfResponsesToReceive = requests.length;
 54         var responses = new Array(numberOfResponsesToReceive);
 55         for (var i = requests.length - 1; i >= 0; i--) {
 56           (function(i) {
 57             _api.getResponseDataAsync(requests[i], function(data) {
 58               responses[i] = data;
 59               -- numberOfResponsesToReceive;
 60               if (numberOfResponsesToReceive == 0) {
 61                 // All responses have been loaded. Combine responses by taking the data from responses and background cells
 62                 var cells = combineResponses(requests, responses, request);
 63                 callback.call(this, cells);
 64               }
 65             });
 66           })(i);
 67         };
 68       } else {
 69         // No need to make requests to the api.
 70         var cells = [];
 71         for (var i = request.row1; i <= request.row2; ++i) {
 72           cells.push([]);
 73           for (var j = request.col1; j <= request.col2; ++j) {
 74             var cellSelector = '.jMatrixBrowse-background-cell[data-row=' + i + '][data-col=' + j + ']';
 75             cells[i-request.row1].push(_elem.find(cellSelector));
 76           }
 77         }
 78         callback.call(this, cells);
 79       }
 80     };
 81 
 82     /**
 83      * Gets the curernt window that has been loaded by the background data manager.
 84      * 
 85      * @returns {Object} windowLoaded - window (row1, col1, row2, col2) of cells that have been loaded. 
 86      */
 87     this.getWindowLoaded = function() {
 88       return _windowLoaded;
 89     };
 90 
 91     // Private methods
 92     /**
 93     * Begins loading all the data into the dom.
 94     * This creates a new container to hold the data inside elem.
 95     */
 96     function beginLoadingData() {
 97 
 98       // Create container for keeping background data.
 99       _backgroundDataContainer = jQuery('<div/>', {
100         className: 'jMatrixBrowse-background-data-container'
101       }).appendTo(_elem);
102 
103       (function loadData(request){
104         setTimeout(function(){
105           // Load Data
106           var response = _api.getResponseDataAsync(request, function(data) {
107 
108             // Load data in DOM
109             for (var i = 0; i < data.length; ++i) {
110               for (var j = 0; j < data[i].length; ++j) {
111                 var backgroundCell = jQuery('<div/>', {
112                   className: 'jMatrixBrowse-background-cell',
113                   'data-row': i + request.row1,
114                   'data-col': j + request.col1,
115                   html: data[i][j]
116                 });
117                 if (_config.getDataReloadStrategy === jMatrixBrowseNs.Constants.RELOAD_CELL_POSITION) {
118                   _elem.find('.jMatrixBrowse-content').append(backgroundCell);
119                   backgroundCell.hide();
120                 } else {
121                   _backgroundDataContainer.append(backgroundCell);
122                 }
123               }
124             }
125 
126             // Update the coordinates of window 
127             _windowLoaded.row2 = request.row2;
128             _windowLoaded.col2 = request.col2;
129 
130             var matrixSize = _api.getMatrixSize();
131 
132             if (request.row2 < matrixSize.height - 1) {
133               // Load more data in the same group of cols.
134               request.row1 = Math.min(request.row1 + backgroundLoadingWindowSize.height + 1, matrixSize.height-1);
135               request.row2 = Math.min(request.row2 + backgroundLoadingWindowSize.height + 1, matrixSize.height-1);
136             } else {
137               // Begin loading data in the next grouop of cols.
138               request.row1 = 0;
139               request.row2 = backgroundLoadingWindowSize.height;
140               if (request.col2 < matrixSize.width - 1) {
141                 // There are more columns to load
142                 request.col1 = Math.min(request.col1 + backgroundLoadingWindowSize.width + 1, matrixSize.width-1);
143                 request.col2 = Math.min(request.col2 + backgroundLoadingWindowSize.width + 1, matrixSize.width-1);
144               } else {
145                 // All columns loaded
146                 // Don't need to request more data now.
147                 _elem.trigger({
148                   type: 'jMatrixBrowseLoadComplete'
149                 });
150                 return;
151               }
152             }
153 
154             loadData(request);
155           });
156 
157         }, jMatrixBrowseNs.Constants.BACKGROUND_DATA_RELOAD_DELAY);
158       })({
159         row1: 0,
160         row2: backgroundLoadingWindowSize.height,
161         col1: 0,
162         col2: backgroundLoadingWindowSize.width
163       });
164 
165     }
166 
167     /**
168     * Forms the requests that should be made to the api to get the remaining cells.
169     * 
170     * @param  {Object} request The requested window.
171     * @returns {Array} Array of requests to be made to api to get cells not already in background.
172     */
173     function getRequiredRequests(request) {
174       var requests = [];
175       var notAllCellsExist = false;
176       for (var j = request.col1; j <= request.col2; ++j) {
177         for (var i = request.row1; i <= request.row2; ++i) {
178           var cellSelector = '.jMatrixBrowse-background-cell[data-row=' + i + '][data-col=' + j + ']';
179           if (_elem.find(cellSelector).length == 0) {
180             // This row doesn't exist
181             // Since we were loading columnwise, we know that all elements after i,j don't exist.
182             requests.push({
183               row1: i,
184               col1: j,
185               row2: request.row2,
186               col2: request.col2
187             });
188             notAllCellsExist = true;
189             break;
190           }
191         }
192         if (notAllCellsExist)
193           break;
194       }
195       if (notAllCellsExist) {
196         if (i > request.row1 && i < request.row2 && j > request.col1 && j < request.col2) {
197           requests.push({
198             row1: request.row1,
199             col1: j+1,
200             row2: i-1,
201             col2: request.col2
202           });
203         }
204       }
205       return requests;
206     }
207 
208     /**
209     * Combines the responses from various api requests and backgrounds to form one matrix of cells.
210     * @param  {Array} requests  Array of requests that were made to the api.
211     * @param  {Array} responses Responses received from the api.
212     * @param  {Object} request  One big request for which we want the data.
213     * @return {ArrayOfArray}    Array of array of cells in the requested window.
214     */
215     function combineResponses(requests, responses, request) {
216       // Make an array of array of cells
217       var cells = new Array(request.row2 - request.row1 + 1);
218       for (var i = cells.length - 1; i >= 0; i--) {
219         cells[i] = new Array(request.col2 - request.col1 + 1);
220       };
221 
222       // Merge the responses in one matrix
223       for (var i = responses.length - 1; i >= 0; --i) {
224         var currentResponse = responses[i];
225         var currentRequest = requests[i];
226         for (var j = currentResponse.length - 1; j >= 0; --j) {
227           for (var k = currentResponse[j].length - 1; k >= 0; --k) {
228             var cell = jQuery('<div/>', {
229               className: 'jMatrixBrowse-background-cell',
230               'data-row': j + currentRequest.row1,
231               'data-col': k + currentRequest.col1,
232               html: currentResponse[j][k]
233             });
234             if (_config.getDataReloadStrategy === jMatrixBrowseNs.Constants.RELOAD_CELL_POSITION) {
235               _elem.find('.jMatrixBrowse-content').append(cell);
236               cell.hide();
237             }
238             cells[j + currentRequest.row1 - request.row1][k + currentRequest.col1 - request.col1] = cell;
239           };
240         };
241       };
242 
243       // Add the already existing background cells to the matrix.
244       for (var j = request.col1; j <= requests[0].col1; ++j) {
245         // If we are at requests[0].col1, we should load only upto request[0].row1 rows from background 
246         // Otherwise, we load until request.row2 rows
247         for (var i = request.row1; i <= request.row2; ++i) {
248           if (j == requests[0].col1 && i >= requests[0].row1)
249             break;
250           var cellSelector = '.jMatrixBrowse-background-cell[data-row=' + i + '][data-col=' + j + ']';
251           cells[i - request.row1][j - request.col1] = _elem.find(cellSelector);
252         };
253       };
254       return cells;
255     }
256     
257     return that;
258   };
259 
260 })(jQuery, jMatrixBrowseNs);
261