Giter Club home page Giter Club logo

react-dynamic-data-table's Introduction

React Dynamic Data Table

npm version npm

This package provides a React Dynamic Data Table component that supports sortable columns, pagination, field mapping, data manipulation, and more.

Installation

You can install this package with either npm or yarn as shown below.

npm install @langleyfoxall/react-dynamic-data-table
yarn add @langleyfoxall/react-dynamic-data-table

Remember to import the DynamicDataTable component where it is needed.

import DynamicDataTable from "@langleyfoxall/react-dynamic-data-table";

Usage

At its most basic, you can create a new <DynamicDataTable /> with just the rows prop.

<DynamicDataTable rows={this.state.users} />

The rows prop expects an array of objects, such as the following.

[
  { name: "Picard", email: "[email protected]"  },
  { name: "Kirk",   email: "[email protected]"    },
  { name: "Sisko",  email: "[email protected]"   }
]

Specifying a CSS class for the table

By default tables are assigned the bootstrap table and table-striped CSS classes. If you need a different table style, you can override these defaults by providing the className prop:

<DynamicDataTable
    className="table table-sm"
    />

Excluding fields

By default, React Dynamic Data Table will render a table containing all fields present in the rows prop. To exclude specific fields, you can use the fieldsToExclude props.

In the example below, the email field will be excluded.

<DynamicDataTable
    rows={this.state.users}
    fieldsToExclude={['email']}
    />

In the example below, all ID fields will be excluded.

<DynamicDataTable
    rows={this.state.users}
    fieldsToExclude={[/_?id/]}
    />

The fieldsToExclude prop expects an array of strings or regex expressions that represent the fields to exclude.

Mapping fields

By default, React Dynamic Data Table creates table headers based on the field name, with underscores replaced with spaces and each word's first letter converted to uppercase. You can override this behaviour with a field map.

In the example below, you can render the email field as 'Email Address'.

<DynamicDataTable
    rows={this.state.users}
    fieldMap={{ email: 'Email address' }}
    />

The fieldMap prop expects an object which maps the rows keys to alternative field names.

Ordering data

The React Dynamic Data Table will display the rows in the order they are provided in the array. However, it is possible to show, in the column header, that the data has been sorted.

In the example below, the name column header will show a down arrow indicating that the data has been sorted by name (ascending).

// this.state.orderByField = 'name';
// this.state.orderByDirection = 'asc';

<DynamicDataTable
    rows={this.state.users}
    orderByField={this.state.orderByField}
    orderByDirection={this.state.orderByDirection}
    />

The orderByField prop expects a string indicating the field to sort by (one of the keys from the rows object).

The orderByDirection expects either asc or desc, meaning ascending or descending respectively.

If you wish to let the end-user sort the data table by clicking on the column headings, you can use the changeOrder prop. This is shown in the example below.

// this.state.orderByField = 'name';
// this.state.orderByDirection = 'asc';

<DynamicDataTable
    rows={this.state.users}
    orderByField={this.state.orderByField}
    orderByDirection={this.state.orderByDirection}
    changeOrder={(field, direction) => this.changeOrder(field, direction)}
    />
changeOrder(field, direction) {
    this.setState({ orderByField: field, orderByDirection: direction }, () => {
        const users = /* Get sorted data from API endpoint */
        this.setState({ users: users });
    });
}

The changeOrder prop expects a callable. This callable should:

  1. Change the orderByField and orderByDirection props, based on the passed field and direction parameters respectively.
  2. Change / re-retrieve the rows prop, such that it is sorted based on the passed field and direction parameters.

Ordering data is enabled for all fields by default. However, if you wish to restrict which fields the ordering is enabled for, pass an array of the field names into the allowOrderingBy prop. An example of this is shown below.

<AjaxDynamicDataTable
    rows={this.state.users}
    allowOrderingBy={[
        'name', 'email'
    ]}
/>

To have the opposite effect simply use disallowOrderingBy:

<AjaxDynamicDataTable
    rows={this.state.users}
    disallowOrderingBy={[
        'dob'
    ]}
/>

Typically, the allowOrderingBy and disallowdOrderingBy props should not be used simultaneously, as this could cause unexpected behaviour.

Ordering fields

By default fields will be ordered as they are passed into the table on each row. To force a specific ordering of columns an array of strings or regex can be passed with the fieldOrder prop. Anything that is not included within fieldOrder will be pushed to the end of the ordered fields.

<DynamicDataTable
    rows={[
        { id: 1, email: '[email protected]', name: 'Langley Foxall' }
    ]}
    fieldOrder={[
        'id', 'name'
    ]}
/>

// Output: id, name, email

Mixing strings and regex is also supported.

<DynamicDataTable
    rows={[
        { id: 1, email: '[email protected]', first_name: 'Langley', last_name: 'Foxall' }
    ]}
    fieldOrder={[
        'id', /_name/
    ]}
/>

// Output: id, first_name, last_name, email

Column Widths

In some cases there may be a need to make some columns be different widths by defining a width, rather than the table changing based off of content.

The columnWidths prop expects an object with column names as the keys and either a string or number as the values.

When a number is passed the width will become a percentage. If a string is passed then it respects whatever unit is set.

<DynamicDataTable
    rows={[
        { id: 1, email: '[email protected]', first_name: 'Langley', last_name: 'Foxall' }
    ]}
    columnWidths={{
        // 10%
        id: 10,

        // 100px
        email: '100px'
    }}
/>

Custom order by icons

When ordering by a field on an element will be rendered next to it. By default these are simple symbols (โ†“ and โ†‘). These can be changed by passing a valid node into orderByAscIcon and orderByDescIcon.

<DynamicDataTable
    orderByAscIcon="Ascending"
    // orderByAscIcon={<p>Ascending</p>}
    // orderByAscIcon={<FancyAscendingIcon />}
/>

You can optionally specify an icon to appear when a sortable field is not the currently sorted field using the orderByIcon prop:

<DynamicDataTable
    orderByIcon="Sortable"
    // orderByAscIcon={<p>Sortable</p>}
    // orderByAscIcon={<FancySortableIcon />}
/>

By default the order by icon will appear following a non-breaking space after the column label. You can instead prepend the icon by specifying the prependOrderByIcon prop; this is particularly useful if you are using css float to position the icon as it will not flow under the text:

<DynamicDataTable
    orderByIcon={<i className="mt-1 fad fa-sort float-right"></i>}
    orderByAscIcon={<i className="mt-1 fad fa-sort-up float-right"></i>}
    orderByDescIcon={<i className="mt-1 fad fa-sort-down float-right"></i>}
    prependOrderByIcon
/>

Pagination

Making pagination work with React Dynamic Data Table requires three extra props. These are the currentPage, totalPages and changePage props. Once these props are set correctly, a Bootstrap style pagination will be displayed below the table.

The currentPage prop expects an integer representing the current page number (one or above).

The totalPages prop expects an integer representing the total number of pages in the data set (one or above). Pagination will only be shown if the total number of pages is greater than one.

The changePage props expect a callable with a page argument, indicating the new page number to load. This callable should:

  1. Load a new page of data into the rows prop based on the passed page argument.
  2. Set the currentPage prop to be equal to the passed page argument.

A example of this is shown below:

// this.state.currentPage = 1;
// this.state.totalPages = 5;

<DynamicDataTable
        rows={this.state.users}
        currentPage={this.state.currentPage}
        totalPages={this.state.totalPages}
        changePage={page => this.changePage(page)}
    />
changePage(page) {
    const users = /* Get page of data from API endpoint */
    this.setState({ users: users, currentPage: page });
}

Pagination is dynamic, showing only a select subset of the available pages as actual buttons. Whether or not an individual button to a page is shown depends on if it is the first or last page (these are always shown), and whether it is within a predefined offset from the current page (a pagination delta). This delta can be changed by passing a paginationDelta prop into DynamicDataTable as shown below:

<DynamicDataTable
        rows={this.state.users}
        currentPage={this.state.currentPage}
        totalPages={this.state.totalPages}
        paginationDelta={6}
    />

Always showing pagination controls

By default the pagination controls are only shown if there are two or more pages of data to be displayed. You can override this behaviour by simply passing the alwaysShowPagination prop:

<DynamicDataTable
        alwaysShowPagination
    />

Per page limiting

Changing the number of entries displaying in the data table is easy. The totalRows, perPage, changePerPage and perPageRenderer allow you to customize a per page limit control.

  • totalRows the total number of rows within the dataset
  • perPage the current per page limit (default: 15)
  • changePerPage handles the logic for changing the perPage prop. This recieved a single argument which is the new limit.
  • perPageOptions the results per page options (default: [10, 15, 30, 50, 75, 100])
  • perPageRender can either be a node or a function.

By default a Bootstrap styled select is displayed if changePerPage is a function.

<DynamicDataTable
    totalRows={totalRows}
    perPage={perPage}
    changePerPage={newPerPage => (
        this.setState({
            perPage: newPerPage
        })
    )}
    perPageRenderer={props => (
        <PerPage {...props} />
    )}
/>

perPageRenderer

The perPageRenderer prop accepts either a node or function. If a valid react element is passed then React.cloneElement is used to bind:

  • totalRows
  • value (see perPage above)
  • onChange (see changePerPage above)
  • perPageOptions

If a function is passed then the props described above are passed in an object.

Row buttons

Row buttons appear on the right hand side of every row in the React Dynamic Data Table. By default, a 'View' button is provided, which simply links the user to the current URL with the row's id appended.

You can completely override the row buttons that are displayed by provided a buttons prop. This prop expects an array of objects, each containing a name and callback.

The name is string, such as 'View', 'Edit', 'Delete', etc.

The callback is a callable with a two arguments. The first is the event object for the button clicked and the second is an object representing the current row.

An example of setting custom row buttons is shown below.

<DynamicDataTable
    rows={this.state.users}
    buttons={[
        {
            name: 'Edit',
            callback: (event, user) => {
                // Show edit user view...
            }
        },
        {
            name: 'Delete',
            callback: (event, user) => {
                // Delete user...
            }
        }
    ]}
    />

buttons can also be given a custom render at the top level, or for multiple array elements. It's worth noting that multiple array elements still respect the dropdown menu.

// Top level example
<DynamicDataTable
  buttons={row => (
    <a>
      <i className="fas fa-fw fa-eye" />
      <span>Totally custom button</span>
    </a>
  )}
/>

// Low level example
<DynamicDataTable
  buttons={[
    {
      render: row => (
        <a>
          <i className="fas fa-fw fa-eye" />
          <span>Totally custom button 1</span>
        </a>
      )
    },
    {
      render: row => (
        <a>
          <i className="fas fa-fw fa-tick" />
          <span>Totally custom button 2</span>
        </a>
      )
    }
  ]}
/>

Rendering custom rows

If you come across a situation where the automatically generated rows are not suitable for your project you can use the rowRenderer prop. This prop expects a callable that receives a single argument, and returns a valid React element, which should be a <tr> element.

The argument passed to the rowRenderer callable is a JavaScript object that contain the following properties.

{
  row,                // Instance of data row
  onClick,            // Row on click handler
  onMouseUp,          // Row on MouseUp handler
  onMouseDown,        // Row on MouseDown handler
  buttons,            // Array of buttons
  actions,            // Array of header actions
  fields,             // Visible fields
  renderCheckboxes,   // Boolean indicating whether to render checkboxes
  disableCheckbox,    // Boolean indicating whether to disable the checkbox per row
  checkboxIsChecked,  // Boolean indicating if checkbox is checked
  onCheckboxChange,   // Callable that is called when a per row checkbox is changed
  dataItemManipulator // Callable that handles manipulation of every item in the data row
}

For implementation details regarding these properties, see the other relevant areas of the documentation.

Clickable rows

Clickable rows allows an onClick prop to be passed. This should be a callable, that will be passed an event object along with an instance of the row that is clicked. It also adds the bootstrap table-hover class onto the table.

<DynamicDataTable
    rows={this.state.users}
    onClick={(event, row) => console.warn(event, row.name)}
/>

Mouse Events

For more complex interactions, such as supporting the ability to Middle-click, you can use the onMouseUp and onMouseDown events instead. It also adds the bootstrap table-hover class onto the table. The onMouseDown and onMouseUp props should be callables, that will be passed an event object along withan instance of the row that is clicked.

<DynamicDataTable
    rows={this.state.users}
    onMouseDown={this.handleMouseDown}
    onMouseUp={this.handleMouseUp}
/>

Context Menus

The ability to right click rows can be enabled by using onContextMenu and rowRenderer. In the example we will use our own @langleyfoxall/react-dynamic-context-menu:

<DynamicDataTable
    rows={this.state.users}
    rowRenderer={options => (
        <DynamicContextMenu
            key={options.key}
            data={options.row}
            menuItems={[
                {
                    label: 'Update',
                    onClick: this.handleUpdate,
                },
                {
                    label: 'Delete',
                    onClick: this.handleDelete,
                },
            ]}
        >
            {DynamicDataTable.rowRenderer(options)}
        </DynamicContextMenu>
    )}
/>

DynamicContextMenu clones the child and adds onContextMenu as a prop. This can also be achieved manually.

<DynamicDataTable
    rows={this.state.users}
    rowRenderer={({ row }) => (
        <tr onContextMenu={() => this.onContextMenu(row)}>
            <td/>
        </tr>
    )}
/>

Hoverable table rows

To enable a hover effect on rows even if onClick is not passed into the table you can use the prop hoverable. This will add a background color on each row when hovered.

<DynamicDataTable
    rows={this.state.users}
    hoverable
/>

Render no data component

If you wish to render something other than the table when no rows are present you can take advantage of noDataComponent which accepts a valid react element. This will replace the table until there are rows.

<DynamicDataTable
    row={[]}
    noDataComponent={(
        <p>I replace the table, not just the text inside it.</p>
    )}
/>

Bulk select checkboxes

If you wish to allow users to bulk select users in a React Dynamic Data Table, you can specify the renderCheckboxes prop. This will render a series of checkboxes against each row, on the left side of the table.

<DynamicDataTable
    rows={this.state.users}
    renderCheckboxes
    />

Bulk select checkboxes are usually combined with bulk actions to perform actions on one or more rows at once.

Disable checkboxes

Checkboxes can also be disabled for each individual row by passing in disabledCheckboxes which should container an array of identifiers. If an identifier is in the array then the checkbox will have disabled set to true.

<DynamicDataTable
    rows={this.state.users}
    renderCheckboxes
    disabledCheckboxes={[1]}
    />

Externally manage checkboxes

Combining isCheckboxChecked, onMasterCheckboxChange and onCheckboxChange allows a row's checkbox state to be managed outside of the datatable while still allowing disabledCheckboxes to work as intended.

In the example below, we are using a Set named checked to store the current status of the data table's checkboxes.

const checked = new Set

<DynamicDataTable
    isCheckboxChecked={({ id }) => checked.has(id)}
    onMasterCheckboxChange={(_, rows) => {
        let all = true

        rows.forEach(({ id }) => checked.has(id) || all = false)

        rows.forEach(({ id }) => {
            if (all) {
                checked.delete(id)
            } else if (!checked.has(id)) {
                checked.add(id)
            }
        })
    }}
    onCheckboxChange={(_, { id }) => {
        if (checked.has(id)) {
            checked.delete(id)
        } else {
            checked.add(id)
        }
    }}
/>

isCheckboxChecked

isCheckboxChecked is called on each re-render of the data table allowing for custom logic to determine if a checkbox is checked. It will receive the current row and the visible rows as arguments.

const checked = new Set

<DynamicDataTable
    isCheckboxChecked={({ id }) => checked.has(id)}
/>

Note: This should only be used if onMasterCheckboxChange and onCheckboxChange are implemented.

onMasterCheckboxChange

onMasterCheckboxChange is called when the master checkbox is clicked. This allows for custom logic for selecting and deselecting multiple rows. It will receive an event object from the input and the visible rows.

The master checkbox refers to the bulk select checkbox found in the top left corner of the data table whenever checkboxes are enabled.

const checked = new Set

<DynamicDataTable
    onMasterCheckboxChange={(_, rows) => {
        let all = true

        rows.forEach(({ id }) => checked.has(id) || all = false)

        rows.forEach(({ id }) => {
            if (all) {
                checked.delete(id)
            } else if (!checked.has(id)) {
                checked.add(id)
            }
        })
    }}
/>

renderMasterCheckbox

renderMasterCheckbox will determine if the master checkbox will be rendered

The master checkbox refers to the bulk select checkbox found in the top left corner of the data table whenever checkboxes are enabled.

<DynamicDataTable
    renderMasterCheckbox
/>

onCheckboxChange

onCheckboxChange is called when a row checkbox is clicked. This allows for custom logic for selecting and deselecting a single row. It will receive an event object from the input and the current row.

const checked = new Set

<DynamicDataTable
    onCheckboxChange={(_, { id }) => {
        if (checked.has(id)) {
            checked.delete(id)
        } else {
            checked.add(id)
        }
    }}
/>

Actions

Actions, when combined with bulk select checkboxes allow you perform actions of multiple rows at once. When in use, a menu will be rendered in the top right of the table allowing your users to choose a bulk action that will be applied to the selected rows.

Actions can also be used without bulk select checkboxes. This could allow for creation of action buttons that are not dependant on existing data, such as a 'Create User' button.

To use actions in your React Dynamic Data Table, you must specify the actions props. This prop expects an array of objects, each containing a name and callback.

The name is string, such as 'Delete user(s)', 'Duplicate user(s)' etc.

The callback is a callable with a single argument. The argument will contain an array of the selected rows.

Examples of how to use actions is shown below.

<DynamicDataTable
    rows={this.state.users}
    renderCheckboxes
    actions={[
        {
            name: 'Delete user(s)',
            callback: (rows) => {
                // Delete users...
            },
        },
    ]}
/>
<DynamicDataTable
    rows={this.state.users}
    actions={[
        {
            name: 'Create user',
            callback: () => {
                // Toggle create user modal...
            },
        },
    ]}
/>

Data Item Manipulation

If you wish to alter row data prior to it being rendered, you may use the dataItemManipulator prop available on the DynamicDataTable. This prop expects a function which will be passed three parameters, the field, the value and the row.

This function will be called once for every cell that is to be rendered.

<DynamicDataTable
    dataItemManipulator={(field, value, row) => {
        switch(field) {
            case 'id':
                return 'ID:' + value;
            case 'reference':
                return value.toUpperCase();
        }

        return value;
    }}
/>

It is also possible to render React components directly, by returning them from this function.

<DynamicDataTable
    dataItemManipulator={(field, value) => {
        switch(field) {
            case 'reference':
                return <ExampleComponent exampleProp={value} />;
        }

        return value;
    }}
/>

If you wish, you can dangerously render HTML directly by returning a string from the dataItemManipulator, you will however need to explicitly specify which fields this should be enabled for. This is done by using the dangerouslyRenderFields prop.

<DynamicDataTable
    dangerouslyRenderFields={['check']}
    dataItemManipulator={(field, value) => {
        switch(field) {
            case 'check':
                return "<i class='fa fa-check'></i>";
        }

        return value;
    }}
/>

Table footer

To display extra data at the bottom of the table a function or node can be passed into the React Dynamic Data Table by using the footer prop.

The footer is displayed directly in a tfoot to allow for multiple rows. So don't forget your trs.

Passing a function

When passing a function into the footer prop it receives an object with:

  • rows: All of the visible rows
  • width: The number of columns in the table

This should return a valid React element.

<DynamicDataTable
    footer={({ rows, width }) => (
        <tr>
            <td colSpan={width}>
                Table footer.
            <td>
        </tr>
    )}
/>

Passing a node

When passing a node or valid React element it is simply output.

<DynamicDataTable
    footer={(
        <tr>
            <td>
                Table footer.
            </td>
        </tr>
    )}
/>

Loading message & indicator

By default, the React Dynamic Data Table will not show indication that it is loading. On slow connections, this may make the table appear unresponsive or sluggish when initialing loading, changing pages, re-ordering, and so on.

To show a loading message, you can set the loading prop to true. This will display a default loading message, which can be changed by altering passing a string into the optional loadingMessage prop. If you wish, you can also pass a React component into the loadingIndicator prop, which will be displayed above the textual loading message.

<DynamicDataTable
    loading={true}
    loadingMessage="User data is now loading..."
    loadingIndicator={(
        <img src="/loading-animation.gif">
    )}
/>

Alternatively, if you wish to replace the entire table while data is being loaded, you can pass a React component into the loadingComponent prop.

<DynamicDataTable
    loading={true}
    loadingComponent={(
        <p>I replace the table, not just the text inside it.</p>
    )}
/>

To display either of these options loading must be set to true. Note that the AJAX Dynamic Data Table handles the loading prop internally but can be overriden.

Error message

In the case that something goes wrong, such as data failing to load, you can display and error message in place of the normal React Dynamic Data Table output.

In order to display an error message, you just need to set the optional errorMessage prop. This prop expects a string such as An error has occurred while loading user data.. If the error is resolved, this prop must be reset to an empty string in order to ensure the data table is displayed.

Editable Columns

If you wish to make certain columns editable you can specify how using the editableColumns prop. This prop accepts an array of object in the following format:

[
    {
        name: 'ExampleColumnText',
        controlled: false,
        type: 'text',
        onChange: (event, column, row, index) => console.log(event, column, row, index),
    },
    {
        name: 'ExampleColumnSelect',
        controlled: false,
        type: 'select',
        onChange: (event, column, row, index) => console.log(event, column, row, index),
        optionsForRow: (row, column) => [
            {
                label: 'One',
                value: 1
             },
            {
                label: 'Two',
                value: 2
            }
        ]
}]

Text Inputs

If you specify that the type of the column is text the column will contain a text input with a value of the column from the row data.

Selects

If you wish to use a select instead of a text input you may specify select as the type. The column will now contain a select input, by default with no options. In order to provide options implement the optionsForRow method. This method will be called with: The row data and Column name in that order. It should return an array of objects in this format:

[
    {
        label: 'Example one',
        value: 1
    },
    {
        label: 'Example two',
        value: 2
    }
]

Receiving input

In order to receive the users input you can provide the onChange method that will be called when the input is changed. This method will be called with the following parameters in the given order: The event from the input, The column name, The row data, The row index.

Controlled and Uncontrolled inputs

A uncontrolled input is an input whose value is controlled by the DOM. This means that it cannot be modified after the default value has been set by React. You will only receive input from the component and will not be able to modify the displayed value.

A controlled input will require you to store the value of the input in the state, the value of the input will be read from state meaning you will have to update state on user input to reflect it in component. In this case it will mean you will have to alter the data passed in as the rows prop.

Indicating active row(s)

If you supply an rowIsActive predicate prop to the data table, any row matching the predicate is given the CSS class table-active:

<DynamicDataTable
    rowIsActive={(row) => row.id === 3}
/>

react-dynamic-data-table's People

Contributors

dextermb avatar divineomega avatar jacobbrassington avatar james34shaw100 avatar jameswilddev avatar jaredkove avatar lfnicklangley avatar nilesb avatar stejaysulli avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-dynamic-data-table's Issues

Documentation required

We require documentation for this package.

  • Installation (via npm or yarn)
  • Usage, including all available props
    • rows
    • fieldsToExclude
    • fieldMap
    • currentPage
    • totalPages
    • orderByField
    • orderByDirection
    • changeOrder
    • renderCheckboxes
    • actions
    • loadingMessage
    • loadingComponent
    • errorMessage
    • noDataMessage
    • dataItemManipulator
    • buttons
  • Example screenshot(s)

Add the ability to order table columns

I think a feature that would be used a decent amount would be the ordering of columns. An idea for implementing this could be as simple as having columnOrder prop that contains strings of field names. Any fields that are in the array get placed in their position and anything that isn't included get appended as they come on the end.

Example:

<DynamicDataTable
    rows={[
        id: 1,
        username: 'dextermb',
        name: 'Dexter Marks-Barber',
        bio: 'Ree',
        email: '[email protected]',
    ]}
    columnOrder={[ 'id', 'name', 'username', 'email' ]}
/>

In the example above the order would end up as:

- id
- name
- username
- email
- bio

Thoughts?

cc @DivineOmega

Add prop for empty state rendering

I think that it would be nice to have a prop named something along the lines of emptyStateRender which would allow you to pass in a:

  • Function
  • Element
  • String

If a function or element is passed then it should return those rather than showing the table with "no data", if a string is passed then it'd just change the empty message.

Thoughts?

cc @NilesB @DivineOmega @JacobBrassington @wheatleyjj

`AjaxDynamicDataTable` `axios` prop

A problem developers may encounter with using the AjaxDynamicDataTable component is that it currently uses its own axios instance, meaning it will not contain any authentication headers that may be required to access the API route.

One thought we had to combat this was to let people pass through an axios instance as prop. This should be optional, and default to the current behaviour.

Example syntax:

<AjaxDynamicDataTable apiUrl={'/api/users/data-table'} axios={window.axios} />

Add `columnWidths` prop

The columnWidths prop would be used to specify width HTML attribute on <th> elements, which would then force all <td> to conform to the width.

<DynamicDataTable
    rows={this.state.users}
    columnWidths={[50, 20, 30]}
/>

This could be easily implemented by looping through the columns and then checking the index in the columnWidths array.

I could see a use case for this where a table only have two columns, currently the table would be split in half. But say one column is id and the other name, the id could should be the smallest.

Thoughts?

cc @NilesB @DivineOmega

Bulk actions should pass the full rows

Currently, the bulk actions pass an array of row.ids to the callback method. I think it would be more flexible to pass an array of all rows. This would remove the requirement for the data row to have an id property.

This would be a breaking change for any existing implementation of bulk actions.

Allow the ability to add grouped rows

I'd like to see the ability to add grouped rows. This can be achieved by having many tbody elements within a table. Within the HTML spec for tables it references this:

<!ELEMENT table - - (caption?, (col*|colgroup*), thead?, tfoot?, tbody+)>

Meaning you can have infinite number of tbodys. Perhaps it would be structured in a way to pass a className which would be set on the different groups. This would be great if you had different data sources with the same columns, for example, to split them up you could add a different background-color.

Reduce scaffolding time by introducing a helper

After using both this package and the Laravel Datatables package I realised that this package could potentially be improved by mimicking what the datatables package does.

The datatables package has its own helper which then wraps around Model::query() and performs all its filtering and pagination on that singular endpoint. This would be ideal to improve the set up time rather than having to repeat yourself when creating the individual endpoints for filtering, searching, etc.

How datatables does it

In your method you can use the following so that the package will then perform all its requests against a singular endpoint.

return datatables(PaymentMethod::query())->toJson();

The helper is just the following:

    /**
     * Helper to make a new DataTable instance from source.
     * Or return the factory if source is not set.
     *
     * @param mixed $source
     * @return \Yajra\DataTables\DataTableAbstract|\Yajra\DataTables\DataTables
     */
    function datatables($source = null)
    {
        if (is_null($source)) {
            return app('datatables');
        }

        return app('datatables')->make($source);
    }

What we could do

We could have our own helper which would perform largely the same and improve scaffolding time. Something along the lines of...

return reactdatatables(PaymentMethod::query())->toJson();

Add all supported props to prop types for the AjaxDynamicDataTable.

Currently in the DynamicDataTable we have all of the supported props being in the prop types object. However in the AjaxDynamicDataTable we only have a few of them with the rest being spread but not in the prop types.

I think it would be useful to add all supported props to the prop types to allow users to get type hinting when using this component.

Unable to do Bulk select

Unable to do bulk select its throw error like

Uncaught TypeError: Cannot read property 'checked' of undefined
at DynamicDataTable.checkboxChange (DynamicDataTable.js:636)
at onChange (DynamicDataTable.js:562)
at HTMLUnknownElement.callCallback (react-dom.development.js:147)
at Object.invokeGuardedCallbackDev (react-dom.development.js:196)
at invokeGuardedCallback (react-dom.development.js:250)
at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:265)
at executeDispatch (react-dom.development.js:571)
at executeDispatchesInOrder (react-dom.development.js:596)
at executeDispatchesAndRelease (react-dom.development.js:695)
at executeDispatchesAndReleaseTopLevel (react-dom.development.js:704)
at Array.forEach ()
at forEachAccumulated (react-dom.development.js:674)
at runEventsInBatch (react-dom.development.js:844)
at runExtractedEventsInBatch (react-dom.development.js:852)
at handleTopLevel (react-dom.development.js:5029)
at batchedUpdates$1 (react-dom.development.js:21463)
at batchedUpdates (react-dom.development.js:2247)
at dispatchEvent (react-dom.development.js:5109)
at react-dom.development.js:21520
at Object.unstable_runWithPriority (scheduler.development.js:255)
at interactiveUpdates$1 (react-dom.development.js:21519)
at interactiveUpdates (react-dom.development.js:2268)
at dispatchInteractiveEvent (react-dom.development.js:5085)

But when I select individually it's working
3
Desktop screenshot
error-for-bulkselect

`dataItemManipulator` prop not documented fully

The dataItemManipulator prop is only documented within the context of rendering custom rows. It is also a prop on the main DynamicDataTable component and, as such, should have its own section in the documentation.

Add `disabled` or `loading` attribute to buttons

Currently you can pass a custom render for a button or an object. The object only accepts:

  • name
  • callback

I think it would be useful to add a loading and/or disabled attribute to the object. This would be used when making network calls or other actions that take a long time.

Allow valid react elements to be used in data manipulator

I want to add a symbol (such as an up/downward facing arrow) within a single column. I'd like to be able to do this within the dataItemManipulator function as I am only changing a single column.

If we want to keep dataItemManipulator clear of elements, then we should look into implementing a low level cellRenderer on DataRow.

Currently the object/array check in renderCell causes a circular JSON.stringify error when attempting to pass a React element into it (as a React element is an object, verified by $$symbol).

Here's the current expression:

if (typeof value === 'object' || typeof value === 'array') {
    value = JSON.stringify(value);
}

Perhaps this could be:

if (!React.isValidElement(value)) {
    value = JSON.stringify(value);
}

Or:

import Util from '@ninetynine/util';

// ...

if (Util.isObject(value) || Util.isArray(value)) {
    value = JSON.stringify(value);
}

It's worth noting that currently null gets stringified as in JavaScript null is an object.

Thoughts?

cc @NilesB @DivineOmega @wheatleyjj

Do not stringify object rows, skip over them instead

Currently within the source object/array get stringified. I propose mapping these to null and then filtering columns in render.

Original:

    render() {
        const { row, fields } = this.props;

        return (
            <tr onClick={() => this.handleOnClick(row)}>
                { this.renderCheckboxCell(row.id) }
                { fields.map(field => this.renderCell(field, row)) }
                { this.renderButtons(row) }
            </tr>
        );
    }

    // ...

    renderCell(field, row) {
        let value = row[field.name];

        value = this.props.dataItemManipulator(field.name, value);

        if (typeof value === 'object' || typeof value === 'array') {
            value = JSON.stringify(value);
        }

        return (
            <td key={`${row.id}_${field.name}`}>{ value }</td>
        );
    }

New:

    render() {
        const { row, fields } = this.props;

        return (
            <tr onClick={() => this.handleOnClick(row)}>
                { this.renderCheckboxCell(row.id) }
                { fields.map(field => this.renderCell(field, row)).filter(field => field) }
                { this.renderButtons(row) }
            </tr>
        );
    }

    // ...

    renderCell(field, row) {
        let value = row[field.name];

        value = this.props.dataItemManipulator(field.name, value);

        if (typeof value === 'object' || typeof value === 'array') {
            return null;
        }

        return (
            <td key={`${row.id}_${field.name}`}>{ value }</td>
        );
    }

Thoughts?

Split DynamicDataTable into multiple components

The DynamicDataTable component is getting quite large, and could be split up. The following new components created could be created to house the related render methods.

  • ActionsCell
  • ActionButton
  • CheckboxCell

Update imports

Currently when I go to use the AJAX table I have to import with:

import AjaxDynamicDataTable from "@langleyfoxall/react-dynamic-data-table/dist/AjaxDynamicDataTable";

I'd like to see something like:

import { AjaxDynamicDataTable } from '@langleyfoxall/react-dynamic-data-table';

Where the default import would be the normal dynamic datatable:

import DynamicDataTable from '@langleyfoxall/react-dynamic-data-table';

But you could also import it through an object:

import { DynamicDataTable, AjaxDynamicDataTable } from '@langleyfoxall/react-dynamic-data-table';

This could be achieved by having a file, other than the DynamicDataTable, as the main property in the package.json.

Thoughts?

Remove anonymous functions

Anonymous functions, while concise & nice looking, have serious performance issues in React.

Anonymous functions by design have no fixed reference, this causes a new function to be created on each render, this leads the component to believe it's props have changed so it re-renders unnecessarily, this impacts all children and can cause serious performance concerns as projects grow.

http://johnnyji.me/react/2016/06/27/why-arrow-functions-murder-react-performance.html

To fix this, all anonymous functions need to be changed to instance methods and if neeeded, bound in the constructor to the component context.

Pagination not work

I put my data in:

  <DynamicDataTable  rows={this.state.fullData} fieldsToExclude={['LEMMA' , 'RESPID', 'ORIGININAL' , 'TEXT_CLEAN', 'LEMMA_ORIGINAL', 'TAG', 'userName', 'TEXT', 'thumbsUpCount', 'reviewCreatedVersion' , 'Language',
   'replyContent', 'repliedAt' ]}
   currentPage={1}
    totalPages={1000}
    paginationDelta={6}
    />

but it show all rows in one page

Default value for `buttons` prop does not handle query strings

buttons: [
{
name: 'View',
callback: (e, row) => {
window.location = `${location.href}/${row.id}`;
}
},
],

Currently with URL parameters:

index?foo=bar  -->  index?foo=bar/1

We can split the current location based on a hash (#) and a question mark (?), then take the first element to return the URL without a query string.

return `${window.location.href.split(/[?#]/)[0]}/${row.id}`;

Source

Add custom row render function prop

I'd like to see a custom row rendering function added. This way we would be able to tweak the rows with endless customization.

renderRow(row) {
    return (
        <tr />
    );
}

<DynamicDataTable
    rows={users}
    rowRenderer={this.renderRow}
/>

The rowRender prop would accept a function that will have an instance of row passed back into it. It would then return a React.isValidElement, ideally a tr.

I'm up for implementing this. Anything else that it should do?

cc @NilesB @DivineOmega

Add ability to pass arbitrary request params to API endpoint

We should allow devs to pass arbitrary request params to API endpoint when using the AjaxDynamicDataTable component. This could be used for to allow users to search/filter the results displayed.

Possible example syntax:

<AjaxDynamicDataTable apiUrl={'http://example.test/web-api/users'} 
    params={[
        { searchQuery: 'test' },
        { createdAfter: '2018-10-01' },
    ]} />

Check for null before stringifying objects

Somewhat related to #30, as null is an object in JavaScript it gets caught by the object if-statement and then gets stringified which converts it to a string.

screen shot 2018-12-13 at 14 35 53

I think it would be best to check for null, and return a string before rendering the cell. Thoughts?

Possible optimizations

I've noticed that in DynamicDataTable.jsx getFields there is a lot of for loops. I feel that this can be written nicer by using pre-existing JavaScript functions such as Array.prototype.forEach. I think the logic can also be simplified.

Here's what I've come up with:

rows.forEach(row => {
    Object.keys(row).forEach(rowFieldName => {
        fields.forEach(field => {
            if(field.name === rowFieldName) {
                const label = rowFieldName.replace(new RegExp('_', 'g'), ' ').trim();

                fields.push({
                    name: rowFieldName,
                    label,
                });
            }
        })
    })
})

Here's what is there:

for (let i = 0; i < rows.length; i++) {
    const row = rows[i];

    const rowFields = Object.keys(row);

    for (let j = 0; j < rowFields.length; j++) {
        const rowFieldName = rowFields[j];
        let exists = false;

        for (let k = 0; k < fields.length; k++) {
            const field = fields[k];

            if (field.name === rowFieldName) {
                exists = true;
                break;
            }
        }

        if (!exists) {
            const label = rowFieldName.replace(new RegExp('_', 'g'), ' ').trim();

            fields.push({
                name: rowFieldName,
                label,
            });
        }
    }
}

I personally think that we should be pushing to use existing JavaScript functions as much as possible which'll make code more compact and in my opinion easier to read.

Thoughts?

cc @DivineOmega @wheatleyjj

Document `AjaxDynamicDataTable` component

The usage of the AjaxDynamicDataTable component in this package, with the following syntax, is not yet documented.

<AjaxDynamicDataTable apiUrl={'http://example.test/web-api/users'}/>

This should be documented, with reference to the related React Dynamic Data Table Laravel API package.

See issue #7 for more information about the development of this feature.

Use reactstrap instead of importing Bootstrap

What you would like to change/add

Add reactstrap as a dependency to manage Bootstrap and change the jsx to use the reactstrap components.

Why you would like to change/add this

At the moment the user must import Bootstrap themselves for styling and functionality such as drop downs to work.

Checklist

  • I have checked that this is not a duplicate issue.

Sources

Add the ability to pass false or null into props.buttons and clickable rows

I am currently working on a project that uses this package, within the project we do not want buttons on the end of the table and we want the row itself to be clickable.

Currently you have to pass an empty array to not have any buttons. I believe that it should allow false or null (or a falsey statement).

<DynamicDataTable
    rows={users}
    buttons={[]}
    dataItemManipulator={this.handleDataManipulation}
/>

I'd also like to see a onRowClick prop or similar that allows an action when a row gets clicked.

Perhaps I'll look into this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.