7

I'm using the Def Extreme Grid and I want to have a button in each row, making it possible to change the data of a row. In the example on buttonClick I want to reset the car brand to an empty string. As the custom cell with the button is defined outside the class with the grid I don't know how to acces the state.

Code Pen

const Cell = props => {
  if (props.column.name === "city") {
    return (
      <td>
        <button>Reset car brand</button>
      </td>
    );
  }

  return <Table.Cell {...props} />;
};
user2952265
  • 1,590
  • 7
  • 21
  • 32

3 Answers3

9

I'm currently working with react grid from DevExtreme and I've faced a similar problem but followed a different solution. What I did was create a new Plugin in order to add a new column called "ActionsColumn". You can pass both nodes and associated callbacks to the plugin. The minimum code would be something like this (sorry, not tested):

import React from 'react'
import PropTypes from 'prop-types'
import IconButton from '@material-ui/core/IconButton'
import { TABLE_HEADING_TYPE } from '@devexpress/dx-grid-core'
import {Getter, Template, Plugin} from '@devexpress/dx-react-core'
import {
    Table,
} from '@devexpress/dx-react-grid-material-ui'

const pluginDependencies = [
    {name: 'Table'},
];

const ACTIONS_COLUMN_TYPE = 'actionsColumnType';

function tableColumnsWithActions(tableColumns, width) {
    return [...tableColumns, {key: ACTIONS_COLUMN_TYPE, type: ACTIONS_COLUMN_TYPE, width: width}];
}

function isHeadingActionsTableCell(tableRow, tableColumn) {
    return tableRow.type === TABLE_HEADING_TYPE && tableColumn.type === ACTIONS_COLUMN_TYPE;
}

function isActionsTableCell(tableRow, tableColumn) {
    return tableRow.type !== TABLE_HEADING_TYPE && tableColumn.type === ACTIONS_COLUMN_TYPE;
}

export class ActionsColumn extends React.PureComponent {
    render() {
        const {
            actions,
            width,
        } = this.props;
        const tableColumnsComputed = ({tableColumns}) => tableColumnsWithActions(tableColumns, width);

        return (
            <Plugin
                name="ActionsColumn"
                dependencies={pluginDependencies}
            >
                <Getter name="tableColumns" computed={tableColumnsComputed}/>

                <Template
                    name="tableCell"
                    predicate={({tableRow, tableColumn}) =>
                    isHeadingActionsTableCell(tableRow, tableColumn)}
                >
                    <Table.Cell>Actions Column</Table.Cell>
                </Template>
                <Template
                    name="tableCell"
                    predicate={({tableRow, tableColumn}) => isActionsTableCell(tableRow, tableColumn)}
                >
                    {params => (
                        <Table.Cell {...params} row={params.tableRow.row}>
                            {actions.map(action => {
                                const id = params.tableRow.row.id;
                                return (
                                    <IconButton onClick={() => action.action(id)}>
                                        {action.icon}
                                    </IconButton>
                                )

                            })}
                       </Table.Cell>
                    )}
                </Template>
            </Plugin>
        );
    }
}
ActionsColumn.propTypes = {
    actions: PropTypes.arrayOf(PropTypes.PropTypes.shape({
        icon: PropTypes.node,
        action: PropTypes.func.isRequired
    })).isRequired,
    width: PropTypes.number
};
ActionsColumn.defaultProps = {
    width: 240,
};

You simply check whether you are in a header row or a regular table row and you just add a header or the actions you defined respectively.

In order to use this plugin, simply include it in your Grid definition after the Table plugin declaration:

render() {
    const {columns, rows} = this.state;
    const actions = [
        {
            icon: <DeleteIcon/>,
            action: doAlert
        },
        {
            icon: <EditIcon/>,
            action: id => alert('edit id: ' + id)
        }
    ];

    return (
        <Grid rows={rows} columns={columns} getRowId={getRowId}>
            <Table/>
            <TableHeaderRow/>
            <ActionsColumn actions={actions}/>
        </Grid>
    )
}

The way I came up with this solution is quite straightforward:

  1. Read "DevExtreme React Grid - Blog Series" by Oliver Sturm
  2. Read React Core documentation.
  3. Check the existing table-edit-column source code at GitHub: here and here

Hope this helps.

rvanlaarhoven
  • 591
  • 6
  • 16
Richie
  • 161
  • 3
6

I have forked your sandbox code and did some additions to it. Hope that's what you are looking for. And below is the same code. Hope this helps.

import React from "react";
import { render } from "react-dom";
import Paper from "@material-ui/core/Paper";
import // State or Local Processing Plugins
"@devexpress/dx-react-grid";
import {
    Grid,
    Table,
    TableHeaderRow
} from "@devexpress/dx-react-grid-material-ui";

class App extends React.PureComponent {
    constructor(props) {
        super(props);

        this.state = {
            columns: [
                { name: "name", title: "Name" },
                { name: "sex", title: "Sex" },
                { name: "city", title: "City" },
                { name: "car", title: "Car" },
                { name: "action", title: "action" }
            ],
            rows: [
                {
                    sex: "Female",
                    name: "Sandra",
                    city: "Las Vegas",
                    car: "Audi A4",
                    action: this.addResetBtn.call(this, { index: 0 })
                },
                { sex: "Male", name: "Paul", city: "Paris", car: "Nissan Altima" },
                { sex: "Male", name: "Mark", city: "Paris", car: "Honda Accord" },
                { sex: "Male", name: "Paul", city: "Paris", car: "Nissan Altima" },
                { sex: "Female", name: "Linda", city: "Austin", car: "Toyota Corolla" },
                {
                    sex: "Male",
                    name: "Robert",
                    city: "Las Vegas",
                    car: "Chevrolet Cruze",
                    action: this.addResetBtn.call(this, { index: 5 })
                },
                { sex: "Female", name: "Lisa", city: "London", car: "BMW 750" },
                { sex: "Male", name: "Mark", city: "Chicago", car: "Toyota Corolla" },
                {
                    sex: "Male",
                    name: "Thomas",
                    city: "Rio de Janeiro",
                    car: "Honda Accord"
                },
                { sex: "Male", name: "Robert", city: "Las Vegas", car: "Honda Civic" },
                { sex: "Female", name: "Betty", city: "Paris", car: "Honda Civic" },
                {
                    sex: "Male",
                    name: "Robert",
                    city: "Los Angeles",
                    car: "Honda Accord"
                },
                {
                    sex: "Male",
                    name: "William",
                    city: "Los Angeles",
                    car: "Honda Civic"
                },
                { sex: "Male", name: "Mark", city: "Austin", car: "Nissan Altima" }
            ]
        };
    }

    addResetBtn = ({ index }) => {
        return (
            <button
                className="btn"
                onClick={this.handleResetClick.bind(this, { index: index })}
            >
                Reset
            </button>
        );
    };

    handleResetClick = ({ index }) => {
        const updatedRows = [...this.state.rows];
        updatedRows[index].car = "";
        this.setState({ rows: updatedRows });
    };

    render() {
        const { rows, columns } = this.state;

        return (
            <Paper>
                <Grid rows={rows} columns={columns}>
                    <Table />
                    <TableHeaderRow />
                </Grid>
            </Paper>
        );
    }
}

render(<App />, document.getElementById("root"));
Rohan Vats
  • 86
  • 5
  • Wow, awesome, you are great! How did you find out? Is that also described in the docs of the grid? – user2952265 Jun 13 '18 at 20:37
  • Not sure if its in the docs or not. But, this made more sense to me. Otherwise, this also depends on how you are injecting the date into the state. You can made this piece of code even cleaner. :+1: – Rohan Vats Jun 14 '18 at 05:34
2

I made a plugin that works with version 2 of dev extreme react grid and... with type script. The whole plugin is in ActionColumns.tsx.

Here is what the result can look like: Two columns with an action in dev extreme react grid

If you want to see it running: https://codesandbox.io/s/m6yve

Here is how to use it:

import React from 'react';
import {
  Grid,
  Table,
  TableHeaderRow,
  Toolbar
} from '@devexpress/dx-react-grid-material-ui';
import { Paper } from '@material-ui/core';
import customers, { IRowType } from "./data";
import { Column } from '@devexpress/dx-react-grid';
import { ActionColumns, IActionColumn } from './ActionColumns';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import RefreshIcon from '@material-ui/icons/Refresh';

interface IAppState {
  columns: Column[];
  rows: IRowType[];
  actionColumns: IActionColumn[];
}

class App extends React.PureComponent<any, IAppState> {
  constructor(props: any) {
    super(props);
    this.state = {
      columns: [
        { name: 'ID', title: 'ID' },
        { name: 'CompanyName', title: 'Company Name' },
        { name: 'Address', title: 'Address' },
        { name: 'MakeAction' },
        { name: 'City', title: 'City' },
        { name: 'State', title: 'State' },
        { name: 'Zipcode', title: 'Zip code' },
        { name: 'Open' }
      ],
      actionColumns: [
        { columnName: "Open", label: "Open details", onClick: this.handleClickOpenDetails, icon: <MoreVertIcon /> },
        { columnName: "MakeAction", label: "Make action", onClick: this.handleClickMakeAction, icon: <RefreshIcon /> },
      ],
      rows: customers
    };
  }
  render() {
    const { rows, columns, actionColumns } = this.state;
    return (
      <Paper>
        <Grid rows={rows} columns={columns}>
          <Table />
          <TableHeaderRow />
          <Toolbar />
          <ActionColumns actionColumns={actionColumns} />
        </Grid>
      </Paper>
    );

  }
  private handleClickOpenDetails(row: any) {
    console.log("open details", row);
  }
  private handleClickMakeAction(row: any) {
    console.log("make action", row);
  }
}
export default App;

and here is the plugin:

import autobind from "autobind-decorator";
import * as React from 'react';
import {
    Template,
    Plugin,
    TemplateConnector,
    Getter,
    Getters,
} from '@devexpress/dx-react-core';
import { TableCell, IconButton, Tooltip } from '@material-ui/core';
import { Table, VirtualTable, TableHeaderRow } from "@devexpress/dx-react-grid-material-ui";
import { TableColumn } from "@devexpress/dx-react-grid";

function makeDictionary<T>(values: T[], getKey: (value: T) => string) {
    return values.reduce((acc, v) => { acc[getKey(v)] = v; return acc; }, {} as { [key: string]: T });
}

const pluginDependencies = [
    { name: 'Table' },
];
export interface IActionColumn {
    columnName: string;
    icon: React.ReactElement<any>;
    label?: string;
    onClick: (row: any) => void;
}
export interface IActionColumnsProps {
    actionColumns: IActionColumn[];
}

@autobind
class ActionColumnsBase extends React.PureComponent<IActionColumnsProps> {
    static ACTION_COLUMN_TYPE = Symbol("ACTION_COLUMN");
    static components = {
        cellComponent: 'Cell',
        headerCellComponent: 'HeaderCell',
        commandComponent: 'Command',
    };
    render() {
        const { actionColumns } = this.props;
        const columnDictionary = makeDictionary(actionColumns, i => i.columnName);

        return (
            <Plugin
                name="ActionColumn"
                dependencies={pluginDependencies}
            >
                <Getter name="tableColumns" computed={this.computeColumns.bind(null, columnDictionary)} />
                <Template name="tableCell" predicate={this.isActionTableHeader.bind(null)}>
                    {(params: any) => (
                        <TemplateConnector>
                            {(getters, actions) => {
                                return (
                                    <TableCell />
                                );
                            }}
                        </TemplateConnector>
                    )}
                </Template>
                <Template name="tableCell" predicate={this.isActionTableCell.bind(null)}>
                    {(params: any) => (
                        <TemplateConnector>
                            {(getters, actions) => {
                                const actionColumn = columnDictionary[params.tableColumn.column.name];
                                const button = (<IconButton
                                    size="small"
                                    aria-label={actionColumn.label}
                                    style={{ verticalAlign: "middle" }}
                                    onClick={actionColumn.onClick.bind(null, params.tableRow.row)} >
                                    {actionColumn.icon}
                                </IconButton>);
                                if (actionColumn.label) {
                                    return (<TableCell align="right">
                                        <Tooltip title={actionColumn.label}>
                                            {button}
                                        </Tooltip>
                                    </TableCell>);
                                }
                                else {
                                    return (<TableCell align="right">
                                        {button}
                                    </TableCell>);
                                }
                            }}
                        </TemplateConnector>
                    )}
                </Template>
            </Plugin>
        );
    }
    private computeColumns(actionColumns: { [key: string]: IActionColumn }, getters: Getters) {
        const tableColumns = getters.tableColumns as TableColumn[];
        const columns = tableColumns.map(tableColumn => {
            if (!tableColumn.column || !actionColumns[tableColumn.column.name]) {
                return tableColumn;
            }
            return { ...tableColumn, type: ActionColumnsBase.ACTION_COLUMN_TYPE, width: 60 };
        });
        return columns;
    }
    private isActionTableCell(params: any) {
        if ((params.tableRow.type === Table.ROW_TYPE || params.tableRow.type === VirtualTable.ROW_TYPE) && params.tableColumn.type === ActionColumnsBase.ACTION_COLUMN_TYPE) {
            return true;
        }
        return false;
    }
    private isActionTableHeader(params: any) {
        if ((params.tableRow.type === TableHeaderRow.ROW_TYPE) && params.tableColumn.type === ActionColumnsBase.ACTION_COLUMN_TYPE) {
            return true;
        }
        return false;
    }
}
export const ActionColumns: React.ComponentType<IActionColumnsProps> & {
    ACTION_COLUMN_TYPE: symbol;
} = ActionColumnsBase;
Stephane
  • 1,359
  • 1
  • 15
  • 27