Newer
Older
piotr.kupczyk@id.ethz.ch
committed
import _ from 'lodash'
import autoBind from 'auto-bind'
piotr.kupczyk@id.ethz.ch
committed
import FileSaver from 'file-saver'
import { stringify } from 'csv-stringify'
piotr.kupczyk@id.ethz.ch
committed
import GridFilterOptions from '@src/js/components/common/grid/GridFilterOptions.js'
piotr.kupczyk@id.ethz.ch
committed
import GridExportOptions from '@src/js/components/common/grid/GridExportOptions.js'
piotr.kupczyk@id.ethz.ch
committed
import GridPagingOptions from '@src/js/components/common/grid/GridPagingOptions.js'
import GridSortingOptions from '@src/js/components/common/grid/GridSortingOptions.js'
import compare from '@src/js/common/compare.js'
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
const LOCAL_GRID_RELOAD_PERIOD = 200
const REMOTE_GRID_RELOAD_PERIOD = 500
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
export default class GridController {
constructor() {
autoBind(this)
piotr.kupczyk@id.ethz.ch
committed
this.cache = {}
piotr.kupczyk@id.ethz.ch
committed
}
init(context) {
piotr.kupczyk@id.ethz.ch
committed
const props = context.getProps()
piotr.kupczyk@id.ethz.ch
committed
let filterMode = GridFilterOptions.GLOBAL_FILTER
piotr.kupczyk@id.ethz.ch
committed
if (props.filterModes) {
filterMode = this._getEnumValue(filterMode, props.filterModes)
if (!filterMode) {
filterMode = props.filterModes.length > 0 ? props.filterModes[0] : null
}
}
piotr.kupczyk@id.ethz.ch
committed
let sortings = []
piotr.kupczyk@id.ethz.ch
committed
if (props.sortings) {
props.sortings.forEach(sorting => {
sortings.push({
columnName: sorting.columnName,
sortDirection: sorting.sortDirection
? sorting.sortDirection
: GridSortingOptions.ASC
})
})
} else if (props.sort) {
piotr.kupczyk@id.ethz.ch
committed
sortings.push({
columnName: props.sort,
sortDirection: props.sortDirection
? props.sortDirection
: GridSortingOptions.ASC
})
}
context.initState({
loaded: false,
loading: false,
piotr.kupczyk@id.ethz.ch
committed
filterMode: filterMode,
filters: {},
piotr.kupczyk@id.ethz.ch
committed
globalFilter: {
operator: GridFilterOptions.OPERATOR_AND,
text: null
},
page: 0,
pageSize: 10,
columnsVisibility: {},
columnsSorting: [],
allColumns: [],
piotr.kupczyk@id.ethz.ch
committed
local: null,
rows: [],
filteredRows: [],
sortedRows: [],
allRows: [],
piotr.kupczyk@id.ethz.ch
committed
selectedRow: null,
piotr.kupczyk@id.ethz.ch
committed
multiselectedRows: {},
piotr.kupczyk@id.ethz.ch
committed
heights: {},
piotr.kupczyk@id.ethz.ch
committed
sortings: sortings,
piotr.kupczyk@id.ethz.ch
committed
totalCount: 0,
piotr.kupczyk@id.ethz.ch
committed
exportState: {
loading: false,
piotr.kupczyk@id.ethz.ch
committed
warnings: null,
piotr.kupczyk@id.ethz.ch
committed
error: null,
fileName: null,
filePath: null
},
piotr.kupczyk@id.ethz.ch
committed
exportOptions: {
piotr.kupczyk@id.ethz.ch
committed
columns: GridExportOptions.COLUMNS.VISIBLE,
rows: GridExportOptions.ROWS.CURRENT_PAGE,
values: GridExportOptions.VALUES.RICH_TEXT,
includeDependencies: true
piotr.kupczyk@id.ethz.ch
committed
}
})
this.context = context
}
piotr.kupczyk@id.ethz.ch
committed
async load() {
try {
const props = this.context.getProps()
if ((props.rows && props.loadRows) || (!props.rows && !props.loadRows)) {
throw new Error(
'Incorrect grid configuration. Please set "rows" or "loadRows" property.'
)
}
if (
(props.columns && props.loadColumns) ||
(!props.columns && !props.loadColumns)
) {
throw new Error(
'Incorrect grid configuration. Please set "columns" or "loadColumns" property.'
)
}
await this.context.setState(() => ({
loading: true
}))
const state = this.context.getState()
const newState = {
...state,
heights: {},
loading: false,
loaded: true
}
let settings = null
piotr.kupczyk@id.ethz.ch
committed
if (!state.loaded) {
settings = await this._loadSettings()
_.merge(newState, settings)
}
let result = {}
piotr.kupczyk@id.ethz.ch
committed
if (props.rows) {
result.rows = props.rows
result.totalCount = props.rows.length
result.local = true
} else if (props.loadRows) {
const columns = {}
piotr.kupczyk@id.ethz.ch
committed
newState.allColumns.forEach(column => {
columns[column.name] = column
})
piotr.kupczyk@id.ethz.ch
committed
const loadedResult = await props.loadRows({
columns: columns,
filterMode: newState.filterMode,
filters: newState.filters,
globalFilter: newState.globalFilter,
page: newState.page,
pageSize: newState.pageSize,
sortings: newState.sortings
})
if (_.isArray(loadedResult)) {
result.rows = loadedResult
result.totalCount = loadedResult.length
result.local = true
} else {
result.rows = loadedResult.rows
result.totalCount = loadedResult.totalCount
result.local = false
}
piotr.kupczyk@id.ethz.ch
committed
}
newState.local = result.local
piotr.kupczyk@id.ethz.ch
committed
if (result.local) {
const { newAllColumns, newColumnsVisibility, newColumnsSorting } =
await this._loadColumns(
result.rows,
newState.columnsVisibility,
newState.columnsSorting
)
newState.allColumns = newAllColumns
newState.columnsVisibility = newColumnsVisibility
newState.columnsSorting = newColumnsSorting
newState.allRows = result.rows
newState.filteredRows = this._filterRows(
newState.allRows,
newState.allColumns,
newState.columnsVisibility,
newState.filterMode,
newState.filters,
newState.globalFilter
)
newState.sortedRows = this._sortRows(
newState.filteredRows,
newState.allColumns,
newState.sortings
)
newState.totalCount = newState.filteredRows.length
const pageCount = Math.max(
Math.ceil(newState.totalCount / newState.pageSize),
1
)
newState.page = Math.min(newState.page, pageCount - 1)
newState.rows = this._pageRows(
newState.sortedRows,
newState.page,
newState.pageSize
)
} else {
newState.allRows = result.rows
newState.filteredRows = result.rows
newState.sortedRows = result.rows
newState.rows = result.rows
newState.totalCount = result.totalCount
const pageCount = Math.max(
Math.ceil(result.totalCount / newState.pageSize),
1
newState.page = Math.min(newState.page, pageCount - 1)
const { newAllColumns, newColumnsVisibility, newColumnsSorting } =
await this._loadColumns(
newState.rows,
newState.columnsVisibility,
newState.columnsSorting
)
newState.allColumns = newAllColumns
newState.columnsVisibility = newColumnsVisibility
newState.columnsSorting = newColumnsSorting
}
piotr.kupczyk@id.ethz.ch
committed
// do not update filters (this would override filter changes that a user could do while grid was loading)
delete newState.filters
delete newState.globalFilter
await this.context.setState(newState)
if (!state.loaded) {
this.selectRow(props.selectedRowId)
this.multiselectRows(props.multiselectedRowIds)
} else {
this.selectRow(newState.selectedRow ? newState.selectedRow.id : null)
this.multiselectRows(Object.keys(newState.multiselectedRows))
}
} catch (error) {
this._onError(error)
}
}
async _loadColumns(rows, columnsVisibility, columnsSorting) {
piotr.kupczyk@id.ethz.ch
committed
const props = this.context.getProps()
const state = this.context.getState()
let newAllColumns = []
const newColumnsVisibility = { ...columnsVisibility }
const newColumnsSorting = [...columnsSorting]
piotr.kupczyk@id.ethz.ch
committed
if (props.columns) {
newAllColumns = props.columns
} else if (props.loadColumns) {
newAllColumns = await props.loadColumns(rows)
}
piotr.kupczyk@id.ethz.ch
committed
newAllColumns = newAllColumns.map(newColumn => {
if (!newColumn.name) {
piotr.kupczyk@id.ethz.ch
committed
throw new Error('column.name cannot be empty')
}
piotr.kupczyk@id.ethz.ch
committed
if (newColumn.exportable && !newColumn.getValue) {
throw new Error(
'column.name cannot be exportable without getValue implementation'
)
}
return this._loadColumn(newColumn)
})
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
// If there is a filter value defined for a column and this column does not exist
// in the new columns list then take it over from the previous columns list.
// This may happen e.g. when a user is filtering by a dynamic column
// and enters a filter value that does not match any row. Without this trick the column
// would disappear and the user would not be able to clear the filter value.
Object.keys(state.filters).forEach(columnName => {
const newColumn = _.find(
newAllColumns,
newColumn => newColumn.name === columnName
)
if (!newColumn) {
const existingColumn = _.find(
state.allColumns,
column => column.name === columnName
)
newAllColumns.push(existingColumn)
}
})
newAllColumns.forEach((newColumn, newColumnIndex) => {
let newColumnVisibility = newColumnsVisibility[newColumn.name]
if (newColumnVisibility === undefined || !newColumn.configurable) {
newColumnsVisibility[newColumn.name] = newColumn.visible
}
let newColumnSorting = _.findIndex(
newColumnsSorting,
columnName => columnName === newColumn.name
)
piotr.kupczyk@id.ethz.ch
committed
if (newColumnSorting === -1) {
piotr.kupczyk@id.ethz.ch
committed
// If a column does not have a sorting value yet, then set its sorting to
// the max sorting of the columns that were before it in the columns list
newColumnSorting = newAllColumns
.slice(0, newColumnIndex)
.reduce((maxSorting, column) => {
const sorting = _.findIndex(
newColumnsSorting,
columnName => columnName === column.name
)
return Math.max(sorting, maxSorting)
}, -1)
newColumnsSorting.splice(newColumnSorting + 1, 0, newColumn.name)
}
})
piotr.kupczyk@id.ethz.ch
committed
return { newAllColumns, newColumnsVisibility, newColumnsSorting }
piotr.kupczyk@id.ethz.ch
committed
}
_loadColumn(column) {
piotr.kupczyk@id.ethz.ch
committed
const defaultMatches = function (value, filter) {
if (filter) {
return value !== null && value !== undefined
? String(value)
.trim()
.toUpperCase()
.includes(filter.trim().toUpperCase())
: false
} else {
return true
}
}
const defaultCompare = compare
return {
...column,
name: column.name,
label: column.label,
getValue: column.getValue,
piotr.kupczyk@id.ethz.ch
committed
matches: (row, filter) => {
piotr.kupczyk@id.ethz.ch
committed
const value = column.getValue({ row, column, operation: 'match' })
piotr.kupczyk@id.ethz.ch
committed
if (column.matchesValue) {
return column.matchesValue({
piotr.kupczyk@id.ethz.ch
committed
value,
row,
column,
filter,
defaultMatches
})
} else {
return defaultMatches(value, filter)
}
},
piotr.kupczyk@id.ethz.ch
committed
compare: (row1, row2, sortDirection) => {
piotr.kupczyk@id.ethz.ch
committed
const value1 = column.getValue({
row: row1,
column,
operation: 'compare'
})
const value2 = column.getValue({
row: row2,
column,
operation: 'compare'
})
piotr.kupczyk@id.ethz.ch
committed
if (column.compareValue) {
return column.compareValue({
piotr.kupczyk@id.ethz.ch
committed
value1,
value2,
row1,
row2,
column,
piotr.kupczyk@id.ethz.ch
committed
sortDirection,
piotr.kupczyk@id.ethz.ch
committed
defaultCompare
})
} else {
return defaultCompare(value1, value2)
}
},
sortable: column.sortable === undefined ? true : column.sortable,
filterable: column.filterable === undefined ? true : column.filterable,
visible: column.visible === undefined ? true : column.visible,
configurable:
piotr.kupczyk@id.ethz.ch
committed
column.configurable === undefined ? true : column.configurable,
piotr.kupczyk@id.ethz.ch
committed
exportable: column.exportable === undefined ? true : column.exportable,
piotr.kupczyk@id.ethz.ch
committed
nowrap: column.nowrap === undefined ? false : column.nowrap,
truncate: column.truncate === undefined ? false : column.truncate,
piotr.kupczyk@id.ethz.ch
committed
metadata: column.metadata === undefined ? {} : column.metadata
}
piotr.kupczyk@id.ethz.ch
committed
}
_sortColumns(columns, columnsSorting) {
columns.sort((c1, c2) => {
const c1Index = _.findIndex(
columnsSorting,
columnName => columnName === c1.name
)
const c2Index = _.findIndex(
columnsSorting,
columnName => columnName === c2.name
)
return c1Index - c2Index
})
piotr.kupczyk@id.ethz.ch
committed
}
async _loadSettings() {
piotr.kupczyk@id.ethz.ch
committed
const props = this.context.getProps()
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
if (!props.loadSettings) {
return {}
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
const loaded = await props.loadSettings()
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
if (!loaded || !_.isObject(loaded)) {
return {}
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
const settings = {}
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
settings.filterMode = this._getEnumValue(
loaded.filterMode,
GridFilterOptions.FILTER_MODE_OPTIONS
)
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
if (props.filterModes) {
settings.filterMode = this._getEnumValue(
settings.filterMode,
props.filterModes
)
}
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
if (_.isObject(loaded.globalFilter)) {
piotr.kupczyk@id.ethz.ch
committed
const globalFilter = {}
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
globalFilter.operator = this._getEnumValue(
loaded.globalFilter.operator,
GridFilterOptions.OPERATOR_OPTIONS
piotr.kupczyk@id.ethz.ch
committed
)
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
if (globalFilter.operator !== undefined) {
settings.globalFilter = globalFilter
}
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
settings.pageSize = this._getEnumValue(
loaded.pageSize,
GridPagingOptions.PAGE_SIZE_OPTIONS
)
piotr.kupczyk@id.ethz.ch
committed
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
if (_.isArray(loaded.sortings)) {
const sortings = []
loaded.sortings.forEach(loadedSorting => {
if (_.isObject(loadedSorting)) {
const sorting = {}
sorting.columnName = this._getStringValue(loadedSorting.columnName)
sorting.sortDirection = this._getEnumValue(
loadedSorting.sortDirection,
GridSortingOptions.SORTING_DIRECTION_OPTIONS
)
if (
sorting.columnName !== undefined &&
sorting.sortDirection !== undefined
) {
sortings.push(sorting)
}
}
})
if (sortings.length > 0) {
settings.sortings = sortings
}
}
if (settings.sortings === undefined) {
const sort = this._getStringValue(loaded.sort)
const sortDirection = this._getEnumValue(
loaded.sortDirection,
GridSortingOptions.SORTING_DIRECTION_OPTIONS
)
if (sort !== undefined && sortDirection !== undefined) {
settings.sortings = [
{
columnName: sort,
sortDirection: sortDirection
}
]
}
}
piotr.kupczyk@id.ethz.ch
committed
settings.columnsVisibility = this._getObjectValue(loaded.columnsVisibility)
settings.columnsSorting = this._getArrayValue(loaded.columnsSorting)
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
if (_.isObject(loaded.exportOptions)) {
piotr.kupczyk@id.ethz.ch
committed
const exportOptions = {}
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
exportOptions.columns = this._getEnumValue(
loaded.exportOptions.columns,
piotr.kupczyk@id.ethz.ch
committed
Object.values(GridExportOptions.COLUMNS)
piotr.kupczyk@id.ethz.ch
committed
)
piotr.kupczyk@id.ethz.ch
committed
exportOptions.rows = this._getEnumValue(
loaded.exportOptions.rows,
piotr.kupczyk@id.ethz.ch
committed
Object.values(GridExportOptions.ROWS)
piotr.kupczyk@id.ethz.ch
committed
)
exportOptions.values = this._getEnumValue(
loaded.exportOptions.values,
piotr.kupczyk@id.ethz.ch
committed
Object.values(GridExportOptions.VALUES)
piotr.kupczyk@id.ethz.ch
committed
)
piotr.kupczyk@id.ethz.ch
committed
exportOptions.includeDependencies = this._getBooleanValue(
loaded.exportOptions.includeDependencies
)
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
if (!_.isEmpty(exportOptions)) {
piotr.kupczyk@id.ethz.ch
committed
settings.exportOptions = exportOptions
}
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
return settings
piotr.kupczyk@id.ethz.ch
committed
}
async _saveSettings() {
piotr.kupczyk@id.ethz.ch
committed
const { onSettingsChange } = this.context.getProps()
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
if (onSettingsChange) {
const state = this.context.getState()
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
let settings = {
piotr.kupczyk@id.ethz.ch
committed
filterMode: state.filterMode,
piotr.kupczyk@id.ethz.ch
committed
globalFilter: {
operator: state.globalFilter.operator
},
piotr.kupczyk@id.ethz.ch
committed
pageSize: state.pageSize,
piotr.kupczyk@id.ethz.ch
committed
sortings: state.sortings,
piotr.kupczyk@id.ethz.ch
committed
columnsVisibility: state.columnsVisibility,
piotr.kupczyk@id.ethz.ch
committed
columnsSorting: state.columnsSorting,
exportOptions: state.exportOptions
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
onSettingsChange(settings)
}
}
piotr.kupczyk@id.ethz.ch
committed
_filterRows(
rows,
columns,
columnsVisibility,
filterMode,
filters,
globalFilter
) {
if (filterMode === GridFilterOptions.GLOBAL_FILTER) {
piotr.kupczyk@id.ethz.ch
committed
if (this._isEmpty(globalFilter.text)) {
piotr.kupczyk@id.ethz.ch
committed
return rows
}
piotr.kupczyk@id.ethz.ch
committed
const tokens = this._split(globalFilter.text)
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
return _.filter([...rows], row => {
piotr.kupczyk@id.ethz.ch
committed
let rowMatches = null
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
if (globalFilter.operator === GridFilterOptions.OPERATOR_AND) {
rowMatches = true
} else if (globalFilter.operator === GridFilterOptions.OPERATOR_OR) {
rowMatches = false
}
tokens: for (let t = 0; t < tokens.length; t++) {
let token = tokens[t]
piotr.kupczyk@id.ethz.ch
committed
let rowMatchesToken = false
piotr.kupczyk@id.ethz.ch
committed
columns: for (let c = 0; c < columns.length; c++) {
let column = columns[c]
piotr.kupczyk@id.ethz.ch
committed
let visible = columnsVisibility[column.name]
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
if (visible) {
piotr.kupczyk@id.ethz.ch
committed
rowMatchesToken = column.matches(row, token)
if (rowMatchesToken) {
break columns
}
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
if (globalFilter.operator === GridFilterOptions.OPERATOR_AND) {
rowMatches = rowMatches && rowMatchesToken
if (!rowMatches) {
break tokens
}
} else if (globalFilter.operator === GridFilterOptions.OPERATOR_OR) {
rowMatches = rowMatches || rowMatchesToken
if (rowMatches) {
break tokens
}
}
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
return rowMatches
piotr.kupczyk@id.ethz.ch
committed
} else if (filterMode === GridFilterOptions.COLUMN_FILTERS) {
return _.filter([...rows], row => {
let matchesAll = true
columns.forEach(column => {
let visible = columnsVisibility[column.name]
if (visible) {
let filter = filters[column.name]
piotr.kupczyk@id.ethz.ch
committed
if (!this._isEmpty(filter)) {
piotr.kupczyk@id.ethz.ch
committed
matchesAll = matchesAll && column.matches(row, filter)
}
}
})
return matchesAll
})
piotr.kupczyk@id.ethz.ch
committed
} else {
return rows
piotr.kupczyk@id.ethz.ch
committed
}
}
piotr.kupczyk@id.ethz.ch
committed
_sortRows(rows, columns, sortings) {
if (sortings && sortings.length > 0) {
const columnSortings = []
sortings.forEach(sorting => {
const column = _.find(columns, ['name', sorting.columnName])
if (column) {
columnSortings.push({
column,
sorting
})
}
})
if (columnSortings.length > 0) {
return rows.sort((t1, t2) => {
piotr.kupczyk@id.ethz.ch
committed
let result = 0
let index = 0
while (index < columnSortings.length && result === 0) {
const { column, sorting } = columnSortings[index]
const sign =
sorting.sortDirection === GridSortingOptions.ASC ? 1 : -1
result = sign * column.compare(t1, t2, sorting.sortDirection)
index++
}
return result
})
}
}
return rows
}
_pageRows(rows, page, pageSize) {
return rows.slice(
page * pageSize,
Math.min(rows.length, (page + 1) * pageSize)
)
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
async selectRow(newSelectedRowId) {
piotr.kupczyk@id.ethz.ch
committed
const { selectable, onSelectedRowChange } = this.context.getProps()
piotr.kupczyk@id.ethz.ch
committed
const { allRows, rows, selectedRow } = this.context.getState()
piotr.kupczyk@id.ethz.ch
committed
if (!selectable) {
return
}
let newSelectedRow = null
if (newSelectedRowId !== null && newSelectedRowId !== undefined) {
piotr.kupczyk@id.ethz.ch
committed
const data = _.find(allRows, row => row.id === newSelectedRowId)
piotr.kupczyk@id.ethz.ch
committed
const visible =
_.findIndex(rows, row => row.id === newSelectedRowId) !== -1
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
newSelectedRow = {
id: newSelectedRowId,
piotr.kupczyk@id.ethz.ch
committed
data,
piotr.kupczyk@id.ethz.ch
committed
visible
piotr.kupczyk@id.ethz.ch
committed
if (!_.isEqual(selectedRow, newSelectedRow)) {
await this.context.setState(() => ({
selectedRow: newSelectedRow
}))
piotr.kupczyk@id.ethz.ch
committed
if (onSelectedRowChange) {
onSelectedRowChange(newSelectedRow)
piotr.kupczyk@id.ethz.ch
committed
}
}
piotr.kupczyk@id.ethz.ch
committed
async multiselectRows(newMultiselectedRowIds) {
piotr.kupczyk@id.ethz.ch
committed
const { multiselectable, onMultiselectedRowsChange } =
this.context.getProps()
piotr.kupczyk@id.ethz.ch
committed
const { local, allRows, rows, multiselectedRows } = this.context.getState()
piotr.kupczyk@id.ethz.ch
committed
if (!multiselectable) {
return
}
piotr.kupczyk@id.ethz.ch
committed
const newMultiselectedRows = {}
piotr.kupczyk@id.ethz.ch
committed
if (newMultiselectedRowIds && newMultiselectedRowIds.length > 0) {
const allRowsMap = {}
allRows.forEach(row => {
allRowsMap[row.id] = row
})
const rowsMap = {}
rows.forEach(row => {
rowsMap[row.id] = row
})
newMultiselectedRowIds.forEach(rowId => {
if (rowId !== null && rowId !== undefined) {
piotr.kupczyk@id.ethz.ch
committed
const visible = rowsMap[rowId] !== undefined
piotr.kupczyk@id.ethz.ch
committed
let data = allRowsMap[rowId]
piotr.kupczyk@id.ethz.ch
committed
if (data) {
newMultiselectedRows[rowId] = {
id: rowId,
data,
visible
}
} else if (!local) {
piotr.kupczyk@id.ethz.ch
committed
const multiselectedRow = multiselectedRows[rowId]
if (multiselectedRow) {
data = multiselectedRow.data
}
piotr.kupczyk@id.ethz.ch
committed
newMultiselectedRows[rowId] = {
id: rowId,
data,
visible
}
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
}
})
}
await this.context.setState(() => ({
multiselectedRows: newMultiselectedRows
}))
if (onMultiselectedRowsChange) {
onMultiselectedRowsChange(newMultiselectedRows)
}
}
piotr.kupczyk@id.ethz.ch
committed
async showRow(rowId) {
const { sortedRows, page, pageSize } = this.context.getState()
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
if (!rowId) {
return
}
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
const index = _.findIndex(sortedRows, ['id', rowId])
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
if (index === -1) {
return
}
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
const newPage = Math.floor(index / pageSize)
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
if (newPage !== page) {
await this.context.setState({
page: newPage
})
await this.load()
}
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
async handleFilterModeChange(filterMode) {
await this.context.setState({
filterMode
})
await this.load()
await this._saveSettings()
}
piotr.kupczyk@id.ethz.ch
committed
async handleFilterChange(column, filter) {
piotr.kupczyk@id.ethz.ch
committed
const { local } = this.context.getState()
await this.context.setState(state => {
const newFilters = {
...state.filters
}
piotr.kupczyk@id.ethz.ch
committed
if (filter && _.trim(filter).length > 0) {
newFilters[column] = filter
} else {
delete newFilters[column]
}
piotr.kupczyk@id.ethz.ch
committed
return {
page: 0,
filters: newFilters
piotr.kupczyk@id.ethz.ch
committed
}
})
if (this.loadTimerId) {
clearTimeout(this.loadTimerId)
this.loadTimerId = null
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
this.loadTimerId = setTimeout(
async () => {
piotr.kupczyk@id.ethz.ch
committed
await this.load()
piotr.kupczyk@id.ethz.ch
committed
},
local ? LOCAL_GRID_RELOAD_PERIOD : REMOTE_GRID_RELOAD_PERIOD
)
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
async handleGlobalFilterChange(newGlobalFilter) {
const { local, globalFilter } = this.context.getState()
piotr.kupczyk@id.ethz.ch
committed
await this.context.setState(() => ({
page: 0,
piotr.kupczyk@id.ethz.ch
committed
globalFilter: newGlobalFilter
piotr.kupczyk@id.ethz.ch
committed
}))
if (this.loadTimerId) {
clearTimeout(this.loadTimerId)
this.loadTimerId = null
}
piotr.kupczyk@id.ethz.ch
committed
this.loadTimerId = setTimeout(
async () => {
piotr.kupczyk@id.ethz.ch
committed
await this.load()
piotr.kupczyk@id.ethz.ch
committed
},
local ? LOCAL_GRID_RELOAD_PERIOD : REMOTE_GRID_RELOAD_PERIOD
)
piotr.kupczyk@id.ethz.ch
committed
if (globalFilter.operator !== newGlobalFilter.operator) {
await this._saveSettings()
}
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
async handleColumnVisibleChange(visibilityMap) {
const { allColumns } = this.context.getState()
piotr.kupczyk@id.ethz.ch
committed
allColumns.forEach(column => {
if (!column.configurable) {
delete visibilityMap[column.name]
}
})
await this.context.setState(state => {
piotr.kupczyk@id.ethz.ch
committed
const newColumnsVisibility = {
...state.columnsVisibility,
...visibilityMap
}
const newFilters = { ...state.filters }
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
Object.keys(visibilityMap).forEach(columnName => {
const visible = visibilityMap[columnName]
if (!visible) {
delete newFilters[columnName]
}
piotr.kupczyk@id.ethz.ch
committed
})
return {
columnsVisibility: newColumnsVisibility,
filters: newFilters
piotr.kupczyk@id.ethz.ch
committed
}
})
await this.load()
await this._saveSettings()
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
async handleColumnOrderChange(sourceIndex, destinationIndex) {
await this.context.setState(state => {
const columns = this.getAllColumns()
const sourceColumn = columns[sourceIndex]
const destinationColumn = columns[destinationIndex]
piotr.kupczyk@id.ethz.ch
committed
const sourceSorting = _.findIndex(
state.columnsSorting,
columnName => columnName === sourceColumn.name
)
const destinationSorting = _.findIndex(
state.columnsSorting,
columnName => columnName === destinationColumn.name
)
piotr.kupczyk@id.ethz.ch
committed
const newColumnsSorting = [...state.columnsSorting]
newColumnsSorting.splice(sourceSorting, 1)
newColumnsSorting.splice(destinationSorting, 0, sourceColumn.name)
piotr.kupczyk@id.ethz.ch
committed
return {
columnsSorting: newColumnsSorting
}
})
await this.load()
await this._saveSettings()
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
async handleSortChange(column, append) {
if (!column.sortable) {
piotr.kupczyk@id.ethz.ch
committed
return
}
piotr.kupczyk@id.ethz.ch
committed
function createInitialSorting(column) {
return {
columnName: column.name,
sortDirection: GridSortingOptions.ASC
}
}
function createReversedSorting(column, sorting) {
return {
columnName: column.name,
sortDirection:
sorting.sortDirection === GridSortingOptions.ASC
? GridSortingOptions.DESC
: GridSortingOptions.ASC
}
}
piotr.kupczyk@id.ethz.ch
committed
await this.context.setState(state => {
piotr.kupczyk@id.ethz.ch
committed
const newSortings = []
const index = _.findIndex(
state.sortings,
sorting => sorting.columnName === column.name
)
const sorting = state.sortings[index]
if (append) {
if (index !== -1) {
newSortings.push(...state.sortings)
newSortings.splice(index, 1)
} else {
newSortings.push(...state.sortings)
newSortings.push(createInitialSorting(column))
}
piotr.kupczyk@id.ethz.ch
committed
} else {
piotr.kupczyk@id.ethz.ch
committed
if (index !== -1) {
newSortings.push(...state.sortings)
newSortings[index] = createReversedSorting(column, sorting)
} else {
newSortings.push(createInitialSorting(column))
piotr.kupczyk@id.ethz.ch
committed
}
}
piotr.kupczyk@id.ethz.ch
committed
return {
page: 0,
sortings: newSortings
}
piotr.kupczyk@id.ethz.ch
committed
})
await this.load()
await this._saveSettings()
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
async handlePageChange(page) {
await this.context.setState(() => ({
page
}))
await this.load()
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
async handlePageSizeChange(pageSize) {
await this.context.setState(() => ({
page: 0,
pageSize
}))
await this.load()
await this._saveSettings()
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
async handleRowClick(row) {
const { onRowClick } = this.context.getProps()
if (onRowClick) {
onRowClick({
id: row.id,
data: row,
visible: true
})
}
}
async handleRowSelect(row) {
piotr.kupczyk@id.ethz.ch
committed
await this.selectRow(row ? row.id : null)
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
async handleRowMultiselect(row) {
const { multiselectedRows } = this.context.getState()
if (row) {
const newMultiselectedRows = { ...multiselectedRows }
if (newMultiselectedRows[row.id]) {