123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403 |
- 'use strict';
- /**
- * Licensed Materials - Property of IBM
- * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2016, 2019
- * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
- */
- define(['../../../lib/@waca/core-client/js/core-client/ui/core/View', '../../../lib/@waca/core-client/js/core-client/utils/UniqueId', '../../../lib/@waca/core-client/js/core-client/utils/BrowserUtils', 'underscore', 'jquery', './GridDataProvider', '../../../util/KeyCodes'], function (BaseView, UniqueId, BrowserUtils, _, $, DataProvider, KeyCodes) {
- 'use strict';
- var cbGetScrolledFastPinnedCornerCellIdx = function cbGetScrolledFastPinnedCornerCellIdx(tdIndex, fixedColumns, fixedRows) {
- //Corner cells positioning in rows are static (never change in tables). When fast horizontal scrolling had
- //taken place and when there is need to resize or update after data change, do not move the corner cells to a another location
- //within a row
- return fixedColumns && fixedRows && tdIndex < fixedColumns ? tdIndex : null;
- };
- var ATTRIBUTES = {
- COLUMN_INDEX: 'col',
- ROW_INDEX: 'row'
- };
- var View = null;
- /**
- * Class representing a grid.
- * @extends {lib/@waca/core-client/js/core-client/ui/core/View}
- */
- View = BaseView.extend({
- events: {
- 'keydown': 'onKeyDown'
- },
- /**
- * @constructs
- * Initialize the class. Takes in an options object that gives some configuration details to the grid class. The options object's
- * parameters are listed below:
- *
- * @param {object} options: An options object
- * @param {array} dataProvider: the data provider object
- * @param {boolean} isGroupEnabled:
- * @param {number} minCellWidth: minimum width of each grid cell when empty text in pixels
- * @param {number} minCellHeight: minimum height of each grid cell when empty text in pixels
- * @param {number} height: 100,
- * @param {function} formatCell: an optional function that takes 4 parameters and will be called per grid cell/DOM node to format that cell.
- * Function declaration should be something like: function formatCell(td, value, rowIndex, colIndex) where
- * @param [object] td: cell node
- * @param [string] value: the value of the cell
- * @param [number] rowIndex: number representing the row index. 0-indexed
- * @param [ number] colIndex: number representing the column index. 0-indexed
- * @param {function} getMoreData: optional function that will be called if we scroll towards the bottom of the grid and there are less than or
- * equal to 5 rows left to render. Useful if there is additional data to be fetched from the server that should be rendered.
- * @param {function} onStartScroll:
- * @param {function} onFinishScroll:
- * @param {object} el:
- * @param {number} fixedRows: indicates how many rows starting from row 0 to pin to the grid (eg. 1 to pin the headers row)
- * @param {number} fixedColumns: indicates how many columns starting from col 0 to pin to the grid (eg. 1 to pin the headers column)
- */
- init: function init() {
- var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
- View.inherited('init', this, arguments);
- this.dataProvider = options.dataProvider || new DataProvider(options.data);
- this.offsetCount = 1;
- //Right now, only autoResizeHorizontal is used - but want to keep the
- //autoResizeVertical logic in case it is used in the future
- this.autoResizeHorizontal = options.autoResizeHorizontal || options.autoResize;
- this.autoResizeVertical = options.autoResizeVertical || options.autoResize;
- this.isRendered = false;
- // Minimum width and height allowed for the cell
- // when we have an empty text. Large text will push
- // the size of the cell.
- this.minCellWidth = options.minCellWidth || 150;
- this.minCellHeight = options.minCellHeight || 30;
- // Used to enable grouping of cells.
- this.isGroupEnabled = options.isGroupEnabled || false;
- this.options = options;
- this.translateX = 0;
- this.translateY = 0;
- this.verticalScrollConfig = {
- isVerticalScrolling: true,
- minSizeProp: 'min-height',
- defaultSize: function () {
- return this.minCellHeight;
- }.bind(this),
- sizeFunction: function sizeFunction($node) {
- return $node.outerHeight();
- },
- paddingProp: 'padding-top',
- translate: function (value) {
- this.translateX = value;
- this.$virtualTable.css('padding-top', value + 'px');
- this.$rowHeadersTable.css('margin-top', value - this.$scrollableArea.scrollTop() + 'px');
- }.bind(this),
- getTranslateValue: function () {
- return this.translateX;
- }.bind(this),
- scrolledRowIdx: 0, //The row (TR Row) that was scrolled to,
- renderedCount: 0,
- scrollFunction: 'scrollTop',
- isOutOfView: function (offset) {
- if (offset > 0) {
- return this.isOutOfViewFromTop(this.$firstRow());
- } else {
- return this.isOutOfViewFromBottom(this.$lastRow());
- }
- }.bind(this)
- };
- if (BrowserUtils.isIE11()) {
- //Work around a bug where the jQuery size functions can return incorrect values for these nodes in IE11 using jQuery 3.3.1.
- this.verticalScrollConfig.sizeFunction = function ($node) {
- return $node[0].offsetHeight;
- };
- }
- this.verticalScrollConfig.getScrolledIdx = function () {
- return this.scrolledRowIdx;
- }.bind(this.verticalScrollConfig);
- this.verticalScrollConfig.setScrolledIdx = function (value) {
- this.scrolledRowIdx = value;
- }.bind(this.verticalScrollConfig);
- this.verticalScrollConfig.recalculateRenderingIndexes = function () {
- if (this.count - this.scrolledRowIdx < this.renderedCount) {
- this.scrolledRowIdx = Math.max(0, this.count - this.renderedCount);
- }
- }.bind(this.verticalScrollConfig);
- this.createCellStat(this.verticalScrollConfig);
- this.horizontalScrollConfig = {
- isVerticalScrolling: false,
- minSizeProp: 'min-width',
- sizeFunction: function sizeFunction($node) {
- return $node.outerWidth();
- },
- defaultSize: function () {
- return this.minCellWidth;
- }.bind(this),
- translate: function (value) {
- this.translateY = value;
- this.$virtualTable.css('padding-left', value + 'px');
- this.$columnHeadersTable.css('margin-left', value - this.$scrollableArea.scrollLeft() + 'px');
- }.bind(this),
- getTranslateValue: function () {
- return this.translateY;
- }.bind(this),
- scrolledColumnIdx: 0, //The column (TD Cell) that was scrolled to,
- renderedCount: 0,
- scrollFunction: 'scrollLeft',
- isOutOfView: function (offset) {
- if (offset > 0) {
- return this.isOutOfViewFromLeft(this.$firstRowFirstCell());
- } else {
- return this.isOutOfViewFromRight(this.$firstRowLastCell());
- }
- }.bind(this)
- };
- if (BrowserUtils.isIE11()) {
- //Work around a bug where the jQuery size functions can return incorrect values for these nodes in IE11 using jQuery 3.3.1.
- this.horizontalScrollConfig.sizeFunction = function ($node) {
- return $node[0].offsetWidth;
- };
- }
- this.horizontalScrollConfig.getScrolledIdx = function () {
- return this.scrolledColumnIdx;
- }.bind(this.horizontalScrollConfig);
- this.horizontalScrollConfig.setScrolledIdx = function (value) {
- this.scrolledColumnIdx = value;
- }.bind(this.horizontalScrollConfig);
- this.horizontalScrollConfig.recalculateRenderingIndexes = function () {
- if (this.count - this.scrolledColumnIdx < this.renderedCount) {
- this.scrolledColumnIdx = Math.max(0, this.count - this.renderedCount);
- }
- }.bind(this.horizontalScrollConfig);
- this.createCellStat(this.horizontalScrollConfig);
- this.dataProvider = options.dataProvider || new DataProvider(options.data);
- this._setProvideDataCount();
- this._setFixedRowsAndColumns({
- fixedRows: options.fixedRows,
- fixedColumns: options.fixedColumns
- });
- },
- /**
- * Dump the grid data and size.
- * Used to generate testcase for the unit test
- *
- * @returns
- */
- _getGridState: function _getGridState() {
- var state = {};
- state.height = this.$scrollableArea.height();
- state.width = this.$scrollableArea.width();
- state.minCellWidth = this.minCellWidth;
- state.minCellHeight = this.minCellHeight;
- state.rows = this.dataProvider.getNumRows();
- state.columns = this.dataProvider.getNumColumns();
- state.renderedRows = this.verticalScrollConfig.renderedCount;
- state.renderedColumns = this.horizontalScrollConfig.renderedCount;
- state.scrollTop = this.$scrollableArea.scrollTop();
- state.scrollLeft = this.$scrollableArea.scrollLeft();
- state.data = [];
- var i, j, row;
- for (i = 0; i < this.dataProvider.getNumRows(); i++) {
- row = [];
- state.data.push(row);
- for (j = 0; j < this.dataProvider.getNumColumns(); j++) {
- var value = this.dataProvider.getValue(i, j);
- var cellValue;
- if (this.options.setValue) {
- var $cell = $('<div>');
- this.options.setValue($cell, value, i, j);
- cellValue = $cell.text();
- } else if (_.isObject(value)) {
- cellValue = value.value;
- } else {
- cellValue = value || '';
- }
- row.push(cellValue);
- }
- }
- return JSON.stringify(state);
- },
- /**
- * Update the dataprovider with a new one, and call onDataChange for a rerender
- * @param {object} dataProvider - the new dataProvider used by the grid
- * @param {object} options - an options object which contains fixedRows and fixedColumns
- * @public
- */
- updateDataProvider: function updateDataProvider(dataProvider, options) {
- this.dataProvider = dataProvider;
- this.onDataChange({
- fixedRows: options.fixedRows,
- fixedColumns: options.fixedColumns
- });
- },
- /**
- * Notify the grid whenever the data provider is modified. This will update the number of rows/columns,
- * number of fixed rows/columns. Takes an options object:
- * isNewDataAvailable flag (boolean) used to keep track whether or not we're
- * appending data to the dataprovider or completely changing the data. If we're merely appending, then we should keep existing cell
- * stats and handle the grid scrolling to keep the right positioning for the grid. Else we can create new cell stats.
- * @param {object} options
- * @public
- */
- onDataChange: function onDataChange() {
- var _this = this;
- var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
- var isForceRedraw = this._setProvideDataCount();
- // Reapply the pinned rows/columns
- this._setFixedRowsAndColumns(options);
- // If this parameter is passed in as true, then we assumed that we were appending data to the dataprovider.
- // In this case we'd like to keep our position in the grid so scrolling vertically will appear seamless. For now
- // we have to do this.scrollTopOffset - 1 pixel because if you scroll down fast enough, scrolling will hang at the bottom
- // of the grid when we fetch additional data (async operation) since handleOffset() will set this.scrollTopOffset to be equal to
- // the top offset of the scrollable area. By subtracting a single pixel, this is inconspicuous to the eye and essentially forces
- // a recalculation of the scrolling.
- if (options.isNewDataAvailable) {
- this.scrollTopOffset -= 1;
- this.handleOffset();
- return Promise.resolve(this.isRendered);
- } else {
- //The data has changed. Force a rerender
- return this.resolveDelayedPromises(options).then(function () {
- _this.reRenderAfterDataChange(isForceRedraw, options);
- return _this.isRendered;
- });
- }
- },
- reRenderAfterDataChange: function reRenderAfterDataChange(isForceRedraw, options) {
- var _this2 = this;
- //silent scroll is used to handle the case where a filter was being set
- //and we didnt want it to trigger a scroll event as this hid the the flyout
- //for the filter
- this.silentScroll = true;
- if (isForceRedraw) {
- this.createCellStat(this.verticalScrollConfig);
- this.createCellStat(this.horizontalScrollConfig);
- }
- this._recalculateRenderingIndexes();
- if (isForceRedraw || !this.isRendered) {
- // redraw the table cells
- this.prepareTableForRendering();
- }
- options.forceScrollUpdate = true;
- this.updateTables(options);
- setTimeout(function () {
- _this2.silentScroll = false;
- }, 500);
- },
- updateTables: function updateTables() {
- var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
- this.updateTableValues();
- //Recalculate stat size statistics after all values have been updated in the UI
- this._reCalcTableStats();
- //Now resize the tables with new stats so all tables line up
- this.updateTableSize();
- this.updateScroll(options.forceScrollUpdate);
- this.autoFitContentIfNeeded();
- },
- resolveDelayedPromises: function resolveDelayedPromises() {
- var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
- return options.onDelayedUpdateReady ? options.onDelayedUpdateReady() : Promise.resolve();
- },
- /**
- * A cell width/height can change after all values are rendered in the grid. When this happens it can cause the tables not to line up.
- * This function fixes this by resetting the column header cells to initial minimum size, recalculate all table stats then resize the table headers so that
- * they line up with the virtual table cells
- */
- _reCalcTableStats: function _reCalcTableStats() {
- this._resetColumnHeaderTableMinCellWidth();
- this._createCellStats();
- this.recordTableStats(this.$table);
- this.recordTableStats(this.$columnHeadersTable);
- this.recordTableStats(this.$rowHeadersTable);
- this.recordTableStats(this.$cornerTable);
- this.resizeTableIfNeeded(this.$table);
- this.resizeTableIfNeeded(this.$columnHeadersTable);
- this.resizeTableIfNeeded(this.$rowHeadersTable);
- this.resizeTableIfNeeded(this.$cornerTable);
- },
- /**
- * Reset column header cells to initial minimum sizes
- *
- */
- _resetColumnHeaderTableMinCellWidth: function _resetColumnHeaderTableMinCellWidth() {
- var $columnHeaderRows = this.$columnHeadersTable.find('tr');
- var $tr = void 0,
- $columns = void 0,
- $td = void 0;
- for (var index = 0; index < this.fixedRows; ++index) {
- $tr = $columnHeaderRows.eq(index);
- $columns = $tr.find('td');
- for (var i = 0; i < $columns.length; i++) {
- $td = $columns.eq(i);
- this.setCellMinWidth($td);
- }
- }
- },
- /**
- * Make sure that the current padding is not too big.
- *
- * If we are re-rendering after an initial render and we have scrolled and then the grid changes size,
- * then we need to update the padding in case it is now too big after resizing the table
- * @private
- */
- _recalculateTablePositionForInitialRendering: function _recalculateTablePositionForInitialRendering() {
- var padding = this.$scrollableArea[this.verticalScrollConfig.scrollFunction]();
- var scrollInfo = this.getScrollIncrement(padding, this.verticalScrollConfig);
- this.verticalScrollConfig.setScrolledIdx(scrollInfo.steps);
- this.verticalScrollConfig.translate(scrollInfo.padding);
- padding = this.$scrollableArea[this.horizontalScrollConfig.scrollFunction]();
- scrollInfo = this.getScrollIncrement(padding, this.horizontalScrollConfig);
- this.horizontalScrollConfig.setScrolledIdx(scrollInfo.steps);
- this.horizontalScrollConfig.translate(scrollInfo.padding);
- },
- /**
- * Recalculate the rendering indexes
- * If the rendering index will only render less than what we are currently rendering,
- * then we need to asjust the index to render more
- *
- */
- _recalculateRenderingIndexes: function _recalculateRenderingIndexes() {
- this.verticalScrollConfig.recalculateRenderingIndexes();
- this.horizontalScrollConfig.recalculateRenderingIndexes();
- },
- _setProvideDataCount: function _setProvideDataCount() {
- var isCountChanged = false;
- // Get the new data count
- if (this.verticalScrollConfig.count !== this.dataProvider.getNumRows()) {
- this.verticalScrollConfig.count = this.dataProvider.getNumRows();
- isCountChanged = true;
- }
- if (this.horizontalScrollConfig.count !== this.dataProvider.getNumColumns()) {
- this.horizontalScrollConfig.count = this.dataProvider.getNumColumns();
- isCountChanged = true;
- }
- return isCountChanged;
- },
- _setFixedRowsAndColumns: function _setFixedRowsAndColumns(options) {
- this.verticalScrollConfig.count = this.dataProvider.getNumRows();
- this.horizontalScrollConfig.count = this.dataProvider.getNumColumns();
- this.fixedRows = !_.isUndefined(options.fixedRows) ? options.fixedRows : this.fixedRows;
- this.fixedColumns = !_.isUndefined(options.fixedColumns) ? options.fixedColumns : this.fixedColumns;
- },
- /**
- * Recalculate positioning for the grid table and then rerender
- * @public
- */
- onResize: function onResize() {
- if (this.$scrollableArea.scrollTop() === 0 && this.$scrollableArea.scrollLeft() === 0 && (this.scrollTopOffset !== 0 || this.scrollLeftOffset !== 0)) {
- // We lost the scrolling state. We need to restore it. This might happen if the node was removed from the dom and restored
- this.resetScroll();
- }
- //reset the cell stats if we have maximized the content so that they don't affect the new rendering
- // We will only clear the affected orientation
- if (this.autoResizeVerticalEnabled) {
- this.createCellStat(this.verticalScrollConfig);
- }
- if (this.autoResizeHorizontalEnabled) {
- this.createCellStat(this.horizontalScrollConfig);
- }
- // Adjust the padding in case we are at the end of the scrolling and we are showing less than what we can after the resize.
- this._recalculateTablePositionForInitialRendering();
- // re-render
- return this.render();
- },
- /**
- * Returns an object detailing some information about the cells
- * @private
- */
- createCellStat: function createCellStat(config) {
- config.stats = {
- map: {},
- totalRecordedSize: 0,
- count: 0,
- minSize: config.defaultSize(),
- maxSize: config.defaultSize(),
- averageSize: config.defaultSize(),
- totalSize: config.count * config.defaultSize()
- };
- },
- _createCellStats: function _createCellStats() {
- this.createCellStat(this.verticalScrollConfig);
- this.createCellStat(this.horizontalScrollConfig);
- },
- cacheMostUsedValues: function cacheMostUsedValues() {
- // Cache the most common used elements so that we
- // can easily have access to them
- this.$cells = this.$table.find('td');
- this.$rows = this.$table.find('tr');
- this.$firstRow = function () {
- return this.$table.find('tr:first');
- }.bind(this);
- this.$lastRow = function () {
- return this.$table.find('tr:last');
- }.bind(this);
- this.$firstRowFirstCell = function () {
- return this.$firstRow().find('td:first');
- }.bind(this);
- this.$firstRowLastCell = function () {
- return this.$firstRow().find('td:last');
- }.bind(this);
- this.horizontalScrollConfig.$firstCell = function () {
- return this.$firstRow().find('td:first');
- }.bind(this); //this.$firstRowFirstCell;
- this.horizontalScrollConfig.$lastCell = function () {
- return this.$firstRow().find('td:last');
- }.bind(this); //this.$firstRowLastCell;
- this.verticalScrollConfig.$firstCell = function () {
- return this.$table.find('tr:first');
- }.bind(this); //this.$firstRow;
- this.verticalScrollConfig.$lastCell = function () {
- return this.$table.find('tr:last');
- }.bind(this); // this.$lastRow;
- },
- /**
- * Render the grid table
- * @public
- */
- render: function render(options) {
- var _this3 = this;
- if (!this.$table) {
- this.$table = $('<table role="grid" cellspacing="0" cellpadding="0" class="gridTable userPrimaryGridFGPaletteColor" style="box-sizing: border-box; border-collapse:collapse;table-layout: fixed"></table>');
- if (this.options.label) {
- this.$table.attr('aria-label', this.options.label);
- }
- this.$columnHeadersTable = $('<table cellspacing="0" cellpadding="0" class="gridColumnHeaders" style="box-sizing: border-box; border-collapse:collapse;table-layout: fixed; position:absolute; top:0px; left:0px; pointer-events:all"></table>');
- this.$rowHeadersTable = $('<table cellspacing="0" cellpadding="0" class="gridRowHeaders" style="box-sizing: border-box; border-collapse:collapse;table-layout: fixed; position:absolute; top:0px; left:0px; pointer-events:all"></table>');
- this.$cornerTable = $('<table cellspacing="0" cellpadding="0" class="cornerTable" style="box-sizing: border-box; border-collapse:collapse;table-layout: fixed; position:absolute; top:0px; left:0px; pointer-events:all"></table>');
- this.$scrollableArea = $('<div style="height:100%; width:100%; overflow:auto;position:relative;text-align:left"></div>');
- this.$scrollableArea.attr('id', UniqueId.get('scrollableArea_'));
- this.$virtualTable = $('<div role="presentation" style="box-sizing: border-box; padding:0px; display:inline-block; min-width:10000px; min-height:10000px">');
- this.$virtualTable.attr('id', UniqueId.get('virtualTable_'));
- this.$virtualTable.append(this.$table);
- this.$scrollableArea.append(this.$virtualTable);
- this.$headerContainer = $('<div style="top:0px;left:0px;overflow:hidden;position:absolute;text-align:left; pointer-events:none"></div>');
- this.$headerContainer.attr('id', UniqueId.get('headerContainer_'));
- this.$headerContainer.append(this.$columnHeadersTable);
- if (this.fixedColumns && this.fixedColumns > 0) {
- this.bHasCornerTable = true;
- this.$headerContainer.append(this.$rowHeadersTable);
- this.$headerContainer.append(this.$cornerTable);
- }
- this.$contentWrapper = $('<div style="height:100%; width:100%;position:relative;" role="application"></div>');
- this.$contentWrapper.attr('id', UniqueId.get('contentWrapper_'));
- this.$contentWrapper.append(this.$scrollableArea);
- this.$contentWrapper.append(this.$headerContainer);
- this.$el.append(this.$contentWrapper);
- this.scrollTopOffset = 0;
- this.scrollLeftOffset = 0;
- this.registerScrollEvents();
- }
- return this.resolveDelayedPromises(options).then(function () {
- _this3.prepareTableForRendering();
- if (_this3.isRendered) {
- _this3.updateTables(options);
- }
- return _this3.isRendered;
- });
- },
- prepareTableForRendering: function prepareTableForRendering() {
- this.$scrollableArea.css('overflow-x', 'auto');
- this.$scrollableArea.css('overflow-y', 'auto');
- this.$headerContainer.height(this.$scrollableArea[0].clientHeight);
- this.$headerContainer.width(this.$scrollableArea[0].clientWidth);
- this._clearTables();
- if (!this.dataProvider.getNumRows()) {
- this.isRendered = false;
- return;
- }
- this.isRendered = this.renderTable();
- if (this.isRendered) {
- this.renderPinnedColumnHeaders();
- if (this.fixedColumns && this.fixedColumns > 0) {
- this.renderPinnedRowHeaders();
- this.renderPinnedCorner();
- }
- }
- this.cacheMostUsedValues();
- },
- _clearTables: function _clearTables() {
- this.$table.empty();
- this.$columnHeadersTable.empty();
- this.$rowHeadersTable.empty();
- this.$cornerTable.empty();
- },
- autoFitContentIfNeeded: function autoFitContentIfNeeded() {
- if (this.autoResizeHorizontal || this.autoResizeVertical) {
- this.autoResizeHorizontalEnabled = false;
- this.autoResizeVerticalEnabled = false;
- var extra, nodes, index, i;
- if (this.autoResizeHorizontal && this.$virtualTable.outerWidth() <= this.$el.innerWidth()) {
- var width = this.$scrollableArea[0].clientWidth / this.horizontalScrollConfig.count - 2;
- extra = 0;
- nodes = [];
- for (index in this.horizontalScrollConfig.stats.map) {
- if (this.horizontalScrollConfig.stats.map.hasOwnProperty(index)) {
- if (this.horizontalScrollConfig.stats.map[index] > width) {
- extra += this.horizontalScrollConfig.stats.map[index] - width;
- } else {
- nodes.push(this.$el.find('td[col="' + index + '"]'));
- }
- }
- }
- width -= extra / nodes.length;
- for (i = 0; i < nodes.length; i++) {
- nodes[i].css('min-width', width + 'px');
- }
- this.$virtualTable.css(this.horizontalScrollConfig.minSizeProp, '0px');
- this.$scrollableArea.css('overflow-x', 'visible');
- this.autoResizeHorizontalEnabled = true;
- }
- if (this.autoResizeVertical && this.$virtualTable.outerHeight() <= this.$el.innerHeight()) {
- var height = this.$scrollableArea[0].clientHeight / this.verticalScrollConfig.count - 2;
- extra = 0;
- nodes = [];
- for (index in this.verticalScrollConfig.stats.map) {
- if (this.verticalScrollConfig.stats.map[index] > height) {
- extra += this.verticalScrollConfig.stats.map[index] - height;
- } else {
- nodes.push(this.$el.find('td[row="' + index + '"]'));
- }
- }
- height -= extra / nodes.length;
- for (i = 0; i < nodes.length; i++) {
- nodes[i].css('height', height + 'px');
- }
- this.$virtualTable.css(this.verticalScrollConfig.minSizeProp, '0px');
- this.$scrollableArea.css('overflow-y', 'visible');
- this.autoResizeVerticalEnabled = true;
- }
- if (this.autoResizeVerticalEnabled || this.autoResizeHorizontalEnabled) {
- this.recordTableStats(this.$table);
- this.resizeTableIfNeeded(this.$columnHeadersTable);
- this.resizeTableIfNeeded(this.$rowHeadersTable);
- }
- }
- },
- registerScrollEvents: function registerScrollEvents() {
- this.$scrollableArea.on('scroll.virtualScroll', this.onScroll.bind(this));
- this.$headerContainer.on('wheel', this.onWheel.bind(this));
- },
- onWheel: function onWheel(event) {
- var scrollTop = this.$scrollableArea.scrollTop();
- if (event.originalEvent.deltaY < 0) {
- this.$scrollableArea.scrollTop(this.$scrollableArea.scrollTop() - 90);
- } else if (event.originalEvent.deltaY > 0) {
- this.$scrollableArea.scrollTop(this.$scrollableArea.scrollTop() + 90);
- }
- if (event.originalEvent.deltaX < 0) {
- this.$scrollableArea.scrollLeft(this.$scrollableArea.scrollLeft() - 90);
- } else if (event.originalEvent.deltaX > 0) {
- this.$scrollableArea.scrollLeft(this.$scrollableArea.scrollLeft() + 90);
- }
- if (scrollTop !== this.$scrollableArea.scrollTop()) {
- event.preventDefault();
- }
- },
- remove: function remove() {
- if (this.scrollTimer) {
- clearTimeout(this.scrollTimer);
- }
- this.$scrollableArea && this.$scrollableArea.remove();
- this.$headerContainer && this.$headerContainer.remove();
- if (this.$contentWrapper) {
- this.$contentWrapper.remove();
- this.$contentWrapper = null;
- }
- this.dataProvider = null;
- View.inherited('remove', this, arguments);
- },
- /**
- * scroll handler. Called when the user scrolls the
- * virtual table
- */
- onScroll: function onScroll() {
- var isScrollStarted = false;
- if (this.scrollTimer) {
- clearTimeout(this.scrollTimer);
- isScrollStarted = true;
- }
- // Notify the start scroll handler
- if (!isScrollStarted && this.options.onStartScroll && !this.silentScroll) {
- this.options.onStartScroll();
- }
- this.scrollTimer = setTimeout(function () {
- this.scrollTimer = null;
- if (this.options.onFinishScroll && !this.silentScroll) {
- this.options.onFinishScroll();
- }
- this._setFocusAfterScroll();
- }.bind(this), 800);
- this.updateScroll();
- },
- /**
- * Update the positions of the pinned headers. Also, if there are 5 or fewer rowers left to render, determine whether or not we
- * need to fetch more data from our server by checking if we passed in a callback function inside our options object in init(). If
- * so, make the call to fetch more data, wait for the response, and then update position and offsets.
- * @private
- */
- updateScroll: function updateScroll(isForceUpdate) {
- var scrollTopOffset = this.$scrollableArea.scrollTop();
- var scrollLeftOffset = this.$scrollableArea.scrollLeft();
- this.updatePinnedHeaderPosition(scrollTopOffset, scrollLeftOffset);
- var scrolledIdx = this.verticalScrollConfig.getScrolledIdx();
- var numberOfDataRowsInGrid = this.options.getMoreData && this.verticalScrollConfig.count - (scrolledIdx + this.verticalScrollConfig.renderedCount);
- if (numberOfDataRowsInGrid !== false && numberOfDataRowsInGrid >= 0 && numberOfDataRowsInGrid < 5 && this.scrollTopOffset <= scrollTopOffset) {
- this.options.getMoreData();
- }
- this.handleOffset(scrollTopOffset, scrollLeftOffset, isForceUpdate);
- },
- resetScroll: function resetScroll() {
- this.$scrollableArea.scrollTop(this.scrollTopOffset);
- this.$scrollableArea.scrollLeft(this.scrollLeftOffset);
- },
- /**
- * Handles the scrolling if we have scrolled horizontally or vertically by changing the vertical/horizontal offsets.
- * @private
- */
- handleOffset: function handleOffset(scrollTop, scrollLeft, isForceUpdate) {
- var scrollTopOffset = scrollTop || this.$scrollableArea.scrollTop();
- var scrollLeftOffset = scrollLeft || this.$scrollableArea.scrollLeft();
- if (this.scrollTopOffset !== scrollTopOffset || isForceUpdate) {
- this.handleScroll(scrollTopOffset - this.scrollTopOffset, this.verticalScrollConfig, isForceUpdate);
- }
- if (this.scrollLeftOffset !== scrollLeftOffset || isForceUpdate) {
- this.handleScroll(scrollLeftOffset - this.scrollLeftOffset, this.horizontalScrollConfig, isForceUpdate);
- }
- // update the current scrolltop and scrollLeft
- // values.
- // Don't use the variables above
- // (scrollTopOffset/scrollLeftOffset), the scroll
- // values might have changed while processing the
- // scroll event
- this.scrollTopOffset = this.$scrollableArea.scrollTop();
- this.scrollLeftOffset = this.$scrollableArea.scrollLeft();
- },
- /**
- * Remove any colspan/rowspan that was set on the cell
- * when the grouping is enabled This method will save
- * the current state of the grouping so it can be
- * restored using the restoreGrouping method
- */
- removeGrouping: function removeGrouping() {
- if (!this.isGroupEnabled) {
- return;
- }
- this.$cells.each(function () {
- // we use font-size set to 0px to hide the text to avoid the height/width impact on the cell
- var $td = $(this);
- var display = $td.css('display');
- var fontSize = $td.css('font-size');
- if (display === 'none') {
- $td.css({
- 'display': '',
- 'visibility': 'hidden',
- 'font-size': '0px'
- });
- }
- if (fontSize) {
- $td.attr({ '_fontSize': fontSize });
- }
- var colspan = $td.attr('colspan');
- $td.attr({ '_colspan': null });
- if (colspan) {
- $td.attr({
- 'colspan': null,
- '_colspan': colspan
- });
- $td.css({ 'font-size': '0px' });
- }
- var rowspan = $td.attr('rowspan');
- $td.attr({ '_rowspan': null });
- if (rowspan) {
- $td.attr({
- 'rowspan': null,
- '_rowspan': rowspan
- });
- $td.css({ 'font-size': '0px' });
- }
- });
- },
- /**
- * Restore any grouping by adding colspan/rowspan to the
- * cells. This method is supposed to restore the
- * grouping that was removed using the removeGrouping
- * method
- * @private
- */
- restoreGrouping: function restoreGrouping() {
- if (!this.isGroupEnabled) {
- return;
- }
- this.$cells.each(function () {
- var $td = $(this);
- var display = $td.css('visibility');
- var fontSize = $td.attr('_fontSize');
- //when restore happens, we used the saved state of the fontSize to set the font-size value
- if (display === 'hidden') {
- $td.css({
- 'display': 'none',
- 'visibility': '',
- 'font-size': fontSize ? fontSize : ''
- });
- $td.attr({
- '_fontSize': null
- });
- }
- var colspan = $td.attr('_colspan');
- if (colspan) {
- $td.attr({
- '_colspan': null,
- 'colspan': colspan,
- '_fontSize': null
- });
- $td.css({
- 'font-size': fontSize ? fontSize : ''
- });
- }
- var rowspan = $td.attr('_rowspan');
- if (rowspan) {
- $td.attr({
- '_rowspan': null,
- 'rowspan': rowspan,
- '_fontSize': null
- });
- $td.css({
- 'font-size': fontSize ? fontSize : ''
- });
- }
- });
- },
- /**
- * Offset is positive when moving right and negative
- * when moving left.
- *
- * @param offset
- */
- handleScroll: function handleScroll(offset, config, isForceUpdate) {
- if (!this.dataProvider.getNumRows()) {
- return;
- }
- // We need to temporarily remove the group to
- // determine the cell width so that we can properly
- // do the proper horizontal scrolling
- this.removeGrouping();
- var shifted = false;
- // Calculate how many columns we are shifting based on the current scroll position
- // if the increment steps is different than the current index we are rendering, then we need to redraw
- var increment = this.getScrollIncrement(this.$scrollableArea[config.scrollFunction](), config);
- if (increment.steps !== config.getScrolledIdx() || isForceUpdate) {
- shifted = this.redrawTable(increment.steps, increment.padding, config);
- }
- if (!shifted) {
- // If nothing has changed, we restore the
- // grouping. Otherwise the grouping would be
- // properly rendered when we shift the values
- this.restoreGrouping();
- } else if (this.options.onAfterGridUpdate) {
- // Cells have been updated as a result of a scroll. Notify the callback if it exists
- this.options.onAfterGridUpdate();
- }
- },
- redrawTable: function redrawTable(newScrolledToRowOrColumnIndex, paddingOffset, config) {
- this.updateTablePadding(paddingOffset, config);
- var redrawn = this.updateIndexAndValues(newScrolledToRowOrColumnIndex, config);
- this.validateTablePadding(paddingOffset, config);
- return redrawn;
- },
- updateIndexAndValues: function updateIndexAndValues(newScrolledToRowOrColumnIndex, config) {
- if (newScrolledToRowOrColumnIndex < 0 || newScrolledToRowOrColumnIndex + config.renderedCount > config.count) {
- return false;
- }
- var previousScrolledIdx = config.getScrolledIdx();
- config.setScrolledIdx(newScrolledToRowOrColumnIndex);
- this.updateTableValues({ previousScrolledIdx: previousScrolledIdx, isVerticalScrolling: config.isVerticalScrolling });
- return true;
- },
- resizeTableIfNeeded: function resizeTableIfNeeded($table) {
- //update the size of the columns in the header here if they differ from stats
- var $rows = $table.find('tr');
- for (var i = 0; i < $rows.length; i++) {
- var $tr = $rows.eq(i);
- var $columns = $tr.find('td');
- for (var j = 0; j < $columns.length; j++) {
- var $td = $columns.eq(j);
- if ($td.hasClass('corner')) {
- if ($td.is(':visible')) {
- this.resizeCorner($td);
- }
- } else {
- this.updateCellSize($td);
- }
- }
- }
- this.resizeCornerTable();
- },
- /** in the column header table, must size the corner width independently when multiple row nesting is involved since it arbitrarily chooses
- * the 2nd cell in the first array to be the visible cell and sizes it according to that cell stat when it should
- * in fact be sized based on the width of the 1st and 2nd(0th and 1st) cell width stat.
- * @param {object} td - the cell
- * @private
- */
- resizeCorner: function resizeCorner($td) {
- var colspan = parseInt($td.attr('colspan'), 10);
- var rowspan = parseInt($td.attr('rowspan'), 10);
- var i;
- var width = 0;
- var height = 0;
- for (i = 0; i < this.fixedColumns; i++) {
- width += this.horizontalScrollConfig.stats.map[i];
- }
- for (i = 0; i < this.fixedRows; i++) {
- height += this.verticalScrollConfig.stats.map[i];
- }
- //prevents the corner from becoming the size of more cells than are visible after scrolling to the right
- if (colspan !== this.fixedColumns) {
- for (i = 0; i < this.fixedColumns - colspan; i++) {
- width -= this.horizontalScrollConfig.stats.map[i];
- }
- }
- if (rowspan !== this.fixedRows) {
- for (i = 0; i < this.fixedRows - rowspan; i++) {
- height -= this.verticalScrollConfig.stats.map[i];
- }
- }
- $td.css('min-width', width + 'px');
- $td.css('height', height + 'px');
- },
- /**
- * Resize the corner table to match the stats set by _resizeCorner
- * @private
- */
- resizeCornerTable: function resizeCornerTable() {
- // we only care about the corner table if we have a pinned header
- if (this.fixedColumns > 0) {
- var width = 0;
- var height = 0;
- for (var i = 0; i < this.fixedColumns; i++) {
- width += this.horizontalScrollConfig.stats.map[i];
- }
- for (var k = 0; k < this.fixedRows; k++) {
- height += this.verticalScrollConfig.stats.map[k];
- }
- this.$corner.css({
- 'min-width': width
- });
- this.$corner.css({
- 'height': height
- });
- }
- },
- /**
- * Update the positions of the pinned column/row headers tables
- * @param {number} scrollTopOffset - the offset we should pad to the top
- * @param {number} scrollLeftOffset - the offset we should pad to te left
- * @private
- */
- updatePinnedHeaderPosition: function updatePinnedHeaderPosition() /*scrollTopOffset, scrollLeftOffset*/{
- this.$columnHeadersTable.css('margin-left', this.horizontalScrollConfig.getTranslateValue() - this.$scrollableArea.scrollLeft() + 'px');
- this.$rowHeadersTable.css('margin-top', this.verticalScrollConfig.getTranslateValue() - this.$scrollableArea.scrollTop() + 'px');
- },
- /**
- * based on the cells that are currently visible, update the minimum widths and heights for the columns and rows, respectively.
- * @param {object} table - the table
- * @private
- */
- recordTableStats: function recordTableStats($table) {
- var $rows = $table.find('tr');
- for (var index = $rows.length - 1; index >= 0; index--) {
- var $tr = $rows.eq(index);
- var $columns = $tr.find('td:visible');
- var $td;
- // set the values and the formats
- for (var i = 0; i < $columns.length; i++) {
- $td = $columns.eq(i);
- var colspan = parseInt($td.attr('colspan'), 10) || 1;
- var rowspan = parseInt($td.attr('rowspan'), 10) || 1;
- //only record the width of cells that have col span 1 not including corners that span multiple columns
- if (colspan === 1 && (!$td.hasClass('corner') || this.fixedColumns === 1)) {
- var col = $td.attr(ATTRIBUTES.COLUMN_INDEX);
- this.recordCellStats($td, this.horizontalScrollConfig, col);
- }
- if (rowspan === 1 && (!$td.hasClass('corner') || this.fixedRows === 1)) {
- var row = $td.attr(ATTRIBUTES.ROW_INDEX);
- this.recordCellStats($td, this.verticalScrollConfig, row);
- }
- }
- }
- },
- /**
- * adjust the size of the virtual table
- * @private
- */
- updateTableSize: function updateTableSize() {
- this.$virtualTable.css(this.horizontalScrollConfig.minSizeProp, this.horizontalScrollConfig.stats.totalSize + 'px');
- this.$virtualTable.css(this.verticalScrollConfig.minSizeProp, this.verticalScrollConfig.stats.totalSize + 'px');
- },
- /**
- * Updates the table padding in the vertical or horizontal directionshi
- * @param {number} newPadding - the new padding for the table
- * @param {config} config - the vertical/horizontal config
- * @private
- */
- updateTablePadding: function updateTablePadding(newPadding, config) {
- // adjust the padding to simulate the scroll
- var padding = newPadding;
- config.translate(padding);
- },
- validateTablePadding: function validateTablePadding(newPadding, config) {
- this.updateTableSize();
- var previousScrolledIdx = void 0;
- if (newPadding <= 0) {
- newPadding = 0;
- if (config.getScrolledIdx() !== 0) {
- previousScrolledIdx = config.getScrolledIdx();
- config.setScrolledIdx(0);
- this.updateTableValues({ previousScrolledIdx: config.getScrolledIdx(), isVerticalScrolling: config.isVerticalScrolling });
- }
- config.translate(newPadding);
- } else if (newPadding > this.$scrollableArea[config.scrollFunction]()) {
- newPadding = this.$scrollableArea[config.scrollFunction]();
- var scrollInfo = this.getScrollIncrement(newPadding, config);
- if (config.getScrolledIdx() !== scrollInfo.steps) {
- previousScrolledIdx = config.getScrolledIdx();
- config.setScrolledIdx(scrollInfo.steps);
- this.updateTableValues({ previousScrolledIdx: previousScrolledIdx, isVerticalScrolling: config.isVerticalScrolling });
- }
- config.translate(newPadding);
- }
- // The virtual table is too big or too small(some
- // cell sizes have changed).. adjust it.
- var expectedTableSize = config.sizeFunction(this.$table) + newPadding;
- var currentTableSize = config.sizeFunction(this.$virtualTable);
- if (config.getScrolledIdx() + config.renderedCount === config.count && expectedTableSize !== currentTableSize) {
- config.stats.totalSize -= currentTableSize - expectedTableSize;
- this.updateTableSize();
- }
- },
- /**
- * @private
- */
- updateTableValues: function updateTableValues() {
- var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
- var previousScrolledIdx = options.previousScrolledIdx;
- var isVerticalScrolling = options.isVerticalScrolling;
- if (this.dataProvider.getNumRows() && !this.partialValuesUpdate(previousScrolledIdx, isVerticalScrolling)) {
- this.allValuesUpdate();
- }
- },
- /**
- * @private
- */
- updateDataVertically: function updateDataVertically(previousScrolledIdx, delta) {
- this.resetTableGroups(this.$table);
- this._updateTableScrolledVertically({ $table: this.$table, previousScrolledIdx: previousScrolledIdx, delta: delta, handleGrouping: true });
- this.updateTableGroups(this.$table);
- //Remove a row and placing it at top or end of the table at the current scroll position.
- // Also, updating the table groups might change the current scroll position
- // We need to restore the scroll position
- var scrollTop = this.$scrollableArea.scrollTop();
- this.$scrollableArea.scrollTop(scrollTop);
- this.recordTableStats(this.$table);
- this.resizeTableIfNeeded(this.$columnHeadersTable);
- this.resizeTableIfNeeded(this.$rowHeadersTable);
- },
- /**
- * @private
- */
- updateRowHeaderVertically: function updateRowHeaderVertically(previousScrolledIdx, delta) {
- this.resetTableGroups(this.$rowHeadersTable);
- this._updateTableScrolledVertically({ $table: this.$rowHeadersTable, previousScrolledIdx: previousScrolledIdx, delta: delta, handleGrouping: true, isRowHeader: true });
- this.updateTableGroups(this.$rowHeadersTable);
- this.recordTableStats(this.$rowHeadersTable);
- },
- /**
- *
- * Do a partial update of the values in the given $rows array.
- * If we are not changing all values, we simply move the rows/columns and keep the unchanged cells. This is done for better performance.
- *
- * @param previousScrolledIdx - old scrolled row or columnd index location before the updated
- * @param isVerticalScrolling - true if scrolling vertically else false
- * @param $rows
- * @returns {Boolean}
- */
- partialValuesUpdate: function partialValuesUpdate(previousScrolledIdx, isVerticalScrolling) {
- var _this4 = this;
- var scrolledVerticalIdx = this.verticalScrollConfig.getScrolledIdx();
- var index = isVerticalScrolling ? scrolledVerticalIdx : this.horizontalScrollConfig.getScrolledIdx();
- var diff = Math.abs(previousScrolledIdx - index);
- var $columnHeaderRows = this.$columnHeadersTable.find('tr');
- var $tableRows = this.$table.find('tr');
- var scrollVertical = isVerticalScrolling && diff < this.verticalScrollConfig.renderedCount;
- var scrollHorizontal = !isVerticalScrolling && diff < this.horizontalScrollConfig.renderedCount;
- if (scrollVertical) {
- var delta = this.fixedRows && this.fixedRows > 0 ? this.fixedRows : 0;
- //updateRowHeaders
- this.updateRowHeaderVertically(previousScrolledIdx, delta);
- //update data
- this.updateDataVertically(previousScrolledIdx, delta);
- this.resizeTableIfNeeded(this.$columnHeadersTable);
- this.resizeTableIfNeeded(this.$rowHeadersTable);
- } else if (scrollHorizontal) {
- this.resetTableGroups(this.$columnHeadersTable);
- var _delta = this.fixedColumns && this.fixedColumns > 0 ? this.fixedColumns : 0;
- var getTableRowIndex = function getTableRowIndex(rowIndex) {
- return scrolledVerticalIdx + rowIndex;
- };
- var options = {
- previousScrolledIdx: previousScrolledIdx,
- delta: _delta,
- cbUpdateCell: function cbUpdateCell($td, isRowHeaders, rowIndex, tdIndex) {
- rowIndex = isRowHeaders ? rowIndex : getTableRowIndex(rowIndex);
- if (previousScrolledIdx < _this4.horizontalScrollConfig.getScrolledIdx()) {
- tdIndex = _this4.horizontalScrollConfig.renderedCount - tdIndex - 1;
- } else {
- tdIndex = tdIndex + _delta;
- }
- _this4.updateCell($td, rowIndex, tdIndex);
- },
- isRowHeaders: true
- };
- //update column headers
- this._updateColumnHeadersWhenScrolleFastdHorizontally($columnHeaderRows, options);
- this.updateTableGroups(this.$columnHeadersTable);
- this.recordTableStats(this.$columnHeadersTable);
- //update data
- this.resetTableGroups(this.$table);
- options.isRowHeaders = false;
- this._updateColumnHeadersWhenScrolleFastdHorizontally($tableRows, options);
- this.updateTableGroups(this.$table);
- this.recordTableStats(this.$table);
- this.resizeTableIfNeeded(this.$columnHeadersTable);
- }
- if (scrollVertical || scrollHorizontal) {
- this.cacheMostUsedValues();
- }
- return scrollVertical || scrollHorizontal;
- },
- /**
- * Updates the table cell column headers after scrolled fast horizontally
- *
- * @param {object} $rows - the jQuery rows object
- * @param {object} options - options to update the table cells position and their contents within an table row
- */
- _updateColumnHeadersWhenScrolleFastdHorizontally: function _updateColumnHeadersWhenScrolleFastdHorizontally($rows) {
- var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
- if ($rows instanceof $) {
- var $td = void 0;
- var i = void 0;
- var j = void 0;
- var isRowHeaders = options.isRowHeaders;
- var delta = options.delta;
- var previousScrolledIdx = options.previousScrolledIdx;
- for (i = 0; i < $rows.length; i++) {
- var $row = $rows.eq(i);
- var $tds = $row.children();
- if (previousScrolledIdx < this.horizontalScrollConfig.getScrolledIdx()) {
- for (j = this.horizontalScrollConfig.getScrolledIdx() - previousScrolledIdx - 1; j >= 0; j--) {
- $td = $tds.eq(j + delta);
- $td.appendTo($row);
- options.cbUpdateCell($td, isRowHeaders, i, j);
- }
- } else {
- var $tdAfter = this.fixedColumns && this.fixedColumns > 0 ? $tds.eq(this.fixedColumns - 1) : null;
- for (j = previousScrolledIdx - this.horizontalScrollConfig.getScrolledIdx() - 1; j >= 0; j--) {
- $td = $tds.eq($tds.length - j - 1);
- if ($tdAfter) {
- $td.insertAfter($tdAfter);
- } else {
- $td.prependTo($row);
- }
- options.cbUpdateCell($td, isRowHeaders, i, j);
- }
- }
- }
- }
- },
- /**
- * Update all the values in the given $rows array.
- *
- * @param $rows
- */
- allValuesUpdate: function allValuesUpdate() {
- var _this5 = this;
- var _cbGetScrolledPinnedRowHeaders = function _cbGetScrolledPinnedRowHeaders(rowIndex) {
- //row headers are pinned, should not be moved when scrolled vertically
- return _this5.fixedRows && rowIndex < _this5.fixedRows ? rowIndex : rowIndex + _this5.verticalScrollConfig.getScrolledIdx();
- };
- //update column headers
- var $columnHeaderRows = this.$columnHeadersTable.find('tr');
- var $tr;
- for (var index = this.fixedRows - 1; index >= 0; index--) {
- $tr = $columnHeaderRows.eq(index);
- this.updateRow({ $tr: $tr, rowIndex: index, handleGrouping: true, isRowHeader: false, cbGetScrolledFastPinnedCornerCellIdx: cbGetScrolledFastPinnedCornerCellIdx });
- }
- this.recordTableStats(this.$columnHeadersTable);
- //update row headers
- var $rowHeaderRows = this.$rowHeadersTable.find('tr');
- for (index = $rowHeaderRows.length - 1; index >= 0; index--) {
- $tr = $rowHeaderRows.eq(index);
- this.updateRow({ $tr: $tr, rowIndex: _cbGetScrolledPinnedRowHeaders(index), handleGrouping: true, isRowHeader: true });
- }
- this.recordTableStats(this.$rowHeadersTable);
- //update corner table
- var $cornerTableRows = this.$cornerTable.find('tr');
- for (index = this.fixedRows - 1; index >= 0; index--) {
- $tr = $cornerTableRows.eq(index);
- this.updateRow({ $tr: $tr, rowIndex: index, handleGrouping: true, isRowHeader: false, cbGetScrolledFastPinnedCornerCellIdx: cbGetScrolledFastPinnedCornerCellIdx });
- }
- this.recordTableStats(this.$cornerTable);
- //update data
- var $tableRows = this.$table.find('tr');
- for (index = $tableRows.length - 1; index >= 0; index--) {
- $tr = $tableRows.eq(index);
- this.updateRow({ $tr: $tr, rowIndex: _cbGetScrolledPinnedRowHeaders(index), handleGrouping: true, isRowHeader: false, cbGetScrolledFastPinnedCornerCellIdx: cbGetScrolledFastPinnedCornerCellIdx });
- }
- this.recordTableStats(this.$table);
- this.resizeTableIfNeeded(this.$columnHeadersTable);
- this.resizeTableIfNeeded(this.$rowHeadersTable);
- },
- /**
- * Updates the passed in row from the table
- * @param {object} tr - the row
- * @param {number} rowIndex - the rowIndex of the cell
- * @param {boolean} handleGrouping
- * @param {boolean} isRowHeader
- * @param {function} cbGetScrolledFastPinnedCornerCellIdx - optional callback to get the correct corner cell colunm index within a row
- * @private
- */
- updateRow: function updateRow() {
- var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
- var $tr = options.$tr;
- var rowIndex = options.rowIndex;
- var handleGrouping = options.handleGrouping;
- var isRowHeader = options.isRowHeader;
- var cbGetScrolledFastPinnedCornerCellIdx = options.cbGetScrolledFastPinnedCornerCellIdx;
- var $columns = $tr.find('td');
- var $td;
- $tr.attr(ATTRIBUTES.ROW_INDEX, rowIndex);
- if (this.options.formatRow) {
- this.options.formatRow($tr, rowIndex);
- }
- // set the values and the formats
- for (var i = 0; i < $columns.length; i++) {
- $td = $columns.eq(i);
- this.updateCell($td, rowIndex, i, handleGrouping, isRowHeader, cbGetScrolledFastPinnedCornerCellIdx);
- }
- },
- /**
- * Updates and, if necessary, formats the passed in cell with new styles and text. Called whenever we need to render the cell,
- * eg. when we scroll/resize, etc
- * @param {object} td - a jQuery object representing the cell
- * @param {number} rowIndex - the rowIndex of the cell
- * @param {number} tdIndex - the column index of the cell. If the grid has been scrolled horizontally, we must manually calculate this value
- * @param {boolean} handleGrouping
- * @param {boolean} isRowHeader
- * @param {function} cbGetScrolledFastPinnedCornerCellIdx - optional callback to get the correct corner cell colunm index within a row
- * @private
- */
- updateCell: function updateCell($td, rowIndex, tdIndex, handleGrouping, isRowHeader, cbGetScrolledFastPinnedCornerCellIdx) {
- var colIndex = cbGetScrolledFastPinnedCornerCellIdx ? cbGetScrolledFastPinnedCornerCellIdx(tdIndex, this.fixedColumns, this.fixedRows) : null;
- if (!_.isNumber(colIndex)) {
- colIndex = isRowHeader ? tdIndex : this.horizontalScrollConfig.getScrolledIdx() + tdIndex;
- }
- var value = this.dataProvider.getValue(rowIndex, colIndex);
- $td.attr(ATTRIBUTES.COLUMN_INDEX, colIndex);
- $td.attr('row', rowIndex);
- this.setCellMinWidth($td);
- $td.attr('data-group', null);
- if (handleGrouping) {
- this.resetGroupSettings($td);
- }
- if (this.options.setValue) {
- this.options.setValue($td, value, rowIndex, colIndex);
- } else {
- var cellValue;
- if (_.isObject(value)) {
- cellValue = value.value;
- } else {
- cellValue = value || '';
- }
- $td.attr('aria-label', cellValue);
- $td.text(cellValue);
- }
- if (this.options.formatCell) {
- this.options.formatCell($td, value, rowIndex, colIndex);
- }
- this.updateCellSize($td);
- if (handleGrouping) {
- this.renderGroup($td, tdIndex);
- }
- },
- /**
- * Updates the cell value with value when GridDataProvider cell value changes
- * @param {number} rowIndex - the rowIndex of the cell
- * @param {number} colIndex - the column index of the cell.
- * @param {number} value - the new value to replace the old one
- * @private
- */
- onCellDataChange: function onCellDataChange(rowIndex, colIndex, value) {
- var $td = this.$table.find('td[' + ATTRIBUTES.ROW_INDEX + '="' + rowIndex + '"][' + ATTRIBUTES.COLUMN_INDEX + '="' + colIndex + '"]');
- if ($td && $td.length && $td.is(':visible') && this.options.setValue) {
- this.options.setValue($td, value, rowIndex, colIndex);
- }
- },
- /**
- * Updates the cell size based on the largest width/height of a cell in the same row/column
- * @param {object} td - a jQuery object represeting the table cell
- * @private
- */
- updateCellSize: function updateCellSize($td) {
- var colIndex = $td.attr(ATTRIBUTES.COLUMN_INDEX);
- var rowIndex = $td.attr(ATTRIBUTES.ROW_INDEX);
- var width = this.horizontalScrollConfig.stats.map[colIndex];
- if (width) {
- //var borderleftSize = parseInt($td.css('border-left-width'), 10);
- $td.css('min-width', width + 'px');
- }
- var height = this.verticalScrollConfig.stats.map[rowIndex];
- if (height) {
- // var borderTopSize = parseInt($td).css('border-top-width'), 10);
- $td.css('height', height + 'px');
- }
- },
- /**
- * @private
- */
- resetTableGroups: function resetTableGroups($table) {
- var $rows = $table.find('tr');
- for (var index = $rows.length - 1; index >= 0; index--) {
- var $tr = $rows.eq(index);
- var $columns = $tr.find('td');
- var $td;
- // set the values and the formats
- for (var i = 0; i < $columns.length; i++) {
- $td = $columns.eq(i);
- this.resetGroupSettings($td);
- }
- }
- },
- /**
- * @private
- */
- updateTableGroups: function updateTableGroups($table) {
- var $rows = $table.find('tr');
- for (var index = $rows.length - 1; index >= 0; index--) {
- var $tr = $rows.eq(index);
- var $columns = $tr.find('td');
- var $td;
- // set the values and the formats
- for (var i = 0; i < $columns.length; i++) {
- $td = $columns.eq(i);
- this.renderGroup($td, i);
- }
- }
- },
- /**
- * @private
- */
- resetGroupSettings: function resetGroupSettings($td) {
- if (this.isGroupEnabled) {
- var fontSize = $td.attr('_fontSize') ? $td.attr('_fontSize') : $td.css('font-size');
- $td.attr({
- 'colspan': null,
- 'rowspan': null,
- '_colspan': null,
- '_rowspan': null,
- '_fontSize': null
- });
- $td.css({
- 'display': '',
- 'visibility': '',
- 'font-size': fontSize
- });
- }
- },
- /**
- * @private
- */
- renderGroup: function renderGroup($td, i) {
- if (this.isGroupEnabled) {
- var group = $td.attr('data-group');
- if (group) {
- var $prev = $td.prev();
- var prevGroup = $prev.attr('data-group');
- if (prevGroup === group) {
- var colspan = $prev.attr('colspan');
- $td.attr('colspan', 1 + parseInt(colspan, 10));
- $td.attr('rowspan', $prev.attr('rowspan'));
- $prev.css('display', 'none');
- } else {
- $td.attr('colspan', '1');
- }
- var $next = $td.parent().next().children().eq(i);
- prevGroup = $next.attr('data-group');
- if (prevGroup === group) {
- var rowspan = $next.attr('rowspan');
- $td.attr('rowspan', 1 + parseInt(rowspan, 10));
- $td.attr('colspan', $next.attr('colspan'));
- $next.css('display', 'none');
- } else {
- $td.attr('rowspan', '1');
- }
- }
- }
- },
- /**
- * @private
- */
- recordCellStats: function recordCellStats($td, config, index) {
- var width = this.getCellSize($td, config);
- var minWidth = config.stats.map[index] || 0;
- if (width > minWidth) {
- this.addCellStat(config.stats, width, index, config.count);
- return width;
- }
- },
- /**
- * Returns the size of a cell
- * @param {object} td - the cell
- * @param {object} config - the horizontal/vertical config object for the cell
- * @private
- */
- getCellSize: function getCellSize($td, config) {
- return config.sizeFunction($td);
- },
- /**
- * @private
- */
- getScrollIncrement: function getScrollIncrement(scrollOffset, config) {
- var index = 0;
- var stats = config.stats;
- var stopAt = config.count;
- var currentOffset = scrollOffset;
- var rendered = config.renderedCount;
- var step;
- var steps = 0;
- var padding = 0;
- while (index < stopAt - rendered && currentOffset > 0) {
- step = stats.map[index];
- step = step ? step : stats.maxSize;
- currentOffset -= step;
- index++;
- if (currentOffset >= 0) {
- padding += step;
- steps++;
- }
- }
- return {
- steps: steps,
- padding: padding
- };
- },
- /**
- * @private
- */
- addCellStat: function addCellStat(stat, size, index, cellCount) {
- var current = stat.map[index];
- if (current && size <= current) {
- return;
- }
- stat.map[index] = size;
- stat.totalRecordedSize += size;
- if (current) {
- stat.totalRecordedSize -= current;
- } else {
- stat.count++;
- }
- stat.minSize = stat.minSize ? Math.min(stat.minSize, size) : size;
- stat.maxSize = Math.max(stat.maxSize || 0, size);
- stat.totalSize = stat.totalRecordedSize + (cellCount - stat.count) * stat.maxSize;
- stat.averageSize = stat.totalSize / cellCount;
- },
- /**
- * Renders the table row by row
- * @private
- */
- renderTable: function renderTable() {
- var currentRow = this.verticalScrollConfig.getScrolledIdx();
- var $tr = null;
- var bottomOffset = 0;
- this.horizontalScrollConfig.renderedCount = 0;
- this.verticalScrollConfig.renderedCount = 0;
- if (this.$el.is(':hidden')) {
- return false;
- }
- do {
- $tr = this.appendRow(this.$table, currentRow, false, this.horizontalScrollConfig.renderedCount);
- if ($tr) {
- if ($tr.is(':hidden')) {
- return false;
- }
- this.verticalScrollConfig.renderedCount++;
- if (!this.horizontalScrollConfig.renderedCount) {
- this.horizontalScrollConfig.renderedCount = $tr.children().length;
- }
- currentRow++;
- if (this.isOutOfViewFromBottom($tr)) {
- bottomOffset++;
- }
- if (bottomOffset === this.offsetCount) {
- break;
- }
- }
- } while ($tr);
- return true;
- },
- /**
- * Renders the pinned column headers
- * @private
- */
- renderPinnedColumnHeaders: function renderPinnedColumnHeaders() {
- for (var i = 0; i < this.fixedRows; i++) {
- this.appendRow(this.$columnHeadersTable, i, false, this.horizontalScrollConfig.renderedCount);
- }
- },
- /**
- * Renders the pineed row headers
- * @private
- */
- renderPinnedRowHeaders: function renderPinnedRowHeaders() {
- var $tr = void 0;
- var scrolledVerticalIdx = this.verticalScrollConfig.getScrolledIdx();
- for (var i = scrolledVerticalIdx; i < scrolledVerticalIdx + this.verticalScrollConfig.renderedCount; i++) {
- if (i === this.verticalScrollConfig.count) {
- break;
- }
- $tr = $('<tr>');
- this.$rowHeadersTable.append($tr);
- $tr.attr(ATTRIBUTES.ROW_INDEX, i);
- for (var j = 0; j < this.fixedColumns; j++) {
- this.appendCell($tr, i, j, false);
- }
- }
- },
- /**
- * Renders the pinned corners
- * @private
- */
- renderPinnedCorner: function renderPinnedCorner() {
- this.$corner = $('<td tabindex="0" style="box-sizing: border-box; min-width: ' + this.minCellWidth + 'px; height: ' + this.minCellHeight + 'px;" col="0" row="0" class="cell corner" data-group="crosstab_corner" colspan="1" rowspan="1">' + this.dataProvider.getValue(0, 0).value + '</td>');
- this.$cornerTable.append($('<tr row=0></tr>'));
- this.$cornerTable.find('tr:first').append(this.$corner);
- },
- /**
- * Append/Prepend a row to the table, and then append/create all the cells for that row
- * @param {object} table - the grid table
- * @param {number} rowIndex - a number representing the row index
- * @param {boolean} isPrepend - a boolean indicating whether to append or prepend the row to the table
- * @param {number} renderColumnCount - number indicating how many columns are rendered to the DOM
- * @private
- */
- appendRow: function appendRow($table, rowIndex, isPrepend, renderColumnCount) {
- var $tr = null;
- if (rowIndex < this.verticalScrollConfig.count) {
- $tr = $('<tr>');
- if (isPrepend) {
- $table.prepend($tr);
- } else {
- $table.append($tr);
- }
- $tr.attr(ATTRIBUTES.ROW_INDEX, rowIndex);
- var $td;
- var colIndex = this.horizontalScrollConfig.getScrolledIdx();
- var rightOffset = 0;
- for (var i = colIndex; i < this.horizontalScrollConfig.count; i++) {
- $td = this.appendCell($tr, rowIndex, i);
- if (!renderColumnCount) {
- if (this.isOutOfViewFromRight($td)) {
- rightOffset++;
- }
- if (rightOffset === this.offsetCount) {
- break;
- }
- } else {
- if (renderColumnCount === i - this.horizontalScrollConfig.getScrolledIdx() + 1) {
- break;
- }
- }
- }
- }
- return $tr;
- },
- /**
- * Create the actual cell, and then set the cell's minimum width
- * @private
- */
- createCell: function createCell() {
- var $td = $('<td tabindex="0" style="box-sizing: border-box">');
- this.setCellMinWidth($td);
- return $td;
- },
- /**
- * Set's the cell's minimum width in pixels
- * @param {object} td - the cell
- * @private
- */
- setCellMinWidth: function setCellMinWidth($td) {
- $td.css({
- 'min-width': this.minCellWidth + 'px',
- 'height': this.minCellHeight + 'px'
- });
- },
- /**
- * Create the actual cell, set the cell's minimum width, and then append/prepend it to the table
- * @param {object} tr - the row in the table to append/prepend the cell to
- * @param {number} rowIndex - number representing the rowIndex of the cell
- * @param {number} colIndex - number representing the colIndex of the cell
- * @param {boolean} isPrepend - a boolean representing whether to prepend the cell to the given row, or to append it to the row
- * @private
- */
- appendCell: function appendCell($tr, rowIndex, colIndex, isPrepend) {
- var $td = this.createCell(rowIndex, colIndex);
- if (isPrepend) {
- $tr.prepend($td);
- } else {
- $tr.append($td);
- }
- return $td;
- },
- _getPositionFromTop: function _getPositionFromTop($el) {
- var top = 0;
- $el.prevAll().each(function () {
- top += $(this).outerHeight();
- });
- return top;
- },
- _getPositionFromLeft: function _getPositionFromLeft($el) {
- var left = 0;
- $el.prevAll().each(function () {
- left += $(this).outerWidth();
- });
- return left;
- },
- /**
- * Function that returns whether or not the element is out of view from the top
- * @param {object} el - a jQuery object
- * @private
- */
- isOutOfViewFromTop: function isOutOfViewFromTop($el) {
- return this._getPositionFromTop($el) + $el.outerHeight() < 0;
- },
- /**
- * Function that returns whether or not the element is out of view from the bottom
- * @param {object} el - a jQuery object
- * @private
- */
- isOutOfViewFromBottom: function isOutOfViewFromBottom($el) {
- return this._getPositionFromTop($el) > this.$scrollableArea.height();
- },
- /**
- * Function that returns whether or not the element is out of view from the right
- * @param {object} el - a jQuery object
- * @private
- */
- isOutOfViewFromRight: function isOutOfViewFromRight($el) {
- return this._getPositionFromLeft($el) > this.$scrollableArea.width();
- },
- /**
- * Function that returns whether or not the element is out of view from the left
- * @param {object} el - a jQuery object
- * @private
- */
- isOutOfViewFromLeft: function isOutOfViewFromLeft($el) {
- return this._getPositionFromLeft($el) + $el.outerWidth() < 0;
- },
- getRowValues: function getRowValues(rowIndex) {
- var rowData = [];
- var nodes = this.getVisibleRowNodes(rowIndex);
- for (var i = 0; i < nodes.length; i++) {
- var $cell = $(nodes[i]);
- rowData.push($cell.text());
- }
- return rowData;
- },
- //Returns an array of tds for the given row index. If there is grouping, it returns
- //the visible td (which may be on a different row)
- getVisibleRowNodes: function getVisibleRowNodes(rowIndex) {
- var nodes = this.$rows.find('[' + ATTRIBUTES.ROW_INDEX + '="' + rowIndex + '"]');
- for (var i = 0; i < nodes.length; i++) {
- var $cell = $(nodes[i]);
- if (!$cell.is(':visible')) {
- var datagroup = $cell.attr('data-group');
- if (!datagroup || !datagroup.length) {
- continue;
- }
- var element = this._getPreviousClosestDatagroupElement($cell, datagroup);
- nodes.splice(i, 1, element[0]);
- }
- }
- return nodes;
- },
- getDisplayedRows: function getDisplayedRows() {
- var a_rowCells = [];
- this.$rows.each(function (idx, row) {
- a_rowCells.push($(row).find('td'));
- }.bind(this));
- return a_rowCells;
- },
- /** return a visible element that matches the given datagroup in the previous rows.
- * @private
- */
- _getPreviousClosestDatagroupElement: function _getPreviousClosestDatagroupElement($srcElement, datagroup) {
- var allPreviousRows = $srcElement.parent().prevAll('tr');
- if (!allPreviousRows.length) {
- return null;
- }
- var prevClosestVisibleDatagroupElement = allPreviousRows.find('td[data-group="' + datagroup + '"]:visible');
- if (!prevClosestVisibleDatagroupElement.length) {
- return null;
- }
- return prevClosestVisibleDatagroupElement.last();
- },
- _targetIsOnTheFirstRowAfterFixedRowHeadersTable: function _targetIsOnTheFirstRowAfterFixedRowHeadersTable($target) {
- return Number($target.attr(ATTRIBUTES.ROW_INDEX)) === this.fixedRows;
- },
- _moveUpToColumnHeadersTableOrCornerTable: function _moveUpToColumnHeadersTableOrCornerTable($target) {
- if ($target.closest('table.gridRowHeaders').length) {
- this.setInitialFocus();
- return;
- }
- var position = this._getCurrentTargetPosition($target);
- var columnPosition = position.column + 1;
- var newRowPosition = position.row;
- var previousRowElement = this.$columnHeadersTable.find('tr:nth-child(' + newRowPosition + ') td:nth-child(' + columnPosition + ')');
- if (previousRowElement.length) {
- this._setFocus(previousRowElement, 'up');
- }
- },
- _moveUp: function _moveUp($target) {
- //:nth-child() is 1-based so need to add 1
- var columnIdx = $target.index() + 1;
- var previousRow = $target.parent().prev('tr');
- if (!previousRow.length) {
- var bTargetIsAtTheTopOfGrid = Number($target.attr(ATTRIBUTES.ROW_INDEX)) === 0;
- if (bTargetIsAtTheTopOfGrid) {
- return;
- }
- var index = this.verticalScrollConfig.getScrolledIdx() - 1;
- this._addColumnOrRowAndSetFocus(this.verticalScrollConfig, index, $target, this._moveUp.bind(this));
- return;
- }
- if (this._targetIsOnTheFirstRowAfterFixedRowHeadersTable($target)) {
- this._moveUpToColumnHeadersTableOrCornerTable($target);
- return;
- }
- var previousRowElement = previousRow.find('td:nth-child(' + columnIdx + ')');
- if (previousRowElement.length > 0 && previousRowElement.is(':visible')) {
- this._setFocus(previousRowElement, 'up');
- return;
- }
- //previous row element that is in the same column is not visible, see if it is in a different column
- var newDatagroup = previousRowElement.attr('data-group');
- //get next visible item in same newDatagroup in same row
- var visibleDatagroupItem = previousRowElement.nextAll('td[data-group="' + newDatagroup + '"]:visible:first');
- if (visibleDatagroupItem && visibleDatagroupItem.length) {
- visibleDatagroupItem.focus();
- return;
- }
- // no visible item found in same row, try previous rows
- previousRowElement = this._getPreviousClosestDatagroupElement(previousRowElement, newDatagroup);
- if (previousRowElement && previousRowElement.length) {
- this._setFocus(previousRowElement, 'up');
- }
- },
- _moveDownToGridFromColumnHeaders: function _moveDownToGridFromColumnHeaders($target, columnIdx) {
- //to get to the next row, add 1 and another 1 as row is 0 based but nth-child is 1-based
- var rowIdx = $target.parent().index() + 2;
- var nextRowElement = this.$table.find('tr:nth-child(' + rowIdx + ') td:nth-child(' + columnIdx + ')');
- if (nextRowElement.length) {
- nextRowElement.focus();
- }
- },
- _moveDown: function _moveDown($target) {
- var columnIdx = $target.index() + 1;
- var colSpan = Number($target.attr('colSpan'));
- if (colSpan > 1) {
- //if colspan is > 1, then on the next row, we want to focus on the first element in the span
- columnIdx = columnIdx - colSpan + 1;
- }
- var oldDatagroup = $target.attr('data-group');
- var nextRows = $target.parent().nextAll('tr');
- if (!nextRows.length) {
- var bTargetIsOnColumnHeaderTable = $target.closest('table.gridColumnHeaders').length > 0;
- if (bTargetIsOnColumnHeaderTable) {
- this._moveDownToGridFromColumnHeaders($target, columnIdx);
- return;
- }
- //the corner table cell's row attribute does not accurately reflect the
- var bTargetIsOnCornerTable = $target.closest('table.cornerTable').length > 0;
- if (bTargetIsOnCornerTable) {
- //setup the nextRows for moving from cornerTable to rowHeadersTable
- nextRows = this.$rowHeadersTable.find('tr');
- }
- }
- //handles moving down on nested row header
- var nextRowElement = nextRows.find('td[data-group!="' + oldDatagroup + '"]:nth-child(' + columnIdx + ')').first();
- var newDatagroup;
- if (nextRowElement.length) {
- if (nextRowElement.is(':visible')) {
- nextRowElement.focus();
- return;
- }
- newDatagroup = nextRowElement.attr('data-group');
- }
- //handles moving down on nested column headers
- //next row element in the same column idx is not visible, see if it is in a different column
- var sameDatagropuItemInRow = nextRowElement.nextAll('td[data-group="' + newDatagroup + '"]:visible:first');
- if (sameDatagropuItemInRow && sameDatagropuItemInRow.length > 0) {
- sameDatagropuItemInRow.focus();
- return;
- }
- //if there is no more visible item, check if all the rows had been retrieved
- var rowIndex = Number(nextRowElement.attr(ATTRIBUTES.ROW_INDEX));
- var totalNumberOfRows = this.verticalScrollConfig.count;
- if (rowIndex < totalNumberOfRows) {
- return;
- }
- //if we get here, it means that the new data-group is not fully rendered so get more rows
- var index = this.verticalScrollConfig.getScrolledIdx() + 1;
- this._addColumnOrRowAndSetFocus(this.verticalScrollConfig, index, $target, this._moveDown.bind(this));
- },
- _moveRightFromCornerTableToColumnHeader: function _moveRightFromCornerTableToColumnHeader($target, oldDatagroup) {
- var firstCellElement = this.$columnHeadersTable.find('tr:visible:first td:first');
- var nextElement = firstCellElement.nextAll('td[data-group!="' + oldDatagroup + '"]:visible:first');
- if (nextElement.length) {
- nextElement.focus();
- }
- },
- _getCurrentTargetPosition: function _getCurrentTargetPosition($target) {
- var colPosition = $target.index();
- var rowPosition = $target.parent().index();
- return {
- 'column': colPosition,
- 'row': rowPosition
- };
- },
- _moveRightFromRowHeadersToGrid: function _moveRightFromRowHeadersToGrid($target) {
- var position = this._getCurrentTargetPosition($target);
- var columnIdx = position.column + 2;
- var rowIdx = position.row + 1;
- var nextColumnElement = this.$table.find('tr:nth-child(' + rowIdx + ') td:nth-child(' + columnIdx + ')');
- if (nextColumnElement.length && nextColumnElement.is(':visible')) {
- nextColumnElement.focus();
- return;
- }
- var datagroup = nextColumnElement.attr('data-group');
- nextColumnElement.find('td[data-group="' + datagroup + '"]:visible:first');
- if (nextColumnElement.length) {
- nextColumnElement.focus();
- }
- },
- _targetIsOnLastColumnInRowHeadersTable: function _targetIsOnLastColumnInRowHeadersTable($target) {
- if (!$target.closest('table.gridRowHeaders').length) {
- return false;
- }
- var columnPosition = Number($target.attr(ATTRIBUTES.COLUMN_INDEX)) + 1;
- return columnPosition === this.fixedColumns;
- },
- _moveRight: function _moveRight($target) {
- var bTargetIsOnCornerTable = $target.closest('table.cornerTable').length > 0;
- var oldDatagroup = $target.attr('data-group');
- if (bTargetIsOnCornerTable) {
- this._moveRightFromCornerTableToColumnHeader($target, oldDatagroup);
- return;
- }
- var nextElement;
- if (this._targetIsOnLastColumnInRowHeadersTable($target)) {
- this._moveRightFromRowHeadersToGrid($target);
- return;
- }
- nextElement = $target.nextAll('td:visible:first');
- if (nextElement.length) {
- nextElement.focus();
- return;
- }
- //There are no visible item to focus on, so check if there's a different data-group following $target
- var nextGroupDataElement = $target.nextAll('td[data-group!="' + oldDatagroup + '"]:first');
- if (!nextGroupDataElement.length) {
- var colIndex = Number($target.attr(ATTRIBUTES.COLUMN_INDEX)) + 1;
- var totalNumberOfColumns = this.horizontalScrollConfig.count;
- if (colIndex === totalNumberOfColumns) {
- return;
- }
- //There isn't a different datagroup and no visible element, so the current datagroup must be partially rendered,
- //so get more data
- var index = this.horizontalScrollConfig.getScrolledIdx() + 1;
- this._addColumnOrRowAndSetFocus(this.horizontalScrollConfig, index, $target, this._moveRight.bind(this));
- return;
- }
- var newDatagroup = nextGroupDataElement.attr('data-group');
- nextElement = this._getPreviousClosestDatagroupElement(nextGroupDataElement, newDatagroup);
- if (nextElement && nextElement.length) {
- nextElement.focus();
- return;
- }
- nextElement = this.$rowHeadersTable.nextAll('td[data-group!="' + oldDatagroup + '"]:first');
- if (nextElement.length) {
- nextElement.focus();
- }
- },
- _addColumnOrRowAndSetFocus: function _addColumnOrRowAndSetFocus(config, index, $target, setFocusFunction) {
- if (typeof setFocusFunction !== 'function') {
- return;
- }
- if (!this.updateIndexAndValues(index, config)) {
- return;
- }
- var colIdx = $target.attr(ATTRIBUTES.COLUMN_INDEX);
- var rowIdx = $target.attr(ATTRIBUTES.ROW_INDEX);
- //find the target node in the updated table
- var table = $target.closest('table');
- var currentElement = table.find('td[col="' + colIdx + '"][row="' + rowIdx + '"]');
- if (!currentElement.length) {
- return;
- }
- setFocusFunction(currentElement);
- },
- _moveLeftToRowHeadersTable: function _moveLeftToRowHeadersTable($target) {
- var position = this._getCurrentTargetPosition($target);
- var rowPosition = position.row + 1; //nth-child is 1-based
- var newColumnPosition = position.column; // column preceding
- var previousColumnElement = this.$rowHeadersTable.find('tr:nth-child(' + rowPosition + ') td:nth-child(' + newColumnPosition + ')');
- previousColumnElement.focus();
- },
- _moveLeftToRowHeadersOrCornerTable: function _moveLeftToRowHeadersOrCornerTable($target) {
- var bTargetIsOnColumnHeaderTable = $target.closest('table.gridColumnHeaders').length > 0;
- if (bTargetIsOnColumnHeaderTable) {
- this.setInitialFocus();
- return;
- }
- this._moveLeftToRowHeadersTable($target);
- },
- _targetIsOnColumnOrDataGroupBeforeRowHeadersTable: function _targetIsOnColumnOrDataGroupBeforeRowHeadersTable($target) {
- var colSpan = $target.attr('colspan');
- if (colSpan) {
- colSpan = Number(colSpan);
- }
- var columnIndex = $target.index();
- var columnIdxToUse;
- if (colSpan && colSpan > 1) {
- columnIdxToUse = columnIndex - colSpan + 1;
- } else {
- columnIdxToUse = columnIndex;
- }
- return this.fixedColumns && columnIdxToUse === this.fixedColumns;
- },
- _moveLeft: function _moveLeft($target) {
- var bTargetIsOnFirstColumn = Number($target.attr(ATTRIBUTES.COLUMN_INDEX)) === 0;
- if (bTargetIsOnFirstColumn) {
- return;
- }
- if (this._targetIsOnColumnOrDataGroupBeforeRowHeadersTable($target)) {
- this._moveLeftToRowHeadersOrCornerTable($target);
- return;
- }
- var previousElement = $target.prevAll('td:visible:first');
- if (previousElement.length) {
- this._setFocus(previousElement, 'left');
- return;
- }
- //There are no visible element to focus on, so check if there is a different datagroup preceding $target
- var oldDatagroup = $target.attr('data-group');
- var previousGroupDataElement = $target.prevAll('td[data-group!="' + oldDatagroup + '"]:first');
- if (!previousGroupDataElement.length) {
- var colIndex = Number($target.attr(ATTRIBUTES.COLUMN_INDEX));
- if (colIndex === 0) {
- return;
- }
- // there isn't a different datagroup, therefore, the current datagroup must be only partially rendered
- // so get more data
- this._addColumnOrRowAndSetFocus(this.horizontalScrollConfig, this.horizontalScrollConfig.getScrolledIdx() - 1, $target, this._moveLeft.bind(this));
- return;
- }
- // There is a different datagroup but since there is no visible element on the same row, it means that the visible element must be on one of the
- // previous rows
- var newDatagroup = previousGroupDataElement.attr('data-group');
- previousElement = this._getPreviousClosestDatagroupElement(previousGroupDataElement, newDatagroup);
- if (previousElement && previousElement.length > 0) {
- this._setFocus(previousElement, 'left');
- return;
- }
- },
- _setFocus: function _setFocus($nodeToFocus, moveDirection) {
- $nodeToFocus.focus();
- var nodeToFocusRect = $nodeToFocus[0].getBoundingClientRect();
- switch (moveDirection) {
- case 'left':
- {
- var rowHeadersTableRect = this.$rowHeadersTable[0].getBoundingClientRect();
- if (nodeToFocusRect.left + 1 > rowHeadersTableRect.right) {
- return;
- }
- var leftScrollAmount = rowHeadersTableRect.width - (nodeToFocusRect.left - rowHeadersTableRect.left);
- this.$scrollableArea.scrollLeft(this.$scrollableArea.scrollLeft() - leftScrollAmount);
- break;
- }
- case 'up':
- {
- var columnHeadersTable = this.$columnHeadersTable[0].getBoundingClientRect();
- if (nodeToFocusRect.top + 1 > columnHeadersTable.bottom) {
- return;
- }
- var topScrollAmount = columnHeadersTable.height - (nodeToFocusRect.top - columnHeadersTable.top);
- this.$scrollableArea.scrollTop(this.$scrollableArea.scrollTop() - topScrollAmount);
- break;
- }
- }
- },
- _moveToFirstRow: function _moveToFirstRow() {
- this.setInitialFocus();
- },
- _moveToLastRow: function _moveToLastRow() {
- this.$table.find('tr:visible:last td:visible:last').focus();
- },
- onKeyDown: function onKeyDown(event) {
- var $target = $(event.target);
- var bStopPropagation = false;
- switch (event.keyCode) {
- case KeyCodes.KEY_UP:
- {
- bStopPropagation = true;
- this._moveUp($target);
- break;
- }
- case KeyCodes.KEY_DOWN:
- {
- bStopPropagation = true;
- this._moveDown($target);
- break;
- }
- case KeyCodes.KEY_RIGHT:
- {
- bStopPropagation = true;
- this._moveRight($target);
- break;
- }
- case KeyCodes.KEY_LEFT:
- {
- bStopPropagation = true;
- this._moveLeft($target);
- break;
- }
- case KeyCodes.KEY_h:
- case KeyCodes.KEY_HOME:
- {
- if (!event.ctrlKey) {
- return;
- }
- bStopPropagation = true;
- this._moveToFirstRow();
- break;
- }
- case KeyCodes.KEY_e:
- case KeyCodes.KEY_END:
- {
- if (!event.ctrlKey) {
- return;
- }
- bStopPropagation = true;
- this._moveToLastRow();
- break;
- }
- }
- if (bStopPropagation) {
- this._savedKeyboardNavigationInfo = {
- 'keycode': event.keyCode,
- '$target': $target
- };
- event.preventDefault();
- event.stopPropagation();
- }
- },
- setInitialFocus: function setInitialFocus() {
- var startingTable = this.bHasCornerTable ? this.$cornerTable : this.$columnHeadersTable;
- var initialNode = startingTable.find('tr:visible:first td:visible:first');
- if (initialNode.length) {
- initialNode.focus();
- }
- },
- _setFocusAfterScroll: function _setFocusAfterScroll() {
- if (!this._savedKeyboardNavigationInfo) {
- return;
- }
- switch (this._savedKeyboardNavigationInfo.keycode) {
- case KeyCodes.KEY_UP:
- this._moveUp(this._savedKeyboardNavigationInfo.$target);
- break;
- case KeyCodes.KEY_DOWN:
- this._moveDown(this._savedKeyboardNavigationInfo.$target);
- break;
- case KeyCodes.KEY_RIGHT:
- this._moveRight(this._savedKeyboardNavigationInfo.$target);
- break;
- case KeyCodes.KEY_LEFT:
- this._moveLeft(this._savedKeyboardNavigationInfo.$target);
- break;
- case KeyCodes.KEY_e:
- case KeyCodes.KEY_END:
- this._moveToLastRow();
- break;
- case KeyCodes.KEY_h:
- case KeyCodes.KEY_HOME:
- this._moveToFirstRow();
- break;
- }
- },
- /**
- * Update the specified table when scrollled vertically
- *
- * @param {object} options - the options to scroll the table vertically
- *
- */
- _updateTableScrolledVertically: function _updateTableScrolledVertically() {
- var _this6 = this;
- var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
- var $table = options.$table;
- var previousScrolledIdx = options.previousScrolledIdx;
- var delta = options.delta;
- var handleGrouping = options.handleGrouping || false;
- var isRowHeader = options.isRowHeader || false;
- var scrolledVerticalIdx = this.verticalScrollConfig.getScrolledIdx();
- var scrollDown = previousScrolledIdx < scrolledVerticalIdx;
- var $rows = $table.find('tr');
- var $trAfter = delta > 0 ? $rows.eq(delta - 1) : null;
- var count = scrollDown ? scrolledVerticalIdx - previousScrolledIdx - 1 : previousScrolledIdx - scrolledVerticalIdx - 1;
- var $row = void 0;
- var rowContext = void 0;
- var _getRowUpdateContext = function _getRowUpdateContext($row, idx) {
- return {
- $tr: $row,
- rowIndex: scrollDown ? _this6.verticalScrollConfig.renderedCount + scrolledVerticalIdx - 1 - idx : idx + scrolledVerticalIdx + delta,
- handleGrouping: handleGrouping,
- isRowHeader: isRowHeader,
- cbGetScrolledFastPinnedCornerCellIdx: cbGetScrolledFastPinnedCornerCellIdx
- };
- };
- for (var idx = count; idx >= 0; idx--) {
- rowContext = null;
- if (scrollDown) {
- $row = $rows.eq(idx + delta);
- $row.appendTo($rows.parent());
- } else {
- $row = $rows.eq($rows.length - idx - 1);
- if ($trAfter) {
- $row.insertAfter($trAfter);
- } else {
- $row.prependTo($rows.parent());
- }
- }
- rowContext = _getRowUpdateContext($row, idx);
- this.updateRow(rowContext);
- }
- }
- });
- View.getColumnPosition = function ($tdNode) {
- return Number($tdNode.attr(ATTRIBUTES.COLUMN_INDEX));
- };
- View.getRowPosition = function ($tdNode) {
- return Number($tdNode.attr(ATTRIBUTES.ROW_INDEX));
- };
- return View;
- });
- //# sourceMappingURL=Grid.js.map
|