docs: function SlickGrid(container, data, columns, options) {
          var _this = this;
          this.container = container;
          this.data = data;
          this.columns = columns;
          // Events
          this.onScroll = new Event();
          this.onSort = new Event();
          this.onHeaderMouseEnter = new Event();
          this.onHeaderMouseLeave = new Event();
          this.onHeaderContextMenu = new Event();
          this.onSubHeaderContextMenu = new Event();
          this.onHeaderClick = new Event();
          this.onHeaderCellRendered = new Event();
          this.onHeaderColumnDragStart = new Event();
          this.onHeaderColumnDrag = new Event();
          this.onHeaderColumnDragEnd = new Event();
          this.onHeadersCreated = new Event(); // Throws once after all headers and subheaders are created (or re-created)
          this.onBeforeHeaderCellDestroy = new Event();
          this.onSubHeaderCellRendered = new Event();
          this.onBeforeSubHeaderCellDestroy = new Event();
          this.onMouseEnter = new Event();
          this.onMouseLeave = new Event();
          this.onClick = new Event();
          this.onDblClick = new Event();
          this.onContextMenu = new Event();
          this.onBeforeKeyDown = new Event();
          this.onKeyDown = new Event();
          this.onAddNewRow = new Event();
          this.onValidationError = new Event();
          this.onViewportChanged = new Event();
          this.onRender = new Event();
          this.onInvalidate = new Event();
          this.onColumnsReordered = new Event();
          this.onColumnsResized = new Event();
          this.onColumnsChanged = new Event();
          this.onCellChange = new Event();
          this.onBeforeEditCell = new Event();
          this.onBeforeCellEditorDestroy = new Event();
          this.onBeforeDestroy = new Event();
          this.onActiveCellChanged = new Event();
          this.onActiveCellPositionChanged = new Event();
          this.onActiveRowChanged = new Event();
          this.onDragInit = new Event();
          this.onDragStart = new Event();
          this.onDrag = new Event();
          this.onDragEnd = new Event();
          this.onSelectedRowsChanged = new Event();
          this.onCellCssStylesChanged = new Event();
          // settings
          this.gridDefaults = {
              rowHeight: 25,
              defaultColumnWidth: 80,
              absoluteColumnMinWidth: 20,
              enableAddRow: false,
              leaveSpaceForNewRows: false,
              editable: true,
              autoEdit: false,
              enableCellNavigation: true,
              enableColumnResize: true,
              asyncEditorLoading: false,
              asyncEditorLoadDelay: 100,
              forceFitColumns: false,
              enableAsyncPostRender: false,
              asyncPostRenderDelay: 50,
              autoHeight: false,
              editorLock: new EditorLock(),
              showSubHeaders: false,
              addRowIndexToClassName: true,
              formatterFactory: undefined,
              editorFactory: undefined,
              selectedCellCssClass: 'selected',
              multiSelect: true,
              enableTextSelectionOnCells: false,
              dataItemColumnValueExtractor: undefined,
              fullWidthRows: false,
              multiColumnSort: false,
              defaultFormatter: defaultFormatter,
              columnHeaderRenderer: this.columnHeaderRenderer,
              subHeaderRenderers: [],
              forceSyncScrolling: false,
              addNewRowCssClass: 'new-row',
              useAntiscroll: false,
              showScrollbarsOnHover: false,
              skipPaging: false,
              appendSubheadersToContainer: false,
              scrollbarSize: undefined,
              maxSupportedCssHeight: undefined,
              resizeOnlyDraggedColumn: true,
              syncColumnCellResize: true
          };
          this.columnDefaults = {
              name: '',
              resizable: true,
              sortable: false,
              minWidth: this.gridDefaults.absoluteColumnMinWidth,
              rerenderOnResize: false,
              headerCssClass: undefined,
              defaultSortAsc: true,
              focusable: true,
              selectable: true
          };
          this.page = 0; // current page
          this.offset = 0; // current page offset
          this.vScrollDir = 1;
          // private
          this.initialized = false;
          this.objectName = 'slickGrid';
          this.uid = this.objectName + '_' + Math.round(1000000 * Math.random());
          this.options = this.gridDefaults;
          // TODO: move all state to this object
          this.state = {
              invalidateSafeCellChangeCallback: null // Function|null
          };
          this.tabbingDirection = 1;
          this.activeCellNode = null;
          this.currentEditor = null;
          this.rowsCache = {};
          this.renderedRows = 0;
          this.prevScrollTop = 0;
          this.scrollTop = 0;
          this.lastRenderedScrollTop = 0;
          this.lastRenderedScrollLeft = 0;
          this.prevScrollLeft = 0;
          this.scrollLeft = 0;
          this.selectedRows = [];
          this.plugins = [];
          this.cellCssClasses = {};
          this.columnIdxById = {};
          this.sortColumns = [];
          this.columnPosLeft = [];
          this.columnPosRight = [];
          // async call handles
          this.h_editorLoader = null;
          this.h_render = null;
          this.h_postrender = null;
          this.postProcessedRows = {};
          this.postProcessToRow = null;
          this.postProcessFromRow = null;
          // perf counters
          this.counter_rows_rendered = 0;
          this.counter_rows_removed = 0;
          /*
            ## Visual Grid Components
        
            To support pinned columns, we slice up the grid regions, and try to be very clear and consistent about the naming.
            All UI region info objects start as an array with a left [0] and right [1] side
            Dom elements are stored at the top level together (still in a left/right pair) because jquery deals with multiple elements nicely. (eg: el.empty(), el.children())
            topViewport.width     // combined width
            topViewport[0].width  // left width
            topViewport.el        // both els
            topViewport.el[0]     // left el
        
                [0]    [1]
                .....................
                .      .            .
                .  TL  .     TR     .
                .      .            .
                .....................
                .      .            .
                .      .            .
                .      .            .
                .  CL  .     CR     .
                .      .            .
                .      .            .
                .      .            .
                .....................
        
            */
          /** The scrolling region */
          this.topViewport = {};
          /** The full size of content (both off and on screen) */
          this.topCanvas = {};
          /** The column headers */
          this.header = {};
          /** Optional rows of cells below the column headers */
          this.subHeaders = {};
          /**
           * Content viewports are wrapped with elements that have the same dimensions
           * as the viewports themselves. This is in service of the antiscroll plugin.
           */
          this.contentViewportWrap = {};
          /** The scrolling region for the grid rows */
          this.contentViewport = {};
          /** Full size of row content, both width and height */
          this.contentCanvas = { widths: [] };
          this.debouncedUpdateAntiscroll = lodash.debounce(function () { return _this.updateAntiscroll(); }, 500);
          this.handleSelectedRangesChanged = function (e, ranges) {
              _this.selectedRows = [];
              var hash = {};
              var maxRow = _this.data.getLength() - 1;
              var maxCell = _this.columns.length - 1;
              for (var i = 0, len = ranges.length; i < len; i++) {
                  for (var j = Math.max(0, ranges[i].fromRow), jlen = Math.min(ranges[i].toRow, maxRow); j <= jlen; j++) {
                      if (!hash[j]) {
                          _this.selectedRows.push(j);
                          hash[j] = {};
                      }
                      for (var k = Math.max(0, ranges[i].fromCell), klen = Math.min(ranges[i].toCell, maxCell); k <= klen; k++) {
                          if (_this.canCellBeSelected(j, k)) {
                              hash[j][_this.columns[k].id] = _this.options.selectedCellCssClass;
                          }
                      }
                  }
              }
              _this.setCellCssStyles(_this.options.selectedCellCssClass, hash);
              _this.trigger(_this.onSelectedRowsChanged, { rows: _this.getSelectedRows() }, e);
          };
          // calculate these only once and share between grid instances
          maxSupportedCssHeight = maxSupportedCssHeight || this.getMaxSupportedCssHeight();
          scrollbarDimensions = scrollbarDimensions || this.measureScrollbar();
          this.options = $.extend({}, this.gridDefaults, options);
          if (this.options.useAntiscroll && !$.isFunction($.fn.antiscroll)) {
              throw new ReferenceError('The { useAntiscroll: true } option was passed to SlickGrid, but the antiscroll library is not loaded. You can download the library here: https://github.com/bcherny/antiscroll.');
          }
          this.validateAndEnforceOptions();
          this.columnDefaults.width = this.options.defaultColumnWidth;
          this.enforceWidthLimits(columns);
          this.editController = {
              commitCurrentEdit: this.commitCurrentEdit.bind(this),
              cancelCurrentEdit: this.cancelCurrentEdit.bind(this)
          };
          if (this.initialized) {
              return;
          }
          this.initialized = true;
          this.$container = $(this.container);
          if (this.$container.length < 1) {
              throw new Error("SlickGrid requires a valid container, " + this.container + " does not exist in the DOM.");
          }
          this.$container.empty().addClass(this.objectName + ' ' + this.uid + ' ui-widget');
          // set up a positioning container if needed
          if (!/relative|absolute|fixed/.test(this.$container.css('position'))) {
              this.$container.css('position', 'relative');
          }
          this.$focusSink = $('<div tabIndex=\'0\' hideFocus class=\'focus-sink\'></div>').appendTo(this.$container);
          /* SlickGrid Dom structure:
            .slickGrid
            .viewport.T.L > .canvas.T.L
            .header
            .subHeaders > .subHeader-row * M
            .viewport.T.R > .canvas.T.R
            .header
            .subHeaders > .subHeader-row * M
            .viewport.C.L > .canvas.C.L
            .row * N
            .viewport.C.R > .canvas.C.R
            .row * N
            */
          // ----------------------- Create the elements
          this.topViewport.el = $('<div class=\'viewport T L\' tabIndex=\'0\' hideFocus />' +
              '<div class=\'viewport T R\' tabIndex=\'0\' hideFocus />');
          this.topCanvas.el = $('<div class=\'canvas T L\' />' +
              '<div class=\'canvas T R\' />');
          this.header.el = $('<div class=\'header\' />' +
              '<div class=\'header\' />');
          this.subHeaders.el = $('<div class=\'subHeaders\' />' +
              '<div class=\'subHeaders\' />');
          if (!this.options.showSubHeaders) {
              this.subHeaders.el.hide();
          }
          this.contentViewportWrap.el = $('<div class=\'viewport-wrap C L\' tabIndex=\'0\' hideFocus />' +
              '<div class=\'viewport-wrap C R\' tabIndex=\'0\' hideFocus />');
          this.contentViewport.el = $('<div class=\'viewport C L antiscroll-inner\' tabIndex=\'0\' hideFocus />' +
              '<div class=\'viewport C R antiscroll-inner\' tabIndex=\'0\' hideFocus />');
          this.contentCanvas.el = $('<div class=\'canvas C L\' tabIndex=\'0\' hideFocus />' +
              '<div class=\'canvas C R\' tabIndex=\'0\' hideFocus />');
          // ----------------------- Matryoshka the elements together
          this.topCanvas.el[0].appendChild(this.header.el[0]);
          this.topCanvas.el[1].appendChild(this.header.el[1]);
          this.topViewport.el[0].appendChild(this.topCanvas.el[0]);
          this.topViewport.el[1].appendChild(this.topCanvas.el[1]);
          this.contentViewport.el[0].appendChild(this.contentCanvas.el[0]);
          this.contentViewport.el[1].appendChild(this.contentCanvas.el[1]);
          this.contentViewportWrap.el[0].appendChild(this.contentViewport.el[0]);
          this.contentViewportWrap.el[1].appendChild(this.contentViewport.el[1]);
          this.injectSubheaders(this.options.appendSubheadersToContainer);
          this.$container.append(this.topViewport.el, this.contentViewportWrap.el);
          this.measureCssSizes(); // Wins award for most 's'es in a row.
          // Default the active canvas to the top left
          this.$activeCanvasNode = this.contentCanvas.el.eq(0);
          this.$focusSink2 = this.$focusSink.clone().appendTo(this.$container); // after the grid, in tab index order.
          // Start of original finishInitialization
          this.calculateViewportWidth();
          // for usability reasons, all text selection in SlickGrid is disabled
          // with the exception of input and textarea elements (selection must
          // be enabled there so that editors work as expected); note that
          // selection in grid cells (grid body) is already unavailable in
          // all browsers except IE
          this.disableSelection(this.header.el); // disable all text selection in header (including input and textarea)
          if (!this.options.enableTextSelectionOnCells) {
              // disable text selection in grid cells except in input and textarea elements
              // (this is IE-specific, because selectstart event will only fire in IE)
              this.contentViewport.el.bind('selectstart.ui', function (event) {
                  return $(event.target).is('input,textarea');
              });
          }
          this.updateColumnCaches();
          this.createCssRules();
          this.updatePinnedState();
          this.setupColumnSort();
          this.bindAncestorScrollEvents();
          this.$container
              .bind('resize.slickgrid', this.resizeCanvas.bind(this));
          this.contentViewport.el
              .bind('scroll', this.scroll.bind(this));
          this.topViewport.el
              .bind('mousewheel', this.onHeaderMouseWheel.bind(this)); // modern browsers only, not in gecko
          this.header.el
              .bind('contextmenu', this.handleHeaderContextMenu.bind(this))
              .bind('click', this.handleHeaderClick.bind(this))
              .delegate('.slick-header-column', 'mouseenter', this.handleHeaderMouseEnter.bind(this))
              .delegate('.slick-header-column', 'mouseleave', this.handleHeaderMouseLeave.bind(this));
          this.subHeaders.el
              .bind('contextmenu', this.handleSubHeaderContextMenu.bind(this));
          this.$focusSink.add(this.$focusSink2)
              .bind('keydown', this.handleKeyDown.bind(this));
          this.contentCanvas.el
              .bind('keydown', this.handleKeyDown.bind(this))
              .bind('click', this.handleClick.bind(this))
              .bind('dblclick', this.handleDblClick.bind(this))
              .bind('contextmenu', this.handleContextMenu.bind(this))
              .bind('draginit', this.handleDragInit.bind(this))
              .bind('dragstart', { distance: 3 }, this.handleDragStart.bind(this))
              .bind('drag', this.handleDrag.bind(this))
              .bind('dragend', this.handleDragEnd.bind(this))
              .delegate('.cell', 'mouseenter', this.handleMouseEnter.bind(this))
              .delegate('.cell', 'mouseleave', this.handleMouseLeave.bind(this));
      }