1 /**
  2  * @fileOverview Contains the jMatrixBrowse plug-in code.
  3  * 
  4  * The core file for jMatrixBrowse that manages the initialization of the 
  5  * component and manages interaction with different parts of the components.
  6  * 
  7  * @version 0.1
  8  * @author Pulkit Goyal <pulkit110@gmail.com> 
  9 */
 10 
 11 /**
 12  * See (http://jquery.com/).
 13  * @name jQuery
 14  * @class 
 15  * See the jQuery Library  (http://jquery.com/) for full details.  This just
 16  * documents the function and classes that are added to jQuery by this plug-in.
 17  */
 18 
 19 /**
 20  * See (http://jquery.com/)
 21  * @name fn
 22  * @class 
 23  * See the jQuery Library  (http://jquery.com/) for full details.  This just
 24  * documents the function and classes that are added to jQuery by this plug-in.
 25  * @memberOf jQuery
 26  */
 27 
 28 /**
 29   * jMatrixBrowseNS - Namespace encapsulating jMatrixBrowse.
 30   *
 31   * @namespace jMatrixBrowseNS
 32   */
 33 var jMatrixBrowseNs = jMatrixBrowseNs || {};
 34 
 35 (function(jQuery, jMatrixBrowseNs) {
 36   /**
 37    * jMatrixBrowse - a jQuery plugin to create large draggable(like Google Maps)
 38    * matrices. 
 39    *
 40    * @class jMatrixBrowse
 41    * @memberOf jQuery.fn
 42    */
 43   jQuery.fn.jMatrixBrowse = function() {
 44 
 45     var _self = this;
 46     var _renderer;              // jMatrixBrowse renderer.
 47     var _configuration;         // jMatrixBrowse configuration manager.
 48     var _api;                   // API handler.
 49     var _backgroundDataManager; // Background data manager.
 50     var _elem; 
 51 
 52     //Public API
 53     /**
 54      * Reload data in the matrix for the visible window. 
 55      */
 56     this.reloadData = function() {
 57       var cellWindow = _configuration.getCellWindow(_renderer.currentCell);
 58       if (cellWindow == undefined) {
 59         console.error('Unable to get cell window.');
 60         return;
 61       }
 62       var response = _api.getResponse(cellWindow);
 63       
 64       if (response && response.data) {
 65         for (var i = 0; i < response.data.length; ++i) {
 66           for (var j = 0; j < response.data[i].length; ++j) {
 67             var cellData = response.data[i][j]; 
 68             jQuery(_renderer.getCellElements()[i][j]).html(cellData);
 69           }
 70         }
 71       }
 72     }
 73     
 74     this.getPosition = function() {
 75       return _renderer.currentCell;
 76     };
 77     
 78     // initialize the plugin.
 79     init(this);
 80     
 81     // Private methods
 82     /**
 83      * Initialize the jMatrixBrowse.
 84      * @param {jQuery object} elem - the element to which to attach the jMatrixBrowse.
 85      */
 86     function init(elem) {
 87       _elem = jQuery('<div/>', {
 88         position: 'absolute'
 89       }).appendTo(elem);
 90       
 91       // Initialize mock api
 92       _api = new jMatrixBrowseNs.APIHandler('test');
 93       
 94       // Initialize configuration, get user options and extend with default.
 95       _configuration = new jMatrixBrowseNs.Configuration(elem, _api);
 96       
 97       // Initialize the jMatrixBrowseRenderer
 98       _renderer = new jMatrixBrowseNs.jMatrixBrowseRenderer(_elem, _configuration, _api);
 99 
100       // Load data after renderer completes initialization.
101       _elem.bind('jMatrixBrowseRendererInitialized', function() {
102         _self.reloadData();
103       });
104       
105       // Listen to events to implement reloading of data and headers
106       // Listen for drag and reposition cells
107       _elem.bind('jMatrixBrowseDrag', function (event) {
108         // Reposition matrix cells
109         checkAndRepositionCells();  
110         // Reposition headers
111         checkAndRepositionHeaders();
112       });
113       
114       _elem.bind('jMatrixBrowseAnimationStep', function (event) {
115         // Reposition matrix cells
116         checkAndRepositionCells();
117         // Reposition headers
118         checkAndRepositionHeaders();
119       });
120 
121       // Listen for drag stop and reposition cells, needed when there is a quick drag.
122       _elem.bind('jMatrixBrowseDragStop', function (event) {
123         if (_configuration.isSnapEnabled() && !_renderer.getIsAnimating()) {
124           _renderer.snapToGrid();
125         }
126         // Reposition matrix cells
127         checkAndRepositionCells();  
128         // Reposition headers
129         checkAndRepositionHeaders();
130       });
131 
132       _elem.bind('jMatrixBrowseAnimationComplete', function (event) {
133         if (_configuration.isSnapEnabled()) {
134           _renderer.snapToGrid();
135         }
136         // Reposition matrix cells
137         checkAndRepositionCells();  
138         // Reposition headers
139         checkAndRepositionHeaders();
140       });
141       
142       // Listen for change and reload new data
143       _elem.bind('jMatrixBrowseChange', function (event) {
144         reloadData({
145           currentCell: event.currentCell,
146           previousCell: event.previousCell,
147           direction: event.direction
148         });
149       });
150       
151       // Listen for click event
152       _elem.bind('jMatrixBrowseClick', function (event) {
153         console.log('click: ' + event.row + ', ' + event.col);
154       });
155 
156       bindShortcuts();
157 
158       // Begin loading data in the background.
159       _backgroundDataManager = new jMatrixBrowseNs.BackgroundDataManager(_elem, _api, _configuration);
160     }
161     
162     /**
163      * Computes the new cell coordinates when a drag results in overflow.
164      * @param {Number} overflow - Type of the overflow.
165      */
166     function computeNewCellCoordinates(overflow) {
167 
168       switch(overflow) {
169         case jMatrixBrowseNs.Constants.OVERFLOW_TOP:
170           ++_renderer.currentCell.row;
171           break;
172           
173         case jMatrixBrowseNs.Constants.OVERFLOW_BOTTOM:
174           --_renderer.currentCell.row;
175           break;
176           
177         case jMatrixBrowseNs.Constants.OVERFLOW_LEFT:
178           ++_renderer.currentCell.col;
179           break;
180           
181         case jMatrixBrowseNs.Constants.OVERFLOW_RIGHT:
182           --_renderer.currentCell.col;
183           break;
184       }
185     }
186 
187     // TODO: Move to Utils or Renderer and use in generate positions for jMatrixBrowseRenderer.
188     /**
189      * Check if the drag is valid.
190      * @param {Number} overflow - Type of the overflow to check for.
191      * @returns {boolean} true if the drag is valid.
192      */
193     function isValidDrag(overflow) {
194       switch(overflow) {
195         case jMatrixBrowseNs.Constants.OVERFLOW_TOP:
196           if (_renderer.currentCell.row - _configuration.getNumberOfBackgroundCells() + _renderer.getCellElements().length - 1 >= _api.getMatrixSize().height-1)
197             return false;
198           return true;
199           
200         case jMatrixBrowseNs.Constants.OVERFLOW_BOTTOM:
201           if (_renderer.currentCell.row <= 0 + _configuration.getNumberOfBackgroundCells())
202             return false;
203           return true;
204           
205         case jMatrixBrowseNs.Constants.OVERFLOW_LEFT:
206           if (_renderer.currentCell.col - _configuration.getNumberOfBackgroundCells() + _renderer.getCellElements()[0].length - 1 >= _api.getMatrixSize().width-1)
207             return false;
208           return true;
209           
210         case jMatrixBrowseNs.Constants.OVERFLOW_RIGHT:
211           if (_renderer.currentCell.col <= 0 + _configuration.getNumberOfBackgroundCells())
212             return false;
213           return true;
214       }
215     }
216     
217     /**
218      * Check and resposition cells that are overflowing.
219      */
220     function checkAndRepositionCells() {
221       var cellsRepositioned = false;
222       
223       // Row on top might overflow from the top.
224       cellsRepositioned = checkAndRepositionCellRow(_renderer.getCellElements(), _renderer.getContainer(), 1, jMatrixBrowseNs.Constants.OVERFLOW_TOP) || cellsRepositioned;
225       // Row on bottom might overflow from the bottom.
226       cellsRepositioned = checkAndRepositionCellRow(_renderer.getCellElements(), _renderer.getContainer(), _renderer.getCellElements().length-2, jMatrixBrowseNs.Constants.OVERFLOW_BOTTOM) || cellsRepositioned;
227       // Column on left might overflow from the left.
228       cellsRepositioned = checkAndRepositionCellCol(_renderer.getCellElements(), _renderer.getContainer(), 1, jMatrixBrowseNs.Constants.OVERFLOW_LEFT) || cellsRepositioned;
229       // Column on right might overflow from the right.
230       cellsRepositioned = checkAndRepositionCellCol(_renderer.getCellElements(), _renderer.getContainer(), _renderer.getCellElements()[0].length-2, jMatrixBrowseNs.Constants.OVERFLOW_RIGHT) || cellsRepositioned;
231       
232       // For handling quick drags, check for positioning again.
233       if (cellsRepositioned) {
234         checkAndRepositionCells();
235         checkAndRepositionHeaders();
236       }
237     }
238     
239     /**
240      * Check and resposition headers according to cell positions.
241      */
242     function checkAndRepositionHeaders() {
243       checkAndRepositionRowHeader(_renderer.getHeaders().row);
244       checkAndRepositionColumnHeader(_renderer.getHeaders().col);
245     }
246     
247     /**
248      * Check and resposition headers according to cell positions.
249      * @param header - row header container
250      */
251     function checkAndRepositionRowHeader(header) {
252       header.children().each(function(index, element) {
253         var containerOffset = _renderer.getContainer().offset();
254         var elementOffset = jQuery(_renderer.getCellElements()[index][0]).offset();
255         var top = elementOffset.top - containerOffset.top;
256         
257         jQuery(element).css({
258           top: top
259         });
260       });
261     }
262     
263     /**
264      * Check and resposition headers according to cell positions.
265      * @param header - column header container
266      */
267     function checkAndRepositionColumnHeader(header) {
268       header.children().each(function(index, element) {
269         var containerOffset = _renderer.getContainer().offset();
270         var elementOffset = jQuery(_renderer.getCellElements()[0][index]).offset();
271         var left = elementOffset.left - containerOffset.left;
272         jQuery(element).css({
273           left: left
274         });
275       });
276     }
277     
278     /**
279      * Checks if the given row is overflowing and repositions if necessary. 
280      * @param {matrix} cellElements - The matrix of DOM objects representing cells.
281      * @param {jQuery Object} container - The container against which to check the overflow.
282      * @param {Number} row - Index of row to check the overflow for.
283      * @param {Number} overflow - Type of the overflow to check for.
284      * @returns {boolean} true if any cells were repositioned. false otherwise.
285      */
286     function checkAndRepositionCellRow(cellElements, container, row, overflow) {
287       if (cellElements[row].length > 0 && jMatrixBrowseNs.Utils.isOverflowing(jQuery(cellElements[row][0]), container, overflow) && isValidDrag(overflow)) {
288         
289         var previousCell = jQuery.extend({}, _renderer.currentCell); // Clone currentCell
290         
291         // There is an overflow.
292         computeNewCellCoordinates(overflow);
293         
294         var cellRow;
295         var direction;
296         
297         switch (overflow) {
298           case jMatrixBrowseNs.Constants.OVERFLOW_TOP:
299             direction = 'top';
300             // The row is overflowing from top. Move it to bottom. 
301             var backgroundTopRow = 0; // TODO: get background top row            
302             if (_renderer.moveRowToEnd(backgroundTopRow) === false) {
303               return false;
304             }            
305             break;
306 
307           case jMatrixBrowseNs.Constants.OVERFLOW_BOTTOM:
308             direction = 'bottom';
309             // The row is overflowing from bottom. Move it to top.
310             var backgroundBottomRow = cellElements.length-1; // TODO: get background bottom row
311             if (_renderer.moveRowToTop(backgroundBottomRow) === false) {
312               return false;
313             }
314             break;
315         }
316         // Trigger event for change
317         _elem.trigger({
318           type: 'jMatrixBrowseChange',
319           previousCell: previousCell,
320           currentCell: _renderer.currentCell,
321           direction: direction
322         });
323         return true;
324       }
325       return false;
326     }
327     
328     /**
329      * Checks if the given column is overflowing and repositions if necessary. 
330      * @param {matrix} cellElements - The matrix of DOM objects representing cells.
331      * @param {jQuery Object} container - The container against which to check the overflow.
332      * @param {Number} col - Index of column to check the overflow for.
333      * @param {Number} overflow - Type of the overflow to check for.
334      * @returns {boolean} true if any cells were repositioned. false otherwise.
335      */
336     function checkAndRepositionCellCol(cellElements, container, col, overflow) {
337       if (jMatrixBrowseNs.Utils.isOverflowing(jQuery(cellElements[0][col]), container, overflow) &&  isValidDrag(overflow)) {
338         
339         var previousCell = jQuery.extend({}, _renderer.currentCell); // Clone currentCell
340         
341         // There is an overflow.
342         computeNewCellCoordinates(overflow);
343         
344         var direction;
345         switch (overflow) {
346           case jMatrixBrowseNs.Constants.OVERFLOW_LEFT:
347             direction = 'left';
348             // The row is overflowing from left. Move it to right. 
349             var backgroundLeftCol = 0; // TODO: Get position of background left col.
350             if (_renderer.moveColToRight(backgroundLeftCol) === false) {
351               return false;
352             }
353             break;
354 
355           case jMatrixBrowseNs.Constants.OVERFLOW_RIGHT:
356             direction = 'right';
357             // The row is overflowing from right. Move it to left. 
358             var backgroundRightCol = cellElements[0].length-1; // TODO: Get position of background left col.
359             if (_renderer.moveColToLeft(backgroundRightCol) === false) {
360               return false;
361             }
362             break;
363         }
364         // Trigger event for change
365         _elem.trigger({
366           type: 'jMatrixBrowseChange',
367           previousCell: previousCell,
368           currentCell: _renderer.currentCell,
369           direction: direction
370         });
371         return true;
372       }
373       return false;
374     }
375 
376     /**
377      * Reload column headers on change of matrix.
378      * @param {Number} event.currentCell - currentCell at the top left
379      * @param {Number} event.previousCell - previousCell at the top left
380      * @param {string} event.direction - direction of drag that triggered the change
381      */
382     function reloadRowHeaders(event) {
383       var rowHeaders = _api.getRowHeadersFromTopRow(event.currentCell.row-_configuration.getNumberOfBackgroundCells());
384       _renderer.getHeaders().row.children().each(function (index, element) {
385         if (index < rowHeaders.length) {
386           jQuery(element).html(rowHeaders[index]);
387         }
388       });
389     }
390     
391     /**
392      * Reload row headers on change of matrix.
393      * @param {Number} event.currentCell - currentCell at the top left
394      * @param {Number} event.previousCell - previousCell at the top left
395      * @param {string} event.direction - direction of drag that triggered the change
396      */
397     function reloadColHeaders(event) {
398       var colHeaders = _api.getColHeadersFromLeftCol(event.currentCell.col-_configuration.getNumberOfBackgroundCells());
399       _renderer.getHeaders().col.children().each(function (index, element) {
400         if (index < colHeaders.length) {
401           jQuery(element).html(colHeaders[index]);
402         }
403       });
404     }
405 
406     /**
407      * Computes the row index needed for reload.
408      * @param {Number} event.currentCell - currentCell at the top left
409      * @param {Number} event.previousCell - previousCell at the top left
410      * @param {string} direction - direction of overflow corresponding to the update
411      * @returns {Object} rowIndex - row1, row2 and rowsNotInBound
412      */
413     function getRowIndexForReload(event, direction) {
414       var rowsNotInBound = 0;
415       var rowIndex;
416       var nRowsReloaded = Math.abs(event.currentCell.row - event.previousCell.row);
417 
418       // Find indices of the first and last rows in matrix.
419       var firstRowIndex = event.currentCell.row - _configuration.getNumberOfBackgroundCells();
420       var lastRowIndex = event.currentCell.row - _configuration.getNumberOfBackgroundCells() + _renderer.getCellElements().length - 1;
421 
422       if (direction === 'top') {
423         // If overflow from top, bottom rows will have to be fetched.
424         // Check if the row is within matrix bounds
425         if (lastRowIndex >= _api.getMatrixSize().height) {
426           // Some rows might not be in bound. Find how many?
427           rowsNotInBound = lastRowIndex - _api.getMatrixSize().height + 1;
428         }
429 
430         if (rowsNotInBound === nRowsReloaded) {
431           // We don't need to reload any row.
432           return -1;
433         }
434         
435         rowIndex = {
436           row1: lastRowIndex - (nRowsReloaded - 1),
437           row2: lastRowIndex - rowsNotInBound
438         };
439       } else if (direction === 'bottom') {
440         // If overflow from bottom, top rows will have to be fetched.
441         // First check if the rows are within matrix bounds
442         if (firstRowIndex < 0) {
443           // Some rows might not be in bound. Find how many?
444           rowsNotInBound = -firstRowIndex;
445         }
446 
447         if (rowsNotInBound === nRowsReloaded) {
448           // We don't need to reload any row.
449           return -1;
450         }
451         
452         rowIndex = {
453           row1: firstRowIndex + rowsNotInBound,
454           row2: firstRowIndex + (nRowsReloaded - 1)
455         };
456       } else if (direction === 'both') {
457         // Check for the rows in both directions.
458         var rowIndexBottom = getRowIndexForReload(event, 'top');
459         var rowIndexTop = getRowIndexForReload(event, 'bottom');
460         if (rowIndexBottom === -1)
461           rowIndexBottom = {row2: lastRowIndex};
462         if (rowIndexTop === -1)
463           rowIndexTop = {row1: firstRowIndex};
464         rowIndex = {
465           row1: rowIndexTop.row1,
466           row2: rowIndexBottom.row2
467         };
468       }
469       rowIndex.rowsNotInBound = rowsNotInBound;
470       return rowIndex;
471     }
472 
473     /**
474      * Computes the col index needed for reload.
475      * @param {Number} event.currentCell - currentCell at the top left
476      * @param {Number} event.previousCell - previousCell at the top left
477      * @param {string} direction - direction of overflow corresponding to the update
478      * @returns {Object} colIndex - col1, col2 and colsNotInBound
479      */
480     function getColIndexForReload(event, direction) {
481       var colsNotInBound = 0;
482       var colIndex;
483       var nColsReloaded = Math.abs(event.currentCell.col - event.previousCell.col);
484 
485       // Find indices of the first and last columns in matrix.
486       var firstColIndex = event.currentCell.col - _configuration.getNumberOfBackgroundCells();
487       var lastColIndex = event.currentCell.col - _configuration.getNumberOfBackgroundCells() + _renderer.getCellElements()[0].length - 1;
488       
489       if (direction === 'left') {
490         // If overflow from left, right columns will have to be fetched.
491         // First check if the column is within matrix bounds
492         if (lastColIndex >= _api.getMatrixSize().width) {
493           // Some columns might not be in bound. Find how many?
494           colsNotInBound = lastColIndex - _api.getMatrixSize().width + 1;
495           if (colsNotInBound === nColsReloaded) {
496             // We don't need to reload any column.
497             return -1;
498           }
499         }
500 
501         colIndex = {
502           col1: lastColIndex - (nColsReloaded - 1),
503           col2: lastColIndex
504         };
505 
506       } else if (direction === 'right'){
507         // If overflow from right, left column will have to be fetched.
508         // First check if the col is within matrix bounds
509         if (firstColIndex < 0) {
510           // Some columns might not be in bound. Find how many?
511           colsNotInBound = -firstColIndex;
512           if (colsNotInBound === nColsReloaded) {
513             // We don't need to reload any column.
514             return -1;
515           }
516         }
517 
518         colIndex = {
519           col1: firstColIndex + colsNotInBound,
520           col2: firstColIndex + (nColsReloaded - 1)
521         };
522       } else if (direction === 'both') {
523         // Check for the rows in both directions.
524         var colIndexRight = getColIndexForReload(event, 'left');
525         var colIndexLeft = getColIndexForReload(event, 'right');
526         if (colIndexRight === -1)
527           colIndexRight = {col2: lastColIndex};
528         if (colIndexLeft === -1)
529           colIndexLeft = {col1: firstColIndex};
530         colIndex = {
531           col1: colIndexLeft.col1,
532           col2: colIndexRight.col2
533         };
534       }
535       colIndex.colsNotInBound = colsNotInBound;
536       return colIndex;
537     }
538     
539     /**
540      * Reload row data on change of matrix.
541      * @param {Number} event.currentCell - currentCell at the top left
542      * @param {Number} event.previousCell - previousCell at the top left
543      * @param {string} event.direction - direction of drag that triggered the change
544      */
545     function reloadRowData(event) {
546       // Index of the rows that would be fetched.
547       var rowIndex = getRowIndexForReload(event, event.direction);
548       if (rowIndex === -1) {
549         // We don't need to reload any row.
550         return;
551       }
552 
553       // Get col index by checking from both sides.
554       var colIndex = getColIndexForReload(event, 'both');
555 
556       // Get data from the background data manager.
557       var cells = _backgroundDataManager.getCellsForRequest({
558         row1: rowIndex.row1,
559         row2: rowIndex.row2,
560         col1: colIndex.col1,
561         col2: colIndex.col2
562       }, function(cells) {
563         updateCellsForReload(rowIndex, colIndex, cells);
564       });
565     }
566     
567     /**
568      * Reload column data on change of matrix.
569      * @param {Number} event.currentCell - currentCell at the top left
570      * @param {Number} event.previousCell - previousCell at the top left
571      * @param {string} event.direction - direction of drag that triggered the change
572      */
573     function reloadColData(event) {
574       // Index of the rows that would be fetched.
575       var colIndex = getColIndexForReload(event, event.direction);
576       if (colIndex === -1) {
577         // We don't need to reload any row.
578         return;
579       }
580       
581       // Get row index by checking from both sides.
582       var rowIndex = getRowIndexForReload(event, 'both');
583 
584       // Get data from the background data manager.
585       var cells = _backgroundDataManager.getCellsForRequest({
586         row1: rowIndex.row1,
587         row2: rowIndex.row2,
588         col1: colIndex.col1,
589         col2: colIndex.col2
590       }, function(cells) {
591         updateCellsForReload(rowIndex, colIndex, cells);
592       });
593     }
594 
595     /**
596      * Updates the cells which have been reloaded for the given row and col indices.
597      * @param  {Object} rowIndex - row1 and row2 for the request that was made.
598      * @param  {Object} colIndex - col1 and col2 for the request that was made.
599      * @param  {Array}  cells    - Array of Array of the response data. 
600      */
601     function updateCellsForReload(rowIndex, colIndex, cells) {
602       for (var i = rowIndex.row1; i <= rowIndex.row2; ++i) {
603         // Find which row needs to be replaced.
604         var rowToBeReplacedIndex = i - _renderer.currentCell.row + _configuration.getNumberOfBackgroundCells();
605         // Check if the row has already gone out of window. 
606         if (rowToBeReplacedIndex < 0 || rowToBeReplacedIndex >= _renderer.getCellElements().length) 
607           continue;
608         var rowToBeReplaced = _renderer.getCellElements()[rowToBeReplacedIndex];
609         for (var j = colIndex.col1; j <= colIndex.col2; ++j) {
610           // Find which col needs to be replaced.
611           var colToBeReplacedIndex = j - _renderer.currentCell.col + _configuration.getNumberOfBackgroundCells();
612           // Check if the column has already gone out of window. 
613           if (colToBeReplacedIndex < 0 || colToBeReplacedIndex >= rowToBeReplaced.length)
614             continue;
615           // Get the cell that is to be replaced.
616           var cell = jQuery(rowToBeReplaced[colToBeReplacedIndex]);
617           updateCellForReload(cell, rowToBeReplacedIndex, colToBeReplacedIndex, cells[i-rowIndex.row1][j-colIndex.col1], i, j);
618         }
619       }
620     }
621 
622     /**
623      * Updates the cell content for reload based on the reload strategy.
624      * @param  {jQuery Object} cell - cell to be replaced/reloaded.
625      * @param  {Number} rowIndex - row index of the cell to be replaced.
626      * @param  {Number} colIndex - column index of the cell to be replaced.
627      * @param  {jQuery Object} newCell - cell to be replaced with. 
628      * @param  {Number} newRowNumber - new row number of the cell.
629      * @param  {Number} newColNumber - new column number of the cell.
630      */
631     function updateCellForReload(cell, rowIndex, colIndex, newCell, newRowNumber, newColNumber) {
632       if (_configuration.getDataReloadStrategy() === jMatrixBrowseNs.Constants.RELOAD_CELL_REPLACEMENT) {
633         // Clone and move the cell from background container to matrix content.
634         newCell = newCell.clone().removeClass('jMatrixBrowse-background-cell').addClass('jMatrixBrowse-cell').css({
635           width: cell.css('width'),
636           height: cell.css('height'),
637           top: cell.css('top'),
638           left: cell.css('left'),
639           position: 'absolute'
640         });
641         cell.replaceWith(newCell);
642         _renderer.getCellElements()[rowIndex][colIndex] = newCell;
643       } else if (_configuration.getDataReloadStrategy() === jMatrixBrowseNs.Constants.RELOAD_HTML_REPLACEMENT) {
644         // Change only the html content of the cell.
645         var html = newCell.html();
646         cell.html(html);
647         cell.attr('data-row', newRowNumber);
648         cell.attr('data-col', newColNumber);
649       } else if (_configuration.getDataReloadStrategy() === jMatrixBrowseNs.Constants.RELOAD_CELL_POSITION) {
650         // Cell is already in the matrix container. Change its position etc. to put it in correct place. 
651         newCell = newCell.removeClass('jMatrixBrowse-background-cell').addClass('jMatrixBrowse-cell').css({
652           width: cell.css('width'),
653           height: cell.css('height'),
654           top: cell.css('top'),
655           left: cell.css('left'),
656           position: 'absolute'
657         });
658         cell.hide();
659         newCell.show();
660        _renderer.getCellElements()[rowIndex][colIndex] = newCell;
661       }
662     }
663     
664     /**
665      * Reload row and column headers on change of matrix.
666      * @param {Number} event.currentCell - currentCell at the top left
667      * @param {Number} event.previousCell - previousCell at the top left
668      * @param {string} event.direction - direction of drag that triggered the change
669      */
670     function reloadHeaders(event) {
671       if (event.direction === 'top' || event.direction === 'bottom') {
672         reloadRowHeaders(event);
673       } else {
674         reloadColHeaders(event);
675       }
676     }
677     
678     /**
679      * Reload row and column data on change of matrix.
680      * @param {Number} event.currentCell - currentCell at the top left
681      * @param {Number} event.previousCell - previousCell at the top left
682      * @param {string} event.direction - direction of drag that triggered the change
683      */
684     function reloadMatrixData(event) {
685       if (event.direction === 'top' || event.direction === 'bottom') {
686         reloadRowData(event);
687       } else {
688         reloadColData(event);
689       }
690     }
691     
692     /**
693      * Reload data on change of matrix.
694      * @param {Number} event.currentCell - currentCell at the top left
695      * @param {Number} event.previousCell - previousCell at the top left
696      * @param {string} event.direction - direction of drag that triggered the change
697      */
698     function reloadData(event) {
699       reloadHeaders(event);
700       reloadMatrixData(event);
701     }
702 
703     /**
704      * Binds shortcuts for browsing.
705      */
706     function bindShortcuts() {
707       // Arrow keys
708       jQuery(document).bind('keydown', 'right', function(e) {
709         e.preventDefault();
710         _renderer.scrollRight();
711       });
712 
713       jQuery(document).bind('keydown', 'left', function(e) {
714         e.preventDefault();
715         _renderer.scrollLeft();
716       });
717 
718       jQuery(document).bind('keydown', 'up', function(e) {
719         e.preventDefault();
720         _renderer.scrollUp();
721       });
722 
723       jQuery(document).bind('keydown', 'down', function(e) {
724         e.preventDefault();
725         _renderer.scrollDown();
726       });
727 
728       // Vi like browsing.
729       jQuery(document).bind('keydown', 'j', function() {
730         _renderer.scrollRight();
731       });
732 
733       jQuery(document).bind('keydown', 'h', function() {
734         _renderer.scrollLeft();
735       });
736 
737       jQuery(document).bind('keydown', 'k', function() {
738         _renderer.scrollUp();
739       });
740 
741       jQuery(document).bind('keydown', 'l', function() {
742         _renderer.scrollDown();
743       });
744 
745       // Page ups and downs using ctrl + arrow keys
746       jQuery(document).bind('keydown', 'ctrl+up', function() {
747         _renderer.pageUp();
748       });
749 
750       jQuery(document).bind('keydown', 'ctrl+down', function() {
751         _renderer.pageDown();
752       });
753 
754       jQuery(document).bind('keydown', 'ctrl+left', function() {
755         _renderer.pageLeft();
756       });
757 
758       jQuery(document).bind('keydown', 'ctrl+right', function() {
759         _renderer.pageRight();
760       });
761       
762       jQuery(document).bind('keydown', 'i', function() {
763         console.log('zoom in');
764         _renderer.zoomIn();
765       });
766 
767       jQuery(document).bind('keydown', 'o', function() {
768         console.log('zoom out');
769         _renderer.zoomOut();
770       });
771 
772       _elem.mousewheel(function(event, delta) {
773         var scrollLeft, scrollRight, scrollUp, scrollRight;
774 
775         if (event.ctrlKey) {
776           // If ctrl key is down, we scroll by page.
777           scrollLeft = _renderer.pageLeft;
778           scrollRight = _renderer.pageRight;
779           // If shift key is down, we do horizontal scroll instead of vertical.
780           scrollDown = (event.shiftKey) ? _renderer.pageRight : _renderer.pageDown;
781           scrollUp = (event.shiftKey) ? _renderer.pageLeft : _renderer.pageUp;
782         } else {
783           scrollLeft = _renderer.scrollLeft;
784           scrollRight = _renderer.scrollRight;
785           // If shift key is down, we do horizontal scroll instead of vertical.
786           scrollDown = (event.shiftKey) ? _renderer.scrollRight : _renderer.scrollDown;
787           scrollUp = (event.shiftKey) ? _renderer.scrollLeft : _renderer.scrollUp;
788         }
789         
790         // Is mouse wheel scrolled horizontally?
791         if (event.originalEvent.wheelDeltaX > 0) {
792           scrollLeft();
793         } else if (event.originalEvent.wheelDeltaX < 0) {
794           scrollRight();
795         }
796 
797         // Is mouse wheel scrolled vertically?
798         if (event.originalEvent.wheelDeltaY > 0) {
799             scrollUp();
800         } else if (event.originalEvent.wheelDeltaY < 0) {
801             scrollDown();
802         }
803       });
804     }
805     
806     return this;
807   };
808 })(jQuery, jMatrixBrowseNs);
809