Oliver's Blog

July 2017 - Posts

  • DevExtreme React Grid - Remote Data Loading Plugin

    This post is part of a series about the DevExtreme React Grid. You can find the introduction and overview to the post series by following this link.

    At the same time, this post is part of a series describing a demo project that employs various real-world patterns and tools to provide access to data in a MongoDB database for the DevExtreme grid widgets. You can find the introduction and overview to the post series by following this link.

    The sample described in this blog post loads data from the data service implemented for my DevExtreme - Real World Patterns post series. The data access pattern has been described previously in this post.

    I have created a new branch react-plugin for my real world patterns demo. The basis of this branch is react-frontend, so the structure is similar to that described in this post.

    Changes to Redux elements

    My new plugin takes care of all the loading functionality. As a result, the related handling that was implemented as Redux actions and sagas in react-frontend is not required anymore.

    grid-saga.js only handles the BATCH_SAVE and BATCH_DISCARD actions now. Since this functionality is still external to the Grid, I left these parts untouched.

    In grid-reducer.js, I have removed handling of the GRID_DATA_LOADED action. GRID_PAGE_SIZE_CHANGED is also gone, since I integrated the special behavior into the plugin to make sure currentPage is adjusted when pageSize changes.

    One new action has been introduced, with this simple handling in the reducer:

    case GRID_RELOAD:
      return {
        ...state,
        reloadState: new Date().getTime()
      };
    

    When a GRID_RELOAD action is dispatched, this leads to a change in Grid state by setting the reloadState field to a new value. I`m using a numeric value for this, but technically it could be anything. The changed state value is used to indicate to the data loading plugin that a reload is required.

    I made changes to the mapDispatchToProps functions for both the grid and the toolbar, to accommodate the changes to the Redux actions.

    Finally, I removed those elements from initial grid state that are not needed anymore (rows, totalCount) and added the reloadState instead.

    The DevExtremeDataServer plugin

    In the render method of the plugin, you can see the building blocks I demonstrated in the post about plugin writing basics. I’ll explain each element in turn.

    Rendered plugin elements

    The first Watcher extracts all fields from Grid state that are relevant for data loading:

    <Watcher
      watch={getter =>
        [
          'sorting',
          'currentPage',
          'pageSize',
          'filters',
          'grouping',
          'expandedGroups'
        ].map(getter)}
      ...
    

    When any of these values change, the current Grid state is captured in plugin state. A conversion takes place for the expandedGroups because the Grid state keeps the data in a Map instead of an array. I’m also setting the field loading to true, which you will see in use further down to display a loading indicator.

    Note that I’m using a rest parameter in the declaration of the onChange delegate. This means I don’t have to repeat the names of the parameters that are already showing right above this code in the watch delegate. The downside is that I need to refer to the values as vals[X] within the body of the delegate.

    onChange={(action, ...vals) => {
      ...
      this.setState({
        sorting: vals[0],
        currentPage: newPage,
        pageSize: vals[2],
        filters: vals[3],
        grouping: vals[4],
        expandedGroups: vals[5] ? Array.from(vals[5].values()) : [],
        loading: true
      });
    

    For the correct currentPage value, a calculation is run if the pageSize has changed.

    const newPage = this.state.pageSize >= 0 &&
      this.state.pageSize !== vals[2]
      ? Math.trunc(vals[1] * this.state.pageSize / vals[2])
      : vals[1];
      ...
    

    At the end of the onChange delegate, you see the first instance where an action is used to trigger functionality made available by a different plugin. The action is called setCurrentPage and it is exported by the PagingState plugin. Using this approach, my plugin can influence state in other elements from a Watcher.

    if (newPage !== vals[1])
      action('setCurrentPage')({
        page: newPage
      });
    

    Moving on, you find three simple Getter elements. These make information from plugin state available to the Grid, by the names of totalCount, rows and loading. Please don’t be confused at this point by the fact that on an initial render run, the fields totalCount and rows don’t contain any useful information! You will see a bit later where these details come from.

    <Getter name="totalCount" value={this.getTotalCount()} />
    <Getter name="rows" value={this.getRows()} />
    <Getter name="loading" value={this.state.loading} />
    

    Note that the property value is used on all three Getter elements, not the previously discussed pureComputed. This is important because the values are simply retrieved from state. For totalCount and rows, two simple helper methods are used:

    getRows() {
      return this.state.loadResult ? this.state.loadResult.rows : [];
    }
    
    getTotalCount() {
      return this.state.loadResult ? this.state.loadResult.totalCount : 0;
    }
    

    If you tried to use pureComputed in this scenario, it wouldn’t work! The function used for pureComputed is expected to be functionally pure and return new results only if its parameters change. Return values from this function are memoized and the change to the state, which is external to the function, would not be recognized.

    The next Getter calculates the correct value for totalPages depending on totalCount and pageSize. The implementation was copied from the PagingState plugin, and conversations with the developers indicate that this may not be required in the future. My comment in the source describes a change I made to the standard logic. Here is the implementation:

    <Getter
      name="totalPages"
      pureComputed={(totalCount, pageSize) =>
        pageSize ? Math.ceil(totalCount / pageSize) : 0}
      connectArgs={getter => [getter('totalCount'), getter('pageSize')]}
    />
    

    The final Watcher of the plugin takes care of another edge case scenario. The edge case occurs when totalPages changes and currentPage has a value that is no longer in range given the new totalPages value. If that happens, the setCurrentPage action is executed to “navigate” to the last valid page in range.

    <Watcher
      watch={getter => [getter('totalPages'), getter('currentPage')]}
      onChange={(action, totalPages, currentPage) => {
        if (totalPages > 0 && totalPages - 1 <= currentPage)
          action('setCurrentPage')({ page: Math.max(totalPages - 1, 0) });
      }}
    />
    

    The last element of the plugin is a Template. Using the same technique I demonstrated in the plugin writing basics post, this template shows a loading indicator as required:

    <Template name="root">
      <div>
        <TemplatePlaceholder />
        {this.state.loading &&
          this.props.useLoadingIndicator &&
          this.props.loadingIndicator()}
      </div>
    </Template>
    

    The lifecycle of the plugin

    I explained the render method of the plugin first because the elements describe a large part of what the plugin “does”: it waits for changes to certain Grid state values and reacts by changing some of its own state, as well as providing changed Grid state values back through Getter elements. However, this is not the end of the story. I pointed out the two fields totalCount and rows in my description, which are not accounted for so far. And of course I have not triggered the actual data loading process yet, because I’m keeping the render method functionally pure.

    The important part of the plugin that I haven’t mentioned yet is its componentDidUpdate method. This is of course a standard React lifecycle method – it is important to keep in mind that plugins are React components!

    I’m making two steps in this method, taking care of two different scenarios. First, a component update may happen because the component’s props have changed. I haven’t mentioned any props yet, but in the code just above, for the loading indicator, you can see me using the values useLoadingIndicator and loadingIndicator from props.

    For componentDidUpdate, the important field from props is the reloadState that was already mentioned at the start of this post. Comparing the previous props to the current ones, I can see whether this has changed since the previous update, and if it has, I need to set the loading state of the plugin accordingly.

    if (this.props.reloadState !== prevProps.reloadState && !this.state.loading)
      this.setState({
        loading: true
      });
    

    The second scenario is that where componentDidUpdate is called as a result of a state change. State only changes when I call setState, and in those cases I need to trigger the data loading process. To be safe, I check individual state fields – it would be possible to optimize this a bit by using a combined state object for all loading-relevant values.

    if (
      prevState.sorting !== this.state.sorting ||
      prevState.currentPage !== this.state.currentPage ||
      prevState.pageSize !== this.state.pageSize ||
      prevState.filters !== this.state.filters ||
      prevState.grouping !== this.state.grouping ||
      prevState.expandedGroups !== this.state.expandedGroups ||
      prevProps.reloadState !== this.props.reloadState
    )
      this.getData(this.getLoadOptions());
    

    And there, finally, is the getData call that fetches data from the remote server. The implementation is asynchronous and when data has been received, I setState again to incorporate the results:

    getData(loadOptions) {
      this.fetchData(loadOptions).then(res => {
        if (res.dataFetched) {
          this.setState({
            reloadState: this.props.reloadState,
            loading: false,
            loadResult: {
              rows: res.data.rows,
              totalCount: res.data.totalCount
            }
          });
        }
      });
    }
    

    The last item to mention at this point is the plugin constructor, where I initialize my state. I also create a “data fetcher context” using the connection URL passed through props:

    constructor(props) {
      super(props);
      this.state = {
        loadResult: undefined,
        reloadState: undefined,
        loading: false
      };
    
      ...
    
      this.fetchData = createDataFetcher(this.props.url);
    }
    

    Here is a summary of the data loading process using the plugin:

    1 - The plugin is instantiated during the process of rendering the Grid. (The use from the Grid is the final part I haven’t shown yet, see below.) The constructor is called to initialize state.

    2 - The plugin render method is executed. The Watcher reacts to the “change” of the values and pulls Grid state into plugin state, also setting loading. Other plugin elements are evaluated, and the Template at the end renders the loading indicator.

    3 - The componentDidUpdate method on the plugin is executed. It finds that there are state changes and triggers the data loading process.

    4 - When data loading finishes, the loaded data is stored in plugin state and the loading flag is reset. The state change triggers a component update.

    5 - On this render run, the Watcher has nothing to react to. The Getter elements push the loaded data into Grid state. The Template doesn’t render the loading indicator anymore.

    6 - componentDidUpdate is executed again, but it does nothing.

    Using the plugin in the Grid

    As you would expect, using the plugin in the Grid is straight-forward and requires only one little block of code:

    <DevExtremeDataServer
      url="//localhost:3000/data/v1/values"
      reloadState={reloadState}
      loadingIndicator={
        activeUI === 'material'
          ? () => <MuiLoadingIndicator />
          : undefined // default uses bootstrap styles
      }
    />
    

    There are no surprises here. The URL for the data service is passed in as a property (most other examples in the real world patterns blog series have this URL hard-coded). The reloadState is bound to the Grid state field, so that reloads can be triggered externally through the GRID_RELOAD Redux action explained at the start of this post.

    For the loadingIndicator, the plugin has default behavior that renders a Bootstrap compatible indicator. I chose this because Bootstrap UI is the main platform at this time (with Material and others coming in the future), but also because that rendering relies exclusively on CSS styles and could be used without Bootstrap. In the code above, I’m alternatively using the MuiLoadingIndicator (which relies on MuiCircularProgress, a standard component), if Material UI has been selected in the demo application.

    Try it!

    This concludes the description of the data loading plugin, and also the React Grid blog series, at least for now. I have a number of ideas and plans, so there will probably be updates in the future – feel free to get in touch if you have any questions or ideas of your own!

    Meanwhile I recommend playing with the samples I’ve provided throughout the series. One more time, the complete repo for the real world patterns series can be found here, and the various branches on this page. The main README has instructions on running the samples in your own environment, and there are separate instructions on using Cloud 9 online IDE.

    Click here to return to the blog series overview

  • DevExtreme React Grid - Plugin Writing Basics

    This post is part of a series about the DevExtreme React Grid. You can find the introduction and overview to the post series by following this link.

    As I explained in the first few posts of this series, the React Grid relies heavily on plugins for its functionality. The post on standard plugins outlines the use of those plugins that come in the box with the grid. On top of that, it is possible to create your own plugins to extend grid functionality.

    Please note that the APIs described in this post are subject to an internal review right now. The React Grid is still at an “alpha” stage right now and details about those APIs (and others) might change in the future, before a final release version is reached.

    In this post I’ll introduce the basics of plugin development for the React Grid. I’m using three rather contrived examples, in order to keep my descriptions short and to the point. I’m also preparing the stage for the last post I’m currently planning for the series, where I will extract my remote data loading functionality into a reusable plugin.

    Plugin structure

    A plugin for the React Grid is a React component in its own right. It has a render method (and it can be implemented as a functional component), but the rendering doesn’t generate any visible elements. The outer element of a plugin is a PluginContainer, and inside that container any number of elements of four different types can be included:

    • The Getter element allows the plugin to provide a value to the Grid.
    • The Template element defines a visual element that will be rendered somewhere (though not as a result of the plugin’s render).
    • The Watcher reacts to changes in the Grid state.
    • The Action element allows the plugin to provide executable functionality to other plugins.

    A first plugin with a Getter

    At the start of the code, I’m importing all the elements I’ll be using from DevExpress.DXReactCore (that corresponds to @devexpress/react-core):

    const {
      Getter,
      Watcher,
      Template,
      TemplatePlaceholder,
      PluginContainer
    } = DevExpress.DXReactCore;
    

    In the first sample I won’t be using the Watcher, Template and TemplatePlaceholder yet. My plugin looks like this:

    class TestPlugin extends React.PureComponent {
      render() {
        return (
          <PluginContainer>
            <Getter
              name="rows"
              connectArgs={getter => [getter("rows")]}
              pureComputed={rows =>
                rows.concat([{ name: "Test Album", artist: "Computer", year: 0 }])}
            />
          </PluginContainer>
        );
      }
    }
    

    You can see the render method, the PluginContainer and the Getter I already described. Three properties are set for the Getter.

    The name is listed first, but logically it is used last. After the value returned by the Getter has been calculated, the name given here is used to store the result in Grid state (yes, that’s Grid state, not plugin state).

    connectArgs receives a delegate, which in turn receives a getter function. This delegate is executed first, and you are expected to access values from Grid state with the help of getter and return a list of those that you’re interested in. My example accesses the rows list (that’s the same one we’ve been setting in Grid props in previous examples). rows is also the name of the Getter, so you can see at this point that the plugin has a chance to analyze the rows and/or “modify” (technically, replace) them.

    The final property I’m using is pureComputed, another delegate. This delegate is called after connectArgs, with the parameters that connectArgs returned. In my case, I will receive the rows from Grid state, and I go on to concat another row to that list.

    Note that pureComputed is expected to be functionally pure. You should not trigger side effects here (e.g. remote data loading), and you should regard the information that is passed to the function as immutable. The concat function I’m using returns a new list, which keeps the function pure.

    To summarize, my plugin works through three steps:

    1 - Using the getter in connectArgs, it retrieves the rows state field from the Grid.

    2 - pureComputed is called with that rows value, and it returns a new array consisting of the old rows plus one new row.

    3 - The value returned by pureComputed is stored in the Grid state field rows again, because the name of the Getter is rows.

    It is possible to have several Getters in a plugin. They are evaluated strictly along the same lines as above, one by one, from top to bottom. Each Getter is able to “see” any information generated by the previous ones.

    To use the plugin in the Grid, I add it just like any of the standard plugins:

    ...
    return (
      <Grid rows={rows} columns={columns}>
        <SortingState />
    
        <LocalSorting />
        <TestPlugin />
    
        <TableView />
        <TableHeaderRow allowSorting />
      </Grid>
    );
    

    I have included sorting functionality in this sample to point out where the TestPlugin should appear between the others. Since the plugin modifies the rows, it needs to appear before TableView, which renders the rows.

    The ordering of LocalSorting and TestPlugin is interesting. Using the order shown above, the TestPlugin will add its extra row to the end of the list after sorting has already occurred. If you swap the two plugins, you will see that the extra row becomes subject to sorting together with the others.

    Here’s the sample for you to play with:

    Using templates

    The Template plugin element, not surprisingly, defines a template. You can use a new name or one that already exists. To “override” a template that exists already, keep in mind that all plugins are evaluated in order. If you want to use the previous content of a template in your definition, you use the TemplatePlaceholder component, which is also available to render content from other templates within your own.

    Here is a plugin that uses the root template to display an overlay on top of the grid. Since the plugin functionality is simple, I have chosen to use a functional component implementation:

    const Overlay = () =>
      <PluginContainer>
        <Template name="root">
          <div>
            <TemplatePlaceholder />
            <div className="overlay"><div>This is my overlay</div></div>
          </div>
        </Template>
      </PluginContainer>;
    

    Note that some CSS is used to style the overlay in my sample.

    The following pair of plugins uses the standard template footer (which is not used by the grid at this time) to show some text. The PluggableFooter introduces its own placeholder footerText and the plugin FooterText defines the template footerText, reusing any existing content from previous definitions.

    const PluggableFooter = () =>
      <PluginContainer>
        <Template name="footer">
          <div><TemplatePlaceholder name="footerText" /></div>
        </Template>
      </PluginContainer>;
    
    const FooterText = props =>
      <PluginContainer>
        <Template name="footerText">
          <span><TemplatePlaceholder />{props.text}</span>
        </Template>
      </PluginContainer>;
    

    In my sample, I’m applying these three plugins like this:

    ...
    <TableView />
    <TableHeaderRow allowSorting />
    <Overlay />
    <PluggableFooter />
    <FooterText text="Some footer text" />
    <FooterText text=" - More footer text" />
    ...
    

    The templating system is simple and powerful, and there are several more features that I’m not going to describe in detail here. It is possible to define templates with a predicate property, which applies the template conditionally, and to utilize getters for access to state as well as actions to execute functionality exported by other plugins.

    Note that the standard templates provided by the Grid are currently high level ones, like root and footer. We are considering customization solutions for individual nested elements, but we are hesitant to create very complex structures of “named” templates that might also introduce performance issues. Further investigation is ongoing at this time and I’ll post updates in the future.

    Here is the complete sample for template plugins:

    The Watcher

    If you would like your plugin to react to changes to the Grid state, you can include a Watcher. Using a delegate, you extract values from Grid state (similar to the Getter), and another delegate is invoked if those values have changed compared to a previous run. Here’s the Watcher from my sample:

    <Watcher
      watch={getter => [getter('sorting')]}
      onChange={(action, sorting) => {
        const artistSorting = sorting.find(s => s.columnName === 'artist');
        this.setState({artistSorting: !!artistSorting});
      }} />
    

    The delegate assigned to the watch property retrieves the sorting configuration from Grid state. This is passed to the onChange delegate together with an action parameter (which is not used in this sample). In the delegate, I find out whether sorting currently includes the artist column and set the plugin state accordingly.

    Note that this pattern of recording information in state is a common one. Remember that the plugin is part of the render function, which should be pure. This means you should not trigger side effects within the Watcher.

    For the sample, I’m using the new state value in a Template, which conditionally shows an overlay similar to that in the sample above:

    <Template name="root">
      <div>
        <TemplatePlaceholder />
        { this.state.artistSorting && 
          <div className="overlay"><div>Artist Sorting Active</div></div> }
      </div>
    </Template>
    

    To test the functionality, sort the grid data by different columns (just click the column headers). You will see that overlay is shown when you sort by the artist column, and it goes away as soon as you sort by something else.

    Here is the sample:

    Actions

    The fourth and final plugin element is called Action. I’m not going to describe this element here in detail because it is a rather advanced scenario to create your own actions. The Grid and its various standard plugins export a number of actions that can be used in plugins (Watcher and Template elements can gain access to actions).

    Coming up

    The next post in the series will take advantage of the plugin elements introduced above to integrate the remote-data-loading functionality from this post in a plugin. You will also see an example of using actions exported by standard plugins.

    Click here to return to the blog series overview

  • DevExtreme React Grid - Remote Data - Integrating with "Real World Patterns"

    This post is part of a series about the DevExtreme React Grid. You can find the introduction and overview to the post series by following this link.

    At the same time, this post is part of a series describing a demo project that employs various real-world patterns and tools to provide access to data in a MongoDB database for the DevExtreme grid widgets. You can find the introduction and overview to the post series by following this link.

    The sample described in this blog post loads data from the data service implemented for my DevExtreme - Real World Patterns post series. The data access pattern has been described previously in this post.

    I have created a new branch react-frontend for the big sample project and added a React based web application to it. The functionality of this application will be largely familiar to you by now if you’ve been following my DevExtreme - React Grid blog series. It utilizes most of the standard functionality of the React Grid, it uses Redux and the various details I pointed out in previous posts are implemented in similar ways.

    New elements and differences compared to the CodePen samples

    In comparison with the samples I created in CodePen to illustrate previous posts, there are several items I want to mention quickly since they differ in the sample application for this post. This is probably not a complete list, but it should cover the major differences.

    1 - Various components and other application building blocks live in their own code files. This is of course an obvious best practice, but in the CodePen samples everything was in one “file”. The file App.js is now the place where the initialization logic lives.

    2 - The toolbar has been extended to accommodate a reload button, a button to create test data, selection buttons for the UI library to use, and a checkbox to toggle the use of custom editors. As a result, there is now state information associated with the toolbar and it has its own reducer. Using the Redux helper combineReducers, the two reducers for the grid and the toolbar are merged for each to handle a piece of the total state.

    3 - I’m also using the package redux-first-router to provide navigation functionality in the app (i.e. for now you see different URLs when you use different UI versions of the grid). The state handled by the routing-specific reducer is also included in the previously described call to combineReducers.

    4 - I’m using the package redux-saga to handle situations where Redux actions result in other actions being triggered, and/or side effects need to be executed. Two sagas are “run” on application startup, one for the toolbar and one for the grid.

    5 - Finally, since I have implemented a feature to allow the user to switch between Bootstrap and Material UI, I have written the Grid and Toolbar code in such a way that both UI variations are created by the same components. I made this decision so that I could point out how the JSX rendered for a component doesn’t change at all if you select a different UI platform (this is true at least for the Grid, while the toolbar uses UI-specific components anyway). However, as we will add supported UI librariesfor React Grid over time, I will probably reconsider this approach for ease of maintenance.

    Working with remote data

    The structure of the React Grid is such that data is always supplied to it via its props. You have seen in previous samples of this blog series how that data can be kept in component state and how it can be loaded from a Redux store. I am still using Redux in this new sample, but now I trigger actions to initiate the loading and editing of data and modify the state accordingly.

    Towards the end of this post, you will find a summary of the big steps you have to go through in order to implement remote data loading. I also recommend checking out this demo, which implements a read-only grid working with remote data, in a much more restricted but also minimal way (compared to my sample).

    Data loading

    The loading process is initially triggered by the ReduxGrid component in its componentDidMount lifecycle method:

    componentDidMount() {
      this.props.dispatch(gridLoad());
    }
    

    Since the loading of data requires triggering side-effects, I’m handling this action in the grid saga. Here is the handling function:

    function* gridLoadHandler(action) {
      yield put(gridStateChange('loading', true));
      const loadOptions = yield select(getLoadOptions);
      const data = yield call(loadData, loadOptions, action.force);
      if (data) yield put(gridDataLoaded(data));
      else yield put(gridStateChange('loading', false));
    }
    

    The process has five steps. First, the grid’s loading state is set to true using the gridStateChange action (put simply triggers another action). This results in a loading indicator being shown, i.e. a transparent panel on top of the grid component with a spinning indicator.

    Second, I use the redux-saga select call to access information from the grid state in the Redux store. The getLoadOptions helper extracts the fields relevant for data loading.

    In the third step, call is used to run the function loadData, which I will explain in a bit more detail below. This is the side effect I was mentioning before, since the data loading process is asynchronous.

    Step four triggers another action, gridDataLoaded, if and when data has been received. This action is handled by the normal grid reducer and its implementation simply includes the newly loaded data and the associated totalCount into grid state.

    case GRID_DATA_LOADED:
      return {
        ...state,
        rows: action.data.rows,
        totalCount: action.data.totalCount,
        loading: false
      };
    

    Finally the loading flag is reset in step five, which removes the loading indicator.

    The functionality of loadData

    The function loadData in grid-saga.js is just a small wrapper around fetchData in data-access.js. The general loading process looks like this:

    1 - Create a query string on the basis of the grid state. As part of the process, the grid state is translated into load options compatible with the DevExtreme data service.

    2 - Check that the new query string is different from the previously used one. This is an optimization present in the current codebase, to avoid duplicate loading in some circumstances. An option is supported to force loading (for the reload feature).

    3 - Distinguish between group queries and others (simple queries). Initiate loading via the Fetch API and handle the results. This happens in the functions groupQuery and simpleQuery.

    4 - For simple queries, the result handling consists only of a simple call that changes the top-level result format (convertSimpleQueryResult). For group queries (createGroupQueryData), the process is complicated because the structures of the data returned from the service and that required by the React Grid are very different. For efficiency, only nested data of groups that are actually expanded is retrieved in separate queries.

    At this point, I don’t want to elaborate on the details of group data creation. You can find the current implementation here, if you’re interested. A few details are likely going to change about the grouping data structures in the future, and I will try to write a summary post about the topic when things are finalized.

    Saving data

    Changes to the editing state are captured in this sample just like in the previous post, including the hasEditingChanges flag. The Save button on the toolbar dispatches a BATCH_SAVE action which is handled in grid-saga.js:

    function* batchSaveHandler(action) {
      const commitParams = yield select(getCommitParams);
      yield call(commitChanges, commitParams);
      yield put(gridResetEditingState());
      yield delay(100);
      yield put(gridLoad(true));
    }
    

    The save handler has several steps. commitParams holds the values extracted from grid state that are relevant for saving. The function commitChanges is called with these parameters, and together with sendChanges towards the end of data-access.js this sends all the change details to the server using POST and PUT requests. There is room for optimization here for cases where many rows need to be changed or added at the same time, but since the editing scenario is an interactive one based on user input, the realistic number of concurrent edits shouldn’t be too large.

    Moving on, the action handler dispatches the GRID_RESET_EDITING_STATE action, which removes the change details from grid state.

    At this point, the handler delays for a moment. This is a workaround solution for the problem that the grid is simply too fast – without the delay, the changed data would not actually be loaded back from the server in the next step. In real-world applications, where you might have certain backend services running on different machines, CQRS and Event Sourcing in action etc, this kind of issue is very common. A hard-coded delay is obviously not very elegant, especially since it might be too large in some cases and too small in others. I recommend reading this blog post for an example that implements update notifications – with this approach, the grid wouldn’t have to reload actively at this point, since it would be notified by the server if and when updates to the data are available.

    Finally, the handler dispatches a GRID_LOAD action, passing true for the force flag to make sure a reload takes place. This reload is quite unavoidable in a remote data scenario, since the changes that were committed might have influenced sort order, grouping etc in ways that the client can’t easily predict.

    Summary of steps for remote data support

    1 - Find the correct places in your architecture from where loading and refreshing of data can be triggered. In my sample I’m reflecting this “data life cycle” in Redux actions, but other approaches are possible. For instance, you can use the componentDidMount and componentDidUpdate lifecycle methods of your React component, like our Remote Data demo shows. If you go that way, be aware that you need to control your component updates precisely and prevent data reloading for situations where it’s not required, since componentDidUpdate is called quite frequently while the user interacts with the Grid.

    2 - Execute the logic that loads your data. Once data is available, put it in a place where a Grid update is triggered, e.g. state or props, or a Redux store or similar that interfaces with state or props.

    3 - For editing support, you can implement an onCommitChanges handler on the EditingState, like I did in this post. Be aware though that you shouldn’t trigger side effects (i.e. remote operations) in that event handler, since it is executed as part of the component render method (which should remain pure). So you would have to record the “requirement to save” in your state and then use logic in componentDidUpdate to execute the remote calls, then change state again when the results arrive.

    In my sample, I chose the approach using Redux and redux-saga, which is cleaner in my eyes since it provides a single place to implement the steps of the saving process. My sample implements batch saving, but of course things could be done in a similar way for single-row saving.

    Try it!

    The sample for this post can be found in the react-frontend branch of my demo project. I recommend you read the readme on that page, and also note that there are instructions now for Running in Cloud 9 – so you don’t need any local build environment to check out this and other branches of the demo.

    Click here to return to the blog series overview

  • DevExtreme React Grid - Batch Saving

    This post is part of a series about the DevExtreme React Grid. You can find the introduction and overview to the post series by following this link.

    In this post, I want to take advantage of the fact that the Redux store gives me external access to the Grid state. As I outlined in the previous post, individual React component can certainly have internal state, even if they are used in an application where overall state management is done with Redux. But sometimes state can be used externally for good reason, and in this example I will implement the functionality of batch saving (and batch discarding of changes), which is not currently implemented by the grid itself.

    Note that the data structures used by the Grid (or specifically, by the EditingState) already take into account that multiple editing operations can happen in parallel. In fact, you can edit multiple rows of data out of the box. But the standard behavior supplies only a per-row Save link, and if the EditingState event onCommitChanges is triggered, there is only ever one change to be committed. Batch Editing should be fully implemented in the Grid at a later point.

    Creating a toolbar

    At the beginning of my sample code (as usual, you can find the whole sample at the bottom of this post), I import two UI elements from the react-bootstrap library:

    const { ButtonToolbar, Button } = ReactBootstrap;
    

    With the help of these two components, I define a toolbar. You see that I’m retrieving the event handlers from props, so you might guess that this component will be integrated as a connected component, just like I did with the Grid in the previous post.

    class ReduxToolbar extends React.PureComponent {
      render() {
        const {
          gridHasEditingChanges,
          onSaveButtonClick,
          onDiscardButtonClick
        } = this.props;
        return (
          <ButtonToolbar>
            <Button
              bsStyle="success"
              disabled={!gridHasEditingChanges}
              onClick={onSaveButtonClick}
            >
              Save Changes
            </Button>
            <Button
              bsStyle="danger"
              disabled={!gridHasEditingChanges}
              onClick={onDiscardButtonClick}
            >
              Discard Changes
            </Button>
          </ButtonToolbar>
        );
      }
    }
    

    In addition to the event handlers, I also receive a flag called gridHasEditingChanges, which is used to activate and deactivate the buttons. This flag represents a piece of grid-specific state information, which is supplied to the toolbar component.

    Changes to the Grid configuration

    I am not using an onCommitChanges event handler anymore on the EditingState. I added a commandTemplate to the TableEditColumn that removes the Save link that is normally shown when the user edits a row. As a result, saving of individual rows is no longer possible.

    <TableEditColumn allowAdding allowEditing allowDeleting 
      commandTemplate={({ id }) => (id === 'commit' ? null : undefined)} />
    

    The commandTemplate function receives an id as a parameter. I’m testing this to see whether I’m looking at the Save command (which has the id commit), and then I return either null or undefined. This may seem confusing and we are considering other options, but the distinction is that null is handled by React in its standard way of not rendering anything at all, while undefined is interpreted by our libraries and results in the standard element being rendered.

    Changes to the Redux elements

    Compared to the sample from the previous post, I have removed the GRID_SAVE action. Instead, I have introduced the following three new actions:

    const BATCH_SAVE = "BATCH_SAVE";
    const BATCH_DISCARD = "BATCH_DISCARD";
    const GRID_EDITING_STATE_CHANGE = "GRID_EDITING_STATE_CHANGE";
    
    const batchSave = () => ({
      type: BATCH_SAVE
    });
    
    const batchDiscard = () => ({
      type: BATCH_DISCARD
    });
    
    const gridEditingStateChange = (stateFieldName, stateFieldValue) => ({
      type: GRID_EDITING_STATE_CHANGE,
      stateFieldName,
      stateFieldValue
    });
    

    The BATCH_SAVE and BATCH_DISCARD actions will be dispatched by the event handlers of the toolbar buttons. GRID_EDITING_STATE_CHANGE is a variation of the standard GRID_STATE_CHANGE and it will be handled specially to track whether or not the editing state currently contains any change information.

    To handle GRID_EDITING_STATE_CHANGE, I have added this block to the grid reducer:

    case GRID_EDITING_STATE_CHANGE:
      const { editingRows, changedRows, addedRows, deletedRows } = state;
      const es = {
        editingRows,
        changedRows,
        addedRows,
        deletedRows
      };
      es[action.stateFieldName] = action.stateFieldValue;
    
      const hasEditingChanges =
        (es.editingRows && es.editingRows.length > 0) ||
        (es.addedRows && es.addedRows.length > 0) ||
        (es.deletedRows && es.deletedRows.length > 0) ||
        (es.changedRows && Object.keys(es.changedRows).length > 0);
    
      return {
        ...state,
        hasEditingChanges,
        [action.stateFieldName]: action.stateFieldValue
      };
    

    The grid state value hasEditingChanges, which I mentioned before as it was being used by the new toolbar, is calculated here depending on the state and the action currently being handled.

    The handling of BATCH_DISCARD is quite simple:

    function discardChangeDetails(state) {
      return {
        ...state,
        editingRows: [],
        addedRows: [],
        changedRows: {},
        deletedRows: [],
        hasEditingChanges: false
      };
    }
    
    // ... in reducer:
    
      case BATCH_DISCARD:
        return discardChangeDetails(state);
    

    The helper discardChangeDetails returns a new state with all the editing related fields reset to their default values. I have created this helper function because it is also used by the BATCH_SAVE action handling. The saving logic itself has not changed from the previous version:

    case BATCH_SAVE:
      return _.flow(
        Array.from(
          commitChanges(state.addedRows, state.changedRows, state.deletedRows)
        )
      )(state);
    

    In the previous post, I mentioned how commitChanges creates a sequence of function calls to reflect the changes recorded in state. For this version, I simply extended that sequence by adding a call to discardChangeDetails:

    function* commitChanges(added, changed, deleted) {
      yield* deleteFunctions(deleted);
      yield* changeFunctions(changed);
      yield* addFunctions(added);
      yield discardChangeDetails;
    }
    

    In other words, after changes have been committed, the resulting state will be passed to discardChangeDetails, where the change details are removed from state before that state is returned.

    Note that there is currently an issue with the handling of delete state, which means that deletion doesn’t work correctly in the current version of my sample. The reason for this is that there is some special handling for the Delete link: you don’t have to click Save after clicking Delete, because this is triggered automatically. Unfortunately I found that this built-in behavior collides with the concept of batch-saving externally. Our devs are looking into this and I will post about it again in the future.

    Integrating the toolbar

    To make the new toolbar into a connected component, I need the same elements that I’m using for the grid: a mapStateToProps function, a mapDispatchToProps function and a call to connect.

    const mapToolbarStateToProps = state => ({
      gridHasEditingChanges: state.hasEditingChanges
    });
    
    const mapToolbarDispatchToProps = dispatch => ({
      onSaveButtonClick: () => dispatch(batchSave()),
      onDiscardButtonClick: () => dispatch(batchDiscard())
    });
    
    const ConnectedToolbar = connect(
      mapToolbarStateToProps,
      mapToolbarDispatchToProps
    )(ReduxToolbar);
    

    These three elements are pretty straight-forward. The only new thing is the way mapToolbarStateToProps accesses a piece of state information that comes from the grid.

    Note that the general recommendation for connected components is to supply them with the precise set of props they require. This statement is meant for each individual component! Theoretically you could have a wrapper that incorporates both the toolbar and the grid, and then push all the relevant state for both components into the common parent. This saves you time creating the mapStateToProps and mapDispatchToProps functions for one of the components. However, it also means that if any of the overall state changes, the parent component with both components inside it would now be re-rendered. In my sample, when gridHasEditingChanges changes, only the toolbar needs to re-render, not the grid. By converting individual components into connected components and supplying them with the distinct state details they require, you optimize your application performance.

    The final part that changes is the call to ReactDOM.render, since I’m adding the ConnectedToolbar into the output:

    ReactDOM.render(
      <div>
        <Provider store={store}>
          <div>
            <ConnectedToolbar />
            <ConnectedGrid />
          </div>
        </Provider>
      </div>,
      document.getElementById("app")
    );
    

    And that’s it. Here is the complete sample, and just like the previous post I recommend using the CodePen debug view and the Redux DevTools Extension to see exactly how state management and action handling work.

    Click here to return to the blog series overview

  • DevExtreme React Grid - Reduxified

    This post is part of a series about the DevExtreme React Grid. You can find the introduction and overview to the post series by following this link.

    Redux implements a variation of the Flux pattern. In simple terms, it provides a pattern and the tools to handle application state in a predictable and consistent way. If you are not familiar with Redux and the ideas behind it, I recommend reading this very short introduction.

    Using Redux in conjunction with the React Grid means to keep state information of the Grid and its various plugins in the Redux store. My sample for this post (as usual, at the bottom) stores all such state information in the Redux store, but it is common enough in real-world applications to make exceptions to this rule. One of the three principles of Redux is to view the store as the single source of truth, but you should keep in mind that the “truth” in question is really only the set of information that is of some meaning to other parts of your application. As such, it is quite legitimate to identify certain parts of a component’s state that should be viewed as internal, and should not be made available to the rest of your code.

    Right at the top of my code, I import four elements from the two libraries redux and react-redux. The latter helps interface between the React components I use, and the Redux store.

    const { createStore, compose } = Redux;
    const { Provider, connect } = ReactRedux;
    
    // As in other posts in this series, if you are working within
    // a build environment, you would usually use import statements
    // like these:
    import { createStore, compose } from 'redux';
    import { Provider, connect } from 'react-redux';
    

    Controlling the Grid

    Working further down in the code, the next important difference to previous versions of my sample is the code at the start of the render function. I used to simply extract the rows and columns from component state, and now I’m getting hold of altogether 31 items from this.props. I won’t replicate the entire code block here, suffice it to mention elements like rows and columns (no surprise there), but also sorting, grouping and selection as well as event handlers like onFiltersChange or commitChanges.

      render() {
        const {
          rows,
          columns,
          sorting,
          ...
          onFiltersChange,
          ...
          commitChanges
        } = this.props;
    

    I will describe later how this happens, but for now it is important to understand that all the state information needed by the Grid and its plugins to do their work now come “from the outside” through the component props. As I mentioned in one of the earlier posts of the series, this is called controlled mode.

    The JSX code that renders all the state plugins has changed in this sample, to configure the plugins with the information I extracted from the props:

    <SortingState sorting={sorting} onSortingChange={onSortingChange} />
    <PagingState
      pageSize={pageSize}
      onPageSizeChange={onPageSizeChange}
      currentPage={currentPage}
      onCurrentPageChange={onCurrentPageChange}
      totalCount={totalCount}
      />
    <FilteringState filters={filters} onFiltersChange={onFiltersChange} />
    ...
    

    I’m calling the class ReduxGrid now. Technically the grid is not tied to Redux in any way – it simply expects to be configured through props, wherever these props come from.

    Actions and reducers

    Redux expects each modification to the store to be modeled by an action. I create helper functions, so-called action creators, to generate the objects that represent actions. I have a simple action to handle arbitrary state changes:

    const GRID_STATE_CHANGE = "GRID_STATE_CHANGE";
    
    const gridStateChange = (stateFieldName, stateFieldValue) => ({
      type: GRID_STATE_CHANGE,
      stateFieldName,
      stateFieldValue
    });
    

    I have special actions for two cases. The first is a change to the pageSize, because I’m going to implement some logic to change currentPage when pageSize changes. Second, there is a gridSave action that will be triggered when the user makes changes to data and saves them.

    const GRID_PAGE_SIZE_CHANGE = "GRID_PAGE_SIZE_CHANGE";
    const GRID_SAVE = "GRID_SAVE";
    
    const gridPageSizeChange = pageSize => ({
      type: GRID_PAGE_SIZE_CHANGE,
      pageSize
    });
    
    const gridSave = ({ added, changed, deleted }) => ({
      type: GRID_SAVE,
      added,
      changed,
      deleted
    });
    

    You can define actions freely and the pattern I’m using to assign an action type is not the only possible one. Of course the more actions you have, the more complex their handling becomes.

    With the actions defined, I also need a reducer to handle them. Reducers are building blocks in Redux that receive the current state and an action to handle, and they are expected to return the resulting state. Redux favors functional purity, so you should regard the state as immutable. In my sample code, I am using standard JavaScript data types (instead of a library like Immutable.js), so it is up to me to make sure that data is not changed in place.

    Here is my reducer with a few parts cut out for now:

    function createGridReducer(initialState) {
      return (state = initialState, action) => {
        switch (action.type) {
          case GRID_STATE_CHANGE:
            return {
              ...state,
              [action.stateFieldName]: action.stateFieldValue
            };
    
          // ... some actions missing here for now
    
          default:
            return state;
        }
      };
    }
    

    The reducer is actually only the part returned by the return statement of the outer function createGridReducer. This pattern allows me to create my reducer by passing in an initialState, instead of hard-coding that initial state. The reducer itself, as promised, accepts the current state and an action as parameters. It looks at the action type and, if the action should have a type that isn’t recognized by this reducer, returns the state directly.

    If the action is recognized by the reducer, a new state object is created and returned. The spread operator (...) makes it easy to include all the old state in the new object, and the change encapsulated by the action overrides the relevant part in the new state.

    If the pageSize changes, I consider also changing the currentPage:

    case GRID_PAGE_SIZE_CHANGE:
      const newPage = Math.trunc(
        state.currentPage * state.pageSize / action.pageSize
      );
      return {
        ...state,
        currentPage: newPage,
        pageSize: action.pageSize
      };
    

    The state returned by a reducer can differ from the original state in any arbitrary way. The caveat is that if you have lots of actions that influence the same state fields, it might become harder to understand state changes in a running application.

    The final action handled by this reducer is GRID_SAVE. The code to handle it is actually short:

    case GRID_SAVE:
      return _.flow(
        Array.from(
          commitChanges(action.added, action.changed, action.deleted)
        )
      )(state);
    

    commitChanges is a generator function that returns a sequence of other function calls. These individual function calls each handle one data change, a new row, a changed row or a deleted row. Since each of these calls is written to accept a state object and return a new state object, I can use the lodash flow helper (which is similar to compose in functional programming languages) to execute the complete sequence of functions and return a final state. Further details of this technique are outside the scope of this article, but feel free to ask about it and I’ll provide some more explanations.

    Note that the implementation of data persistence has been done for purposes of this demo. Since data modifications are interactive, I don’t need to expect vast numbers of them happening at the same time. (In fact, at this point of the demo there will only ever be one change at a time. I will implement batch saving in the next upcoming post of the series.) As a result, there won’t be any performance problems due to the fact that each individual change function creates a new state object. However, in some real-world scenarios, large numbers of changes would break performance with this approach. I recommend looking into batch mutation support in Immutable.js for such cases.

    Connecting the grid

    At this point, I have implemented my action handling, and a Grid variation that expects to receive all its configuration details through its props. I have not created the Redux store yet, but I will soon - and then the question is how the information from the store finds its way to the Grid. The react-redux library provides helpers for this purpose, which are explained in detail on this page.

    I will take advantage of a helper function called connect. This function creates a wrapper for a React component, for instance my ReduxGrid. However, it can’t do everything itself and I need to supply two helper functions to connect.

    The first helper is usually called mapStateToProps. You can guess from the name what this function is supposed to do: it takes the complete state from the Redux store and returns the subset required by the connected component, which will be passed to the component as props. This is important for applications more complex than this step of the sample, because the store will have lots of information that apply to other parts of the application. In this sample, the store only has grid information, so my implementation is this:

    const mapGridStateToProps = state => state;
    

    The second helper has the common name mapDispatchToProps. We haven’t heard of this dispatch thing yet that apparently needs mapping. It is a function that allows us to dispatch actions to the Redux store. Logically, this is required in component event handlers – when the user interacts with the component, events are triggered and actions need to be dispatched. The mapDispatchToProps function receives the dispatch function as a parameter and it is expected to return an object that will be merged with the props returned by mapStateToProps before it goes to the component.

    For my implementation, I need to create all the event handlers you have already seen at the start of the render function: onFiltersChange, onSelectionChange and many others like those, and also commitChanges. Here is my code, shortened a bit:

    const mapGridDispatchToProps = dispatch => {
      const stateChangeEventHandlers = [
        { event: "onSortingChange", field: "sorting" },
        { event: "onCurrentPageChange", field: "currentPage" },
        { event: "onFiltersChange", field: "filters" },
        // ... other events that use gridStateChange 
      ].reduce((r, v) => {
        r[v.event] = val => dispatch(gridStateChange(v.field, val));
        return r;
      }, {});
      return {
        ...stateChangeEventHandlers,
        onPageSizeChange: pageSize => dispatch(gridPageSizeChange(pageSize)),
        commitChanges: changes => dispatch(gridSave(changes))
      };
    };
    

    Many of the grid events need handlers that just do this:

    dispatch(gridStateChange('FIELD', VALUE));
    

    Using the array at the start of the function makes it a bit easier to maintain these events, which are all generated by the reduce call. Before I return the resulting object, I add specific handlers that trigger my two “special” actions using the creators gridPageSizeChange and gridSave. Of course you are free to add all the simple change event handlers to the return structure directly, in case you find the reduce approach too complicated.

    Now for real… connecting the grid to the store

    With the helpers sorted out, I can finally call the connect helper:

    const ConnectedGrid = connect(mapGridStateToProps, mapGridDispatchToProps)(
      ReduxGrid
    );
    

    In order to create the store, I need to call into my createGridReducer function and pass the initial state. This includes my demo data as well as all the grid options (shortened a bit for brevity):

    const gridReducer = createGridReducer({
      columns: [
      ...
      ],
      rows: [
      ...
      ],
      sorting: [{ columnName: "name", direction: "asc" }],
      currentPage: 0,
      totalCount: 0,
      pageSize: 10,
      allowedPageSizes: [0, 5, 10, 20],
      filters: [],
      grouping: [],
      expandedGroups: [],
      selection: [],
      expandedRows: [],
      order: ["name", "artist", "year"],
      editingRows: [],
      addedRows: [],
      changedRows: {},
      deletedRows: []
    });
    

    The helper createStore that I imported all the way at the top of my code can create a store now, as easily as this: createStore(gridReducer). In the sample, I’m using one more parameter:

    const store = createStore(
      gridReducer,
      window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
    );
    

    The strange part that follows the reducer parameter enables the Redux application to interact with the Redux DevTools Extension, which can be installed in Chrome and allows you valuable insight into the inner workings of Redux in the application.

    Finally, there’s one more question that needs answering. The ConnectedGrid has been created by the connect helper, and I was pointing out then that this is meant to make store information available to props. But how does the ConnectedGrid know where the store is? The answer can be found in this final snippet:

    ReactDOM.render(
      <div>
        <Provider store={store}>
          <ConnectedGrid />
        </Provider>
      </div>,
      document.getElementById("app")
    );
    

    I utilize the Provider component, again imported at the top of the code, and wrap the ConnectedGrid in it. The Provider in turn receives the store reference in its props and “provides” it for all connected components in its scope.

    Take a deep breath

    Phew.

    Right?

    Now the whole thing is complete, you can run it. I recommend clicking the Edit on CodePen link in the top right corner of the embedded sample below. Once you are in CodePen, click the Change View button and select Debug Mode. This opens the running sample in a separate page, without any of the CodePen elements around it. Make sure you have the Redux DevTools Extension installed and hit F12. In the Developer Tools window, select Redux from the top menu. Now interact with the grid, and you’ll see the resulting actions popping up in the Redux debugger. Be sure to check out the details of the state changes, and activate the slider at the bottom to enable time-traveling for the actions!

    Click here to return to the blog series overview

  • DevExtreme React Grid - Custom Editors

    This post is part of a series about the DevExtreme React Grid. You can find the introduction and overview to the post series by following this link.

    At this point of its development, the DevExtreme React Grid doesn’t have any built-in functionality to utilize data type specific editors. However, it is easily possible to embed your own editors for certain types, and there is no shortage of existing solutions for UI libraries like Bootstrap.

    In the sample for this post (below), I am assigning template functions to editCellTemplate on the TableEditRow plugin and filterCellTemplate on the TableFilterRow.

    <TableFilterRow filterCellTemplate={ this.selectFilterEditor } />
    <TableEditRow editCellTemplate={ this.selectCellEditor } />
    

    It is a bit unfortunate that the two template functions differ in their signatures, so that the processes of embedding an editor for data editing vs embedding it for filtering are slightly different as well. Our developers are considering this use case and I hope this will become easier in the future.

    Here are the two functions selectCellEditor and selectFilterEditor:

    selectCellEditor({ column, value, onValueChange }) {
      if (column.name === "year")
        return (<CellIntEditor value={value} onValueChange={onValueChange} />);
      else
        return undefined;
    }
    
    selectFilterEditor({ column, filter, setFilter }) {
      const  setFilterNumber = 
        number => setFilter(number ? { value: number } : null);
      if (column.name === "year")
        return (<FilterIntEditor 
          value={filter ? filter.value : null}
          onValueChange={setFilterNumber} />);
      else
        return undefined;
    }
    

    In both cases the main distinction is about the column that requires an editor at this point. In my sample, I’m using a special number editor and this can be activated for the year column. In other cases I’m returning undefined.

    Note that undefined has a special meaning here by indicating that the default editor for the column should be rendered. If you were to return null instead of undefined, you would get no editor at all (which is standard React behavior for null values.)

    selectCellEditor receives the two parameters value and onValueChange, which allow the editor to hook into the editing logic of the Grid. The value should be displayed (and provided for editing) by the editor, an onValueChange will have to be called when the editor changes the value, to notify the Grid of the change.

    For selectFilterEditor, things are logically the same, but different in their implementation. The filter parameter contains a structure as defined for FilteringState, and the value that will be shown by the editor needs to extracted from that structure first. In a similar way, the correct structure needs to be created for the setFilter call, which requires a bit of extra logic.

    With these items in place, what remains is the implementation of the CellIntEditor and FilterIntEditor components, which are returned by the two functions. The two components are implemented as functional components and they use a common editor called NumericInput, available here.

    const IntEditor = ({ value, onValueChange }) => (
      <NumericInput
        className="form-control"
        value={value}
        onChange={valueAsNumber => onValueChange(valueAsNumber)}
        />
    );
    
    const CellIntEditor = ({ value, onValueChange }) => (
      <td>
        <IntEditor value={value} onValueChange={onValueChange} />
      </td>
    );
    
    const FilterIntEditor = ({ value, onValueChange }) => (
      <th>
        <IntEditor value={value} onValueChange={onValueChange} />
      </th> 
    );
    

    Another slightly unfortunate difference between data editors and filter editors is visible in this implementation. The template is expected to wrap the actual editors in HTML cell elements, which is required with the aim of increasing flexibility. However, for a data editor the wrapping element should be <td>, whereas the filter lives in the table head and requires <th> instead. Like I said before, I hope we’ll be able to iron out some of those differences in order make it easier to embed general-purpose editors.

    As a final note: I’m going to introduce a larger sample in one of the upcoming posts of this series, where I will embed a date editor in addition to a number editor, and cover Material UI in addition to Bootstrap. The mechanisms are the same as those explained in this post, but the upcoming demo provides some additional illustrations.

    That’s all! Here is the sample with the embedded editors:

    Click here to return to the blog series overview

  • DevExtreme React Grid - Standard Plugins

    This post is part of a series about the DevExtreme React Grid. You can find the introduction and overview to the post series by following this link.

    Out of the box, the React Grid comes with several plugins that enable certain control behavior. In a later post, I will show how these plugins can be controlled from React, but for now I will take advantage of the internal functionality of the plugins. Sometimes the term uncontrolled is used for this scenario, hinting at the fact that there is no external influence on a component’s behavior.

    Note for clarity that there is one component that is already controlled at this point: the Grid itself, which receives values for its rows and columns from my code.

    Plugin categories

    All the standard plugins can be sorted into three categories. For details on each plugin that I don’t mention here, please check out the documentation. I will not mention every plugin by name, since I expect the list to change as development progresses.

    State

    The first category is that of state. All state plugins have names ending in State, like FilteringState and PagingState. These plugins need to be added to the Grid first, i.e. before any plugins from the other categories below.

    State plugins provide fields and events for situations where state will be stored elsewhere (the controlled mode mentioned above), and for the uncontrolled mode I’m describing now, they may have defaultXXX fields that enable you to preset the state.

    In my sample, shown at the bottom of this post, I’m using all the currently supported state plugins:

    <SortingState
      defaultSorting={[{ columnName: "name", direction: "asc" }]}
    />
    <PagingState />
    <FilteringState />
    <GroupingState />
    <EditingState onCommitChanges={this.commitChanges} />
    <SelectionState />
    <RowDetailState />
    <ColumnOrderState defaultOrder={["name", "artist", "year"]} />
    

    This configuration supplies default options to two of the plugins, and I’m implementing one of the events to support data saving functionality.

    State plugins are imported from DevExpress.ReactGrid in my browser sample, or from @devexpress/dx-react-grid in an app that uses a build environment. State plugins do not implement any UI specific or visualization functionality.

    Local functionality

    I’ll call the second category local functionality. The plugins of this kind supply implementations of grid features, like grouping and sorting. They do this “locally”, i.e. without interaction with a remote service. While there may be configurable options for these plugins, the general idea is to provide out-of-the-box functionality, like our grid components do on other platforms.

    The local functionality plugins depend on the state plugins from above. For example, you cannot use the LocalPaging plugin if you haven’t also added the PagingState. LocalPaging must appear after PagingState to satisfy the dependency.

    In my sample, I’m using all currently available local functionality plugins without further configuration:

    <LocalSorting />
    <LocalFiltering />
    <LocalGrouping />
    <LocalPaging />
    

    Local functionality plugins are imported in the same way as state plugins, since they also don’t supply visualization functionality.

    Visualization

    The final category is that of visualization. Plugins in this category are responsible for the rendering of UI elements and information.

    Many of these plugins provide options to configure their behavior, and to enable and disable certain features. Some of the plugins also offer options to customize templates they use to render themselves.

    The plugins usually have dependencies, and these may include other plugins from the visualization category. For example, the TableHeaderRow depends on the TableView, but it can also utilize information from SortingState and GroupingState (state plugins) as well as DragDropContext (a visualization plugin). Details on dependencies can be found in their documentation (for instance here for TableHeaderRow).

    Here are the plugins I’m using in my sample. You can see several options, including one to customize a template.

    <DragDropContext />
    <TableView allowColumnReordering />
    <TableHeaderRow allowSorting allowGrouping allowDragging />
    <TableFilterRow />
    <TableSelection highlightSelected showSelectAll />
    <TableEditRow />
    <TableEditColumn allowAdding allowEditing allowDeleting />
    <TableRowDetail template={this.tableRowDetailTemplate} />
    <PagingPanel allowedPageSizes={[0, 5, 10, 20]} />
    <GroupingPanel allowSorting />
    <TableGroupRow />
    

    For visualization plugins, order is particularly important. Of course they mostly have to appear after the state and local functionality plugins, but there are also category-internal dependencies. Most plugins must appear after TableView, but even TableView depends on DragDropContext, which is why the latter is listed first in the sample code. Also, the TableGroupRow appears behind all other plugins to correctly combine functionality of grouping, selection, row detail etc.

    Note that there is one plugin I’m not using in my sample, which is called VirtualTableView. This provides an alternative to the standard TableView, implementing virtual scrolling.

    Plugins of the visualization category are specific to the UI library you use. In my sample, I’m using Bootstrap 3, so these plugins are imported from DevExpress.DXReactGridBootstrap3 or @devexpress/dx-react-grid-bootstrap3. Other UI libraries will be supported in the future, with Material UI being closest to completion. If you use a different supported UI library, you need to import the types from a different source.

    Note that in some cases it is possible to accidentally import a type from wrong package. For instance, DragDropContext also exists in DevExpress.DXReactGrid or @devexpress/dx-react-grid. You will receive console error messages due to missing templates if you import a visualization plugin from the wrong package.

    Other sample functionality

    The sample code implements a template that is used to configure TableRowDetail. This plugin shows an expansion arrow next to each row, and the template renders the content shown if the user expands a row. For real-world applications, this feature enables all sorts of complicated master/detail scenarios, since the template can render whatever you like. For my sample, I chose to use a simple line of output to demonstrate the feature:

    tableRowDetailTemplate({ row }) {
      return <div>Details about '{row.name}' by {row.artist}</div>;
    }
    

    A function called getRowId is implemented to retrieve a unique id value from a row of data:

    getRowId(row) {
      return row.id;
    }
    

    This function is configured as a delegate for the Grid component itself. Various plugins may require ids for individual rows, and you will generally have to supply a helper function to extract whatever value (or combination of values) you would like to use to identify rows.

    Finally, the sample implements data persistence in component state, in the commitChanges function. The function is configured as the event handler for the onCommitChanges event on the EditingState, so it is executed when the user adds or edits a row and clicks Save, or when the Delete link is used. The details of the inner workings are not relevant and the implementation is supplied only for demonstration purposes.

    Note that the commitChanges function is bound to the this context of the App class in the constructor, so the function has access to the App component state.

    Try it

    Here is the CodePen for the sample described above:

    Click here to return to the blog series overview

  • DevExtreme React Grid - Basic Setup

    This post is part of a series about the DevExtreme React Grid. You can find the introduction and overview to the post series by following this link.

    Getting started

    Most applications that use React are developed with some kind of build environment. Many introductions and tutorials exist for React, including the one on the project website, so I’m not going to reproduce this part here.

    As a quick and easy way tool to create a new React based app, using a standard build environment, I recommend create-react-app.

    On the basis of a React application, we have a short getting started section on the first page of the React Grid documentation. I didn’t have any trouble getting the React Grid to work correctly following these instructions, but please let me know in case you encounter any issues!

    For the purposes of this blog series, I decided to prepare a few examples in CodePen. In each of these “Pens”, you can see in the JavaScript page of the Settings how I’ve added the required references. This is not a generally recommended approach for real-world applications! For demos however, it is brilliant since you can play with my samples directly.

    The most basic Grid setup

    My first example shows a realistic minimum configuration. You can see the Pen a few paragraphs below and I’ll explain the code of the demo here.

    At the top of the file, you see the one line of code that would be different if I weren’t working in a Pen:

    const { Grid, TableView, TableHeaderRow } = DevExpress.DXReactGridBootstrap3;
    

    I included a comment in the Pen to point out the difference. Whichever syntax you use, you end up with the three elements Grid, TableView and TableHeaderRow, which are now available for use in the rest of the code. The TableHeaderRow is technically optional, but I decided to include it in my miminum setup to create a recognizable grid appearance.

    class App extends React.PureComponent {
    

    This line creates a new component called App. It is derived from the React PureComponent, the recommended base class for most React components since React version 15.3.

    In the constructor of the component, I preset the component state to include a column definition and some sample data:

    constructor(props) {
      super(props);
    
      this.state = {
        columns: [
          { name: 'name', title: 'Name' },
          { name: 'artist', title: 'Artist'},
          { name: 'year', title: 'Year' }
        ],
        rows: [ ... ]
      };
    }
    

    Of course your data would probably come from some other source in most real-world applications, but it is easy to see that you would only need to initialize the state in some other way to apply any real-world data retrieval technique you use.

    The render function of the component retrieves the two lists columns and rows from state and then returns a JSX structure that renders the three components I loaded at the start of the code. The column and row content is included by means of the curly brace expressions, and thereby passed to the Grid element.

    render() {
      const { rows, columns } = this.state;
    
      return (
        <Grid rows={rows} columns={columns}>
          <TableView />
          <TableHeaderRow />
        </Grid>
      );
    }
    

    The TableView and TableHeaderRow are plugins to the Grid, which is why they appear nested as children.

    Note that the order of plugin elements is important! The TableHeaderRow could not possibly do its job if a TableView weren’t available first. Keep this in mind for all plugins: order is relevant. Our documentation pages define dependencies of each plugin, right at the top of each page.

    The final line of this first demo renders the App component into the HTML page. The latter contains nothing but one div: <div id="app"></div>.

    Here’s the CodePen that shows the steps of this first example. Please note that you can click the Edit on CodePen link in the top right corner to bring up the full editor, where you can also see project settings including dependencies.

    Click here to return to the blog series overview

  • DevExtreme React Grid - Blog Series

    With our 17.1 release in May, we introduced a CTP of the upcoming DevExtreme React Grid. Some documentation and demos are available for the grid, and you can track our development work in the GitHub repository.

    Please note that the CTP status of the project means that it’s work in progress. You may find bugs and there could be significant breaking changes before a release status is reached.

    I have spent some time with the React Grid and I should first say that I’m very impressed - not to say anything bad about our other grids, but this one seems to be the fastest yet!

    I decided to publish a series of posts about my work with the grid (links will be added as posts are published):

LIVE CHAT

Chat is one of the many ways you can contact members of the DevExpress Team.
We are available Monday-Friday between 7:30am and 4:30pm Pacific Time.

If you need additional product information, write to us at info@devexpress.com or call us at +1 (818) 844-3383

FOLLOW US

DevExpress engineers feature-complete Presentation Controls, IDE Productivity Tools, Business Application Frameworks, and Reporting Systems for Visual Studio, along with high-performance HTML JS Mobile Frameworks for developers targeting iOS, Android and Windows Phone. Whether using WPF, ASP.NET, WinForms, HTML5 or Windows 10, DevExpress tools help you build and deliver your best in the shortest time possible.

Copyright © 1998-2017 Developer Express Inc.
All trademarks or registered trademarks are property of their respective owners