vendor/assets/javascripts/slickgrid/plugins/rowdetailview.js in slickgrid-2.3.16.1 vs vendor/assets/javascripts/slickgrid/plugins/rowdetailview.js in slickgrid-2.4.5
- old
+ new
@@ -1,54 +1,83 @@
/***
* A plugin to add row detail panel
* Original StackOverflow question & article making this possible (thanks to violet313)
* https://stackoverflow.com/questions/10535164/can-slickgrids-row-height-be-dynamically-altered#29399927
* http://violet313.org/slickgrids/#intro
- *
*
* USAGE:
- *
* Add the slick.rowDetailView.(js|css) files and register the plugin with the grid.
*
* AVAILABLE ROW DETAIL OPTIONS:
- * cssClass: A CSS class to be added to the row detail
- * loadOnce: Booleang flag, when True will load the data once and then reuse it.
- * preTemplate: Template that will be used before the async process (typically used to show a spinner/loading)
- * postTemplate: Template that will be loaded once the async function finishes
- * process: Async server function call
- * panelRows: Row count to use for the template panel
- * useRowClick: Boolean flag, when True will open the row detail on a row click (from any column), default to False
- *
+ * cssClass: A CSS class to be added to the row detail
+ * expandedClass: Extra classes to be added to the expanded Toggle
+ * collapsedClass: Extra classes to be added to the collapse Toggle
+ * loadOnce: Defaults to false, when set to True it will load the data once and then reuse it.
+ * preTemplate: Template that will be used before the async process (typically used to show a spinner/loading)
+ * postTemplate: Template that will be loaded once the async function finishes
+ * process: Async server function call
+ * panelRows: Row count to use for the template panel
+ * useRowClick: Boolean flag, when True will open the row detail on a row click (from any column), default to False
+ * keyPrefix: Defaults to '_', prefix used for all the plugin metadata added to the item object (meta e.g.: padding, collapsed, parent)
+ * collapseAllOnSort: Defaults to true, which will collapse all row detail views when user calls a sort. Unless user implements a sort to deal with padding
+ * saveDetailViewOnScroll: Defaults to true, which will save the row detail view in a cache when it detects that it will become out of the viewport buffer
+ * useSimpleViewportCalc: Defaults to false, which will use simplified calculation of out or back of viewport visibility
+ *
* AVAILABLE PUBLIC OPTIONS:
* init: initiliaze the plugin
+ * expandableOverride: callback method that user can override the default behavior of making every row an expandable row (the logic to show or not the expandable icon).
* destroy: destroy the plugin and it's events
* collapseAll: collapse all opened row detail panel
- * getColumnDefinition: get the column definitions
+ * collapseDetailView: collapse a row by passing the item object (row detail)
+ * expandDetailView: expand a row by passing the item object (row detail)
+ * getColumnDefinition: get the column definitions
+ * getExpandedRows: get all the expanded rows
+ * getFilterItem: takes in the item we are filtering and if it is an expanded row returns it's parents row to filter on
* getOptions: get current plugin options
+ * resizeDetailView: resize a row detail view, it will auto-calculate the number of rows it needs
+ * saveDetailView: save a row detail view content by passing the row object
* setOptions: set or change some of the plugin options
- *
+ *
* THE PLUGIN EXPOSES THE FOLLOWING SLICK EVENTS:
- * onAsyncResponse: This event must be used with the "notify" by the end user once the Asynchronous Server call returns the item detail
- * Event args:
- * itemDetail: Item detail returned from the async server call
- * detailView: An explicit view to use instead of template (Optional)
+ * onAsyncResponse: This event must be used with the "notify" by the end user once the Asynchronous Server call returns the item detail
+ * Event args:
+ * item: Item detail returned from the async server call
+ * detailView: An explicit view to use instead of template (Optional)
*
* onAsyncEndUpdate: Fired when the async response finished
- * Event args:
- * grid: Reference to the grid.
- * itemDetail: Column definition.
- *
+ * Event args:
+ * grid: Reference to the grid.
+ * item: Item data context
+ *
* onBeforeRowDetailToggle: Fired before the row detail gets toggled
- * Event args:
- * grid: Reference to the grid.
- * item: Column definition.
- *
+ * Event args:
+ * grid: Reference to the grid.
+ * item: Item data context
+ *
* onAfterRowDetailToggle: Fired after the row detail gets toggled
- * Event args:
- * grid: Reference to the grid.
- * item: Column definition.
+ * Event args:
+ * grid: Reference to the grid.
+ * item: Item data context
+ * expandedRows: Array of the Expanded Rows
*
+ * onRowOutOfViewportRange: Fired after a row becomes out of viewport range (user can't see the row anymore)
+ * Event args:
+ * grid: Reference to the grid.
+ * item: Item data context
+ * rowId: Id of the Row object (datacontext) in the Grid
+ * rowIndex: Index of the Row in the Grid
+ * expandedRows: Array of the Expanded Rows
+ * rowIdsOutOfViewport: Array of the Out of viewport Range Rows
+ *
+ * onRowBackToViewportRange: Fired after the row detail gets toggled
+ * Event args:
+ * grid: Reference to the grid.
+ * item: Item data context
+ * rowId: Id of the Row object (datacontext) in the Grid
+ * rowIndex: Index of the Row in the Grid
+ * expandedRows: Array of the Expanded Rows
+ * rowIdsOutOfViewport: Array of the Out of viewport Range Rows
*/
(function ($) {
// register namespace
$.extend(true, window, {
"Slick": {
@@ -56,63 +85,138 @@
"RowDetailView": RowDetailView
}
}
});
-
+ /** Constructor of the Row Detail View Plugin */
function RowDetailView(options) {
var _grid;
+ var _gridOptions;
+ var _gridUid;
+ var _expandableOverride = null;
var _self = this;
+ var _lastRange = null;
var _expandedRows = [];
var _handler = new Slick.EventHandler();
+ var _outsideRange = 5;
+ var _visibleRenderedCellCount = 0;
var _defaults = {
- columnId: "_detail_selector",
- cssClass: null,
- toolTip: "",
- width: 30
+ columnId: '_detail_selector',
+ cssClass: 'detailView-toggle',
+ expandedClass: null,
+ collapsedClass: null,
+ keyPrefix: '_',
+ loadOnce: false,
+ collapseAllOnSort: true,
+ saveDetailViewOnScroll: true,
+ useSimpleViewportCalc: false,
+ alwaysRenderColumn: true,
+ toolTip: '',
+ width: 30,
+ maxRows: null
};
-
+ var _keyPrefix = _defaults.keyPrefix;
+ var _gridRowBuffer = 0;
+ var _rowIdsOutOfViewport = [];
var _options = $.extend(true, {}, _defaults, options);
+ /**
+ * Initialize the plugin, which requires user to pass the SlickGrid Grid object
+ * @param grid: SlickGrid Grid object
+ */
function init(grid) {
+ if (!grid) {
+ throw new Error('RowDetailView Plugin requires the Grid instance to be passed as argument to the "init()" method');
+ }
_grid = grid;
+ _gridUid = grid.getUID();
+ _gridOptions = grid.getOptions() || {};
_dataView = _grid.getData();
+ _keyPrefix = _options && _options.keyPrefix || '_';
// Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3
+ _gridRowBuffer = _grid.getOptions().minRowBuffer;
_grid.getOptions().minRowBuffer = _options.panelRows + 3;
_handler
.subscribe(_grid.onClick, handleClick)
- .subscribe(_grid.onSort, handleSort)
- .subscribe(_grid.onScroll, handleScroll);
+ .subscribe(_grid.onScroll, handleScroll);
- _grid.getData().onRowCountChanged.subscribe(function () { _grid.updateRowCount(); _grid.render(); });
- _grid.getData().onRowsChanged.subscribe(function (e, a) { _grid.invalidateRows(a.rows); _grid.render(); });
+ // Sort will, by default, Collapse all of the open items (unless user implements his own onSort which deals with open row and padding)
+ if (_options.collapseAllOnSort) {
+ _handler.subscribe(_grid.onSort, collapseAll);
+ _expandedRows = [];
+ _rowIdsOutOfViewport = [];
+ }
+ _handler.subscribe(_grid.getData().onRowCountChanged, function () {
+ _grid.updateRowCount();
+ _grid.render();
+ });
+
+ _handler.subscribe(_grid.getData().onRowsChanged, function (e, a) {
+ _grid.invalidateRows(a.rows);
+ _grid.render();
+ });
+
// subscribe to the onAsyncResponse so that the plugin knows when the user server side calls finished
subscribeToOnAsyncResponse();
+
+ // if we use the alternative & simpler calculation of the out of viewport range
+ // we will need to know how many rows are rendered on the screen and we need to wait for grid to be rendered
+ // unfortunately there is no triggered event for knowing when grid is finished, so we use 250ms delay and it's typically more than enough
+ if (_options.useSimpleViewportCalc) {
+ _handler.subscribe(_grid.onRendered, function (e, args) {
+ if (args && args.endRow) {
+ _visibleRenderedCellCount = args.endRow - args.startRow;
+ }
+ });
+ }
}
+ /** destroy the plugin and it's events */
function destroy() {
_handler.unsubscribeAll();
_self.onAsyncResponse.unsubscribe();
_self.onAsyncEndUpdate.unsubscribe();
_self.onAfterRowDetailToggle.unsubscribe();
_self.onBeforeRowDetailToggle.unsubscribe();
+ _self.onRowOutOfViewportRange.unsubscribe();
+ _self.onRowBackToViewportRange.unsubscribe();
}
- function getOptions(options) {
+ /** Get current plugin options */
+ function getOptions() {
return _options;
}
+ /** set or change some of the plugin options */
function setOptions(options) {
_options = $.extend(true, {}, _options, options);
}
-
+
+ /** Find a value in an array and return the index when (or -1 when not found) */
+ function arrayFindIndex(sourceArray, value) {
+ if (sourceArray) {
+ for (var i = 0; i < sourceArray.length; i++) {
+ if (sourceArray[i] === value) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /** Handle mouse click event */
function handleClick(e, args) {
+ var dataContext = _grid.getDataItem(args.row);
+ if (!checkExpandableOverride(args.row, dataContext, _grid)) {
+ return;
+ }
+
// clicking on a row select checkbox
- if (_options.useRowClick || _grid.getColumns()[args.cell].id === _options.columnId && $(e.target).hasClass("detailView-toggle")) {
+ if (_options.useRowClick || _grid.getColumns()[args.cell].id === _options.columnId && $(e.target).hasClass(_options.cssClass)) {
// if editing, try to commit
if (_grid.getEditorLock().isActive() && !_grid.getEditorLock().commitCurrentEdit()) {
e.preventDefault();
e.stopImmediatePropagation();
return;
@@ -120,134 +224,251 @@
var item = _dataView.getItem(args.row);
// trigger an event before toggling
_self.onBeforeRowDetailToggle.notify({
- "grid": _grid,
- "item": item
+ 'grid': _grid,
+ 'item': item
}, e, _self);
- toggleRowSelection(item);
+ toggleRowSelection(args.row, item);
// trigger an event after toggling
_self.onAfterRowDetailToggle.notify({
- "grid": _grid,
- "item": item
+ 'grid': _grid,
+ 'item': item,
+ 'expandedRows': _expandedRows,
}, e, _self);
e.stopPropagation();
e.stopImmediatePropagation();
}
}
- // Sort will just collapse all of the open items
- function handleSort(e, args) {
- collapseAll();
+ /** If we scroll save detail views that go out of cache range */
+ function handleScroll(e, args) {
+ if (_options.useSimpleViewportCalc) {
+ calculateOutOfRangeViewsSimplerVersion();
+ } else {
+ calculateOutOfRangeViews();
+ }
}
- // If we scroll save detail views that go out of cache range
- function handleScroll(e, args) {
-
- var range = _grid.getRenderedRange();
+ /** Calculate when expanded rows become out of view range */
+ function calculateOutOfRangeViews() {
+ if (_grid) {
+ var renderedRange = _grid.getRenderedRange();
+ // Only check if we have expanded rows
+ if (_expandedRows.length > 0) {
+ // Assume scroll direction is down by default.
+ var scrollDir = 'DOWN';
+ if (_lastRange) {
+ // Some scrolling isn't anything as the range is the same
+ if (_lastRange.top === renderedRange.top && _lastRange.bottom === renderedRange.bottom) {
+ return;
+ }
- var start = (range.top > 0 ? range.top : 0);
- var end = (range.bottom > _dataView.getLength() ? range.bottom : _dataView.getLength());
-
- // Get the item at the top of the view
- var topMostItem = _dataView.getItemByIdx(start);
-
- // Check it is a parent item
- if (topMostItem._parent == undefined)
- {
- // This is a standard row as we have no parent.
- var nextItem = _dataView.getItemByIdx(start + 1);
- if(nextItem !== undefined && nextItem._parent !== undefined)
- {
- // This is likely the expanded Detail Row View
- // Check for safety
- if(nextItem._parent == topMostItem)
- {
- saveDetailView(topMostItem);
- }
+ // If our new top is smaller we are scrolling up
+ if (_lastRange.top > renderedRange.top ||
+ // Or we are at very top but our bottom is increasing
+ (_lastRange.top === 0 && renderedRange.top === 0) && _lastRange.bottom > renderedRange.bottom) {
+ scrollDir = 'UP';
}
+ }
}
- // Find the bottom most item that is likely to go off screen
- var bottomMostItem = _dataView.getItemByIdx(end - 1);
+ _expandedRows.forEach(function (row) {
+ var rowIndex = _dataView.getRowById(row.id);
- // If we are a detailView and we are about to go out of cache view
- if(bottomMostItem._parent !== undefined)
- {
- saveDetailView(bottomMostItem._parent);
-
+ var rowPadding = row[_keyPrefix + 'sizePadding'];
+ var rowOutOfRange = arrayFindIndex(_rowIdsOutOfViewport, row.id) >= 0;
+
+ if (scrollDir === 'UP') {
+ // save the view when asked
+ if (_options.saveDetailViewOnScroll) {
+ // If the bottom item within buffer range is an expanded row save it.
+ if (rowIndex >= renderedRange.bottom - _gridRowBuffer) {
+ saveDetailView(row);
+ }
+ }
+
+ // If the row expanded area is within the buffer notify that it is back in range
+ if (rowOutOfRange && rowIndex - _outsideRange < renderedRange.top && rowIndex >= renderedRange.top) {
+ notifyBackToViewportWhenDomExist(row, row.id);
+ }
+
+ // if our first expanded row is about to go off the bottom
+ else if (!rowOutOfRange && (rowIndex + rowPadding) > renderedRange.bottom) {
+ notifyOutOfViewport(row, row.id);
+ }
+ }
+ else if (scrollDir === 'DOWN') {
+ // save the view when asked
+ if (_options.saveDetailViewOnScroll) {
+ // If the top item within buffer range is an expanded row save it.
+ if (rowIndex <= renderedRange.top + _gridRowBuffer) {
+ saveDetailView(row);
+ }
+ }
+
+ // If row index is i higher than bottom with some added value (To ignore top rows off view) and is with view and was our of range
+ if (rowOutOfRange && (rowIndex + rowPadding + _outsideRange) > renderedRange.bottom && rowIndex < rowIndex + rowPadding) {
+ notifyBackToViewportWhenDomExist(row, row.id);
+ }
+
+ // if our row is outside top of and the buffering zone but not in the array of outOfVisable range notify it
+ else if (!rowOutOfRange && rowIndex < renderedRange.top) {
+ notifyOutOfViewport(row, row.id);
+ }
+ }
+ });
+ _lastRange = renderedRange;
+ }
+ }
+
+ /** This is an alternative & more simpler version of the Calculate when expanded rows become out of view range */
+ function calculateOutOfRangeViewsSimplerVersion() {
+ if (_grid) {
+ var renderedRange = _grid.getRenderedRange();
+
+ _expandedRows.forEach(function (row) {
+ var rowIndex = _dataView.getRowById(row.id);
+ var isOutOfVisibility = checkIsRowOutOfViewportRange(rowIndex, renderedRange);
+ if (!isOutOfVisibility && arrayFindIndex(_rowIdsOutOfViewport, row.id) >= 0) {
+ notifyBackToViewportWhenDomExist(row, row.id);
+ } else if (isOutOfVisibility) {
+ notifyOutOfViewport(row, row.id);
+ }
+ });
+ }
+ }
+
+ /**
+ * Check if the row became out of visible range (when user can't see it anymore)
+ * @param rowIndex
+ * @param renderedRange from SlickGrid
+ */
+ function checkIsRowOutOfViewportRange(rowIndex, renderedRange) {
+ if (Math.abs(renderedRange.bottom - _gridRowBuffer - rowIndex) > _visibleRenderedCellCount * 2) {
+ return true;
+ }
+ return false;
+ }
+
+ /** Send a notification, through "onRowOutOfViewportRange", that is out of the viewport range */
+ function notifyOutOfViewport(item, rowId) {
+ var rowIndex = item.rowIndex || _dataView.getRowById(item.id);
+
+ _self.onRowOutOfViewportRange.notify({
+ 'grid': _grid,
+ 'item': item,
+ 'rowId': rowId,
+ 'rowIndex': rowIndex,
+ 'expandedRows': _expandedRows,
+ 'rowIdsOutOfViewport': syncOutOfViewportArray(rowId, true)
+ }, null, _self);
+ }
+
+ /** Send a notification, through "onRowBackToViewportRange", that a row came back to the viewport */
+ function notifyBackToViewportWhenDomExist(item, rowId) {
+ var rowIndex = item.rowIndex || _dataView.getRowById(item.id);
+
+ setTimeout(function () {
+ // make sure View Row DOM Element really exist before notifying that it's a row that is visible again
+ if ($('.cellDetailView_' + item.id).length) {
+ _self.onRowBackToViewportRange.notify({
+ 'grid': _grid,
+ 'item': item,
+ 'rowId': rowId,
+ 'rowIndex': rowIndex,
+ 'expandedRows': _expandedRows,
+ 'rowIdsOutOfViewport': syncOutOfViewportArray(rowId, false)
+ }, null, _self);
}
+ }, 100);
}
-
+
+ /**
+ * This function will sync the out of viewport array whenever necessary.
+ * The sync can add a row (when necessary, no need to add again if it already exist) or delete a row from the array.
+ * @param rowId: number
+ * @param isAdding: are we adding or removing a row?
+ */
+ function syncOutOfViewportArray(rowId, isAdding) {
+ var arrayRowIndex = arrayFindIndex(_rowIdsOutOfViewport, rowId);
+
+ if (isAdding && arrayRowIndex < 0) {
+ _rowIdsOutOfViewport.push(rowId);
+ } else if (!isAdding && arrayRowIndex >= 0) {
+ _rowIdsOutOfViewport.splice(arrayRowIndex, 1);
+ }
+ return _rowIdsOutOfViewport;
+ }
+
// Toggle between showing and hiding a row
- function toggleRowSelection(row) {
- _grid.getData().beginUpdate();
- HandleAccordionShowHide(row);
- _grid.getData().endUpdate();
+ function toggleRowSelection(rowNumber, dataContext) {
+ if (!checkExpandableOverride(rowNumber, dataContext, _grid)) {
+ return;
+ }
+
+ _dataView.beginUpdate();
+ handleAccordionShowHide(dataContext);
+ _dataView.endUpdate();
}
- // Collapse all of the open items
+ /** Collapse all of the open items */
function collapseAll() {
+ _dataView.beginUpdate();
for (var i = _expandedRows.length - 1; i >= 0; i--) {
- collapseItem(_expandedRows[i]);
+ collapseDetailView(_expandedRows[i], true);
}
+ _dataView.endUpdate();
}
-
- // Saves the current state of the detail view
- function saveDetailView(item)
- {
- var view = $("#innerDetailView_" + item.id);
- if (view)
- {
- var html = $("#innerDetailView_" + item.id).html();
- if(html !== undefined)
- {
- item._detailContent = html;
- }
- }
- }
-
- // Colapse an Item so it is notlonger seen
- function collapseItem(item) {
-
+
+ /** Colapse an Item so it is not longer seen */
+ function collapseDetailView(item, isMultipleCollapsing) {
+ if (!isMultipleCollapsing) {
+ _dataView.beginUpdate();
+ }
// Save the details on the collapse assuming onetime loading
if (_options.loadOnce) {
- saveDetailView(item);
+ saveDetailView(item);
}
-
- item._collapsed = true;
- for (var idx = 1; idx <= item._sizePadding; idx++) {
- _dataView.deleteItem(item.id + "." + idx);
+
+ item[_keyPrefix + 'collapsed'] = true;
+ for (var idx = 1; idx <= item[_keyPrefix + 'sizePadding']; idx++) {
+ _dataView.deleteItem(item.id + '.' + idx);
}
- item._sizePadding = 0;
+ item[_keyPrefix + 'sizePadding'] = 0;
_dataView.updateItem(item.id, item);
// Remove the item from the expandedRows
_expandedRows = _expandedRows.filter(function (r) {
return r.id !== item.id;
});
+
+ if (!isMultipleCollapsing) {
+ _dataView.endUpdate();
+ }
}
- // Expand a row given the dataview item that is to be expanded
- function expandItem(item) {
- item._collapsed = false;
+ /** Expand a row given the dataview item that is to be expanded */
+ function expandDetailView(item) {
+ item[_keyPrefix + 'collapsed'] = false;
_expandedRows.push(item);
-
+
// In the case something went wrong loading it the first time such a scroll of screen before loaded
- if (!item._detailContent) item._detailViewLoaded = false;
-
+ if (!item[_keyPrefix + 'detailContent']) item[_keyPrefix + 'detailViewLoaded'] = false;
+
// display pre-loading template
- if (!item._detailViewLoaded || _options.loadOnce !== true) {
- item._detailContent = _options.preTemplate(item);
+ if (!item[_keyPrefix + 'detailViewLoaded'] || _options.loadOnce !== true) {
+ item[_keyPrefix + 'detailContent'] = _options.preTemplate(item);
} else {
_self.onAsyncResponse.notify({
- "itemDetail": item,
- "detailView": item._detailContent
+ 'item': item,
+ 'itemDetail': item,
+ 'detailView': item[_keyPrefix + 'detailContent']
}, undefined, this);
applyTemplateNewLineHeight(item);
_dataView.updateItem(item.id, item);
return;
@@ -258,198 +479,288 @@
// async server call
_options.process(item);
}
+ /** Saves the current state of the detail view */
+ function saveDetailView(item) {
+ var view = $('.' + _gridUid + ' .innerDetailView_' + item.id);
+ if (view) {
+ var html = $('.' + _gridUid + ' .innerDetailView_' + item.id).html();
+ if (html !== undefined) {
+ item[_keyPrefix + 'detailContent'] = html;
+ }
+ }
+ }
+
/**
* subscribe to the onAsyncResponse so that the plugin knows when the user server side calls finished
- * the response has to be as "args.itemDetail" with it's data back
+ * the response has to be as "args.item" (or "args.itemDetail") with it's data back
*/
- function subscribeToOnAsyncResponse() {
- _self.onAsyncResponse.subscribe(function (e, args) {
- if (!args || !args.itemDetail) {
- throw 'Slick.RowDetailView plugin requires the onAsyncResponse() to supply "args.itemDetail" property.'
+ function subscribeToOnAsyncResponse() {
+ _self.onAsyncResponse.subscribe(function (e, args) {
+ if (!args || (!args.item && !args.itemDetail)) {
+ throw 'Slick.RowDetailView plugin requires the onAsyncResponse() to supply "args.item" property.'
}
+ // we accept item/itemDetail, just get the one which has data
+ var itemDetail = args.item || args.itemDetail;
+
// If we just want to load in a view directly we can use detailView property to do so
if (args.detailView) {
- args.itemDetail._detailContent = args.detailView;
+ itemDetail[_keyPrefix + 'detailContent'] = args.detailView;
} else {
- args.itemDetail._detailContent = _options.postTemplate(args.itemDetail);
+ itemDetail[_keyPrefix + 'detailContent'] = _options.postTemplate(itemDetail);
}
- args.itemDetail._detailViewLoaded = true;
+ itemDetail[_keyPrefix + 'detailViewLoaded'] = true;
+ _dataView.updateItem(itemDetail.id, itemDetail);
- var idxParent = _dataView.getIdxById(args.itemDetail.id);
- _dataView.updateItem(args.itemDetail.id, args.itemDetail);
-
// trigger an event once the post template is finished loading
_self.onAsyncEndUpdate.notify({
- "grid": _grid,
- "itemDetail": args.itemDetail
+ 'grid': _grid,
+ 'item': itemDetail,
+ 'itemDetail': itemDetail
}, e, _self);
});
}
- function HandleAccordionShowHide(item) {
+ /** When row is getting toggled, we will handle the action of collapsing/expanding */
+ function handleAccordionShowHide(item) {
if (item) {
- if (!item._collapsed) {
- collapseItem(item);
+ if (!item[_keyPrefix + 'collapsed']) {
+ collapseDetailView(item);
} else {
- expandItem(item);
+ expandDetailView(item);
}
}
}
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
+
+ /** Get the Row Detail padding (which are the rows dedicated to the detail panel) */
var getPaddingItem = function (parent, offset) {
var item = {};
for (var prop in _grid.getData()) {
item[prop] = null;
}
- item.id = parent.id + "." + offset;
+ item.id = parent.id + '.' + offset;
- //additional hidden padding metadata fields
- item._collapsed = true;
- item._isPadding = true;
- item._parent = parent;
- item._offset = offset;
+ // additional hidden padding metadata fields
+ item[_keyPrefix + 'collapsed'] = true;
+ item[_keyPrefix + 'isPadding'] = true;
+ item[_keyPrefix + 'parent'] = parent;
+ item[_keyPrefix + 'offset'] = offset;
return item;
}
//////////////////////////////////////////////////////////////
- //create the detail ctr node. this belongs to the dev & can be custom-styled as per
+ // create the detail ctr node. this belongs to the dev & can be custom-styled as per
//////////////////////////////////////////////////////////////
function applyTemplateNewLineHeight(item) {
- // the height seems to be calculated by the template row count (how many line of items does the template have)
+ // the height is calculated by the template row count (how many line of items does the template view have)
var rowCount = _options.panelRows;
- //calculate padding requirements based on detail-content..
- //ie. worst-case: create an invisible dom node now &find it's height.
- var lineHeight = 13; //we know cuz we wrote the custom css innit ;)
- item._sizePadding = Math.ceil(((rowCount * 2) * lineHeight) / _grid.getOptions().rowHeight);
- item._height = (item._sizePadding * _grid.getOptions().rowHeight);
-
+ // calculate padding requirements based on detail-content..
+ // ie. worst-case: create an invisible dom node now & find it's height.
+ var lineHeight = 13; // we know cuz we wrote the custom css init ;)
+ item[_keyPrefix + 'sizePadding'] = Math.ceil(((rowCount * 2) * lineHeight) / _gridOptions.rowHeight);
+ item[_keyPrefix + 'height'] = (item[_keyPrefix + 'sizePadding'] * _gridOptions.rowHeight);
var idxParent = _dataView.getIdxById(item.id);
- for (var idx = 1; idx <= item._sizePadding; idx++) {
+ for (var idx = 1; idx <= item[_keyPrefix + 'sizePadding']; idx++) {
_dataView.insertItem(idxParent + idx, getPaddingItem(item, idx));
}
}
-
+ /** Get the Column Definition of the first column dedicated to toggling the Row Detail View */
function getColumnDefinition() {
return {
id: _options.columnId,
- name: "",
+ name: '',
toolTip: _options.toolTip,
- field: "sel",
+ field: 'sel',
width: _options.width,
resizable: false,
sortable: false,
+ alwaysRenderColumn: _options.alwaysRenderColumn,
cssClass: _options.cssClass,
formatter: detailSelectionFormatter
};
}
+ /** return the currently expanded rows */
+ function getExpandedRows() {
+ return _expandedRows;
+ }
+
+ /** The Formatter of the toggling icon of the Row Detail */
function detailSelectionFormatter(row, cell, value, columnDef, dataContext) {
+ if (!checkExpandableOverride(row, dataContext, grid)) {
+ return null;
+ } else {
+ if (dataContext[_keyPrefix + 'collapsed'] == undefined) {
+ dataContext[_keyPrefix + 'collapsed'] = true,
+ dataContext[_keyPrefix + 'sizePadding'] = 0, //the required number of pading rows
+ dataContext[_keyPrefix + 'height'] = 0, //the actual height in pixels of the detail field
+ dataContext[_keyPrefix + 'isPadding'] = false,
+ dataContext[_keyPrefix + 'parent'] = undefined,
+ dataContext[_keyPrefix + 'offset'] = 0
+ }
- if (dataContext._collapsed == undefined) {
- dataContext._collapsed = true,
- dataContext._sizePadding = 0, //the required number of pading rows
- dataContext._height = 0, //the actual height in pixels of the detail field
- dataContext._isPadding = false,
- dataContext._parent = undefined,
- dataContext._offset = 0
- }
+ if (dataContext[_keyPrefix + 'isPadding'] == true) {
+ // render nothing
+ }
+ else if (dataContext[_keyPrefix + 'collapsed']) {
+ var collapsedClasses = _options.cssClass + ' expand ';
+ if (_options.collapsedClass) {
+ collapsedClasses += _options.collapsedClass;
+ }
+ return '<div class="' + collapsedClasses + '"></div>';
+ }
+ else {
+ var html = [];
+ var rowHeight = _gridOptions.rowHeight;
- if (dataContext._isPadding == true) {
- //render nothing
- } else if (dataContext._collapsed) {
- return "<div class='detailView-toggle expand'></div>";
- } else {
- var html = [];
- var rowHeight = _grid.getOptions().rowHeight;
- var bottomMargin = 5;
+ var outterHeight = dataContext[_keyPrefix + 'sizePadding'] * _gridOptions.rowHeight;
+ if (_options.maxRows !== null && dataContext[_keyPrefix + 'sizePadding'] > _options.maxRows) {
+ outterHeight = _options.maxRows * rowHeight;
+ dataContext[_keyPrefix + 'sizePadding'] = _options.maxRows;
+ }
- //V313HAX:
- //putting in an extra closing div after the closing toggle div and ommiting a
- //final closing div for the detail ctr div causes the slickgrid renderer to
- //insert our detail div as a new column ;) ~since it wraps whatever we provide
- //in a generic div column container. so our detail becomes a child directly of
- //the row not the cell. nice =) ~no need to apply a css change to the parent
- //slick-cell to escape the cell overflow clipping.
+ //V313HAX:
+ //putting in an extra closing div after the closing toggle div and ommiting a
+ //final closing div for the detail ctr div causes the slickgrid renderer to
+ //insert our detail div as a new column ;) ~since it wraps whatever we provide
+ //in a generic div column container. so our detail becomes a child directly of
+ //the row not the cell. nice =) ~no need to apply a css change to the parent
+ //slick-cell to escape the cell overflow clipping.
- //sneaky extra </div> inserted here-----------------v
- html.push("<div class='detailView-toggle collapse'></div></div>");
+ //sneaky extra </div> inserted here-----------------v
+ var expandedClasses = _options.cssClass + ' collapse ';
+ if (_options.expandedClass) expandedClasses += _options.expandedClass;
+ html.push('<div class="' + expandedClasses + '"></div></div>');
- html.push("<div id='cellDetailView_", dataContext.id, "' class='dynamic-cell-detail' "); //apply custom css to detail
- html.push("style='height:", dataContext._height, "px;"); //set total height of padding
- html.push("top:", rowHeight, "px'>"); //shift detail below 1st row
- html.push("<div id='detailViewContainer_", dataContext.id, "' class='detail-container' style='max-height:" + (dataContext._height - rowHeight + bottomMargin) + "px'>"); //sub ctr for custom styling
- html.push("<div id='innerDetailView_" , dataContext.id , "'>" , dataContext._detailContent, "</div></div>");
- //&omit a final closing detail container </div> that would come next
+ html.push('<div class="dynamic-cell-detail cellDetailView_', dataContext.id, '" '); //apply custom css to detail
+ html.push('style="height:', outterHeight, 'px;'); //set total height of padding
+ html.push('top:', rowHeight, 'px">'); //shift detail below 1st row
+ html.push('<div class="detail-container detailViewContainer_', dataContext.id, '" style="min-height:' + dataContext[_keyPrefix + 'height'] + 'px">'); //sub ctr for custom styling
+ html.push('<div class="innerDetailView_', dataContext.id, '">', dataContext[_keyPrefix + 'detailContent'], '</div></div>');
+ // &omit a final closing detail container </div> that would come next
- return html.join("");
+ return html.join('');
+ }
}
return null;
}
+ /** Resize the Row Detail View */
function resizeDetailView(item) {
- if (!item) return;
-
- // Grad each of the dom items
- var mainContainer = document.getElementById('detailViewContainer_' + item.id);
- var cellItem = document.getElementById('cellDetailView_' + item.id);
- var inner = document.getElementById('innerDetailView_' + item.id);
-
- if (!mainContainer || !cellItem || !inner) return;
-
- for (var idx = 1; idx <= item._sizePadding; idx++) {
- _dataView.deleteItem(item.id + "." + idx);
+ if (!item) {
+ return;
}
-
- var rowHeight = _grid.getOptions().rowHeight; // height of a row
- var lineHeight = 13; //we know cuz we wrote the custom css innit ;)
-
- // Get the inner Item height as this will be the actual size
- var itemHeight = inner.clientHeight;
-
- // Now work out how many rows
- var rowCount = Math.ceil(itemHeight / rowHeight) + 1;
-
- item._sizePadding = Math.ceil(((rowCount * 2) * lineHeight) / rowHeight);
- item._height = (item._sizePadding * rowHeight);
-
- // If the padding is now more than the original minRowBuff we need to increase it
- if (_grid.getOptions().minRowBuffer < item._sizePadding)
- {
- // Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3
- _grid.getOptions().minRowBuffer =item._sizePadding + 3;
+
+ // Grad each of the DOM elements
+ var mainContainer = document.querySelector('.' + _gridUid + ' .detailViewContainer_' + item.id);
+ var cellItem = document.querySelector('.' + _gridUid + ' .cellDetailView_' + item.id);
+ var inner = document.querySelector('.' + _gridUid + ' .innerDetailView_' + item.id);
+
+ if (!mainContainer || !cellItem || !inner) {
+ return;
}
-
- mainContainer.setAttribute("style", "max-height: " + item._height + "px");
- if (cellItem) cellItem.setAttribute("style", "height: " + item._height + "px;top:" + rowHeight + "px");
-
+
+ for (var idx = 1; idx <= item[_keyPrefix + 'sizePadding']; idx++) {
+ _dataView.deleteItem(item.id + '.' + idx);
+ }
+
+ var rowHeight = _gridOptions.rowHeight; // height of a row
+ var lineHeight = 13; // we know cuz we wrote the custom css innit ;)
+
+ // remove the height so we can calculate the height
+ mainContainer.style.minHeight = null;
+
+ // Get the scroll height for the main container so we know the actual size of the view
+ var itemHeight = mainContainer.scrollHeight;
+
+ // Now work out how many rows
+ var rowCount = Math.ceil(itemHeight / rowHeight);
+
+ item[_keyPrefix + 'sizePadding'] = Math.ceil(((rowCount * 2) * lineHeight) / rowHeight);
+ item[_keyPrefix + 'height'] = itemHeight;
+
+ var outterHeight = (item[_keyPrefix + 'sizePadding'] * rowHeight);
+ if (_options.maxRows !== null && item[_keyPrefix + 'sizePadding'] > _options.maxRows) {
+ outterHeight = _options.maxRows * rowHeight;
+ item[_keyPrefix + 'sizePadding'] = _options.maxRows;
+ }
+
+ // If the padding is now more than the original minRowBuff we need to increase it
+ if (_grid.getOptions().minRowBuffer < item[_keyPrefix + 'sizePadding']) {
+ // Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3
+ _grid.getOptions().minRowBuffer = item[_keyPrefix + 'sizePadding'] + 3;
+ }
+
+ mainContainer.setAttribute('style', 'min-height: ' + item[_keyPrefix + 'height'] + 'px');
+ if (cellItem) cellItem.setAttribute('style', 'height: ' + outterHeight + 'px; top:' + rowHeight + 'px');
+
var idxParent = _dataView.getIdxById(item.id);
- for (var idx = 1; idx <= item._sizePadding; idx++) {
- _dataView.insertItem(idxParent + idx, getPaddingItem(item, idx));
+ for (var idx = 1; idx <= item[_keyPrefix + 'sizePadding']; idx++) {
+ _dataView.insertItem(idxParent + idx, getPaddingItem(item, idx));
}
+
+ // Lastly save the updated state
+ saveDetailView(item);
}
-
+
+ /** Takes in the item we are filtering and if it is an expanded row returns it's parents row to filter on */
+ function getFilterItem(item) {
+ if (item[_keyPrefix + 'isPadding'] && item[_keyPrefix + 'parent']) {
+ item = item[_keyPrefix + 'parent'];
+ }
+ return item;
+ }
+
+ function checkExpandableOverride(row, dataContext, grid) {
+ if (typeof _expandableOverride === 'function') {
+ return _expandableOverride(row, dataContext, grid);
+ }
+ return true;
+ }
+
+ /**
+ * Method that user can pass to override the default behavior or making every row an expandable row.
+ * In order word, user can choose which rows to be an available row detail (or not) by providing his own logic.
+ * @param overrideFn: override function callback
+ */
+ function expandableOverride(overrideFn) {
+ _expandableOverride = overrideFn;
+ }
+
$.extend(this, {
"init": init,
"destroy": destroy,
+ "pluginName": "RowDetailView",
+
"collapseAll": collapseAll,
+ "collapseDetailView": collapseDetailView,
+ "expandDetailView": expandDetailView,
+ "expandableOverride": expandableOverride,
"getColumnDefinition": getColumnDefinition,
+ "getExpandedRows": getExpandedRows,
+ "getFilterItem": getFilterItem,
"getOptions": getOptions,
+ "resizeDetailView": resizeDetailView,
+ "saveDetailView": saveDetailView,
"setOptions": setOptions,
+
+ // events
"onAsyncResponse": new Slick.Event(),
"onAsyncEndUpdate": new Slick.Event(),
"onAfterRowDetailToggle": new Slick.Event(),
"onBeforeRowDetailToggle": new Slick.Event(),
- "resizeDetailView": resizeDetailView
+ "onRowOutOfViewportRange": new Slick.Event(),
+ "onRowBackToViewportRange": new Slick.Event()
});
}
})(jQuery);