slickgrid
Advanced tools
Comparing version 2.4.0 to 2.4.1
@@ -19,2 +19,3 @@ /*** | ||
* forceFitTitle: "Force fit columns", // default to "Force fit columns" | ||
* headerColumnValueExtractor: "Extract the column label" // default to column.name | ||
* syncResizeTitle: "Synchronous resize", // default to "Synchronous resize" | ||
@@ -32,2 +33,3 @@ * } | ||
function SlickColumnPicker(columns, grid, options) { | ||
var _grid = grid; | ||
var $list; | ||
@@ -45,6 +47,10 @@ var $menu; | ||
forceFitTitle: "Force fit columns", | ||
syncResizeTitle: "Synchronous resize" | ||
syncResizeTitle: "Synchronous resize", | ||
headerColumnValueExtractor: | ||
function (columnDef) { | ||
return columnDef.name; | ||
} | ||
}; | ||
function init() { | ||
function init(grid) { | ||
grid.onHeaderContextMenu.subscribe(handleHeaderContextMenu); | ||
@@ -75,4 +81,4 @@ grid.onColumnsReordered.subscribe(updateColumnOrder); | ||
function destroy() { | ||
grid.onHeaderContextMenu.unsubscribe(handleHeaderContextMenu); | ||
grid.onColumnsReordered.unsubscribe(updateColumnOrder); | ||
_grid.onHeaderContextMenu.unsubscribe(handleHeaderContextMenu); | ||
_grid.onColumnsReordered.unsubscribe(updateColumnOrder); | ||
$(document.body).off("mousedown", handleBodyMouseDown); | ||
@@ -96,2 +102,3 @@ $("div.slick-columnpicker").hide(options.fadeSpeed); | ||
var $li, $input; | ||
var columnLabel; | ||
for (var i = 0; i < columns.length; i++) { | ||
@@ -102,10 +109,16 @@ $li = $("<li />").appendTo($list); | ||
if (grid.getColumnIndex(columns[i].id) != null) { | ||
if (_grid.getColumnIndex(columns[i].id) != null) { | ||
$input.attr("checked", "checked"); | ||
} | ||
if (options.columnPicker.headerColumnValueExtractor) { | ||
columnLabel = options.columnPicker.headerColumnValueExtractor(columns[i]); | ||
} else { | ||
columnLabel = defaults.headerColumnValueExtractor(columns[i]); | ||
} | ||
$("<label />") | ||
.html(columns[i].name) | ||
.prepend($input) | ||
.appendTo($li); | ||
.html(columnLabel) | ||
.prepend($input) | ||
.appendTo($li); | ||
} | ||
@@ -125,3 +138,3 @@ | ||
.appendTo($li); | ||
if (grid.getOptions().forceFitColumns) { | ||
if (_grid.getOptions().forceFitColumns) { | ||
$input.attr("checked", "checked"); | ||
@@ -139,3 +152,3 @@ } | ||
.appendTo($li); | ||
if (grid.getOptions().syncColumnCellResize) { | ||
if (_grid.getOptions().syncColumnCellResize) { | ||
$input.attr("checked", "checked"); | ||
@@ -161,6 +174,6 @@ } | ||
// of the current column sort. | ||
var current = grid.getColumns().slice(0); | ||
var current = _grid.getColumns().slice(0); | ||
var ordered = new Array(columns.length); | ||
for (var i = 0; i < ordered.length; i++) { | ||
if ( grid.getColumnIndex(columns[i].id) === undefined ) { | ||
if ( _grid.getColumnIndex(columns[i].id) === undefined ) { | ||
// If the column doesn't return a value from getColumnIndex, | ||
@@ -180,6 +193,6 @@ // it is hidden. Leave it in this position. | ||
if (e.target.checked) { | ||
grid.setOptions({forceFitColumns:true}); | ||
grid.autosizeColumns(); | ||
_grid.setOptions({forceFitColumns:true}); | ||
_grid.autosizeColumns(); | ||
} else { | ||
grid.setOptions({forceFitColumns:false}); | ||
_grid.setOptions({forceFitColumns:false}); | ||
} | ||
@@ -191,5 +204,5 @@ return; | ||
if (e.target.checked) { | ||
grid.setOptions({syncColumnCellResize:true}); | ||
_grid.setOptions({syncColumnCellResize:true}); | ||
} else { | ||
grid.setOptions({syncColumnCellResize:false}); | ||
_grid.setOptions({syncColumnCellResize:false}); | ||
} | ||
@@ -212,4 +225,4 @@ return; | ||
grid.setColumns(visibleColumns); | ||
onColumnsChanged.notify({columns: visibleColumns, grid: grid}); | ||
_grid.setColumns(visibleColumns); | ||
onColumnsChanged.notify({columns: visibleColumns, grid: _grid}); | ||
} | ||
@@ -222,5 +235,6 @@ } | ||
init(); | ||
init(_grid); | ||
return { | ||
"init": init, | ||
"getAllColumns": getAllColumns, | ||
@@ -227,0 +241,0 @@ "destroy": destroy, |
@@ -22,3 +22,3 @@ /*** | ||
* menuWidth: 18, // width that will be use to resize the column header container (18 by default) | ||
* resizeOnShowHeaderRow: true, // true by default | ||
* resizeOnShowHeaderRow: false, // false by default | ||
* | ||
@@ -98,2 +98,4 @@ * // the last 2 checkboxes titles | ||
var _grid = grid; | ||
var _gridOptions; | ||
var _gridUid = (grid && grid.getUID) ? grid.getUID() : ''; | ||
var _isMenuOpen = false; | ||
@@ -103,2 +105,3 @@ var _options = options; | ||
var $list; | ||
var $button; | ||
var $menu; | ||
@@ -108,3 +111,3 @@ var columnCheckboxes; | ||
hideForceFitButton: false, | ||
hideSyncResizeButton: false, | ||
hideSyncResizeButton: false, | ||
fadeSpeed: 250, | ||
@@ -118,14 +121,23 @@ forceFitTitle: "Force fit columns", | ||
function init(grid) { | ||
_gridOptions = grid.getOptions(); | ||
var gridMenuWidth = (_options.gridMenu && _options.gridMenu.menuWidth) || _defaults.menuWidth; | ||
var $header = $('.slick-header'); | ||
var $header; | ||
if (_gridOptions && _gridOptions.frozenColumn && _gridOptions.frozenColumn > 0 ) { | ||
$header = $('.' + _gridUid + ' .slick-header-right'); | ||
} else { | ||
$header = $('.' + _gridUid + ' .slick-header-left'); | ||
} | ||
$header.attr('style', 'width: calc(100% - ' + gridMenuWidth +'px)'); | ||
// subscribe to the grid, when it's destroyed, we should also destroy the Grid Menu | ||
grid.onBeforeDestroy.subscribe(destroy); | ||
// if header row is enabled, we need to resize it's width also | ||
var enableResizeHeaderRow = (_options.gridMenu && _options.gridMenu.resizeOnShowHeaderRow != undefined) ? _options.gridMenu.resizeOnShowHeaderRow : _defaults.resizeOnShowHeaderRow; | ||
if(enableResizeHeaderRow) { | ||
var $headerrow = $('.slick-headerrow'); | ||
if(enableResizeHeaderRow && _options.showHeaderRow) { | ||
var $headerrow = $('.' + _gridUid + '.slick-headerrow'); | ||
$headerrow.attr('style', 'width: calc(100% - ' + gridMenuWidth +'px)'); | ||
} | ||
var $button = $('<button class="slick-gridmenu-button"/>'); | ||
$button = $('<button class="slick-gridmenu-button"/>'); | ||
if (_options.gridMenu && _options.gridMenu.iconCssClass) { | ||
@@ -156,3 +168,3 @@ $button.addClass(_options.gridMenu.iconCssClass); | ||
// Hide the menu on outside click. | ||
$(document.body).on("mousedown", handleBodyMouseDown); | ||
$(document.body).on("mousedown." + _gridUid, handleBodyMouseDown); | ||
@@ -163,3 +175,3 @@ // destroy the picker if user leaves the page | ||
// add on click handler for the Grid Menu itself | ||
$button.on("click", showGridMenu); | ||
$button.on("click." + _gridUid, showGridMenu); | ||
} | ||
@@ -173,5 +185,7 @@ | ||
_grid.onColumnsReordered.unsubscribe(updateColumnOrder); | ||
$(document.body).off("mousedown", handleBodyMouseDown); | ||
_grid.onBeforeDestroy.unsubscribe(); | ||
$(document.body).off("mousedown." + _gridUid, handleBodyMouseDown); | ||
$("div.slick-gridmenu").hide(_options.fadeSpeed); | ||
$menu.remove(); | ||
$button.remove(); | ||
} | ||
@@ -306,2 +320,3 @@ | ||
$list.appendTo($menu); | ||
_isMenuOpen = true; | ||
} | ||
@@ -312,4 +327,2 @@ | ||
hideMenu(e); | ||
} else { | ||
_isMenuOpen = true; | ||
} | ||
@@ -316,0 +329,0 @@ } |
{ | ||
"name": "slickgrid", | ||
"version": "2.4.0", | ||
"version": "2.4.1", | ||
"description": "A lightning fast JavaScript grid/spreadsheet", | ||
@@ -5,0 +5,0 @@ "main": "slick.core.js", |
@@ -31,2 +31,3 @@ (function ($) { | ||
readOnlyMode: suppresses paste | ||
headerColumnValueExtractor : option to specify a custom column header value extractor function | ||
*/ | ||
@@ -71,8 +72,17 @@ var _grid; | ||
function getDataItemValueForColumn(item, columnDef) { | ||
function getHeaderValueForColumn(columnDef) { | ||
if (_options.headerColumnValueExtractor) { | ||
var val = _options.headerColumnValueExtractor(columnDef); | ||
if (val) { return val; } | ||
} | ||
return columnDef.name; | ||
} | ||
function getDataItemValueForColumn(item, columnDef, e) { | ||
if (_options.dataItemColumnValueExtractor) { | ||
var dataItemColumnValueExtractorValue = _options.dataItemColumnValueExtractor(item, columnDef); | ||
var val = _options.dataItemColumnValueExtractor(item, columnDef); | ||
if (dataItemColumnValueExtractorValue) | ||
return dataItemColumnValueExtractorValue; | ||
if (val) { return val; } | ||
} | ||
@@ -88,3 +98,4 @@ | ||
'position':{'top':0, 'left':0}, // a dummy position required by some editors | ||
'grid':_grid | ||
'grid':_grid, | ||
'event':e | ||
}; | ||
@@ -354,3 +365,3 @@ var editor = new columnDef.editor(editorArgs); | ||
if (columns[j].name.length > 0) | ||
clipTextHeaders.push(columns[j].name); | ||
clipTextHeaders.push(getHeaderValueForColumn(columns[j])); | ||
} | ||
@@ -361,3 +372,3 @@ clipTextRows.push(clipTextHeaders.join("\t")); | ||
for (var j=range.fromCell; j< range.toCell+1 ; j++){ | ||
clipTextCells.push(getDataItemValueForColumn(dt, columns[j])); | ||
clipTextCells.push(getDataItemValueForColumn(dt, columns[j], e)); | ||
} | ||
@@ -446,2 +457,6 @@ clipTextRows.push(clipTextCells.join("\t")); | ||
function setIncludeHeaderWhenCopying(includeHeaderWhenCopying) { | ||
_options.includeHeaderWhenCopying = includeHeaderWhenCopying; | ||
} | ||
$.extend(this, { | ||
@@ -455,5 +470,6 @@ "init": init, | ||
"onCopyCancelled": new Slick.Event(), | ||
"onPasteCells": new Slick.Event() | ||
"onPasteCells": new Slick.Event(), | ||
"setIncludeHeaderWhenCopying" : setIncludeHeaderWhenCopying | ||
}); | ||
} | ||
})(jQuery); | ||
})(jQuery); |
@@ -44,3 +44,3 @@ (function ($) { | ||
.css("position", "absolute") | ||
.appendTo(grid.getCanvasNode()); | ||
.appendTo(grid.getActiveCanvasNode()); | ||
} | ||
@@ -47,0 +47,0 @@ |
@@ -13,2 +13,4 @@ (function ($) { | ||
var _canvas; | ||
var _gridOptions; | ||
var _$activeCanvas; | ||
var _dragging; | ||
@@ -24,2 +26,8 @@ var _decorator; | ||
// Frozen row & column variables | ||
var _rowOffset; | ||
var _columnOffset; | ||
var _isRightCanvas; | ||
var _isBottomCanvas; | ||
function init(grid) { | ||
@@ -30,2 +38,3 @@ options = $.extend(true, {}, _defaults, options); | ||
_canvas = _grid.getCanvasNode(); | ||
_gridOptions = _grid.getOptions(); | ||
_handler | ||
@@ -47,2 +56,22 @@ .subscribe(_grid.onDragInit, handleDragInit) | ||
function handleDragInit(e, dd) { | ||
// Set the active canvas node because the decorator needs to append its | ||
// box to the correct canvas | ||
_$activeCanvas = $( _grid.getActiveCanvasNode( e ) ); | ||
var c = _$activeCanvas.offset(); | ||
_rowOffset = 0; | ||
_columnOffset = 0; | ||
_isBottomCanvas = _$activeCanvas.hasClass( 'grid-canvas-bottom' ); | ||
if ( _gridOptions.frozenRow > -1 && _isBottomCanvas ) { | ||
_rowOffset = ( _gridOptions.frozenBottom ) ? $('.grid-canvas-bottom').height() : $('.grid-canvas-top').height(); | ||
} | ||
_isRightCanvas = _$activeCanvas.hasClass( 'grid-canvas-right' ); | ||
if ( _gridOptions.frozenColumn > -1 && _isRightCanvas ) { | ||
_columnOffset = $('.grid-canvas-left').width(); | ||
} | ||
// prevent the grid from cancelling drag'n'drop by default | ||
@@ -82,11 +111,24 @@ e.stopImmediatePropagation(); | ||
var end = _grid.getCellFromPoint( | ||
e.pageX - $(_canvas).offset().left, | ||
e.pageY - $(_canvas).offset().top); | ||
e.pageX - _$activeCanvas.offset().left + _columnOffset, | ||
e.pageY - _$activeCanvas.offset().top + _rowOffset | ||
); | ||
if (!_grid.canCellBeSelected(end.row, end.cell)) { | ||
return; | ||
if (_gridOptions.frozenColumn < 0) { | ||
if (!_grid.canCellBeSelected(end.row, end.cell)) { | ||
return; | ||
} | ||
} else { | ||
// when having frozen column(s), we need to do extra checks | ||
if ( (!_grid.canCellBeSelected( end.row, end.cell ) ) | ||
|| ( !_isRightCanvas && ( end.cell > _gridOptions.frozenColumn ) ) | ||
|| ( _isRightCanvas && ( end.cell <= _gridOptions.frozenColumn ) ) | ||
|| ( !_isBottomCanvas && ( end.row >= _gridOptions.frozenRow ) ) | ||
|| ( _isBottomCanvas && ( end.row < _gridOptions.frozenRow ) ) | ||
) { | ||
return; | ||
} | ||
} | ||
dd.range.end = end; | ||
_currentlySelectedRange = dd.range; | ||
_decorator.show(new Slick.Range(dd.range.start.row, dd.range.start.cell, end.row, end.cell)); | ||
@@ -128,2 +170,2 @@ } | ||
} | ||
})(jQuery); | ||
})(jQuery); |
@@ -63,2 +63,20 @@ (function ($) { | ||
function rangesAreEqual(range1, range2) { | ||
var areDifferent = (range1.length !== range2.length); | ||
if (!areDifferent) { | ||
for (var i = 0; i < range1.length; i++) { | ||
if ( | ||
range1[i].fromCell !== range2[i].fromCell | ||
|| range1[i].fromRow !== range2[i].fromRow | ||
|| range1[i].toCell !== range2[i].toCell | ||
|| range1[i].toRow !== range2[i].toRow | ||
) { | ||
areDifferent = true; | ||
break; | ||
} | ||
} | ||
} | ||
return !areDifferent; | ||
} | ||
function setSelectedRanges(ranges) { | ||
@@ -68,4 +86,7 @@ // simple check for: empty selection didn't change, prevent firing onSelectedRangesChanged | ||
// if range has not changed, don't fire onSelectedRangesChanged | ||
var rangeHasChanged = !rangesAreEqual(_ranges, ranges); | ||
_ranges = removeInvalidRanges(ranges); | ||
_self.onSelectedRangesChanged.notify(_ranges); | ||
if (rangeHasChanged) { _self.onSelectedRangesChanged.notify(_ranges); } | ||
} | ||
@@ -72,0 +93,0 @@ |
@@ -12,3 +12,3 @@ (function ($) { | ||
var _grid; | ||
var _self = this; | ||
var _selectAll_UID = createUID(); | ||
var _handler = new Slick.EventHandler(); | ||
@@ -19,5 +19,9 @@ var _selectedRowsLookup = {}; | ||
cssClass: null, | ||
hideSelectAllCheckbox: false, | ||
toolTip: "Select/Deselect All", | ||
width: 30 | ||
width: 30, | ||
hideInColumnTitleRow: false, | ||
hideInFilterHeaderRow: true | ||
}; | ||
var _isSelectAllChecked = false; | ||
@@ -29,6 +33,12 @@ var _options = $.extend(true, {}, _defaults, options); | ||
_handler | ||
.subscribe(_grid.onSelectedRowsChanged, handleSelectedRowsChanged) | ||
.subscribe(_grid.onClick, handleClick) | ||
.subscribe(_grid.onHeaderClick, handleHeaderClick) | ||
.subscribe(_grid.onKeyDown, handleKeyDown); | ||
.subscribe(_grid.onSelectedRowsChanged, handleSelectedRowsChanged) | ||
.subscribe(_grid.onClick, handleClick) | ||
.subscribe(_grid.onKeyDown, handleKeyDown); | ||
if (!_options.hideInFilterHeaderRow) { | ||
addCheckboxToFilterHeaderRow(grid); | ||
} | ||
if (!_options.hideInColumnTitleRow) { | ||
_handler.subscribe(_grid.onHeaderClick, handleHeaderClick) | ||
} | ||
} | ||
@@ -40,4 +50,43 @@ | ||
function getOptions() { | ||
return _options; | ||
} | ||
function setOptions(options) { | ||
_options = $.extend(true, {}, _options, options); | ||
if (_options.hideSelectAllCheckbox) { | ||
hideSelectAllFromColumnHeaderTitleRow(); | ||
hideSelectAllFromColumnHeaderFilterRow(); | ||
} else { | ||
if (!_options.hideInColumnTitleRow) { | ||
if (_isSelectAllChecked) { | ||
_grid.updateColumnHeader(_options.columnId, "<input id='header-selector" + _selectAll_UID + "' type='checkbox' checked='checked'><label for='header-selector" + _selectAll_UID + "'></label>", _options.toolTip); | ||
} else { | ||
_grid.updateColumnHeader(_options.columnId, "<input id='header-selector" + _selectAll_UID + "' type='checkbox'><label for='header-selector" + _selectAll_UID + "'></label>", _options.toolTip); | ||
} | ||
_handler.subscribe(_grid.onHeaderClick, handleHeaderClick); | ||
} else { | ||
hideSelectAllFromColumnHeaderTitleRow(); | ||
} | ||
if (!_options.hideInFilterHeaderRow) { | ||
var selectAllContainer = $("#filter-checkbox-selectall-container"); | ||
selectAllContainer.show(); | ||
selectAllContainer.find('input[type="checkbox"]').prop("checked", _isSelectAllChecked); | ||
} else { | ||
hideSelectAllFromColumnHeaderFilterRow(); | ||
} | ||
} | ||
} | ||
function hideSelectAllFromColumnHeaderTitleRow() { | ||
_grid.updateColumnHeader(_options.columnId, "", ""); | ||
} | ||
function hideSelectAllFromColumnHeaderFilterRow() { | ||
$("#filter-checkbox-selectall-container").hide(); | ||
} | ||
function handleSelectedRowsChanged(e, args) { | ||
var UID = createUID(); | ||
var selectedRows = _grid.getSelectedRows(); | ||
@@ -58,7 +107,14 @@ var lookup = {}, row, i; | ||
_grid.render(); | ||
_isSelectAllChecked = selectedRows.length && selectedRows.length == _grid.getDataLength(); | ||
if (selectedRows.length && selectedRows.length == _grid.getDataLength()) { | ||
_grid.updateColumnHeader(_options.columnId, "<input id='header-selector" + UID + "' type='checkbox' checked='checked'><label for='header-selector" + UID + "'></label>", _options.toolTip); | ||
} else { | ||
_grid.updateColumnHeader(_options.columnId, "<input id='header-selector" + UID + "' type='checkbox'><label for='header-selector" + UID + "'></label>", _options.toolTip); | ||
if (!_options.hideInColumnTitleRow && !_options.hideSelectAllCheckbox) { | ||
if (_isSelectAllChecked) { | ||
_grid.updateColumnHeader(_options.columnId, "<input id='header-selector" + _selectAll_UID + "' type='checkbox' checked='checked'><label for='header-selector" + _selectAll_UID + "'></label>", _options.toolTip); | ||
} else { | ||
_grid.updateColumnHeader(_options.columnId, "<input id='header-selector" + _selectAll_UID + "' type='checkbox'><label for='header-selector" + _selectAll_UID + "'></label>", _options.toolTip); | ||
} | ||
} | ||
if (!_options.hideInFilterHeaderRow) { | ||
var selectAllElm = $("#header-filter-selector" + _selectAll_UID); | ||
selectAllElm.prop("checked", _isSelectAllChecked); | ||
} | ||
@@ -169,7 +225,5 @@ } | ||
function getColumnDefinition() { | ||
var UID = createUID(); | ||
return { | ||
id: _options.columnId, | ||
name: "<input id='header-selector" + UID + "' type='checkbox'><label for='header-selector" + UID + "'></label>", | ||
name: (_options.hideSelectAllCheckbox || _options.hideInColumnTitleRow) ? "" : "<input id='header-selector" + _selectAll_UID + "' type='checkbox'><label for='header-selector" + _selectAll_UID + "'></label>", | ||
toolTip: _options.toolTip, | ||
@@ -181,2 +235,3 @@ field: "sel", | ||
cssClass: _options.cssClass, | ||
hideSelectAllCheckbox: _options.hideSelectAllCheckbox, | ||
formatter: checkboxSelectionFormatter | ||
@@ -186,2 +241,15 @@ }; | ||
function addCheckboxToFilterHeaderRow(grid) { | ||
grid.onHeaderRowCellRendered.subscribe(function(e, args) { | ||
if (args.column.field === "sel") { | ||
$(args.node).empty(); | ||
$("<span id='filter-checkbox-selectall-container'><input id='header-filter-selector" + _selectAll_UID + "' type='checkbox'><label for='header-filter-selector" + _selectAll_UID + "'></label></span>") | ||
.appendTo(args.node) | ||
.on('click', function(evnt) { | ||
handleHeaderClick(evnt, args) | ||
}); | ||
} | ||
}); | ||
} | ||
function createUID() { | ||
@@ -207,5 +275,7 @@ return Math.round(10000000 * Math.random()); | ||
"selectRows": selectRows, | ||
"getColumnDefinition": getColumnDefinition | ||
"getColumnDefinition": getColumnDefinition, | ||
"getOptions": getOptions, | ||
"setOptions": setOptions, | ||
}); | ||
} | ||
})(jQuery); |
@@ -19,9 +19,30 @@ /** | ||
/** | ||
* DraggableGrouping plugin to show/hide tooltips when columns are too narrow to fit content. | ||
* @constructor | ||
* @param {boolean} [options.enableForCells=true] - Enable tooltip for grid cells | ||
* @param {boolean} [options.enableForHeaderCells=false] - Enable tooltip for header cells | ||
* @param {number} [options.maxToolTipLength=null] - The maximum length for a tooltip | ||
/*** | ||
* A plugin to add Draggable Grouping feature. | ||
* | ||
* USAGE: | ||
* | ||
* Add the plugin .js & .css files and register it with the grid. | ||
* | ||
* | ||
* The plugin expose the following methods: | ||
* destroy: used to destroy the plugin | ||
* setDroppedGroups: provide option to set default grouping on loading | ||
* clearDroppedGroups: provide option to clear grouping | ||
* getSetupColumnReorder: its function to setup draggable feature agains Header Column, should be passed on grid option. Also possible to pass custom function | ||
* | ||
* | ||
* The plugin expose the following event(s): | ||
* onGroupChanged: pass the grouped columns to who subscribed. | ||
* | ||
* @param options {Object} Options: | ||
* deleteIconCssClass: an extra CSS class to add to the delete button (default undefined), if deleteIconCssClass && deleteIconImage undefined then slick-groupby-remove-image class will be added | ||
* deleteIconImage: a url to the delete button image (default undefined) | ||
* groupIconCssClass: an extra CSS class to add to the grouping field hint (default undefined) | ||
* groupIconImage: a url to the grouping field hint image (default undefined) | ||
* dropPlaceHolderText: option to specify set own placeholder note text | ||
* | ||
*/ | ||
function DraggableGrouping(options) { | ||
@@ -34,6 +55,8 @@ var _grid; | ||
var dropboxPlaceholder; | ||
var groupToggler; | ||
var _self = this; | ||
var _defaults = { | ||
}; | ||
var onGroupChanged = new Slick.Event(); | ||
/** | ||
@@ -50,8 +73,72 @@ * Initialize plugin. | ||
dropbox = $(_grid.getPreHeaderPanel()); | ||
dropbox.html("<div class='slick-placeholder'>Drop a column header here to group by the column</div>"); | ||
var dropPlaceHolderText = options.dropPlaceHolderText || 'Drop a column header here to group by the column'; | ||
dropbox.html("<div class='slick-placeholder'>" + dropPlaceHolderText + "</div><div class='slick-group-toggle-all expanded' style='display:none'></div>"); | ||
dropboxPlaceholder = dropbox.find(".slick-placeholder"); | ||
dropboxPlaceholder = dropbox.find(".slick-placeholder"); | ||
groupToggler = dropbox.find(".slick-group-toggle-all"); | ||
setupColumnDropbox(); | ||
_grid.onHeaderCellRendered.subscribe(function (e, args) { | ||
var column = args.column; | ||
var node = args.node; | ||
if (!$.isEmptyObject(column.grouping)) { | ||
var groupableIcon = $("<span class='slick-column-groupable' />"); | ||
if(options.groupIconCssClass) groupableIcon.addClass(options.groupIconCssClass) | ||
if(options.groupIconImage) groupableIcon.css("background", "url(" + options.groupIconImage + ") no-repeat center center"); | ||
$(node).css('cursor', 'pointer').append(groupableIcon); | ||
} | ||
}) | ||
for (var i = 0; i < _gridColumns.length; i++) { | ||
var columnId = _gridColumns[i].field; | ||
_grid.updateColumnHeader(columnId); | ||
} | ||
} | ||
function setupColumnReorder(grid, $headers, headerColumnWidthDiff, setColumns, setupColumnResize, columns, getColumnIndex, uid, trigger) { | ||
$headers.filter(":ui-sortable").sortable("destroy"); | ||
var $headerDraggableGroupBy = $(grid.getPreHeaderPanel()); | ||
$headers.sortable({ | ||
distance: 3, | ||
cursor: "default", | ||
tolerance: "intersection", | ||
helper: "clone", | ||
placeholder: "slick-sortable-placeholder ui-state-default slick-header-column", | ||
forcePlaceholderSize: true, | ||
appendTo: "body", | ||
start: function(e, ui) { | ||
$(ui.helper).addClass("slick-header-column-active"); | ||
$headerDraggableGroupBy.find(".slick-placeholder").show(); | ||
$headerDraggableGroupBy.find(".slick-dropped-grouping").hide(); | ||
}, | ||
beforeStop: function(e, ui) { | ||
$(ui.helper).removeClass("slick-header-column-active"); | ||
var hasDroppedColumn = $headerDraggableGroupBy.find(".slick-dropped-grouping").length; | ||
if(hasDroppedColumn > 0){ | ||
$headerDraggableGroupBy.find(".slick-placeholder").hide(); | ||
$headerDraggableGroupBy.find(".slick-dropped-grouping").show(); | ||
} | ||
}, | ||
stop: function(e) { | ||
if (!grid.getEditorLock().commitCurrentEdit()) { | ||
$(this).sortable("cancel"); | ||
return; | ||
} | ||
var reorderedIds = $headers.sortable("toArray"); | ||
var reorderedColumns = []; | ||
for (var i = 0; i < reorderedIds.length; i++) { | ||
reorderedColumns.push(columns[getColumnIndex(reorderedIds[i].replace(uid, ""))]); | ||
} | ||
setColumns(reorderedColumns); | ||
trigger(grid.onColumnsReordered, { | ||
grid: grid | ||
}); | ||
e.stopPropagation(); | ||
setupColumnResize(); | ||
} | ||
}); | ||
} | ||
/** | ||
@@ -61,2 +148,3 @@ * Destroy plugin. | ||
function destroy() { | ||
onGroupChanged.unsubscribe(); | ||
} | ||
@@ -112,6 +200,18 @@ | ||
columnsGroupBy = newGroupingOrder; | ||
updateGroupBy(); | ||
updateGroupBy("sort-group"); | ||
} | ||
}); | ||
emptyDropbox = dropbox.html(); | ||
groupToggler.on('click', function(e) { | ||
if (this.classList.contains('collapsed')) { | ||
this.classList.remove('collapsed'); | ||
this.classList.add('expanded'); | ||
_dataView.expandAllGroups(); | ||
} else { | ||
this.classList.add('collapsed'); | ||
this.classList.remove('expanded'); | ||
_dataView.collapseAllGroups(); | ||
} | ||
}); | ||
} | ||
@@ -133,6 +233,12 @@ | ||
if (e.id == columnid) { | ||
if (e.grouping != null) { | ||
if (e.grouping != null && !$.isEmptyObject(e.grouping)) { | ||
var entry = $("<div id='" + _gridUid + e.id + "_entry' data-id='" + e.id + "' class='slick-dropped-grouping'>"); | ||
var span = $("<span class='slick-groupby-remove'></span>").text(column.text() + " ") | ||
span.appendTo(entry); | ||
var groupText = $("<div style='display: inline-flex'>" + column.text() + "</div>") | ||
groupText.appendTo(entry); | ||
var groupRemoveIcon = $("<div class='slick-groupby-remove'> </div>") | ||
if(options.deleteIconCssClass) groupRemoveIcon.addClass(options.deleteIconCssClass); | ||
if(options.deleteIconImage) groupRemoveIcon.css("background", "url(" + options.deleteIconImage + ") no-repeat center right"); | ||
if(!options.deleteIconCssClass && !options.deleteIconImage) groupRemoveIcon.addClass('slick-groupby-remove-image'); | ||
groupRemoveIcon.appendTo(entry); | ||
$("</div>").appendTo(entry); | ||
@@ -145,2 +251,3 @@ entry.appendTo(container); | ||
}); | ||
groupToggler.css('display', 'block'); | ||
} | ||
@@ -151,3 +258,3 @@ } | ||
columnsGroupBy.push(column); | ||
updateGroupBy(); | ||
updateGroupBy("add-group"); | ||
} | ||
@@ -157,3 +264,3 @@ | ||
var text = entry; | ||
$("#" + _gridUid + id + "_entry").on('click', function() { | ||
$("#" + _gridUid + id + "_entry >.slick-groupby-remove").on('click', function() { | ||
$(this).off('click'); | ||
@@ -164,4 +271,16 @@ removeGroupBy(id, column, text); | ||
function setDroppedGroups(groupingInfo) { | ||
groupingInfos = (groupingInfo instanceof Array) ? groupingInfo : [groupingInfo]; | ||
dropboxPlaceholder.hide() | ||
for (var i = 0; i < groupingInfos.length; i++) { | ||
var column = $(_grid.getHeaderColumn(groupingInfos[i])); | ||
handleGroupByDrop(dropbox, column); | ||
} | ||
} | ||
function clearDroppedGroups() { | ||
columnsGroupBy = []; | ||
updateGroupBy("clear-all"); | ||
dropbox.find(".slick-dropped-grouping").remove(); | ||
groupToggler.css("display", "none"); | ||
dropboxPlaceholder.show() | ||
} | ||
@@ -192,8 +311,9 @@ | ||
} | ||
updateGroupBy(); | ||
updateGroupBy("remove-group"); | ||
} | ||
function updateGroupBy() { | ||
function updateGroupBy(originator) { | ||
if (columnsGroupBy.length == 0) { | ||
_dataView.setGrouping([]); | ||
onGroupChanged.notify({ caller: originator, groupColumns: [] }); | ||
return; | ||
@@ -209,2 +329,3 @@ } | ||
*/ | ||
onGroupChanged.notify({ caller: originator, groupColumns: groupingArray}) | ||
} | ||
@@ -216,5 +337,8 @@ | ||
"destroy": destroy, | ||
"clearDroppedGroups": clearDroppedGroups | ||
"onGroupChanged": onGroupChanged, | ||
"setDroppedGroups": setDroppedGroups, | ||
"clearDroppedGroups": clearDroppedGroups, | ||
"getSetupColumnReorder": setupColumnReorder, | ||
}); | ||
} | ||
})(jQuery); | ||
})(jQuery); |
@@ -6,17 +6,21 @@ /*** | ||
* 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: | ||
@@ -26,27 +30,51 @@ * init: initiliaze the plugin | ||
* 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 | ||
*/ | ||
@@ -63,22 +91,48 @@ (function ($) { | ||
/** Constructor of the Row Detail View Plugin */ | ||
function RowDetailView(options) { | ||
var _grid; | ||
var _gridOptions; | ||
var _gridUid; | ||
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, | ||
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; | ||
@@ -88,12 +142,37 @@ | ||
.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() { | ||
@@ -105,15 +184,32 @@ _handler.unsubscribeAll(); | ||
_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) { | ||
// 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 | ||
@@ -130,4 +226,4 @@ if (_grid.getEditorLock().isActive() && !_grid.getEditorLock().commitCurrentEdit()) { | ||
_self.onBeforeRowDetailToggle.notify({ | ||
"grid": _grid, | ||
"item": item | ||
'grid': _grid, | ||
'item': item | ||
}, e, _self); | ||
@@ -139,4 +235,5 @@ | ||
_self.onAfterRowDetailToggle.notify({ | ||
"grid": _grid, | ||
"item": item | ||
'grid': _grid, | ||
'item': item, | ||
'expandedRows': _expandedRows, | ||
}, e, _self); | ||
@@ -149,86 +246,193 @@ | ||
// 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(); | ||
_dataView.beginUpdate(); | ||
handleAccordionShowHide(row); | ||
_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); | ||
@@ -240,19 +444,24 @@ | ||
}); | ||
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); | ||
@@ -272,28 +481,41 @@ applyTemplateNewLineHeight(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); | ||
@@ -303,8 +525,9 @@ }); | ||
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); | ||
} | ||
@@ -316,2 +539,4 @@ } | ||
////////////////////////////////////////////////////////////// | ||
/** Get the Row Detail padding (which are the rows dedicated to the detail panel) */ | ||
var getPaddingItem = function (parent, offset) { | ||
@@ -323,9 +548,9 @@ var item = {}; | ||
} | ||
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; | ||
@@ -336,16 +561,15 @@ 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)); | ||
@@ -355,9 +579,9 @@ } | ||
/** 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, | ||
@@ -371,22 +595,38 @@ resizable: false, | ||
/** 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 (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 | ||
} | ||
if (dataContext._isPadding == true) { | ||
//render nothing | ||
} else if (dataContext._collapsed) { | ||
return "<div class='detailView-toggle expand'></div>"; | ||
} else { | ||
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 = _grid.getOptions().rowHeight; | ||
var bottomMargin = 5; | ||
var rowHeight = _gridOptions.rowHeight; | ||
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: | ||
@@ -401,12 +641,14 @@ //putting in an extra closing div after the closing toggle div and ommiting a | ||
//sneaky extra </div> inserted here-----------------v | ||
html.push("<div class='detailView-toggle collapse'></div></div>"); | ||
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(''); | ||
} | ||
@@ -416,44 +658,68 @@ 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; | ||
} | ||
$.extend(this, { | ||
@@ -463,5 +729,13 @@ "init": init, | ||
"collapseAll": collapseAll, | ||
"collapseDetailView": collapseDetailView, | ||
"expandDetailView": expandDetailView, | ||
"getColumnDefinition": getColumnDefinition, | ||
"getExpandedRows": getExpandedRows, | ||
"getFilterItem": getFilterItem, | ||
"getOptions": getOptions, | ||
"resizeDetailView": resizeDetailView, | ||
"saveDetailView": saveDetailView, | ||
"setOptions": setOptions, | ||
// events | ||
"onAsyncResponse": new Slick.Event(), | ||
@@ -471,5 +745,6 @@ "onAsyncEndUpdate": new Slick.Event(), | ||
"onBeforeRowDetailToggle": new Slick.Event(), | ||
"resizeDetailView": resizeDetailView | ||
"onRowOutOfViewportRange": new Slick.Event(), | ||
"onRowBackToViewportRange": new Slick.Event() | ||
}); | ||
} | ||
})(jQuery); |
## This is the 6pac SlickGrid repo | ||
Check out the NEW SlickGrid Website! http://slickgrid.net/ | ||
This is the acknowledged most active non-customised fork of SlickGrid. | ||
@@ -9,2 +11,2 @@ | ||
Also check out the [wiki](https://github.com/6pac/SlickGrid/wiki) for news and documentation. | ||
Also check out the [wiki](https://github.com/6pac/SlickGrid/wiki) for news and documentation. |
@@ -27,4 +27,6 @@ /*** | ||
"GlobalEditorLock": new EditorLock(), | ||
"TreeColumns": TreeColumns, | ||
"keyCode": { | ||
SPACE: 8, | ||
BACKSPACE: 8, | ||
@@ -492,4 +494,176 @@ DELETE: 46, | ||
} | ||
/** | ||
* | ||
* @param {Array} treeColumns Array com levels of columns | ||
* @returns {{hasDepth: 'hasDepth', getTreeColumns: 'getTreeColumns', extractColumns: 'extractColumns', getDepth: 'getDepth', getColumnsInDepth: 'getColumnsInDepth', getColumnsInGroup: 'getColumnsInGroup', visibleColumns: 'visibleColumns', filter: 'filter', reOrder: reOrder}} | ||
* @constructor | ||
*/ | ||
function TreeColumns(treeColumns) { | ||
var columnsById = {}; | ||
function init() { | ||
mapToId(treeColumns); | ||
} | ||
function mapToId(columns) { | ||
columns | ||
.forEach(function (column) { | ||
columnsById[column.id] = column; | ||
if (column.columns) | ||
mapToId(column.columns); | ||
}); | ||
} | ||
function filter(node, condition) { | ||
return node.filter(function (column) { | ||
var valid = condition.call(column); | ||
if (valid && column.columns) | ||
column.columns = filter(column.columns, condition); | ||
return valid && (!column.columns || column.columns.length); | ||
}); | ||
} | ||
function sort(columns, grid) { | ||
columns | ||
.sort(function (a, b) { | ||
var indexA = getOrDefault(grid.getColumnIndex(a.id)), | ||
indexB = getOrDefault(grid.getColumnIndex(b.id)); | ||
return indexA - indexB; | ||
}) | ||
.forEach(function (column) { | ||
if (column.columns) | ||
sort(column.columns, grid); | ||
}); | ||
} | ||
function getOrDefault(value) { | ||
return typeof value === 'undefined' ? -1 : value; | ||
} | ||
function getDepth(node) { | ||
if (node.length) | ||
for (var i in node) | ||
return getDepth(node[i]); | ||
else if (node.columns) | ||
return 1 + getDepth(node.columns); | ||
else | ||
return 1; | ||
} | ||
function getColumnsInDepth(node, depth, current) { | ||
var columns = []; | ||
current = current || 0; | ||
if (depth == current) { | ||
if (node.length) | ||
node.forEach(function(n) { | ||
if (n.columns) | ||
n.extractColumns = function() { | ||
return extractColumns(n); | ||
}; | ||
}); | ||
return node; | ||
} else | ||
for (var i in node) | ||
if (node[i].columns) { | ||
columns = columns.concat(getColumnsInDepth(node[i].columns, depth, current + 1)); | ||
} | ||
return columns; | ||
} | ||
function extractColumns(node) { | ||
var result = []; | ||
if (node.hasOwnProperty('length')) { | ||
for (var i = 0; i < node.length; i++) | ||
result = result.concat(extractColumns(node[i])); | ||
} else { | ||
if (node.hasOwnProperty('columns')) | ||
result = result.concat(extractColumns(node.columns)); | ||
else | ||
return node; | ||
} | ||
return result; | ||
} | ||
function cloneTreeColumns() { | ||
return $.extend(true, [], treeColumns); | ||
} | ||
init(); | ||
this.hasDepth = function () { | ||
for (var i in treeColumns) | ||
if (treeColumns[i].hasOwnProperty('columns')) | ||
return true; | ||
return false; | ||
}; | ||
this.getTreeColumns = function () { | ||
return treeColumns; | ||
}; | ||
this.extractColumns = function () { | ||
return this.hasDepth()? extractColumns(treeColumns): treeColumns; | ||
}; | ||
this.getDepth = function () { | ||
return getDepth(treeColumns); | ||
}; | ||
this.getColumnsInDepth = function (depth) { | ||
return getColumnsInDepth(treeColumns, depth); | ||
}; | ||
this.getColumnsInGroup = function (groups) { | ||
return extractColumns(groups); | ||
}; | ||
this.visibleColumns = function () { | ||
return filter(cloneTreeColumns(), function () { | ||
return this.visible; | ||
}); | ||
}; | ||
this.filter = function (condition) { | ||
return filter(cloneTreeColumns(), condition); | ||
}; | ||
this.reOrder = function (grid) { | ||
return sort(treeColumns, grid); | ||
}; | ||
this.getById = function (id) { | ||
return columnsById[id]; | ||
}; | ||
this.getInIds = function (ids) { | ||
return ids.map(function (id) { | ||
return columnsById[id]; | ||
}); | ||
} | ||
} | ||
})(jQuery); | ||
@@ -30,9 +30,6 @@ /*** | ||
this.init = function () { | ||
var navOnLR = args.grid.getOptions().editorCellNavOnLRKeys; | ||
$input = $("<INPUT type=text class='editor-text' />") | ||
.appendTo(args.container) | ||
.on("keydown.nav", function (e) { | ||
if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) { | ||
e.stopImmediatePropagation(); | ||
} | ||
}) | ||
.on("keydown.nav", navOnLR ? handleKeydownLRNav : handleKeydownLRNoNav) | ||
.focus() | ||
@@ -100,12 +97,8 @@ .select(); | ||
this.init = function () { | ||
$input = $("<INPUT type=text class='editor-text' />"); | ||
$input.on("keydown.nav", function (e) { | ||
if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) { | ||
e.stopImmediatePropagation(); | ||
} | ||
}); | ||
$input.appendTo(args.container); | ||
$input.focus().select(); | ||
var navOnLR = args.grid.getOptions().editorCellNavOnLRKeys; | ||
$input = $("<INPUT type=text class='editor-text' />") | ||
.appendTo(args.container) | ||
.on("keydown.nav", navOnLR ? handleKeydownLRNav : handleKeydownLRNoNav) | ||
.focus() | ||
.select(); | ||
}; | ||
@@ -170,12 +163,8 @@ | ||
this.init = function () { | ||
$input = $("<INPUT type=text class='editor-text' />"); | ||
$input.on("keydown.nav", function (e) { | ||
if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) { | ||
e.stopImmediatePropagation(); | ||
} | ||
}); | ||
$input.appendTo(args.container); | ||
$input.focus().select(); | ||
var navOnLR = args.grid.getOptions().editorCellNavOnLRKeys; | ||
$input = $("<INPUT type=text class='editor-text' />") | ||
.appendTo(args.container) | ||
.on("keydown.nav", navOnLR ? handleKeydownLRNav : handleKeydownLRNoNav) | ||
.focus() | ||
.select(); | ||
}; | ||
@@ -222,3 +211,3 @@ | ||
} | ||
var decPlaces = getDecimalPlaces(); | ||
@@ -547,2 +536,3 @@ if (decPlaces !== null | ||
var $container = $("body"); | ||
var navOnLR = args.grid.getOptions().editorCellNavOnLRKeys; | ||
@@ -560,3 +550,3 @@ $wrapper = $("<DIV style='z-index:10000;position:absolute;background:white;padding:5px;border:3px solid gray; -moz-border-radius:10px; border-radius:10px;'/>") | ||
$wrapper.find("button:last").on("click", this.cancel); | ||
$input.on("keydown", this.handleKeyDown); | ||
$input.on("keydown", this.handleKeyDown); | ||
@@ -579,2 +569,13 @@ scope.position(args.position); | ||
args.grid.navigateNext(); | ||
} else if (e.which == $.ui.keyCode.LEFT || e.which == $.ui.keyCode.RIGHT) { | ||
if (args.grid.getOptions().editorCellNavOnLRKeys) { | ||
var cursorPosition = this.selectionStart; | ||
var textLength = this.value.length; | ||
if (e.keyCode === $.ui.keyCode.LEFT && cursorPosition === 0) { | ||
args.grid.navigatePrev(); | ||
} | ||
if (e.keyCode === $.ui.keyCode.RIGHT && cursorPosition >= textLength-1) { | ||
args.grid.navigateNext(); | ||
} | ||
} | ||
} | ||
@@ -647,2 +648,23 @@ }; | ||
} | ||
/* | ||
* Depending on the value of Grid option 'editorCellNavOnLRKeys', us | ||
* Navigate to the cell on the left if the cursor is at the beginning of the input string | ||
* and to the right cell if it's at the end. Otherwise, move the cursor within the text | ||
*/ | ||
function handleKeydownLRNav(e) { | ||
var cursorPosition = this.selectionStart; | ||
var textLength = this.value.length; | ||
if ((e.keyCode === $.ui.keyCode.LEFT && cursorPosition > 0) || | ||
e.keyCode === $.ui.keyCode.RIGHT && cursorPosition < textLength-1) { | ||
e.stopImmediatePropagation(); | ||
} | ||
} | ||
function handleKeydownLRNoNav(e) { | ||
if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) { | ||
e.stopImmediatePropagation(); | ||
} | ||
} | ||
})(jQuery); |
@@ -47,3 +47,3 @@ (function ($) { | ||
function defaultGroupCellFormatter(row, cell, value, columnDef, item) { | ||
function defaultGroupCellFormatter(row, cell, value, columnDef, item, grid) { | ||
if (!options.enableExpandCollapse) { | ||
@@ -66,4 +66,4 @@ return item.title; | ||
function defaultTotalsCellFormatter(row, cell, value, columnDef, item) { | ||
return (columnDef.groupTotalsFormatter && columnDef.groupTotalsFormatter(item, columnDef)) || ""; | ||
function defaultTotalsCellFormatter(row, cell, value, columnDef, item, grid) { | ||
return (columnDef.groupTotalsFormatter && columnDef.groupTotalsFormatter(item, columnDef, grid)) || ""; | ||
} | ||
@@ -117,3 +117,3 @@ | ||
function handleGridKeyDown(e, args) { | ||
if (options.enableExpandCollapse && (e.which == $.ui.keyCode.SPACE)) { | ||
if (options.enableExpandCollapse && (e.which == Slick.keyCode.SPACE)) { | ||
var activeCell = this.getActiveCell(); | ||
@@ -120,0 +120,0 @@ if (activeCell) { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
91986
0
12
0
3514106
123