October 2013 - Posts

React and the Report and Dashboard Server

I recently posted about some news in v18.1 of our Report and Dashboard Server and it made me curious to see how an interface could show a report without any ASP.NET components. We have several demos available that show the use of JavaScript applications as report viewing or editing front-ends (such as this one for React), but they assume that there’s a .NET based back-end, usually using ASP.NET MVC. The associated instructions also use bower, which isn’t something I would do today when creating a React app.

I set myself the goal to create a React app using create-react-app (duh!) and implement access to Report and Dashboard Server to show a report in the app, using the HTML5 Document Viewer. The basic steps of a solution are outlined on this documentation page, but again assume ASP.NET MVC.

The post below describes the steps I took in enough detail for you to follow along if you’re interested. Please let me know if you find anything missing or inadequately explained!

React and the DevExpress Report and Dashboard Server

Preparation

After setting up Report and Dashboard Server in a virtual machine, I configured the system with a Data Model and I created a report. I also set up email sending for the server and created a user account, which is necessary so I can authenticate the remote viewer using that account. Following the instructions I linked in the previous paragraph, I activated CORS.

Now I set out to create a first piece of code to run on Node.js, which contacts the server, retrieves a security token and then a list of available reports. Here’s what I ended up with:

const base = 'http://192.168.1.234:83';
const data = r => r.data;

axios
  .post(
    base + '/oauth/token',
    qs.stringify({
      username: 'sturm',
      password: 'secret',
      grant_type: 'password'
    }),
    {
      headers: { 'content-type': 'application/x-www-form-urlencoded' }
    }
  )
  .then(data)
  .then(d => d.access_token)
  .then(token => {
    console.log('Token: ', token);
    return axios
      .get(base + '/api/documents', {
        headers: { Authorization: 'Bearer ' + token }
      })
      .then(data)
      .then(d => {
        console.dir(d);
      });
  })
  .catch(err => {
    console.error(err);
  });

From the top, here are the steps of the process. First, I have an address and a port for the server, which depend on the server configuration. Port 83 is the default for a new installation and I went with that. You can see that I’m using HTTP as the protocol — more about that below.

Using axios (because I like Promises), I make a POST request to the server, using the path /oauth/token. The information I send is URL-encoded and I achieve this using the qs library. There are other solutions for this, but qs is easy to use and works on Node as well as in the browser. As a result of this call, I receive a Bearer Token from the server, which I need to use in further calls to prove authorization.

Note that the token expires sooner or later — mine were valid for twenty minutes each — so you should make sure to retrieve a new one close enough to the actual call you’re going to make!

To retrieve the list of available reports, I make a GET request to /api/documents and I pass the token as part of the Authorization header. This is of course a standard OAuth mechanism. The result is a list of available reports:

[
  {
    id: 1,
    categoryId: 1,
    modifiedById: 0,
    name: 'Venues',
    description: '',
    modifiedBy: 'DESKTOP-TMVPFDM\\sturm',
    modifiedWhen: '2018-05-23T10:08:06.867',
    documentType: 'Report',
    allowModify: false,
    allowDelete: false,
    optimisticLock: 0
  }
],

That was simple enough! There’s just one important thing to sort out…

HTTP - or what?

By default, the OAuth token can not be retrieved from the server using HTTP, it requires HTTPS. There is a good reason: if a third party should get hold of the token, they would be able to access the server using my credentials!

Since I’m running Report and Dashboard Server in a local virtual machine, I can’t easily get hold of a “real” (i.e. not a self-signed) SSL certificate. IIS will gladly provide a self-signed certificate for development purposes, but this is not accepted by a client without some extra steps. On Node, I can tell the client to ignore validation errors by using an HTTPS Agent:

const agent = new https.Agent({
  host: '192.168.1.234',
  port: 443,
  path: '/',
  rejectUnauthorized: false
});

However, this agent mechanism is not available in the browser, since browsers are not allowed to ignore certificate validation issues without direct user confirmation. This means that a self-signed certificate is useless for AJAX calls, even during development.

For my development and test scenario, I chose to deactivate the HTTPS requirement for OAuth. However, before I tell you how that works, you must understand that this is NEVER an acceptable solution for a real-world server outside a dev environment! I recommend you get a signed certificate for your server, for example using Windows ACME Simple in conjunction with Let’s Encrypt.

Now, here’s how to allow Report and Dashboard Server to use HTTP for OAuth, purely for development purposes and entirely at your own risk. Find the table GlobalSettings in the server database (by default called DevExpressReportServer). In that table, find the record with the Key value OAuthServerAllowInsecureHttp and set the associated Value to True. Restart the service using IIS Manager and you’ll be able to run code like mine above.

The React app

With all the preparation and research out of the way, I created a new React app:

create-react-app report-server-react

Since I never see the point in using yarn, I switched things to npm and made sure everything was installed correctly:

cd report-server-react
rm -rf yarn.lock node_modules
npm install

Now I installed required packages, starting with the ones to support the communication logic already shown above:

npm install --save axios qs

I also need the packages for the DevExpress functionality, plus globalize and cldr to support internationalization:

npm install --save cldr globalize devextreme devexpress-reporting

Finally, I need a special loader for webpack:

npm install --save-dev html-loader

Now for some code

I edited the file src/App.js and removed the default component implementation and the logo import. I added some import lines to pull in all the required library functionality:

import axios from 'axios';
import qs from 'qs';
import ko from 'knockout';
import 'devextreme/dist/css/dx.common.css';
import 'devextreme/dist/css/dx.light.css';
import 'devexpress-reporting/css/web-document-viewer-light.min.css';

const viewerHtml = require('devexpress-reporting/dx-web-document-viewer').Html;

I added a new App class:

class App extends Component {
  constructor(props) {
    super(props);
    this.state = { reportName: 'report/1' };
  }
  render() {
    return <ReportViewer reportUrl={this.state.reportName} />;
  }
}

Like in some of our existing examples, the value passed to the reportUrl attribute is retrieved from state. In the demo, I’m setting the value statically to report/<id> (that’s the id from the report list). Of course it is easy to imagine that the value would be retrieved from some other source in a real application.

Finally, here is the ReportViewer component. Again, this is based on our other examples. It is possible to separate the token retrieval logic, but I left it in place here to keep the structure simple.

class ReportViewer extends React.Component {
  constructor(props) {
    super(props);
    this.reportUrl = ko.observable(props.reportUrl);
  }
  render() {
    return (
      <div>
        <div ref="innerScript" />
        <div className="fullscreen" data-bind="dxReportViewer: $data" />
      </div>
    );
  }
  componentWillReceiveProps(newProps) {
    this.reportUrl(newProps.reportUrl);
  }
  componentDidMount() {
    this.refs.innerScript.innerHTML = viewerHtml;
    const baseUrl = 'http://192.168.1.234:83';
    axios
      .post(
        baseUrl + '/oauth/token',
        qs.stringify({
          username: 'sturm',
          password: 'secret',
          grant_type: 'password'
        }),
        {
          headers: { 'content-type': 'application/x-www-form-urlencoded' }
        }
      )
      .then(r => r.data)
      .then(d => d.access_token)
      .then(token => {
        ko.applyBindings(
          {
            reportUrl: this.reportUrl,
            remoteSettings: {
              serverUri: baseUrl,
              authToken: token
            }
          },
          this.refs.viewer
        );
      });
  }
  componentWillUnmount() {
    ko.cleanNode(this.refs.viewer);
  }
}

Once more, from the top, here are the important details of the implementation. First, in the constructor, there is a call to Knockout. This library is used by the document viewer internally, so I need to interface with it from my code. I create an observable value, initializing it with the reportUrl from my own props.

In the render method, two container divs are created. The second one has a data-bind attribute, which is later recognized by Knockout. Since the attribute uses the dxReportViewer binding type, Knockout will integrate the viewer in place of the container component.

In componentWillReceiveProps, I update the internal observable value if and when the same value in the props changes. This in turn updates the viewer since Knockout reacts to the change to the observable value.

The method componentDidMount is called when the React component is ready, and you’ll recognize most of the code in there. The new part is the call to ko.applyBindings: at this point, Knockout receives a data model, which in this case is a structure with document viewer parameters. These are used by the data binding described above, since there is the $data variable in the attribute.

The only remaining element is a bit of clean-up logic in componentWillUnmount.

One final code addition was required: I edited the file src/App.css and added this block at the end, so that the viewer appears in a full-screen setup:

.fullscreen {
  position: absolute;
  top: 0px;
  left: 0;
  right: 0;
  bottom: 20px;
}

And then… nothing worked

With all the code in place, it should have been possible to run the application at this point. Since I used create-react-app, everything was set up for a simple command:

npm start

However, there were errors reported when I did this. The reason was that the globalize and cldr libraries are not compatible with webpack by default — and webpack is used by the create-react-app infrastructure, even if it’s almost invisible.

To solve this problem (and one more, below) it is necessary to modify the hidden webpack configuration that has been generated by create-react-app. Unfortunately this is not supported in the default setup. In fact, the makers of create-react-app advise that whenever you feel the need to break out of the standard system, you should let them know why, so they can fix the original issue rather than support customization.

Note: The project react-app-rewired aims to provide customizability of projects created using create-react-app, at a tradeoff. For real-world scenarios I recommend considering this seriously, for the demo however I didn’t want to complicate matters further by bringing in additional tools.

There is a mechanism in place for the purpose of breaking out, and it’s called eject. Once you eject your project, you are free to modify details like the webpack config files, but you will not be able to use your project with new versions of create-react-app and react-scripts in the future. There are lots of discussions to be found online on the pros and cons of ejecting. For the purposes of this demo, I chose to do it:

npm run eject

Note: If you’re following along with my description, please click the survey link displayed at the end of the eject process and let the project maintainers know that you ejected due to lack of support for CLDR, Globalize and custom loaders.

Adjusting webpack config files

I edited the file config/webpack.config.dev.js and found the block resolve.alias. It contained an item called react-native by default, and I added these lines to solve the integration problems that came up for CLDR and Globalize.

cldr$: 'cldrjs',
cldr: 'cldrjs/dist/cldr',
globalize: 'globalize/dist/globalize'

I made one more change by integrating the html-loader you saw already when I added the package. This was necessary because the devexpress-reporting package contains two files with HTML snippets, and these need to be supported by the loader. I found the block modules.rules and navigated towards the end of it. There is a segment there for the configuration of the file-loader, with a comment right behind it indicating that any custom loaders need to be added ahead of that block. I inserted my setup code in the correct position:

{
  test: [/\.html$/],
  include: [
    path.resolve(paths.appNodeModules, 'devexpress-reporting/html')
  ],
  loader: require.resolve('html-loader')
},

This configuration means that for any HTML files located in the devexpress-reporting package folder, the html-loader is used.

If you intend you build a production version of your project (npm run build), you will need to make the same changes to the file config/webpack.config.prod.js.

That’s it!

If you have been following along, all that remains at this point is to run the application:

npm start

This opens a page in a browser automatically, at http://localhost:3000, showing the running application. If all goes well, it will contact the Report and Dashboard Server, retrieve the report and show it in the HTML5 Document Viewer!

I’m really interested in your thoughts. Will you use this type of integration in your own applications? Please feel free to get back to us if you need more help with your own integration scenarios!

Finally, here is a repository with my own demo app. If you change the URL strings to point to your own server, this should work fine!

.NET Charts - New Crosshair Features (v18.1)

In addition to Crosshair Cursor performance improvements mentioned in our recent post about performance improvements, we extended the Crosshair functionality on the basis of some very popular customer requests. The improvements apply to Chart Controls for WinForms and WPF, as well as ASP.NET WebForms and MVC, in our upcoming release v18.1.

New Crosshair Line mode: Free

We introduced a new Crosshair Cursor mode where snapping is disabled and lines are shown directly at the current mouse pointer position:

Free Lines

To enable this feature, set the property ChartControl.CrosshairOptions.LineMode to Free.

Note that the property CrosshairOptions.SnapMode (link for WinForms and ASP.NET, link for WPF) is still used in this mode to determine which values are selected for inclusion in the crosshair output.

Crosshair Details in the legend

In certain situations it can be convenient to display Crosshair Cursor information in the Legend panel instead of the standard “tooltip” popup window. This image shows a legend filled with Crosshair detail for the year 2016:

Crosshair Information in the Legend

Use the property ChartControl.CrosshairOptions.ContentShowMode to move the Crosshair Cursor data to the legend. Note that it is also possible to customize this option for an individual series using the Series.CrosshairContentShowMode property.

Include extra field values in Crosshair information

The final new feature is perhaps the best of the lot: you can now display arbitrary extra data source field values in the Crosshair output using the pattern syntax (link for WinForms and ASP.NET, link for WPF).

For example, the following text pattern includes the value of a field called Discount in the Crosshair output: {S}: {V:F2} (Discount: {Discount:P0}). The newly extended pattern syntax interprets the part {Discount:P0} as an instruction to include an extra field value.

Extra Field Values

React Data Grid - Tree Data and Banded Columns (v1.2)

Our upcoming release v1.2 of the React Grid contains two new big features: support for hierarchical data, and banded columns.

Hierarchical Data Support

Hierarchical data can be displayed in a tree structure. This feature is made available through three new plugins: TreeDataState, CustomTreeData and TableTreeColumn.

Tree Data

A guide for the Tree Data feature as well as an online sample are already available. You can see in the demo that other Grid functionality like Sorting, Filtering, Selection etc. can be used in conjunction with Tree Data.

Banded Columns

The second big new feature is Banded Columns, which are visual groups of columns. The TableBandHeader plugin is responsible for the implementation.

Banded Columns

A guide page is available for Banded Columns, which includes samples. This online demo also includes a Banded Columns configuration. Note that columns can still be resized and rearranged when they are included in a band!

Availability

At this time, a beta version of React Grid v1.2 is available. You can install it using the next tag:

npm i --save @devexpress/dx-react-core@next @devexpress/dx-react-grid@next
npm i --save @devexpress/dx-react-grid-material-ui@next

As usual, any feedback is greatly appreciated!

DevExtreme - New React Wrappers vs Native React Components (v18.1)

This is exciting news: in our upcoming v18.1 release, we will have the full set of more than 65 new React components available, based on our existing DevExtreme widgets! This includes Charts, DataGrid, Scheduler, PivotGrid, and many others. The feature set, functionality and appearance of these React components is the same as that of the DevExtreme widgets.

The new React components “wrap” the core implementations of the existing DevExtreme widgets, adapting them to the React core API. That’s why we call this new library DevExtreme React Wrappers. You can follow the development progress in this GitHub repo.

DevExtreme Native React Components vs React Wrappers

Two Sets of Components for React

The availability of this new set of React components means that we now offer two different libraries for the platform. The library we released first currently has just one component (the React Data Grid), and we call it DevExtreme Reactive Components for React. Admittedly, the name is a bit complicated, but we need to distinguish from the same components that will be available for Vue in the near future! Charts and Scheduler are going to be released soon, and there is overall a lot of traction in this ongoing development.

The DevExtreme Reactive Components were written from the ground up targeting the React and Vue libraries with a shared core. On the other hand, DevExtreme React Wrappers reuse our DevExtreme widgets and make them available as React components.

Both approaches have their advantages. Please read on for more detailed explanations.

Comparing Our Two Offerings

To help you decide which of our libraries for React better suits your development needs, we categorized some of the differences:

Core UI Packages (Building Blocks)

DevExtreme Reactive Components are built on top of popular third party React UI libraries (react-bootstrap, reactstrap and material-ui). This means that our native components use the theming approaches supplied by these libraries, as well as included and third-party themes. Your application will benefit from the controls, the consistent APIs and the look&feel technologies that come with the libraries.

The DevExtreme React Wrappers don’t rely on third-party UI building blocks. They are built with their own markup and style-sheets. They support our own theming system, which supplies customizable themes including Bootstrap and Material Design. You can also import any Bootstrap LESS file to customize these controls with your favorite paid or free Bootstrap theme.

Level of Control

DevExtreme Reactive Components use the native React approach where all UI state is exposed via component properties. This gives you complete control over all aspects of state, and the components can be fully controlled or uncontrolled or anywhere in between. The rendering layers of the components are separate packages, and they can be replaced partially or completely. Reactive Components are configured by composing ‘plugins’, providing granular configuration choices for individual use cases in your application.

DevExtreme React Wrappers do not use this native React technique and therefore appear more like black boxes, components that perform some configuration work internally and invisibly. This can result in reduced manual control, but the automated mechanisms are smart, and you may well find it easier to configure root component properties rather than composing and configuring individual plugins. In the React sense, Wrappers can be used as uncontrolled and partially — not fully, since they expose only parts of their state! — controlled components.

You may prefer one approach over the other — we feel that both have their merits.

Feature Sets and Extensibility

DevExtreme Reactive Components are less than a year old and offer fewer components, less functionality and a smaller feature set than DevExtreme React Wrappers. The individual components are growing and so is the library as a whole. This set of components is young, but due to the modular plugin-based concept, extensibility is fantastic, with the best possible opportunities for you to add your own features or customize built-in ones.

DevExtreme React Wrappers are new for the upcoming v18.1 release, but since they are based on the DevExtreme widgets, they come with a much large feature set. However, some extensibility scenarios are not easy due to the comparatively monolithic component architecture — for instance, it’s not easy to replace the pager for the Data Grid with a custom one. Fortunately the Grid supports pager customization in itself, so you probably don’t need to replace the pager! This is meant as an illustrative example of a complicated customization scenario.

Here is a table with some comparison aspects.

Aspect DevExtreme React Wrappers DevExtreme Reactive Components
Components 65+ React Components. Option to use feature/widget from the growing DevExtreme library. React Data Grid, Charts and Scheduler planned. Option to use native React components from react-bootstrap, reactstrap or material-ui.
Performance Built-in features provide rich high-performance functionality. Features modularized in plugins, immutable state, memoization, React optimizations, Virtual DOM and lightweight markup result in extreme performance.
Server-Side Rendering Not Available Yet Available
Using Redux Available Available
Ease of Configuration Easy, configuration via root component properties Moderate, configuration via plugin composition

Code Examples

To leave you with an impression of the structure used for configuration purposes by the two component libraries, we have prepared the following examples. Both are shown using uncontrolled (stateful) modes.

DevExtreme Data Grid React Wrapper

<DataGrid
  dataSource={rows}
  paging={{ pageSize: 10 }}
  filterRow={{ visible: true }}
  columns={[
    { dataField: 'orderId', caption: 'Order ID' },
    { dataField: 'country', sortOrder: 'asc' },
    { dataField: 'region' },
    { dataField: 'date', dataType: 'date', filterValue: '2013/04/01' },
    { dataField: 'amount', format: 'currency' }
  ]}
/>

DevExtreme Native React Data Grid

<Grid
  rows={rows}
  columns={[
    { name: 'orderId', title: 'Order ID' },
    { name: 'country', title: 'Country' },
    { name: 'region', title: 'Region' },
    { name: 'date', title: 'Date' },
    { name: 'amount', title: 'Amount' },
  ]}>
  <FilteringState
    defaultFilters={[{ columnName: 'date', value: '2013/04/01' }]} />
  <SortingState
    defaultSorting={[
    { columnName: 'country', direction: 'asc' } ]} />
  <PagingState
    defaultPageSize={10} />
  <IntegratedFiltering />
  <IntegratedSorting />
  <IntegratedPaging />
  <CurrencyTypeProvider for={['amount']} />
  <Table />
  <TableHeaderRow showSortingControls />
  <TableFilterRow />
  <PagingPanel />
  <Toolbar />
</Grid>

What Do You Think?

We are excited about the launch of this full set of new React Components. We would love to see the great React apps you’ll build with our DevExtreme React Wrappers. In v18.1, they will be in a CTP stage. We are considering further improvements, such as server-side rendering support, Virtual DOM utilization for rendering, better state management capabilities, etc. Please try the components, and let us know which improvements you would like to see in the RTM release.

If you have any questions about the distinction between the two libraries for React, please describe your use cases and we will do our best to help you choose the appropriate DevExtreme Components for React.

Join the Webinar

Sign up for our upcoming “New in v18.1 - DevExtreme HTML / JS Controls” webinar where you’ll have a chance to see our React libraries demonstrated live and ask any questions about our React, Vue, or other client-side offerings.

Join the webinar: https://attendee.gotowebinar.com/register/9186007723238099714

.NET Charts - Data Aggregation Improvements (v18.1)

The details in this post apply to the ChartControl in WPF and Windows Forms, the WebChartControl in ASP.NET WebForms, and the control created by the DevExpress.Chart() function when using ASP.NET MVC Extensions.

Additional flexibility for MeasureUnit

When visualizing DateTime values using a chart, it is possible to customize the detail level by setting the DateTimeScaleOptions.MeasureUnit property to a predefined unit (e.g. day, hour, minute, second, etc).

For our upcoming v18.1 release, we extended this functionality by introducing the property MeasureUnitMultiplier, which accepts an integer value. For instance, by setting the MeasureUnit to Minute and the MeasureUnitMultiplier to 15, you configure a measure unit of 15 minutes.

MeasureUnitMultiplier

It is important to mention that this feature is not currently compatible with work time and work day options. When a custom measure unit is specified, i.e. with a MeasureUnitMultiplier other than 1, these features are disabled.

Custom aggregation functions

In addition to custom measure units, we also improved the Data Aggregation mechanism by introducing a custom aggregation function API.

To enable this feature, set the AggregateFunction property for the axis scale to Custom and specify a callback that calculates aggregated values.

Custom Aggregate Function

Below is the sample custom aggregation function that transforms values into OHLC format for the image above.

XYDiagram diagram = chartControl.Diagram as XYDiagram;
diagram.AxisX.DateTimeScaleOptions.AggregateFunction = AggregateFunction.Custom;
diagram.AxisX.DateTimeScaleOptions.CustomAggregateFunction = new OhlcAggregateFunction();

class OhlcAggregateFunction : CustomAggregateFunction {
  public override double[] Calculate(GroupInfo groupInfo) {
    double open = groupInfo.Values1.First();
    double close = groupInfo.Values1.Last();
    double high = Double.MinValue;
    double low = Double.MaxValue;

    foreach (double value in groupInfo.Values1) {
      if (high < value) high = value;
      if (low > value) low = value;
    }

    return new double[] { high, low, open, close };
  }
}
More Posts