| Quick Start Guide | |
|---|---|
| What You Will Need |
|
| Controls Referenced | |
| Tutorial Concept | Build a responsive JavaScript dashboard with Wijmo using configurable UI components, data binding, and interactive visualization controls. Implement flexible dashboard layouts, connect charts and grids to application data, and manage user-driven updates to create scalable, data-intensive web interfaces. |
Modern business applications often need to do more than display a static report. Users want to filter information, review metrics, visualize trends, and inspect the individual records behind those numbers without moving between several screens.
A dashboard brings those workflows together. Instead of maintaining separate data pipelines for a chart, grid, filters, and KPI cards, we can keep a shared data layer and update the entire interface whenever the user changes a filter.
In this article, we'll cover the following topics:
- Setting Up the JavaScript Application
- Creating the Dashboard Layout
- Styling the Dashboard
- Creating the Dashboard Data
- Managing Dashboard Data with CollectionView
- Adding the Dashboard Filters
- Displaying the Detailed Data with FlexGrid
- Turning the Current View into Chart Data
- Creating the FlexChart
- Calculating the KPI Cards
- Connecting the Dashboard
In this article, we'll build a dynamic JavaScript dashboard using Wijmo's FlexGrid, FlexChart, ComboBox, and CollectionView. The finished dashboard will display order data, provide interactive region and status filters, calculate summary KPIs, plot revenue over time, and show the matching records in a JavaScript DataGrid.
Ready to get started? Download Wijmo Today!
Understanding the Dashboard Architecture
Before we start building out the dashboard, it helps to establish how information will move through our application.
The dashboard will have four layers:
- Order data provides the original business records.
- CollectionView manages the currently filtered set of orders.
- FlexGrid displays the records contained in that filtered view.
- FlexChart and the KPI cards summarize the same filtered records.
The filter controls change the CollectionView rather than directly manipulating the grid or chart. CollectionView's filter callback determines which records belong in the current view, and its items property provides the resulting filtered collection.
This separation is useful because FlexGrid remains responsible for displaying detailed records, while CollectionView manages the data state. The chart and KPI calculations can then derive their values from the same current view.
Whenever a user changes a filter, we'll refresh the CollectionView and recalculate the dashboard summary.
Setting Up the JavaScript Application
We'll use Vite to create a lightweight Pure JavaScript application.
From a terminal, create the project:
npm create vite@latest wijmo-dashboard -- --template vanilla
cd wijmo-dashboard
npm install
Next, install Wijmo:
npm install @mescius/wijmo.all
Then, add the following imports in src/main.js:
import '@mescius/wijmo.styles/wijmo.css';
import { CollectionView } from '@mescius/wijmo';
import { FlexGrid } from '@mescius/wijmo.grid';
import {
ChartType,
FlexChart,
Series
} from '@mescius/wijmo.chart';
import { ComboBox } from '@mescius/wijmo.input';
import './style.css';
The Wijmo stylesheet provides the base styling for the controls. The remaining imports give us only the classes required by our dashboard.
Creating the Dashboard Layout
Next, replace the content in index.html with the dashboard structure:
<main class="dashboard">
<header class="dashboard-header">
<div>
<h1>Sales Dashboard</h1>
<p>Monitor revenue and review the orders behind each result.</p>
</div>
</header>
<section class="filters" aria-label="Dashboard filters">
<div class="filter-field">
<label for="regionFilter">Region</label>
<div id="regionFilter"></div>
</div>
<div class="filter-field">
<label for="statusFilter">Status</label>
<div id="statusFilter"></div>
</div>
</section>
<section class="kpis" aria-label="Sales summary">
<article class="kpi-card">
<span class="kpi-label">Revenue</span>
<strong id="revenueKpi">$0</strong>
</article>
<article class="kpi-card">
<span class="kpi-label">Orders</span>
<strong id="ordersKpi">0</strong>
</article>
<article class="kpi-card">
<span class="kpi-label">Average Order</span>
<strong id="averageKpi">$0</strong>
</article>
</section>
<section class="dashboard-panel">
<h2>Revenue Trend</h2>
<div id="revenueChart"></div>
</section>
<section class="dashboard-panel">
<h2>Order Details</h2>
<div id="ordersGrid"></div>
</section>
</main>
<script type="module" src="/src/main.js"></script>
There are three ways to present the data here.
The KPI cards answer immediate questions such as total revenue and order count. FlexChart shows how revenue changes over time, while FlexGrid gives users access to the underlying records.
Styling the Dashboard
Add the following styles to src/style.css:
:root {
font-family:
Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
sans-serif;
background: #f5f7fa;
color: #1f2937;
}
body {
margin: 0;
}
.dashboard {
width: min(1200px, calc(100% - 32px));
margin: 0 auto;
padding: 32px 0 48px;
}
.dashboard-header h1 {
margin-bottom: 6px;
}
.dashboard-header p {
margin-top: 0;
color: #64748b;
}
.filters {
display: flex;
flex-wrap: wrap;
gap: 16px;
margin: 28px 0;
}
.filter-field {
width: 220px;
}
.filter-field label {
display: block;
margin-bottom: 6px;
font-weight: 600;
}
.kpis {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
margin-bottom: 24px;
}
.kpi-card,
.dashboard-panel {
background: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
}
.kpi-card {
padding: 20px;
}
.kpi-label {
display: block;
margin-bottom: 8px;
color: #64748b;
}
.kpi-card strong {
font-size: 1.75rem;
}
.dashboard-panel {
margin-bottom: 24px;
padding: 20px;
}
.dashboard-panel h2 {
margin-top: 0;
}
#revenueChart {
height: 350px;
}
#ordersGrid {
height: 420px;
}
@media (max-width: 700px) {
.kpis {
grid-template-columns: 1fr;
}
.filter-field {
width: 100%;
}
}
Giving the chart and grid explicit heights is particularly important in dashboard layouts so they have enough space to render useful content. The responsive rule also moves the KPI cards into a single column on smaller screens.
Now, if we run the application, we'll see the following layout:

Creating the Dashboard Data
Now we need some records to display.
For this tutorial, we'll generate fictional sales orders so the example remains self-contained:
function createOrders() {
const regions = ['North', 'South', 'East', 'West'];
const products = ['Monitor', 'Office Chair', 'Laptop'];
const statuses = ['Complete', 'Pending', 'Processing'];
const prices = [249, 399, 1299];
return Array.from({ length: 48 }, (_, index) => {
const productIndex = index % products.length;
const quantity = (index % 5) + 1;
return {
id: 1001 + index,
date: new Date(2026, index % 6, (index % 24) + 1),
region: regions[index % regions.length],
product: products[productIndex],
status: statuses[index % statuses.length],
quantity,
revenue: quantity * prices[productIndex]
};
});
}
const orders = createOrders();
Each record contains the fields our dashboard needs to filter, calculate metrics, chart revenue, and display order-level details.
In a production application, this array would more likely come from an API. The important part is that the client receives a consistent data model before binding it to the dashboard.
Managing Dashboard Data with CollectionView
We can now create the CollectionView:
const view = new CollectionView(orders);
CollectionView wraps a regular JavaScript array and exposes the resulting view through properties such as items. It also supports operations including filtering, sorting, grouping, paging, and current-item management. You can learn more using Wijmo's CollectionView demo.
For this dashboard, filtering is what's important.
Rather than maintaining separate filtered arrays for FlexGrid, FlexChart, and the KPIs, we'll keep a single filtered view and have the other parts of the interface work from it.
Adding the Dashboard Filters
The first filter lets users select a region, while the second limits the dashboard to an order status.
Create two ComboBox controls:
const regionFilter = new ComboBox('#regionFilter', {
itemsSource: [
'All Regions',
'North',
'South',
'East',
'West'
],
selectedIndex: 0,
isEditable: false
});
const statusFilter = new ComboBox('#statusFilter', {
itemsSource: [
'All Statuses',
'Complete',
'Pending',
'Processing'
],
selectedIndex: 0,
isEditable: false
});
ComboBox is designed around selecting values from a list and supports both strings and object-based data sources. You can try it out in this ComboBox control demo.
Next, define the CollectionView filter:
view.filter = item => {
const region = regionFilter.selectedItem;
const status = statusFilter.selectedItem;
const matchesRegion =
region === 'All Regions' || item.region === region;
const matchesStatus =
status === 'All Statuses' || item.status === status;
return matchesRegion && matchesStatus;
};
The filter callback returns true when a record belongs in the current view.
If the user leaves both controls on their default values, every record passes the filter. Choosing North, for example, limits the CollectionView to orders where region is North.
We'll connect the controls to the rest of the dashboard after creating the grid and chart.
Displaying the Detailed Data with FlexGrid
With our CollectionView ready, we can bind it directly to FlexGrid:
const grid = new FlexGrid('#ordersGrid', {
itemsSource: view,
autoGenerateColumns: false,
isReadOnly: true,
columns: [
{
binding: 'id',
header: 'Order ID',
width: 100
},
{
binding: 'date',
header: 'Order Date',
format: 'MMM d, yyyy',
width: 130
},
{
binding: 'region',
header: 'Region',
width: 110
},
{
binding: 'product',
header: 'Product',
width: '*'
},
{
binding: 'status',
header: 'Status',
width: 120
},
{
binding: 'quantity',
header: 'Quantity',
width: 100
},
{
binding: 'revenue',
header: 'Revenue',
format: 'c0',
width: 120
}
]
});
We've set autoGenerateColumns to false because this is a production-style dashboard where we know which fields users need. Explicit columns give us direct control over headers, ordering, widths, and formats.
FlexGrid's data-binding model supports CollectionView directly, so the grid represents the view rather than requiring us to create another filtered data array.

At this point, the grid displays all 48 records because our two filters are still set to their All options.
Turning the Current View into Chart Data
Our FlexGrid needs detailed order records, but our chart should answer a different question:
How is revenue changing from month to month?
That means we need to aggregate the current records before giving them to FlexChart.
Add this helper:
function buildMonthlyRevenue(items) {
const totals = new Map();
items.forEach(item => {
const year = item.date.getFullYear();
const month = item.date.getMonth();
const key = `${year}-${month}`;
if (!totals.has(key)) {
totals.set(key, {
month: new Date(year, month, 1),
revenue: 0
});
}
totals.get(key).revenue += item.revenue;
});
return Array.from(totals.values())
.sort((a, b) => a.month - b.month);
}
The function groups the filtered orders by month and sums their revenue.
Keeping this transformation separate from the chart configuration is intentional. FlexChart is responsible for visualizing the data; our application is responsible for deciding what a monthly revenue figure means.
Creating the FlexChart
Now create the chart:
const chart = new FlexChart('#revenueChart', {
chartType: ChartType.LineSymbols,
itemsSource: buildMonthlyRevenue(view.items),
bindingX: 'month',
axisX: {
format: 'MMM'
},
axisY: {
format: 'c0',
title: 'Revenue'
},
series: [
{
binding: 'revenue',
name: 'Revenue'
}
]
});
FlexChart's standard binding pattern uses itemsSource for the records, bindingX for the category or X value, and one or more series bindings for plotted values.
Here, each generated record looks roughly like this:
{
month: new Date(2026, 0, 1),
revenue: 15480
}
The month property drives the X axis, while the revenue property controls the series' vertical values.

A line chart works well in this case because our X values represent ordered points in time, and the main question is how revenue changes across those periods. You can explore more using Wijmo's FlexChart demo.
Calculating the KPI Cards
We can calculate the summary cards from exactly the same filtered items:
const currencyFormatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
maximumFractionDigits: 0
});
function updateKpis(items) {
const revenue = items.reduce(
(total, item) => total + item.revenue,
0
);
const orderCount = items.length;
const averageOrder =
orderCount > 0 ? revenue / orderCount : 0;
document.querySelector('#revenueKpi').textContent =
currencyFormatter.format(revenue);
document.querySelector('#ordersKpi').textContent =
orderCount.toLocaleString();
document.querySelector('#averageKpi').textContent =
currencyFormatter.format(averageOrder);
}
Notice that we're calculating the KPIs from the records in the current CollectionView rather than the original orders array.

That distinction is what makes them dashboard metrics instead of static page statistics.
Connecting the Dashboard
We now have all of the individual pieces. The last step is connecting them.
Create an updateDashboard function:
function updateDashboard() {
const items = view.items;
updateKpis(items);
chart.itemsSource = buildMonthlyRevenue(items);
}
Then, create the filter handler:
function applyFilters() {
view.refresh();
updateDashboard();
}
CollectionView's refresh() method recreates the current view using its active filtering, sorting, and grouping settings.
Finally, listen for changes from both ComboBox controls:
regionFilter.selectedIndexChanged.addHandler(applyFilters);
statusFilter.selectedIndexChanged.addHandler(applyFilters);
updateDashboard();
Now the dashboard is connected.
When a user selects a different region or status:
- The ComboBox raises its selection event.
applyFilters()refreshes CollectionView.- FlexGrid reflects the newly filtered view.
updateDashboard()reads the currentview.items.- The KPI totals are recalculated.
- The monthly chart data is rebuilt.
- The existing FlexChart receives its new
itemsSource.
We're updating the existing controls rather than destroying and recreating them each time the dashboard state changes.
Running the Application
Start the Vite development server:
npm run dev
Open the local address Vite provides in your browser.

The dashboard should initially show all of the generated sales records. You'll see three KPI cards at the top, a monthly revenue chart in the center, and FlexGrid containing the individual orders below it.
Now select a region from the first ComboBox.
The grid should immediately narrow to that region, while the KPI cards and revenue chart recalculate from the same filtered orders. Changing the status filter narrows the data again.
Combining the filters gives us behavior such as:
Region: All
Status: Complete
Every dashboard component now describes the same subset of data.
Ready to try it out? Download Wijmo Today!
Conclusion
In this article, we built a dynamic JavaScript dashboard using Wijmo FlexGrid, FlexChart, ComboBox, and CollectionView.
We started by creating a shared CollectionView around our order data. Two ComboBox controls then changed the active filter, while FlexGrid displayed the resulting detailed records. From that same filtered collection, we calculated KPI values and generated a monthly revenue dataset for FlexChart.
The key idea is to keep the dashboard centered on shared application state. The grid is responsible for detailed tabular interaction; the chart visualizes a useful trend; the input controls manage user choices; and CollectionView connects those pieces through a consistent data view.
From here, the same pattern can be adapted to inventory monitoring, financial reporting, project management, customer analytics, operational metrics, or other line-of-business applications where users need both a high-level summary and access to the records behind it.
Happy coding!