4

I'm just wondering if someone has already been able to change the color of a row, in React Griddle, by clicking on it (just once).

I'm experimenting things with JQuery, and even with Griddle Metadata, but it may be done in a cleaner way ?

Edit : I'm using React 15, Griddle inside MantraJS/Meteor, getting the data in my react Component using a Mantra container.

I can get the data by using onClick event, but not able to switch the background color in the onClick event, or playing with Metadatas.

Thanks !

EDIT : I use another view to display the content of the table, so for now I don't need to change the background of my tables cells, but if I found a solution I'll complete this post

awzx
  • 1,023
  • 2
  • 12
  • 31
  • I've had a look on this library, and this could be a great alternative to React-griddle ! Thanks ! – awzx Sep 10 '16 at 23:45

2 Answers2

4

You can use react-griddle props rowMetadata and onRowClick to do this:

class ComponentWithGriddle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      selectedRowId: 0,
    };
  }
  onRowClick(row) {
    this.setState({ selectedRowId: row.props.data.id });
  }
  render() {
    const rowMetadata = {
      bodyCssClassName: rowData => (rowData.id === this.state.selectedRowId ? 'selected' : ''),
    };
    return (
      <Griddle
        ...
        rowMetadata={rowMetadata}
        onRowClick={this.onRowClick.bind(this)}
      />
    );
  }
}

Now this adds a selected class to the selected <tr> elements, so you can use custom styles to add colors or whatever styles you want to apply to the selected row.

Note that a more convenient API for selecting rows has been called for in the Griddle Github issues.

Waiski
  • 9,214
  • 3
  • 21
  • 30
  • Almost ! , it selects all my rows now, I'm trying to figure out why... Because you code looks to make sense. I wasn't sure about this property : rowData.id, because it's undefined when you try to log it in the console, But thanks by the way, really appreciated ;) ! – awzx Sep 11 '16 at 00:04
  • @DavidB. Apparently your data doesn't have an `id` field. You need some unique identifier for each row to make this work. You can just switch `id` in my code (both at `row.props.data.id` and `rowData.id`) to whatever unique field you have. – Waiski Sep 11 '16 at 09:26
  • Actually, I can't access to rowData._id in metadata declaration, but the field _id exists (I'm using MeteorJS and MongoDB) – awzx Sep 11 '16 at 11:02
  • @DavidB. That's strange. Could you edit your question to show how exactly you get the data and render the Griddle. – Waiski Sep 11 '16 at 11:20
  • I switched to React Semantic UI tables and implemented my own Data Tables to solve this "issue" and handling row selection – awzx Nov 21 '16 at 03:49
  • @Waiski I can't seem to get the background color to change. The class is applied to the row, but it's not changing colour. I've even set up the class. Ah, the td is covering up the colour. What should I do? – A. L Jan 04 '17 at 04:17
  • How do we achieve this in griddle 1.0 ? – Sudhir Apr 09 '18 at 12:44
3

For whatever reason, I couldn't get Waiski's answer to work for me at all. I'm assuming that something must have changed in Griddle over the past two years. It looks like the current prevailing advice on the Web is to "implement row selection as a plugin", but I couldn't find any examples of that either. After a long hard look at the code for the Position plugin’s TableEnhancer on GitHub and a bunch of trial and error I eventually managed to cobble together the following row selection plugin for Griddle in TypeScript:

import * as React from "react";
import * as Redux from "redux";
import Griddle, { connect, GriddlePlugin, components } from "griddle-react";

export type RowId = string | number;
export type RowClickHandler = (event: React.MouseEvent<Element>, rowId: RowId) => void;
export type RowIdGetter<TData> = (rowData: TData) => RowId;

export interface IRowEnhancerProps {
    rowClickHandler: RowClickHandler;
    rowId: RowId;
    isSelected: boolean;
}

export class RowSelector<TData> {

    private _rowClickHandler: RowClickHandler = null;
    private _rowIdGetter: RowIdGetter<TData>;

    constructor(rowClickHandler: RowClickHandler, rowIdGetter: (rowData: TData) => RowId) {
        this._rowClickHandler = rowClickHandler;
        this._rowIdGetter = rowIdGetter;
    }

    public rowIdToSelect: RowId;

    public plugin: GriddlePlugin = {
        components: {
            RowEnhancer: (OriginalComponent: React.ComponentClass<components.RowProps>) =>
                this.rowSelectionEnhancer(OriginalComponent)
        }
    }

    private rowSelectionEnhancer(
        OriginalComponent: React.ComponentClass<components.RowProps>
        ): React.ComponentClass<components.RowProps> {

        const rowDataSelector = (state, { griddleKey }) => {
            return state
                .get('data')
                .find(rowMap => rowMap.get('griddleKey') === griddleKey)
                .toJSON();
        };

        return Redux.compose(

            connect((state, props) => {

                const rowData: TData = rowDataSelector(state, props as { griddleKey });
                const rowId: RowId = this._rowIdGetter(rowData);

                return {
                    ...props,
                    rowClickHandler: this._rowClickHandler,
                    rowId: rowId,
                    isSelected: rowId.toString() === this.rowIdToSelect.toString()
                };
            })

        )(class extends React.Component<IRowEnhancerProps, any>{

            public render() {
                return (
                    <OriginalComponent
                        {...this.props}
                        onClick={(event) => this.props.rowClickHandler(event, this.props.rowId)}
                        className={this.props.isSelected ? "selected" : ""}
                    />
                );
            }
        });
    }
}

Here's a rough outline of how it's used by a component. (Note that I had to selectively extract this example from a much larger and more complicated component, so there might be some errors/inconsistencies; sorry about that. It should still give a good overall idea of the approach.)

import * as React from "react";
import Griddle, { RowDefinition, plugins, GriddlePlugin} from "griddle-react";

import * as MyGriddlePlugins from "../GriddlePlugins";

export interface IPartInfo {
    serialNumber: number,
    name: string,
    location: string
}
export interface IPartListProps{
    parts: IPartInfo[],
    selectedSerialNumber: number
}

export class PartList extends React.Component<IPartListProps, void > {

    private rowSelector: MyGriddlePlugins.RowSelector<IPartInfo>;
    private rowIdGetter: MyGriddlePlugins.RowIdGetter<IPartInfo>;

    constructor(props?: IPartListProps, context?: any) {
        super(props, context);

        this._rowClickHandler = this._rowClickHandler.bind(this);
        this.rowSelector = new MyGriddlePlugins.RowSelector(
            this._rowClickHandler, 
            this._rowIdGetter);
    }

    private _rowClickHandler: MyGriddlePlugins.RowClickHandler = 
        (event: React.MouseEvent<Element>, selectedSerialNumber: MyGriddlePlugins.RowId) => {
        if (selectedSerialNumber !== this.props.selectedSerialNumber) {
            /* 
            Set state, dispatch an action, do whatever.  The main point is that you
            now have the actual event from the click on the row and the id value from
            your data in a function on your component.  If you can trigger another
            render pass from here and set a fresh value for this.rowSelector.rowIdToSelect 
            then the "selected" CSS class will be applied to whatever row this click
            event just came form so you can style it however you like. 
            */
        }
    }

    private _rowIdGetter: (rowData: IPartInfo) => MyGriddlePlugins.RowId =
        (rowData: IPartInfo) => rowData.serialNumber;

    public render(): JSX.Element {

        this.rowSelector.rowIdToSelect = this.props.selectedSerialNumber;

        return (
            <div>
                <Griddle
                    data={this.props.parts}
                    plugins={[plugins.LocalPlugin, this.rowSelector.plugin]}
                >
                    <RowDefinition>
                        <ColumnDefinition id="name" title="Part Name" />
                        <ColumnDefinition id="location" title="Installed Location" />
                        <ColumnDefinition id="serailNumber" title="Serial Number" />
                    </RowDefinition>
                </Griddle>
            </div>
        );
    }
}

So, what's actually going on here? The component creates an instance of the plugin class at instantiation time, passing in an event handler to capture the click on the row and an accessor function to retrieve your ID value (not an inscrutable internal ID) from a row of your data. Just before the component returns its rendering, a value is set on the component's instance of the plugin, that way, when Griddle renders the plugin has the data to figure out when it's on a selected row and then adjust the CSS accordingly. The handler function from your component is then assigned to the row's onClick handler so your component can get the data from the click and do whatever it needs to do.

This passes the "It works for me" test (on React 15.6) which in my case is a straightforward master/detail view driven by a traditional table implemented through Griddle. I have no idea how well it would work with some of Griddle's more advanced features.

Bernard Hymmen
  • 825
  • 1
  • 7
  • 14