cheetah-grid
Version:
Cheetah Grid is a high performance grid engine that works on canvas
313 lines (292 loc) • 8.47 kB
text/typescript
import type {
ActionListener,
CellAddress,
EventListenerId,
GetRadioEditorGroup,
LayoutObjectId,
RadioEditorOption,
} from "../../ts-types";
import { bindCellClickAction, bindCellKeyAction } from "./actionBind";
import {
cellEquals,
event,
extend,
isPromise,
obj,
then,
} from "../../internal/utils";
import {
isDisabledRecord,
isReadOnlyRecord,
toggleValue,
} from "./action-utils";
import { DG_EVENT_TYPE } from "../../core/DG_EVENT_TYPE";
import { Editor } from "./Editor";
import type { GridInternal } from "../../ts-types-internal";
import type { RangePasteContext } from "./BaseAction";
import { animate } from "../../internal/animate";
import { getRadioColumnStateId } from "../../internal/symbolManager";
import { toBoolean } from "../utils";
const RADIO_COLUMN_STATE_ID = getRadioColumnStateId();
export class RadioEditor<T> extends Editor<T> {
protected _group: GetRadioEditorGroup<T> | undefined;
private _checkAction: ActionListener | undefined;
constructor(option: RadioEditorOption<T> = {}) {
super(option);
this._group = option.group;
this._checkAction = option.checkAction;
}
clone(): RadioEditor<T> {
return new RadioEditor(this);
}
/** @deprecated Use checkAction instead. */
get group(): GetRadioEditorGroup<T> | undefined {
return this._group;
}
/** @deprecated Use checkAction instead. */
set group(group: GetRadioEditorGroup<T> | undefined) {
this._group = group;
}
get checkAction(): ActionListener | undefined {
return this._checkAction;
}
set checkAction(checkAction: ActionListener | undefined) {
this._checkAction = checkAction;
}
bindGridEvent(
grid: GridInternal<T>,
cellId: LayoutObjectId
): EventListenerId[] {
let _state = grid[RADIO_COLUMN_STATE_ID];
if (!_state) {
_state = { block: {}, elapsed: {} };
obj.setReadonly(grid, RADIO_COLUMN_STATE_ID, _state);
}
const state = _state;
const action = (cell: CellAddress): void => {
this._action(grid, cell);
};
function isTarget(col: number, row: number): boolean {
return grid.getLayoutCellId(col, row) === cellId;
}
return [
...bindCellClickAction(grid, cellId, {
action,
mouseOver: (e) => {
if (isDisabledRecord(this.disabled, grid, e.row)) {
return false;
}
state.mouseActiveCell = {
col: e.col,
row: e.row,
};
const range = grid.getCellRange(e.col, e.row);
grid.invalidateCellRange(range);
return true;
},
mouseOut: (e) => {
delete state.mouseActiveCell;
const range = grid.getCellRange(e.col, e.row);
grid.invalidateCellRange(range);
},
}),
...bindCellKeyAction(grid, cellId, {
action,
}),
// paste value
grid.listen(DG_EVENT_TYPE.PASTE_CELL, (e) => {
if (e.multi) {
// ignore multi cell values
return;
}
const selectionRange = grid.selection.range;
if (!cellEquals(selectionRange.start, selectionRange.end)) {
// ignore multi paste values
return;
}
if (!isTarget(e.col, e.row)) {
return;
}
event.cancel(e.event);
const pasteValue = e.normalizeValue.trim();
if (isRejectValue(pasteValue)) {
// Not a boolean
const record = grid.getRowRecord(e.row);
if (!isPromise(record)) {
grid.fireListeners("rejected_paste_values", {
detail: [
{
col: e.col,
row: e.row,
record,
define: grid.getColumnDefine(e.col, e.row),
pasteValue,
},
],
});
}
return;
}
if (!toBoolean(pasteValue)) {
return;
}
action({
col: e.col,
row: e.row,
});
}),
];
}
onPasteCellRangeBox(
grid: GridInternal<T>,
cell: CellAddress,
value: string,
context: RangePasteContext
): void {
if (
isReadOnlyRecord(this.readOnly, grid, cell.row) ||
isDisabledRecord(this.disabled, grid, cell.row)
) {
return;
}
const pasteValue = value.trim();
if (isRejectValue(pasteValue)) {
// Not a boolean
context.reject();
return;
}
if (!toBoolean(pasteValue)) {
return;
}
this._action(grid, {
col: cell.col,
row: cell.row,
});
}
onDeleteCellRangeBox(): void {
// noop
}
private _action(grid: GridInternal<T>, cell: CellAddress): void {
const state = grid[RADIO_COLUMN_STATE_ID]!;
const range = grid.getCellRange(cell.col, cell.row);
const cellKey = `${range.start.col}:${range.start.row}`;
if (
isReadOnlyRecord(this.readOnly, grid, cell.row) ||
isDisabledRecord(this.disabled, grid, cell.row) ||
state.block[cellKey]
) {
return;
}
grid.doGetCellValue(cell.col, cell.row, (value) => {
if (toBoolean(value)) {
return;
}
if (this._checkAction) {
// User behavior
const record = grid.getRowRecord(cell.row);
this._checkAction(record, extend(cell, { grid }));
return;
}
if (this._group) {
// Backward compatibility
const state = grid[RADIO_COLUMN_STATE_ID]!;
const targets = this._group({ grid, col: cell.col, row: cell.row });
targets.forEach(({ col, row }) => {
const range = grid.getCellRange(col, row);
const cellKey = `${range.start.col}:${range.start.row}`;
if (
isReadOnlyRecord(this.readOnly, grid, cell.row) ||
isDisabledRecord(this.disabled, grid, cell.row) ||
state.block[cellKey]
) {
return;
}
actionCell(grid, col, row, col === cell.col && row === cell.row);
});
return;
}
// default behavior
const field = grid.getField(cell.col, cell.row)!;
const recordStartRow = grid.getRecordStartRowByRecordIndex(
grid.getRecordIndexByRow(cell.row)
);
/** Original DataSource */
const { dataSource } = grid.dataSource;
const girdRecords = getAllRecordsFromGrid(grid);
for (let index = 0; index < dataSource.length; index++) {
const record = dataSource.get(index);
const showData = girdRecords.find((d) => d.record === record);
if (showData) {
actionCell(
grid,
cell.col,
showData.row,
showData.row === recordStartRow
);
} else {
// Hidden record
then(dataSource.getField(index, field), (value) => {
if (!toBoolean(value)) {
return;
}
dataSource.setField(index, field, toggleValue(value));
});
}
}
});
}
}
function getAllRecordsFromGrid<T>(grid: GridInternal<T>) {
const result = [];
const { rowCount, recordRowCount } = grid;
for (
let targetRow = grid.frozenRowCount;
targetRow < rowCount;
targetRow += recordRowCount
) {
const record = grid.getRowRecord(targetRow);
result.push({ row: targetRow, record });
}
return result;
}
function actionCell<T>(
grid: GridInternal<T>,
col: number,
row: number,
flag: boolean
): void {
grid.doGetCellValue(col, row, (value) => {
if (toBoolean(value) === flag) {
return;
}
const state = grid[RADIO_COLUMN_STATE_ID]!;
const range = grid.getCellRange(col, row);
const cellKey = `${range.start.col}:${range.start.row}`;
const ret = grid.doChangeValue(col, row, toggleValue);
if (ret) {
const onChange = (): void => {
// checkbox animation
animate(200, (point) => {
if (point === 1) {
delete state.elapsed[cellKey];
} else {
state.elapsed[cellKey] = point;
}
grid.invalidateCellRange(range);
});
};
if (isPromise(ret)) {
state.block[cellKey] = true;
ret.then(() => {
delete state.block[cellKey];
onChange();
});
} else {
onChange();
}
}
});
}
function isRejectValue(pasteValue: string) {
return toggleValue(toggleValue(pasteValue)) !== pasteValue;
}