DevExtreme - Real World Patterns - DevExtreme Widgets

Oliver's Blog
03 April 2017

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 front-end web application is the last “big” component of the demo application, and it is technically quite simple. The server-side index.js launches an Express web server to make a few static files available:

const express = require("express");

var app = express();

app.use(require("morgan")("dev"));
app.use(express.static("static"));

app.listen(8080, function() {
    console.log("Web server running on port 8080");
});

The static files themselves contain what little application logic there is in this simple demo, and they configure the data grid and the pivot grid to access data through a custom data store implementation.

The JavaScript API I’m using is jQuery, to keep things simple and universal. As you may know, DevExtreme also supports bindings to Angular and Knockout. Using either of these bindings doesn’t change the way the widgets work on a low level, it mainly changes the syntax of your code to integrate the widgets. I plan to show alternative UI implementations in the future.

The implementation of the custom store adheres to the description in the DevExtreme documentation. The modification functions are very simple, since they trigger a single AJAX call. Here’s the update function as an example:

update: function(key, value) {
  return $.ajax({
    url: BASEDATA + "/" + encodeURIComponent(key),
    method: "PUT",
    data: JSON.stringify(value),
    contentType: "application/json"
  });
}

The load function is a bit more complicated, since it has to transfer the loadOptions to the server. I started out with an implementation from our knowledge base, but I made a few modifications as I implemented the various parts of the data service.

Since I’m using the options in the same format on the server, it would be possible to simplify this code by just transferring the options directly, but I decided to leave this code explicit, making it more likely for future changes to the structure to be noticed early. At the end of the function, an AJAX call is made to the server to retrieve the data and other details, with the latter being passed back with the resolve function:

$.getJSON(BASEDATA, params).done(function(res) {
  var details = {};
  if (options.requireTotalCount) details.totalCount = res.totalCount;
  if (options.requireGroupCount) details.groupCount = res.groupCount;
  if (options.totalSummary) details.summary = res.summary;

  d.resolve(res.data, details);
});

Setting up the widgets

While the configuration of the widgets is mostly straight-forward, there are a few things that need to be done correctly. The data grid uses this block to set up its data connection to the custom data store (from dataGrid.js):

dataSource: {
  store: dataStore
}

Technically there is an additional class in the data access chain between the widget and the server: the DataSource. As you can see in the documentation, it is possible to instantiate a DataSource given a data store, and by assigning the object with the store property to the dataSource property of the grid configuration, this is done automatically. In addition, to take advantage of the server-side functionality implemented in the demo, it is important to activate remoteOperations correctly:

remoteOperations: {
  filtering: true,
  grouping: true,
  groupPaging: true,
  paging: true,
  sorting: true,
  summary: true
}

As you can see, there are several individual flags for remoteOperations. I don’t normally recommend using those individually. There might be edge case scenarios where it is useful to activate one remote operation and not the other, but it is certainly very easy to end up with performance-killing behavior by setting these options selectively – so be careful if you do!

Note that you can also set remoteOperations: true, which looks like it will activate all remote operations at once. However, as the docs explain, this does not activate groupPaging together with the other options. The option groupPaging was only introduced in version 16.2 of DevExtreme and our developers were afraid of breaking behavior, so they kept it separate. I’m using the longer explicit syntax in the demo to activate all remote operation features.

For the pivot grid, the setup is simpler (from pivotGrid.js):

dataSource: {
  remoteOperations: true,
  store: dataStore,

  // ... field configuration
}

There are no separate remote operation flags for the pivot grid, setting remoteOperations to true takes care of everything at once.

The data service public endpoint

One part of the demo project that hasn’t been mentioned yet is the data service that the custom store implementation talks to. This is implemented in the web-proxy service.

The service start-up is the most complicated of all the services, because it uses an Express web server as well as an elaborate Seneca configuration to interface with other services. The web server activates CORS support (without any restrictions) to enable browsers from any source to access the public service.

The purpose of web-proxy is to provide a REST-style interface to the other services. There are several Seneca modules that make this easier, but I decided implement the service manually for this demo since it allows me to carefully parse and check parameters passed to this public interface, before they are passed on to other services.

The routing for the REST service is configured in routes.js. Routing utilizes functionality from the seneca-web module together with the seneca-web-adapter-express adapter, to translate incoming URL requests into Seneca messages. These messages are received by the service implemented in proxy.js, which works through three steps for each message:

  1. Validate the message parameters, i.e. the values that were sent with the REST service request.
  2. Send a message onwards to the responsible back-end service and receive the response.
  3. Return that response to the caller, adding details like the correct HTTP status code depending on the outcome of the operation, and additional HTTP headers required by the REST conventions.

For the message list, which implements the complex parametrized query functionality, this process is the most complicated because almost all incoming parameters are checked before the options are passed on to the query-service. The basic philosophy I have implemented (here and in the query-service) is to ignore elements that are misformed or otherwise not understood, so that the service usually returns data even if parameters are invalid. Log instructions that output to the service console help debugging issues with invalid parameters during development.

Since the web-proxy service simply performs some validated routing, there isn’t much more to say about what it does. Please feel free to ask if you have any questions!

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.
No Comments

Please login or register to post comments.