Skip to content

Commit be087d8

Browse files
committed
fix: convert to class component & minor changes
1 parent 2aa59bc commit be087d8

8 files changed

Lines changed: 397 additions & 313 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "Node.js",
3+
"image": "mcr.microsoft.com/devcontainers/javascript-node:22"
4+
}
Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,56 @@
1-
# Query Builder Overview Sample
1+
<!-- NOTE: do not change this file because it's auto re-generated from template: -->
2+
<!-- https://github.com/IgniteUI/igniteui-react-examples/tree/vnext/templates/sample/ReadMe.md -->
23

3-
This sample demonstrates the Query Builder component with Grid integration.
4+
This folder contains implementation of React application with example of Overview feature using [Query Builder](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html) component.
45

5-
## Features
66

7-
- Query Builder with entity selection (Customers/Orders)
8-
- Dynamic field management based on selected entity
9-
- Expression tree construction with filtering logic
10-
- Grid integration with auto-generated columns
11-
- API integration with Northwind backend
12-
- Dynamic column visibility based on return fields
13-
- Loading state handling
7+
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
8+
<body>
9+
<a target="_blank" href="https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html" rel="noopener noreferrer">
10+
<img height="40px" style="border-radius: 0rem" alt="View Docs" src="https://dl.infragistics.com/x/img/browsers/button-docs.png"/>
11+
</a>
12+
<a target="_blank" href="./src/index.tsx" rel="noopener noreferrer">
13+
<img height="40px" style="border-radius: 0rem; max-width: 100%;" alt="View Code" src="https://dl.infragistics.com/x/img/browsers/button-code.png"/>
14+
</a>
15+
<a target="_blank" href="https://www.infragistics.com/react-demos/samples/interactions/query-builder/overview" rel="noopener noreferrer">
16+
<img height="40px" style="border-radius: 0rem; max-width: 100%;" alt="Run Sample" src="https://dl.infragistics.com/x/img/browsers/button-run.png"/>
17+
</a>
18+
<a target="_blank" href="https://codesandbox.io/s/github/IgniteUI/igniteui-react-examples/tree/master/samples/interactions/query-builder/overview?fontsize=14&hidenavigation=1&theme=dark&view=preview&file=/src/index.tsx" rel="noopener noreferrer">
19+
<img height="40px" style="border-radius: 0rem; max-width: 100%;" alt="Run Sample" src="https://dl.infragistics.com/x/img/browsers/button-sandbox.png"/>
20+
</a>
21+
</body>
22+
</html>
1423

15-
## Running the Sample
24+
## Branches
1625

17-
```bash
18-
npm install
19-
npm start
26+
> **_NOTE:_** You should use [master](https://github.com/IgniteUI/igniteui-react-examples/tree/master) branch of this repository if you want to run samples on your computer. Use the [vnext](https://github.com/IgniteUI/igniteui-react-examples/tree/vnext) branch only when you want to contribute new samples to this repository.
27+
28+
## Instructions
29+
30+
Follow these instructions to run this example:
31+
32+
33+
```
34+
git clone https://github.com/IgniteUI/igniteui-react-examples.git
35+
git checkout master
36+
cd ./igniteui-react-examples
37+
cd ./samples/interactions/query-builder/overview
38+
```
39+
40+
open above folder in VS Code or type:
41+
```
42+
code .
43+
```
44+
45+
In terminal window, run:
46+
```
47+
npm install --legacy-peer-deps
48+
npm run-script start
2049
```
2150

22-
## API Integration
51+
Then open http://localhost:4200/ in your browser
2352

24-
The sample connects to the Northwind Query Builder API:
25-
- Endpoint: `https://data-northwind.indigo.design/QueryBuilder/ExecuteQuery`
26-
- Method: POST
27-
- Body: Expression tree JSON
2853

29-
## Components Used
54+
## Learn More
3055

31-
- `IgcQueryBuilderComponent` - Main query builder component
32-
- `IgcGridComponent` - Data grid for displaying results
33-
- `IgcFilteringExpressionsTree` - Expression tree structure
56+
To learn more about **Ignite UI for React** components, check out the [React documentation](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html).

samples/interactions/query-builder/overview/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "example-ignite-ui-react",
3-
"description": "This project provides example of using Ignite UI for React components",
2+
"name": "react-query-builder-overview",
3+
"description": "This project provides example of Query Builder Overview using Ignite UI for React components",
44
"author": "Infragistics",
55
"version": "1.4.0",
66
"license": "",
Lines changed: 143 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect, useRef } from 'react';
1+
import React from 'react';
22
import ReactDOM from 'react-dom/client';
33
import './index.css';
44

@@ -39,39 +39,27 @@ interface Entity {
3939
fields: Field[];
4040
}
4141

42-
const QueryBuilderOverview: React.FC = () => {
43-
const queryBuilderRef = useRef<IgcQueryBuilderComponent>(null);
44-
const gridRef = useRef<IgcGridComponent>(null);
45-
const [expressionTree, setExpressionTree] = useState<IgcExpressionTree | null>(null);
46-
47-
// Define field structures
48-
const customersFields: Field[] = [
49-
{ field: 'customerId', dataType: 'string' },
50-
{ field: 'companyName', dataType: 'string' },
51-
{ field: 'contactName', dataType: 'string' },
52-
{ field: 'contactTitle', dataType: 'string' }
53-
];
54-
55-
const ordersFields: Field[] = [
56-
{ field: 'orderId', dataType: 'number' },
57-
{ field: 'customerId', dataType: 'string' },
58-
{ field: 'employeeId', dataType: 'number' },
59-
{ field: 'shipperId', dataType: 'number' },
60-
{ field: 'orderDate', dataType: 'date' },
61-
{ field: 'requiredDate', dataType: 'date' },
62-
{ field: 'shipVia', dataType: 'string' },
63-
{ field: 'freight', dataType: 'number' },
64-
{ field: 'shipName', dataType: 'string' },
65-
{ field: 'completed', dataType: 'boolean' }
66-
];
67-
68-
const entities: Entity[] = [
69-
{ name: 'Customers', fields: customersFields },
70-
{ name: 'Orders', fields: ordersFields }
71-
];
72-
73-
// Initialize expression tree
74-
useEffect(() => {
42+
interface SampleState {
43+
expressionTree: IgcExpressionTree | null;
44+
}
45+
46+
export default class Sample extends React.Component<any, SampleState> {
47+
private queryBuilderRef: React.RefObject<IgcQueryBuilderComponent>;
48+
private gridRef: React.RefObject<IgcGridComponent>;
49+
50+
constructor(props: any) {
51+
super(props);
52+
53+
this.queryBuilderRef = React.createRef();
54+
this.gridRef = React.createRef();
55+
56+
this.state = {
57+
expressionTree: null
58+
};
59+
}
60+
61+
componentDidMount() {
62+
// Initialize expression tree
7563
const tree = new IgcFilteringExpressionsTree();
7664
tree.operator = FilteringLogic.And;
7765
tree.entity = 'Orders';
@@ -88,46 +76,89 @@ const QueryBuilderOverview: React.FC = () => {
8876
'completed'
8977
];
9078

91-
setExpressionTree(tree);
92-
}, []);
79+
this.setState({ expressionTree: tree });
9380

94-
// Set up query builder
95-
useEffect(() => {
96-
if (!queryBuilderRef.current || !expressionTree) return undefined;
81+
// Set up query builder
82+
if (this.queryBuilderRef.current && tree) {
83+
const queryBuilder = this.queryBuilderRef.current;
84+
queryBuilder.entities = this.entities as any;
85+
queryBuilder.expressionTree = tree;
9786

98-
const queryBuilder = queryBuilderRef.current;
99-
queryBuilder.entities = entities as any;
100-
queryBuilder.expressionTree = expressionTree;
87+
queryBuilder.addEventListener('expressionTreeChange', this.handleExpressionTreeChange);
88+
}
10189

102-
const handleExpressionTreeChange = (event: CustomEvent<IgcExpressionTree>) => {
103-
setExpressionTree(event.detail);
104-
};
90+
// Set up grid
91+
if (this.gridRef.current) {
92+
const grid = this.gridRef.current;
93+
grid.height = '420px';
94+
grid.autoGenerate = true;
95+
}
96+
}
10597

106-
queryBuilder.addEventListener('expressionTreeChange', handleExpressionTreeChange as EventListener);
98+
componentDidUpdate(prevProps: any, prevState: any) {
99+
// Fetch data when expression tree changes
100+
if (prevState.expressionTree !== this.state.expressionTree && this.state.expressionTree) {
101+
this.fetchData();
102+
}
107103

108-
return () => {
109-
queryBuilder.removeEventListener('expressionTreeChange', handleExpressionTreeChange as EventListener);
110-
};
111-
}, [expressionTree?.entity]); // Only re-run if entity changes
112-
113-
// Set up grid
114-
useEffect(() => {
115-
if (!gridRef.current) return undefined;
116-
117-
const grid = gridRef.current;
118-
grid.height = '420px';
119-
grid.autoGenerate = true;
120-
}, []);
121-
122-
// Calculate which columns should be visible based on returnFields
123-
const calculateColumnsInView = () => {
124-
if (!gridRef.current || !expressionTree) return;
125-
126-
const grid = gridRef.current;
104+
// Update query builder if expression tree changed
105+
if (this.queryBuilderRef.current && this.state.expressionTree &&
106+
prevState.expressionTree !== this.state.expressionTree) {
107+
const queryBuilder = this.queryBuilderRef.current;
108+
queryBuilder.expressionTree = this.state.expressionTree;
109+
}
110+
}
111+
112+
componentWillUnmount() {
113+
if (this.queryBuilderRef.current) {
114+
this.queryBuilderRef.current.removeEventListener('expressionTreeChange', this.handleExpressionTreeChange);
115+
}
116+
}
117+
118+
private handleExpressionTreeChange = (event: CustomEvent<IgcExpressionTree>) => {
119+
this.setState({ expressionTree: event.detail });
120+
};
121+
122+
private get customersFields(): Field[] {
123+
return [
124+
{ field: 'customerId', dataType: 'string' },
125+
{ field: 'companyName', dataType: 'string' },
126+
{ field: 'contactName', dataType: 'string' },
127+
{ field: 'contactTitle', dataType: 'string' }
128+
];
129+
}
130+
131+
private get ordersFields(): Field[] {
132+
return [
133+
{ field: 'orderId', dataType: 'number' },
134+
{ field: 'customerId', dataType: 'string' },
135+
{ field: 'employeeId', dataType: 'number' },
136+
{ field: 'shipperId', dataType: 'number' },
137+
{ field: 'orderDate', dataType: 'date' },
138+
{ field: 'requiredDate', dataType: 'date' },
139+
{ field: 'shipVia', dataType: 'string' },
140+
{ field: 'freight', dataType: 'number' },
141+
{ field: 'shipName', dataType: 'string' },
142+
{ field: 'completed', dataType: 'boolean' }
143+
];
144+
}
145+
146+
private get entities(): Entity[] {
147+
return [
148+
{ name: 'Customers', fields: this.customersFields },
149+
{ name: 'Orders', fields: this.ordersFields }
150+
];
151+
}
152+
153+
private calculateColumnsInView = () => {
154+
if (!this.gridRef.current || !this.state.expressionTree) return;
155+
156+
const grid = this.gridRef.current;
157+
const expressionTree = this.state.expressionTree;
127158
const returnFields = expressionTree.returnFields ?? [];
128159

129160
if (returnFields.length === 0 || returnFields[0] === '*') {
130-
const selectedEntity = entities.find(e => e.name === expressionTree.entity);
161+
const selectedEntity = this.entities.find(e => e.name === expressionTree.entity);
131162
const selectedEntityFields = (selectedEntity?.fields ?? []).map(f => f.field);
132163

133164
grid.columns.forEach(column => {
@@ -140,58 +171,55 @@ const QueryBuilderOverview: React.FC = () => {
140171
}
141172
};
142173

143-
// Fetch data when expression tree changes
144-
useEffect(() => {
145-
if (!expressionTree || !gridRef.current) return;
146-
147-
const fetchData = async () => {
148-
const grid = gridRef.current;
149-
if (!grid) return;
150-
151-
grid.isLoading = true;
152-
153-
try {
154-
const response = await fetch(`${API_ENDPOINT}/QueryBuilder/ExecuteQuery`, {
155-
method: 'POST',
156-
headers: { 'Content-Type': 'application/json' },
157-
body: JSON.stringify(expressionTree)
158-
});
159-
160-
if (!response.ok) {
161-
throw new Error(`ExecuteQuery failed: ${response.status} ${response.statusText}`);
162-
}
163-
164-
const json = await response.json();
165-
const data = (Object.values(json)[0] as any[]) ?? [];
166-
grid.data = data;
167-
168-
// Calculate column visibility after data loads
169-
await new Promise(resolve => requestAnimationFrame(() => resolve(null)));
170-
calculateColumnsInView();
171-
} catch (err) {
172-
console.error(err);
173-
grid.data = [];
174-
} finally {
175-
grid.isLoading = false;
174+
private async fetchData() {
175+
const grid = this.gridRef.current;
176+
const expressionTree = this.state.expressionTree;
177+
178+
if (!grid || !expressionTree) return;
179+
180+
grid.isLoading = true;
181+
182+
try {
183+
const response = await fetch(`${API_ENDPOINT}/QueryBuilder/ExecuteQuery`, {
184+
method: 'POST',
185+
headers: { 'Content-Type': 'application/json' },
186+
body: JSON.stringify(expressionTree)
187+
});
188+
189+
if (!response.ok) {
190+
throw new Error(`ExecuteQuery failed: ${response.status} ${response.statusText}`);
176191
}
177-
};
178192

179-
fetchData();
180-
}, [expressionTree]);
193+
const json = await response.json();
194+
const data = (Object.values(json)[0] as any[]) ?? [];
195+
grid.data = data;
196+
197+
// Calculate column visibility after data loads
198+
await new Promise(resolve => requestAnimationFrame(() => resolve(null)));
199+
this.calculateColumnsInView();
200+
} catch (err) {
201+
console.error(err);
202+
grid.data = [];
203+
} finally {
204+
grid.isLoading = false;
205+
}
206+
}
181207

182-
return (
183-
<div className="container sample ig-typography">
184-
<div className="wrapper">
185-
<igc-query-builder ref={queryBuilderRef} id="queryBuilder"></igc-query-builder>
186-
187-
<div className="output-area">
188-
<igc-grid ref={gridRef} id="grid"></igc-grid>
208+
public render(): JSX.Element {
209+
return (
210+
<div className="container sample ig-typography">
211+
<div className="wrapper">
212+
<igc-query-builder ref={this.queryBuilderRef} id="queryBuilder"></igc-query-builder>
213+
214+
<div className="output-area">
215+
<igc-grid ref={this.gridRef} id="grid"></igc-grid>
216+
</div>
189217
</div>
190218
</div>
191-
</div>
192-
);
193-
};
219+
);
220+
}
221+
}
194222

195-
// Rendering component in the React DOM
196-
const root = ReactDOM.createRoot(document.getElementById('root')!);
197-
root.render(<QueryBuilderOverview />);
223+
// rendering above component in the React DOM
224+
const root = ReactDOM.createRoot(document.getElementById('root'));
225+
root.render(<Sample/>);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "Node.js",
3+
"image": "mcr.microsoft.com/devcontainers/javascript-node:22"
4+
}

0 commit comments

Comments
 (0)