Oliver's Blog

October 2017 - Posts

  • Custom Sorting, Grouping, Filtering and More in DevExtreme Grid for React

    We have received a lot of valuable feedback from users of our React Grid alpha versions. Many thanks to everybody! Some of the feedback has already been used as a basis for enhancements to the component. I should mention that we tend not to extend the Grid as a “black box” with lots of built-in features. Usually, we just provide customization capabilities to implement new features on top of our React Grid. Mostly we make API improvements that allow a developer to customize grid behavior and appearance by replacing some of its core algorithms or UI templates. Here is an overview of some recent changes.

    Custom Sorting, Grouping, Filtering and More

    Custom data accessors

    Document-based NoSQL databases are getting more and more popular, which means the data from the server is not flat and tabular, but has complex structures involving nested objects and arrays. It is inconvenient to flatten the data every time it’s obtained from the server, before passing it to the Grid, and a flattening step makes it more complicated to send modified data back to the server. To display nested or computed data, you can now specify a getCellValue function in a column configuration object:

    const rows = [
      { user: { firstName: 'John', lastName: 'Smith' } }
      /* ... */
    ];
    const columns = [
      {
        name: 'firstName',
        title: 'First Name',
        getCellValue: row => (row.user ? row.user.firstName : undefined)
      },
      {
        name: 'lastName',
        title: 'Last Name',
        getCellValue: row => (row.user ? row.user.lastName : undefined)
      },
      /* ... */
    ];
    
    /* ... */
    
    <Grid
      rows={rows}
      columns={columns}
    />
    

    If your data grid is editable, you also need a way to pass cell changes back to the row object. We introduced a createRowChange function, which accepts the whole row and the new cell value as parameters, and should return a “row change” object that reflects changes made to the row. Note that the original row should be regarded immutable, so we create and return a new object with the modified fields. This change is merged into the changedRows state via the EditingState plugin.

    const rows = [
      { user: { firstName: 'John', lastName: 'Smith' } }
      /* ... */
    ];
    const columns = [
      {
        name: 'firstName',
        title: 'First Name',
        getCellValue: row => (row.user ? row.user.firstName : undefined),
        createRowChange: (row, value) => ({
          user: {
            ...row.user,
            firstName: value,
          },
        }),
      },  
      /* ... */
    ];
    

    Please refer to the data accessors guide for demos and more details.

    Formatters and editors for custom data types

    Now you can define any custom data type and specify how a value of this type should be displayed and edited. This capability is provided by the DataTypeProvider plugin. All you need to do is to add a dataType to the column configuration object and set up formatterTemplate and editorTemplate properties for the DataTypeProvider, which will be used to show or edit the column value:

    const rows = [
      { product: 'SolarOne', price: '3039' }
    ];
    const columns = [
      { name: 'product', title: 'Product' },
      { name: 'amount', title: 'Sale Amount', dataType: 'currency' }
    ];
    
    <Grid
      rows={rows}
      columns={columns}
    >
      <DataTypeProvider
        type="currency"
        formatterTemplate={({ value }) => <span>${value}</span>}
        editorTemplate={({ value, onValueChange }) => (
          <span>
            $<input
              type="number"
              value={value}
              onChange={e => onValueChange(Number(e.target.value))}
            />
          </span>
        )}   
      />
      <TableView/>
    </Grid>
    

    Please refer to the data types guide for demos and more details.

    Custom filtering predicates

    The default React Grid filtering predicate uses a case-insensitive “contains” algorithm. Now you can specify custom filtering predicates using the getColumnPredicate property on the LocalFiltering plugin. If the getColumnPredicate function returns undefined for a column, the default filtering predicate is used.

    const startsWithPredicate = (value, filter) => 
      String(value).startsWith(String(filter.value));
    const getColumnPredicate = columnName => {
      if (columnName === 'city') {
        return startsWithPredicate;
      }
    };
    
    <Grid
      rows={rows}
      columns={columns}
    >
      <FilteringState />
      <LocalFiltering getColumnPredicate={getColumnPredicate} />
      <TableView />
      <TableHeaderRow />
      <TableFilterRow />
    </Grid>
    

    Please refer to the filtering guide for demos and more details.

    Custom sorting compare functions

    In some scenarios, custom sorting algorithms are required. For instance, you might have an enumeration value represented by its ID in your database. Imagine a ‘priority’ column with valid values “Low”, “Normal” and “High”. You would want this column to be sorted by the underlying numeric values, not alphabetically. The getColumnCompare property of the LocalSorting plugin allows you to implement this scenario.

    Note that the comparison function is expected to implement a three-way comparison. For brevity, the following example uses a simple method of calculating a result for numeric values.

    const priorityWeights = {
      Low: 0,
      Normal: 1,
      High: 2
    };
    
    const comparePriority = (valA, valB) => 
      priorityWeights[valB] - priorityWeights[valA];
    
    const getColumnCompare = columnName => {
      if (columnName === 'priority') {
        return comparePriority;
      }
    }
    
    <Grid
      rows={rows}
      columns={columns}
    >
      <SortingState />
      <LocalSorting
        getColumnCompare={getColumnCompare}
      />
      <TableView />
      <TableHeaderRow allowSorting />
    </Grid>
    

    If the getColumnCompare function returns undefined, it applies the default sorting algorithm.

    Please refer to the sorting guide for demos and more details.

    Custom grouping values

    Data grouping is a powerful feature that helps visualize and analyze large numbers of rows. Usually, a particular row is classified to belong to a specific group by its exact column value. For instance, two rows can belong to one group if they have equal values for the ‘city’ column. Sometimes, your application requires data grouping to use more complex algorithms for row classification.

    One common scenario is to group items by only the first character of a string property. For instance, you might need to group people by the first characters of their last names. Another use case is to group orders by year, month, day, or other custom intervals. Our Grid provides the capability to perform these kinds of custom groupings by implementing the getColumnIdentity property of the LocalGrouping plugin:

    const byFirstLetter = value => ({
      key: value.substr(0, 1)
    });
    const getColumnIdentity = (columnName) => {
      if (columnName === 'city') {
        return byFirstLetter;
      }
    };
    
    <Grid
      rows={rows}
      columns={columns}
    >
      <GroupingState />
      <LocalGrouping getColumnIdentity={getColumnIdentity} />
      <TableView />
      <TableHeaderRow />
      <TableGroupRow />
    </Grid>
    

    Please refer to the grouping guide for demos and more details.

    Remote and custom local grouping

    In some cases, your data might be grouped already. For instance, the data may have been obtained from a server that supports grouping itself. You would like to pass this data to the Grid “as is”, while retaining the standard grouping UI with its interactive group expanding/collapsing features. The new CustomGrouping plugin has been introduced for this purpose. You need to configure it to describe your data structure and then pass the grouped data to the grid.

    const columns = columns: [
      { name: 'name', title: 'Name' },
      { name: 'sex', title: 'Sex' },
    ];
    
    const groupedData = [{
      key: 'Male',
      items: [
        { id: 1, name: 'Paul', sex: 'Male' },
        { id: 2, name: 'John', sex: 'Male' },
      ],
    }, {
      key: 'Female',
      items: [
        { id: 3, name: 'Jane', sex: 'Female' },
        { id: 4, name: 'Kate', sex: 'Female' },
      ],
    }];
    
    const getChildGroups = groups => groups
      .map(group => ({ key: group.key, childRows: group.items }));
    
    const grouping = [{ columnName: 'sex' }];
    
    <Grid rows={groupedData} columns={columns}>
      <GroupingState grouping={grouping} />
      <CustomGrouping getChildGroups={getChildGroups} />
      <TableView />
      <TableHeaderRow />
      <TableGroupRow />
    </Grid>
    

    Please refer to the custom grouping and remote grouping guides for demos and more details.

    UI customization via templates

    To render individual UI elements, our React Grid uses templates that are passed via props to the Grid’s UI plugins. This happens automatically and internally, so you don’t normally see these templates specified anywhere. A template is a function that returns a React Element depending on the arguments it receives. If you need to modify the default appearance or behavior you can replace default templates with custom ones using template properties on the various plugins.

    const customRowTemplate = ({ children, row }) => (
      <tr onClick={() => alert(row.id)}>
        {children}
      </tr>
    );
    
    <Grid
      rows={rows}
      columns={columns}
    >
      <TableView tableRowTemplate={customRowTemplate} />
      <TableHeaderRow />
    </Grid>
    

    Please refer to the appearance customization guide for demos and more details. This approach is applicable to both the Bootstrap React Grid and the React Grid for Material Design.

    Feel free to download your copy from npm and let us know what you think.

  • React Data Grid for Google Material Design

    Material-UI (the Material Design React component library) version 1.0 is about to release, and so is our DevExtreme React Grid v1.0 (at the same time as DevExtreme v17.2). The very first pull request that started our work on Material UI integration was merged four months ago. Since then, our Material UI data grid has caught up to its Bootstrap counterpart. In this post I’d like to sum up what has been done to support the Google Material Design Data Table in the DevExtreme React Grid.

    React Grid Material UI

    Editing UI

    Editing is one of the most demanded features of any data grid. It allows users to create, update and delete rows right in the visual data table. To provide this functionality, the React Grid renders specific editing controls if the editing-related plugins are used. You can customize the appearance of editing controls. For instance, you can specify the text and icons you want to see in your application, or provide custom value editors for certain columns.

    Read our React Grid docs about editing (they apply for Bootstrap and Material UI)

    Editing UI

    Filtering UI

    A typical real-world LOB application visualizes and manages a lot of data. Search capabilities are crucial to the end-user experience. Our Material UI data grid has a set of plugins that implement filtering functionality, such as the Filter Row, special row shown right underneath the table header, containing filter editors used to enter filter values.

    Read our React Grid docs about filtering (they apply for Bootstrap and Material UI)

    Filtering UI

    Grouping UI

    Another technique that simplifies work with large amount of data is to group information by criteria. End users can browse grouped rows and expand or collapse them as needed. The Google Material Design guidelines don’t contain specifications for a data table grouping UI. We did our best to provide UI for this feature that would be consistent with the guideline intentions. Multi-level grouping by several columns or custom values is also available.

    Read our React Grid docs about grouping (they apply for Bootstrap and Material UI)

    Grouping UI

    Paging UI

    Row paging is a must-have common feature that is expected by almost every data grid user. As you would expect, the Google Material Design guidelines contain specifications for this feature and we observe them in our Material UI Data Grid for React. The Grid provides controls to change page size, switch the current page and to display the number of available pages as well as the range of visible rows. Paging is integrated with the Grouping feature, so the Grid shows the current page group header on each page, if grouping is configured.

    Read our React Grid docs about paging (they apply for Bootstrap and Material UI)

    Paging UI

    Selection UI

    In web applications that allow data manipulation, you usually need row selection capabilities. An end-user might want to delete rows or perform batch updates, for which rows need to be selectable. Selections can incorporate individual rows, all rows on the current page, or all rows loaded in the Grid. The appearance of row selection in data tables is specified in the Material Design guidelines and we have implemented it for the DevExtreme React Grid accordingly.

    Read our React Grid docs about selection (they apply for Bootstrap and Material UI)

    Selection UI

    Row Detail UI

    When your data row is represented as a complex structure, the Detail Row feature can be very helpful. It allows the Grid to display many rows in a compact view when collapsed, but end users can expand individual rows as required to display row details. You can also implement master/detail scenarios using the Detail Row feature.

    Read our React Grid docs about row details (they apply for Bootstrap and Material UI)

    Row Details UI

    Light, Dark and Custom Material Design Themes

    There are two standard Material Design themes: Light and Dark. They are both supported by the React Data Grid for Material UI. If you don’t like these standard themes you can configure your own, for instance to reflect the colors of your company branding.

    See our React Grid theming demos (switch to Material UI)

    Dark Material Design Theme

    Lots of other features

    In time for our final release we expect all other React Grid features supported by the Bootstrap Grid to be available for Material Design as well. Sorting, column reordering, column resizing, the column chooser and others are available now, while virtual scrolling for Material UI will be implemented soon.

    Other features

    Feel free to download your copy from npm and let us know what you think!

  • New Funnel Chart Widget (Coming soon in v17.2)

    A Funnel Chart is a type of chart often used to visualize a value at different stages of a process and to assess value changes throughout those stages. Funnels are useful to identify potential issues in processes.

    A typical funnel report represents stages of a process in the form of upturned trapezoids. The longer and shorter bases of the trapezoids represent the value at the start and end the given stage, respectively. Although the funnel chart is named after the household funnel, the resemblance is superficial. Unlike a real funnel, not everything that is “poured in” at the top of a funnel chart flows through to the bottom.

    An Example Use Case

    For this post, we focus on using a funnel chart to analyze a conversion rate and identify bottlenecks. This type of report is usually called Conversion Funnel or Sales Funnel.

    The conversion rate is one of the most important aspects of paid inclusion campaigns. The conversion rate of a website, for example, can show what percentage of all visitors performed a desired action: bought a product, filled out a form, etc. The bigger the conversion rate, the more successful is the website and a related paid inclusion campaign.

    One way of raising the conversion rate is getting rid of bottlenecks. A bottleneck is one process in a chain of processes whose limited throughput reduces the throughput of the entire chain. Simply put, it’s a point where the measured value drops by the largest margin. We created a sample funnel chart to illustrate the concepts of conversion rate and bottleneck. We used the jQuery Funnel widget, a data visualization component new to DevExtreme in the upcoming v.17.2 release. Note that as usual the component is also available as an Angular Component, ASP.NET MVC and ASP.NET Core MVC Controls and a Knockout binding.

    Conversion Rate Funnel Chart

    As you can see, 18% of the website visitors renewed their subscriptions, so this is the conversion rate of the website. But most of the visitors decided not to subscribe after contacting support. This bottleneck is made obvious by the funnel chart, and it might be possible to improve things, for example, by providing some extra training for the support team.

    Why a New Widget?

    DevExtreme subscribers are familiar with our impressive library of data visualization components. Specifically, there is the feature-rich Chart widget with its many series types. We considered implementing the Funnel chart as a new series type for the Chart widget, but we decided to introduce it as a separate component, because the Cartesian XY coordinate system used by the Chart is not a good fit for the Funnel.

    However, Funnel and Chart share many visual elements like the legend, the title, tooltips and color schemes, and most importantly they share the simplicity of configuration. For example, the following code is sufficient to create a Funnel with default styling:

    $("#funnel").dxFunnel({ 
      dataSource: [ 
        { argument: "Visited the Website", value: 9152 }, 
        { argument: "Downloaded a Trial", value: 6879 }, 
        { argument: "Contacted Support", value: 5121 }, 
        { argument: "Subscribed", value: 2224 }, 
        { argument: "Renewed", value: 1670 } 
      ], 
      title: "Website Conversions", 
      argumentField: "argument", 
      valueField: "value" 
    });
    

    Try It Now!

    The Funnel widget is included in the v17.2 pre-release that is available via npm right now. Please note that this pre-release may contain some bugs and is not intended to be used in production.

    npm install devextreme@17.2.1-pre-17262
    

    We will provide full documentation when v17.2 is released, meanwhile you can learn more about Funnel configuration from this PR on GitHub.

    If you have thoughts about this new DevExtreme widget, please share them with us!

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