diff --git a/samples/grids/grid/filtering-options.7z b/samples/grids/grid/filtering-options.7z
new file mode 100644
index 0000000000..323f415422
Binary files /dev/null and b/samples/grids/grid/filtering-options.7z differ
diff --git a/samples/grids/grid/filtering-remote/.eslintrc.js b/samples/grids/grid/filtering-remote/.eslintrc.js
new file mode 100644
index 0000000000..7168b71441
--- /dev/null
+++ b/samples/grids/grid/filtering-remote/.eslintrc.js
@@ -0,0 +1,78 @@
+// https://www.robertcooper.me/using-eslint-and-prettier-in-a-typescript-project
+module.exports = {
+ parser: "@typescript-eslint/parser", // Specifies the ESLint parser
+ parserOptions: {
+ ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
+ sourceType: "module", // Allows for the use of imports
+ ecmaFeatures: {
+ jsx: true // Allows for the parsing of JSX
+ }
+ },
+ settings: {
+ react: {
+ version: "999.999.999" // Tells eslint-plugin-react to automatically detect the version of React to use
+ }
+ },
+ extends: [
+ "eslint:recommended",
+ "plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react
+ "plugin:@typescript-eslint/recommended" // Uses the recommended rules from @typescript-eslint/eslint-plugin
+ ],
+ rules: {
+ // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
+ "default-case": "off",
+ "jsx-a11y/alt-text": "off",
+ "jsx-a11y/iframe-has-title": "off",
+ "no-undef": "off",
+ "no-unused-vars": "off",
+ "no-extend-native": "off",
+ "no-throw-literal": "off",
+ "no-useless-concat": "off",
+ "no-mixed-operators": "off",
+ "no-prototype-builtins": "off",
+ "no-mixed-spaces-and-tabs": 0,
+ "prefer-const": "off",
+ "prefer-rest-params": "off",
+ "@typescript-eslint/no-unused-vars": "off",
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-inferrable-types": "off",
+ "@typescript-eslint/no-useless-constructor": "off",
+ "@typescript-eslint/no-use-before-define": "off",
+ "@typescript-eslint/no-non-null-assertion": "off",
+ "@typescript-eslint/interface-name-prefix": "off",
+ "@typescript-eslint/prefer-namespace-keyword": "off",
+ "@typescript-eslint/explicit-function-return-type": "off",
+ "@typescript-eslint/explicit-module-boundary-types": "off"
+ },
+ "overrides": [
+ {
+ "files": ["*.ts", "*.tsx"],
+ "rules": {
+ "default-case": "off",
+ "jsx-a11y/alt-text": "off",
+ "jsx-a11y/iframe-has-title": "off",
+ "no-var": "off",
+ "no-undef": "off",
+ "no-unused-vars": "off",
+ "no-extend-native": "off",
+ "no-throw-literal": "off",
+ "no-useless-concat": "off",
+ "no-mixed-operators": "off",
+ "no-mixed-spaces-and-tabs": 0,
+ "no-prototype-builtins": "off",
+ "prefer-const": "off",
+ "prefer-rest-params": "off",
+ "@typescript-eslint/no-unused-vars": "off",
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-inferrable-types": "off",
+ "@typescript-eslint/no-useless-constructor": "off",
+ "@typescript-eslint/no-use-before-define": "off",
+ "@typescript-eslint/no-non-null-assertion": "off",
+ "@typescript-eslint/interface-name-prefix": "off",
+ "@typescript-eslint/prefer-namespace-keyword": "off",
+ "@typescript-eslint/explicit-function-return-type": "off",
+ "@typescript-eslint/explicit-module-boundary-types": "off"
+ }
+ }
+ ]
+ };
\ No newline at end of file
diff --git a/samples/grids/grid/filtering-remote/ReadMe.md b/samples/grids/grid/filtering-remote/ReadMe.md
new file mode 100644
index 0000000000..6d7315e7ac
--- /dev/null
+++ b/samples/grids/grid/filtering-remote/ReadMe.md
@@ -0,0 +1,56 @@
+
+
+
+This folder contains implementation of React application with example of Filtering Options feature using [Grid](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html) component.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Branches
+
+> **_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.
+
+## Instructions
+
+Follow these instructions to run this example:
+
+
+```
+git clone https://github.com/IgniteUI/igniteui-react-examples.git
+git checkout master
+cd ./igniteui-react-examples
+cd ./samples/grids/grid/filtering-options
+```
+
+open above folder in VS Code or type:
+```
+code .
+```
+
+In terminal window, run:
+```
+npm install --legacy-peer-deps
+npm run-script start
+```
+
+Then open http://localhost:4200/ in your browser
+
+
+## Learn More
+
+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).
diff --git a/samples/grids/grid/filtering-remote/package.json b/samples/grids/grid/filtering-remote/package.json
new file mode 100644
index 0000000000..02dbc47aea
--- /dev/null
+++ b/samples/grids/grid/filtering-remote/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "example-ignite-ui-react",
+ "description": "This project provides example of using Ignite UI for React components",
+ "author": "Infragistics",
+ "version": "1.4.0",
+ "license": "",
+ "homepage": ".",
+ "private": true,
+ "scripts": {
+ "start": "set PORT=4200 && react-scripts --max_old_space_size=10240 start",
+ "build": "react-scripts --max_old_space_size=10240 build ",
+ "test": "react-scripts test --env=jsdom",
+ "eject": "react-scripts eject",
+ "lint": "eslint ./src/**/*.{ts,tsx}"
+ },
+ "dependencies": {
+ "igniteui-dockmanager": "1.16.1",
+ "igniteui-react": "19.0.2",
+ "igniteui-react-core": "19.0.0",
+ "igniteui-react-grids": "19.0.2",
+ "igniteui-react-inputs": "19.0.0",
+ "igniteui-react-layouts": "19.0.0",
+ "igniteui-webcomponents": "6.0.0",
+ "lit-html": "^3.2.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-scripts": "^5.0.1",
+ "tslib": "^2.4.0"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.2.0",
+ "@types/node": "^18.11.7",
+ "@types/react": "^18.0.24",
+ "@types/react-dom": "^18.0.8",
+ "eslint": "^8.33.0",
+ "eslint-config-react": "^1.1.7",
+ "eslint-plugin-react": "^7.20.0",
+ "react-app-rewired": "^2.2.1",
+ "typescript": "^4.8.4",
+ "worker-loader": "^3.0.8"
+ },
+ "browserslist": [
+ ">0.2%",
+ "not dead",
+ "not ie <= 11",
+ "not op_mini all"
+ ]
+}
diff --git a/samples/grids/grid/filtering-remote/public/index.html b/samples/grids/grid/filtering-remote/public/index.html
new file mode 100644
index 0000000000..e2d3265576
--- /dev/null
+++ b/samples/grids/grid/filtering-remote/public/index.html
@@ -0,0 +1,11 @@
+
+
+
+ Sample | Ignite UI | React | infragistics
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/grids/grid/filtering-remote/sandbox.config.json b/samples/grids/grid/filtering-remote/sandbox.config.json
new file mode 100644
index 0000000000..07f53508eb
--- /dev/null
+++ b/samples/grids/grid/filtering-remote/sandbox.config.json
@@ -0,0 +1,5 @@
+{
+ "infiniteLoopProtection": false,
+ "hardReloadOnChange": false,
+ "view": "browser"
+}
\ No newline at end of file
diff --git a/samples/grids/grid/filtering-remote/src/NwindData.json b/samples/grids/grid/filtering-remote/src/NwindData.json
new file mode 100644
index 0000000000..c00b03ec8d
--- /dev/null
+++ b/samples/grids/grid/filtering-remote/src/NwindData.json
@@ -0,0 +1,458 @@
+[
+ {
+ "ProductID": 1,
+ "ProductName": "Chai",
+ "SupplierID": 1,
+ "CategoryID": 1,
+ "QuantityPerUnit": "10 boxes x 20 bags",
+ "UnitPrice": 18,
+ "UnitsInStock": 39,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 10,
+ "Discontinued": false,
+ "OrderDate": "2012-02-12",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Fun-Tasty Co.",
+ "LastInventory": "2018-06-12"
+ },
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 2,
+ "ProductName": "Chang",
+ "SupplierID": 1,
+ "CategoryID": 1,
+ "QuantityPerUnit": "24 - 12 oz bottles",
+ "UnitPrice": 19,
+ "UnitsInStock": 17,
+ "UnitsOnOrder": 40,
+ "ReorderLevel": 25,
+ "Discontinued": true,
+ "OrderDate": "2003-03-17",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ },
+ {
+ "ProductID": 3,
+ "ProductName": "Aniseed Syrup",
+ "SupplierID": 1,
+ "CategoryID": 2,
+ "QuantityPerUnit": "12 - 550 ml bottles",
+ "UnitPrice": 10,
+ "UnitsInStock": 13,
+ "UnitsOnOrder": 70,
+ "ReorderLevel": 25,
+ "Discontinued": false,
+ "OrderDate": "2006-03-17",
+ "Rating": 3,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ },
+ {
+ "Shop": "Street Market",
+ "LastInventory": "2018-12-12"
+ },
+ {
+ "Shop": "24/7 Market",
+ "LastInventory": "2018-11-11"
+ }
+ ]
+ },
+ {
+ "ProductID": 4,
+ "ProductName": "Chef Antons Cajun Seasoning",
+ "SupplierID": 2,
+ "CategoryID": 2,
+ "QuantityPerUnit": "48 - 6 oz jars",
+ "UnitPrice": 22,
+ "UnitsInStock": 53,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2016-03-17",
+ "Rating": 3,
+ "Locations": [
+ {
+ "Shop": "Fun-Tasty Co.",
+ "LastInventory": "2018-06-12"
+ },
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ },
+ {
+ "Shop": "Street Market",
+ "LastInventory": "2018-12-12"
+ }
+ ]
+ },
+ {
+ "ProductID": 5,
+ "ProductName": "Chef Antons Gumbo Mix",
+ "SupplierID": 2,
+ "CategoryID": 2,
+ "QuantityPerUnit": "36 boxes",
+ "UnitPrice": 21.35,
+ "UnitsInStock": 0,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": true,
+ "OrderDate": "2011-11-11",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ },
+ {
+ "ProductID": 6,
+ "ProductName": "Grandmas Boysenberry Spread",
+ "SupplierID": 3,
+ "CategoryID": 2,
+ "QuantityPerUnit": "12 - 8 oz jars",
+ "UnitPrice": 25,
+ "UnitsInStock": 0,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 25,
+ "Discontinued": false,
+ "OrderDate": "2017-12-17",
+ "Rating": 4,
+ "Locations": [
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ },
+ {
+ "ProductID": 7,
+ "ProductName": "Uncle Bobs Organic Dried Pears",
+ "SupplierID": 3,
+ "CategoryID": 7,
+ "QuantityPerUnit": "12 - 1 lb pkgs.",
+ "UnitPrice": 30,
+ "UnitsInStock": 150,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 10,
+ "Discontinued": false,
+ "OrderDate": "2016-07-17",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Fun-Tasty Co.",
+ "LastInventory": "2018-06-12"
+ },
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ },
+ {
+ "Shop": "Street Market",
+ "LastInventory": "2018-12-12"
+ }
+ ]
+ },
+ {
+ "ProductID": 8,
+ "ProductName": "Northwoods Cranberry Sauce",
+ "SupplierID": 3,
+ "CategoryID": 2,
+ "QuantityPerUnit": "12 - 12 oz jars",
+ "UnitPrice": 40,
+ "UnitsInStock": 6,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2018-01-17",
+ "Rating": 4,
+ "Locations": [
+ {
+ "Shop": "Fun-Tasty Co.",
+ "LastInventory": "2018-06-12"
+ },
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 9,
+ "ProductName": "Mishi Kobe Niku",
+ "SupplierID": 4,
+ "CategoryID": 6,
+ "QuantityPerUnit": "18 - 500 g pkgs.",
+ "UnitPrice": 97,
+ "UnitsInStock": 29,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": true,
+ "OrderDate": "2010-02-17",
+ "Rating": 4,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 10,
+ "ProductName": "Ikura",
+ "SupplierID": 4,
+ "CategoryID": 8,
+ "QuantityPerUnit": "12 - 200 ml jars",
+ "UnitPrice": 31,
+ "UnitsInStock": 31,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2008-05-17",
+ "Rating": 3,
+ "Locations": [
+ {
+ "Shop": "Wall Market",
+ "LastInventory": "2018-12-06"
+ }
+ ]
+ },
+ {
+ "ProductID": 11,
+ "ProductName": "Queso Cabrales",
+ "SupplierID": 5,
+ "CategoryID": 4,
+ "QuantityPerUnit": "1 kg pkg.",
+ "UnitPrice": 21,
+ "UnitsInStock": 22,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 30,
+ "Discontinued": false,
+ "OrderDate": "2009-01-17",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Fun-Tasty Co.",
+ "LastInventory": "2018-06-12"
+ },
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 12,
+ "ProductName": "Queso Manchego La Pastora",
+ "SupplierID": 5,
+ "CategoryID": 4,
+ "QuantityPerUnit": "10 - 500 g pkgs.",
+ "UnitPrice": 38,
+ "UnitsInStock": 86,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2015-11-17",
+ "Rating": 3,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 13,
+ "ProductName": "Konbu",
+ "SupplierID": 6,
+ "CategoryID": 8,
+ "QuantityPerUnit": "2 kg box",
+ "UnitPrice": 6,
+ "UnitsInStock": 24,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 5,
+ "Discontinued": false,
+ "OrderDate": "2015-03-17",
+ "Rating": 2,
+ "Locations": [
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ },
+ {
+ "ProductID": 14,
+ "ProductName": "Tofu",
+ "SupplierID": 6,
+ "CategoryID": 7,
+ "QuantityPerUnit": "40 - 100 g pkgs.",
+ "UnitPrice": 23.25,
+ "UnitsInStock": 35,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2017-06-17",
+ "Rating": 4,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ },
+ {
+ "Shop": "Street Market",
+ "LastInventory": "2018-12-12"
+ }
+ ]
+ },
+ {
+ "ProductID": 15,
+ "ProductName": "Genen Shouyu",
+ "SupplierID": 6,
+ "CategoryID": 2,
+ "QuantityPerUnit": "24 - 250 ml bottles",
+ "UnitPrice": 15.5,
+ "UnitsInStock": 39,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 5,
+ "Discontinued": false,
+ "OrderDate": "2014-03-17",
+ "Rating": 4,
+ "Locations": [
+ {
+ "Shop": "Local Market",
+ "LastInventory": "2018-07-03"
+ },
+ {
+ "Shop": "Wall Market",
+ "LastInventory": "2018-12-06"
+ }
+ ]
+ },
+ {
+ "ProductID": 16,
+ "ProductName": "Pavlova",
+ "SupplierID": 7,
+ "CategoryID": 3,
+ "QuantityPerUnit": "32 - 500 g boxes",
+ "UnitPrice": 17.45,
+ "UnitsInStock": 29,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 10,
+ "Discontinued": false,
+ "OrderDate": "2018-03-28",
+ "Rating": 2,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ },
+ {
+ "Shop": "Street Market",
+ "LastInventory": "2018-12-12"
+ },
+ {
+ "Shop": "24/7 Market",
+ "LastInventory": "2018-11-11"
+ }
+ ]
+ },
+ {
+ "ProductID": 17,
+ "ProductName": "Alice Mutton",
+ "SupplierID": 7,
+ "CategoryID": 6,
+ "QuantityPerUnit": "20 - 1 kg tins",
+ "UnitPrice": 39,
+ "UnitsInStock": 0,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": true,
+ "OrderDate": "2015-08-17",
+ "Rating": 2,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 18,
+ "ProductName": "Carnarvon Tigers",
+ "SupplierID": 7,
+ "CategoryID": 8,
+ "QuantityPerUnit": "16 kg pkg.",
+ "UnitPrice": 62.5,
+ "UnitsInStock": 42,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2005-09-27",
+ "Rating": 2,
+ "Locations": [
+ {
+ "Shop": "24/7 Market",
+ "LastInventory": "2018-11-11"
+ },
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ },
+ {
+ "ProductID": 19,
+ "ProductName": "Teatime Chocolate Biscuits",
+ "SupplierID": 8,
+ "CategoryID": 3,
+ "QuantityPerUnit": "",
+ "UnitPrice": 9.2,
+ "UnitsInStock": 25,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 5,
+ "Discontinued": false,
+ "OrderDate": "2001-03-17",
+ "Rating": 2,
+ "Locations": [
+ {
+ "Shop": "Local Market",
+ "LastInventory": "2018-07-03"
+ }
+ ]
+ },
+ {
+ "ProductID": 20,
+ "ProductName": "Sir Rodneys Marmalade",
+ "SupplierID": 8,
+ "CategoryID": 3,
+ "QuantityPerUnit": "4 - 100 ml jars",
+ "UnitPrice": 4.5,
+ "UnitsInStock": 40,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2005-03-17",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/samples/grids/grid/filtering-remote/src/RemoteService.ts b/samples/grids/grid/filtering-remote/src/RemoteService.ts
new file mode 100644
index 0000000000..1fc3ae35f8
--- /dev/null
+++ b/samples/grids/grid/filtering-remote/src/RemoteService.ts
@@ -0,0 +1,143 @@
+const DATA_URL = 'https://services.odata.org/V4/Northwind/Northwind.svc/Products';
+const EMPTY_STRING = '';
+const NULL_VALUE: null = null;
+
+export enum FILTER_OPERATION {
+ CONTAINS = 'contains',
+ STARTS_WITH = 'startswith',
+ ENDS_WITH = 'endswith',
+ EQUALS = 'eq',
+ DOES_NOT_EQUAL = 'ne',
+ GREATER_THAN = 'gt',
+ LESS_THAN = 'lt',
+ LESS_THAN_EQUAL = 'le',
+ GREATER_THAN_EQUAL = 'ge'
+}
+
+export class RemoteService {
+ public static getData(
+ filteringArgs?: any,
+ sortingArgs?: any
+ ): Promise {
+ const url = this.buildDataUrl(filteringArgs, sortingArgs);
+ return fetch(url)
+ .then((res) => res.json())
+ .then((data) => data.value); // only return the actual items
+ }
+
+ private static buildDataUrl(filteringArgs: any, sortingArgs: any): string {
+ let baseQuery = `${DATA_URL}?$count=true&$top=1000`;
+ const parts: string[] = [];
+
+ const sortExpr = this.buildSortExpression(sortingArgs);
+ if (sortExpr) parts.push(sortExpr);
+
+ const filterExpr = this.buildFilterExpression(filteringArgs);
+ if (filterExpr) parts.push(filterExpr);
+
+ return `${baseQuery}&${parts.join('&')}`;
+ }
+
+ private static buildFilterExpression(filteringArgs: any): string {
+ if (!filteringArgs?.filteringOperands?.length) return '';
+
+ const expression = this.buildAdvancedFilterExpression(
+ filteringArgs.filteringOperands,
+ filteringArgs.operator
+ );
+
+ return expression ? `$filter=${expression}` : '';
+ }
+
+ private static buildAdvancedFilterExpression(operands: any[], operator: any): string {
+ let filterExpression = '';
+
+ operands.forEach((operand, index) => {
+ if (operand.filteringOperands) {
+ const subExpr = this.buildAdvancedFilterExpression(
+ operand.filteringOperands,
+ operand.operator
+ );
+ if (subExpr) {
+ if (index > 0) filterExpression += ` ${this.getFilteringLogic(operator)} `;
+ filterExpression += subExpr;
+ }
+ return;
+ }
+
+ const { fieldName, searchVal, condition } = operand;
+ if (searchVal === undefined || condition === undefined) return;
+
+ const isNumber = typeof searchVal === 'number';
+ const filterValue = isNumber ? searchVal : `'${searchVal}'`;
+ let filterPart = '';
+
+ if (index > 0) filterExpression += ` ${this.getFilteringLogic(operator)} `;
+
+ switch (condition.name) {
+ case 'contains':
+ filterPart = `contains(${fieldName}, ${filterValue})`;
+ break;
+ case 'startsWith':
+ filterPart = `startswith(${fieldName}, ${filterValue})`;
+ break;
+ case 'endsWith':
+ filterPart = `endswith(${fieldName}, ${filterValue})`;
+ break;
+ case 'equals':
+ filterPart = `${fieldName} eq ${filterValue}`;
+ break;
+ case 'doesNotEqual':
+ filterPart = `${fieldName} ne ${filterValue}`;
+ break;
+ case 'greaterThan':
+ filterPart = `${fieldName} gt ${filterValue}`;
+ break;
+ case 'greaterThanOrEqualTo':
+ filterPart = `${fieldName} ge ${filterValue}`;
+ break;
+ case 'lessThan':
+ filterPart = `${fieldName} lt ${filterValue}`;
+ break;
+ case 'lessThanOrEqualTo':
+ filterPart = `${fieldName} le ${filterValue}`;
+ break;
+ case 'null':
+ filterPart = `${fieldName} eq null`;
+ break;
+ case 'notNull':
+ filterPart = `${fieldName} ne null`;
+ break;
+ case 'empty':
+ filterPart = `length(${fieldName}) eq 0`;
+ break;
+ case 'notEmpty':
+ filterPart = `length(${fieldName}) gt 0`;
+ break;
+ }
+
+ filterExpression += filterPart;
+ });
+
+ return filterExpression;
+ }
+
+ private static buildSortExpression(sortingArgs: any[]): string {
+ if (!sortingArgs || sortingArgs.length === 0) return '';
+
+ const sortStrings = sortingArgs.map(sort => {
+ const dir = sort.dir === 2 ? 'desc' : 'asc'; // 1 = asc, 2 = desc
+ return `${sort.fieldName} ${dir}`;
+ });
+
+ return sortStrings.length > 0 ? `$orderby=${sortStrings.join(',')}` : '';
+ }
+
+ private static getFilteringLogic(operator: any): string {
+ switch (operator) {
+ case 0: return 'and';
+ case 1: return 'or';
+ default: return 'and';
+ }
+ }
+}
diff --git a/samples/grids/grid/filtering-remote/src/index.css b/samples/grids/grid/filtering-remote/src/index.css
new file mode 100644
index 0000000000..98682b8543
--- /dev/null
+++ b/samples/grids/grid/filtering-remote/src/index.css
@@ -0,0 +1,2 @@
+/* shared styles are loaded from: */
+/* https://static.infragistics.com/xplatform/css/samples */
diff --git a/samples/grids/grid/filtering-remote/src/index.tsx b/samples/grids/grid/filtering-remote/src/index.tsx
new file mode 100644
index 0000000000..6c8fd62a70
--- /dev/null
+++ b/samples/grids/grid/filtering-remote/src/index.tsx
@@ -0,0 +1,77 @@
+import React, { useEffect, useState, useRef } from 'react';
+import ReactDOM from 'react-dom/client';
+import { RemoteService } from './RemoteService'
+import { IgrGrid, IgrColumn } from 'igniteui-react-grids';
+import { IgrSortingExpressionEventArgs, IgrFilteringExpressionsTreeEventArgs } from 'igniteui-react-grids';
+import 'igniteui-react-grids/grids/themes/light/bootstrap.css';
+
+const RemoteFilteringGrid = () => {
+ const [data, setData] = useState([]);
+ const [isLoading, setIsLoading] = useState(false);
+ const debounceRef = useRef(null);
+
+ const fetchData = async (filterExpressions: any = null, sortExpressions: any[] = []) => {
+ try {
+ setIsLoading(true);
+ const result = await RemoteService.getData(filterExpressions, sortExpressions);
+ setData(result);
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleSortingExpressionsChange = (event: IgrSortingExpressionEventArgs) => {
+ const sortExpressions = event.detail;
+
+ if (debounceRef.current) clearTimeout(debounceRef.current);
+ debounceRef.current = setTimeout(() => {
+ fetchData(null, sortExpressions);
+ }, 300);
+ };
+
+ const handleFilteringExpressionsTreeChange = (event: IgrFilteringExpressionsTreeEventArgs) => {
+ const filterExpressions = event.detail;
+
+ if (debounceRef.current) clearTimeout(debounceRef.current);
+ debounceRef.current = setTimeout(() => {
+ fetchData(filterExpressions, []);
+ }, 500);
+ };
+
+ useEffect(() => {
+ fetchData();
+ }, []);
+
+ return (
+
+
Remote Filtering & Sorting Grid
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+// rendering above function to the React DOM
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render();
+
+export default RemoteFilteringGrid;
diff --git a/samples/grids/grid/filtering-remote/src/react-app-env.d.ts b/samples/grids/grid/filtering-remote/src/react-app-env.d.ts
new file mode 100644
index 0000000000..6431bc5fc6
--- /dev/null
+++ b/samples/grids/grid/filtering-remote/src/react-app-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/samples/grids/grid/filtering-remote/tsconfig.json b/samples/grids/grid/filtering-remote/tsconfig.json
new file mode 100644
index 0000000000..42c6ace1da
--- /dev/null
+++ b/samples/grids/grid/filtering-remote/tsconfig.json
@@ -0,0 +1,45 @@
+{
+ "compilerOptions": {
+ "resolveJsonModule": true,
+ "esModuleInterop": true,
+ "baseUrl": ".",
+ "outDir": "build/dist",
+ "module": "esnext",
+ "target": "es5",
+ "lib": [
+ "es6",
+ "dom"
+ ],
+ "sourceMap": true,
+ "allowJs": true,
+ "jsx": "react-jsx",
+ "moduleResolution": "node",
+ "rootDir": "src",
+ "forceConsistentCasingInFileNames": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": true,
+ "noImplicitAny": true,
+ "noUnusedLocals": false,
+ "importHelpers": true,
+ "suppressImplicitAnyIndexErrors": true,
+ "allowSyntheticDefaultImports": true,
+ "skipLibCheck": true,
+ "strict": false,
+ "isolatedModules": true,
+ "noEmit": true
+ },
+ "exclude": [
+ "node_modules",
+ "build",
+ "scripts",
+ "acceptance-tests",
+ "webpack",
+ "jest",
+ "src/setupTests.ts",
+ "**/odatajs-4.0.0.js",
+ "config-overrides.js"
+ ],
+ "include": [
+ "src"
+ ]
+}
diff --git a/samples/grids/grid/remote-virtualization/.eslintrc.js b/samples/grids/grid/remote-virtualization/.eslintrc.js
new file mode 100644
index 0000000000..7168b71441
--- /dev/null
+++ b/samples/grids/grid/remote-virtualization/.eslintrc.js
@@ -0,0 +1,78 @@
+// https://www.robertcooper.me/using-eslint-and-prettier-in-a-typescript-project
+module.exports = {
+ parser: "@typescript-eslint/parser", // Specifies the ESLint parser
+ parserOptions: {
+ ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
+ sourceType: "module", // Allows for the use of imports
+ ecmaFeatures: {
+ jsx: true // Allows for the parsing of JSX
+ }
+ },
+ settings: {
+ react: {
+ version: "999.999.999" // Tells eslint-plugin-react to automatically detect the version of React to use
+ }
+ },
+ extends: [
+ "eslint:recommended",
+ "plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react
+ "plugin:@typescript-eslint/recommended" // Uses the recommended rules from @typescript-eslint/eslint-plugin
+ ],
+ rules: {
+ // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
+ "default-case": "off",
+ "jsx-a11y/alt-text": "off",
+ "jsx-a11y/iframe-has-title": "off",
+ "no-undef": "off",
+ "no-unused-vars": "off",
+ "no-extend-native": "off",
+ "no-throw-literal": "off",
+ "no-useless-concat": "off",
+ "no-mixed-operators": "off",
+ "no-prototype-builtins": "off",
+ "no-mixed-spaces-and-tabs": 0,
+ "prefer-const": "off",
+ "prefer-rest-params": "off",
+ "@typescript-eslint/no-unused-vars": "off",
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-inferrable-types": "off",
+ "@typescript-eslint/no-useless-constructor": "off",
+ "@typescript-eslint/no-use-before-define": "off",
+ "@typescript-eslint/no-non-null-assertion": "off",
+ "@typescript-eslint/interface-name-prefix": "off",
+ "@typescript-eslint/prefer-namespace-keyword": "off",
+ "@typescript-eslint/explicit-function-return-type": "off",
+ "@typescript-eslint/explicit-module-boundary-types": "off"
+ },
+ "overrides": [
+ {
+ "files": ["*.ts", "*.tsx"],
+ "rules": {
+ "default-case": "off",
+ "jsx-a11y/alt-text": "off",
+ "jsx-a11y/iframe-has-title": "off",
+ "no-var": "off",
+ "no-undef": "off",
+ "no-unused-vars": "off",
+ "no-extend-native": "off",
+ "no-throw-literal": "off",
+ "no-useless-concat": "off",
+ "no-mixed-operators": "off",
+ "no-mixed-spaces-and-tabs": 0,
+ "no-prototype-builtins": "off",
+ "prefer-const": "off",
+ "prefer-rest-params": "off",
+ "@typescript-eslint/no-unused-vars": "off",
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-inferrable-types": "off",
+ "@typescript-eslint/no-useless-constructor": "off",
+ "@typescript-eslint/no-use-before-define": "off",
+ "@typescript-eslint/no-non-null-assertion": "off",
+ "@typescript-eslint/interface-name-prefix": "off",
+ "@typescript-eslint/prefer-namespace-keyword": "off",
+ "@typescript-eslint/explicit-function-return-type": "off",
+ "@typescript-eslint/explicit-module-boundary-types": "off"
+ }
+ }
+ ]
+ };
\ No newline at end of file
diff --git a/samples/grids/grid/remote-virtualization/ReadMe.md b/samples/grids/grid/remote-virtualization/ReadMe.md
new file mode 100644
index 0000000000..6d7315e7ac
--- /dev/null
+++ b/samples/grids/grid/remote-virtualization/ReadMe.md
@@ -0,0 +1,56 @@
+
+
+
+This folder contains implementation of React application with example of Filtering Options feature using [Grid](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html) component.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Branches
+
+> **_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.
+
+## Instructions
+
+Follow these instructions to run this example:
+
+
+```
+git clone https://github.com/IgniteUI/igniteui-react-examples.git
+git checkout master
+cd ./igniteui-react-examples
+cd ./samples/grids/grid/filtering-options
+```
+
+open above folder in VS Code or type:
+```
+code .
+```
+
+In terminal window, run:
+```
+npm install --legacy-peer-deps
+npm run-script start
+```
+
+Then open http://localhost:4200/ in your browser
+
+
+## Learn More
+
+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).
diff --git a/samples/grids/grid/remote-virtualization/package.json b/samples/grids/grid/remote-virtualization/package.json
new file mode 100644
index 0000000000..02dbc47aea
--- /dev/null
+++ b/samples/grids/grid/remote-virtualization/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "example-ignite-ui-react",
+ "description": "This project provides example of using Ignite UI for React components",
+ "author": "Infragistics",
+ "version": "1.4.0",
+ "license": "",
+ "homepage": ".",
+ "private": true,
+ "scripts": {
+ "start": "set PORT=4200 && react-scripts --max_old_space_size=10240 start",
+ "build": "react-scripts --max_old_space_size=10240 build ",
+ "test": "react-scripts test --env=jsdom",
+ "eject": "react-scripts eject",
+ "lint": "eslint ./src/**/*.{ts,tsx}"
+ },
+ "dependencies": {
+ "igniteui-dockmanager": "1.16.1",
+ "igniteui-react": "19.0.2",
+ "igniteui-react-core": "19.0.0",
+ "igniteui-react-grids": "19.0.2",
+ "igniteui-react-inputs": "19.0.0",
+ "igniteui-react-layouts": "19.0.0",
+ "igniteui-webcomponents": "6.0.0",
+ "lit-html": "^3.2.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-scripts": "^5.0.1",
+ "tslib": "^2.4.0"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.2.0",
+ "@types/node": "^18.11.7",
+ "@types/react": "^18.0.24",
+ "@types/react-dom": "^18.0.8",
+ "eslint": "^8.33.0",
+ "eslint-config-react": "^1.1.7",
+ "eslint-plugin-react": "^7.20.0",
+ "react-app-rewired": "^2.2.1",
+ "typescript": "^4.8.4",
+ "worker-loader": "^3.0.8"
+ },
+ "browserslist": [
+ ">0.2%",
+ "not dead",
+ "not ie <= 11",
+ "not op_mini all"
+ ]
+}
diff --git a/samples/grids/grid/remote-virtualization/public/index.html b/samples/grids/grid/remote-virtualization/public/index.html
new file mode 100644
index 0000000000..e2d3265576
--- /dev/null
+++ b/samples/grids/grid/remote-virtualization/public/index.html
@@ -0,0 +1,11 @@
+
+
+
+ Sample | Ignite UI | React | infragistics
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/grids/grid/remote-virtualization/sandbox.config.json b/samples/grids/grid/remote-virtualization/sandbox.config.json
new file mode 100644
index 0000000000..07f53508eb
--- /dev/null
+++ b/samples/grids/grid/remote-virtualization/sandbox.config.json
@@ -0,0 +1,5 @@
+{
+ "infiniteLoopProtection": false,
+ "hardReloadOnChange": false,
+ "view": "browser"
+}
\ No newline at end of file
diff --git a/samples/grids/grid/remote-virtualization/src/NwindData.json b/samples/grids/grid/remote-virtualization/src/NwindData.json
new file mode 100644
index 0000000000..c00b03ec8d
--- /dev/null
+++ b/samples/grids/grid/remote-virtualization/src/NwindData.json
@@ -0,0 +1,458 @@
+[
+ {
+ "ProductID": 1,
+ "ProductName": "Chai",
+ "SupplierID": 1,
+ "CategoryID": 1,
+ "QuantityPerUnit": "10 boxes x 20 bags",
+ "UnitPrice": 18,
+ "UnitsInStock": 39,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 10,
+ "Discontinued": false,
+ "OrderDate": "2012-02-12",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Fun-Tasty Co.",
+ "LastInventory": "2018-06-12"
+ },
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 2,
+ "ProductName": "Chang",
+ "SupplierID": 1,
+ "CategoryID": 1,
+ "QuantityPerUnit": "24 - 12 oz bottles",
+ "UnitPrice": 19,
+ "UnitsInStock": 17,
+ "UnitsOnOrder": 40,
+ "ReorderLevel": 25,
+ "Discontinued": true,
+ "OrderDate": "2003-03-17",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ },
+ {
+ "ProductID": 3,
+ "ProductName": "Aniseed Syrup",
+ "SupplierID": 1,
+ "CategoryID": 2,
+ "QuantityPerUnit": "12 - 550 ml bottles",
+ "UnitPrice": 10,
+ "UnitsInStock": 13,
+ "UnitsOnOrder": 70,
+ "ReorderLevel": 25,
+ "Discontinued": false,
+ "OrderDate": "2006-03-17",
+ "Rating": 3,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ },
+ {
+ "Shop": "Street Market",
+ "LastInventory": "2018-12-12"
+ },
+ {
+ "Shop": "24/7 Market",
+ "LastInventory": "2018-11-11"
+ }
+ ]
+ },
+ {
+ "ProductID": 4,
+ "ProductName": "Chef Antons Cajun Seasoning",
+ "SupplierID": 2,
+ "CategoryID": 2,
+ "QuantityPerUnit": "48 - 6 oz jars",
+ "UnitPrice": 22,
+ "UnitsInStock": 53,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2016-03-17",
+ "Rating": 3,
+ "Locations": [
+ {
+ "Shop": "Fun-Tasty Co.",
+ "LastInventory": "2018-06-12"
+ },
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ },
+ {
+ "Shop": "Street Market",
+ "LastInventory": "2018-12-12"
+ }
+ ]
+ },
+ {
+ "ProductID": 5,
+ "ProductName": "Chef Antons Gumbo Mix",
+ "SupplierID": 2,
+ "CategoryID": 2,
+ "QuantityPerUnit": "36 boxes",
+ "UnitPrice": 21.35,
+ "UnitsInStock": 0,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": true,
+ "OrderDate": "2011-11-11",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ },
+ {
+ "ProductID": 6,
+ "ProductName": "Grandmas Boysenberry Spread",
+ "SupplierID": 3,
+ "CategoryID": 2,
+ "QuantityPerUnit": "12 - 8 oz jars",
+ "UnitPrice": 25,
+ "UnitsInStock": 0,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 25,
+ "Discontinued": false,
+ "OrderDate": "2017-12-17",
+ "Rating": 4,
+ "Locations": [
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ },
+ {
+ "ProductID": 7,
+ "ProductName": "Uncle Bobs Organic Dried Pears",
+ "SupplierID": 3,
+ "CategoryID": 7,
+ "QuantityPerUnit": "12 - 1 lb pkgs.",
+ "UnitPrice": 30,
+ "UnitsInStock": 150,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 10,
+ "Discontinued": false,
+ "OrderDate": "2016-07-17",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Fun-Tasty Co.",
+ "LastInventory": "2018-06-12"
+ },
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ },
+ {
+ "Shop": "Street Market",
+ "LastInventory": "2018-12-12"
+ }
+ ]
+ },
+ {
+ "ProductID": 8,
+ "ProductName": "Northwoods Cranberry Sauce",
+ "SupplierID": 3,
+ "CategoryID": 2,
+ "QuantityPerUnit": "12 - 12 oz jars",
+ "UnitPrice": 40,
+ "UnitsInStock": 6,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2018-01-17",
+ "Rating": 4,
+ "Locations": [
+ {
+ "Shop": "Fun-Tasty Co.",
+ "LastInventory": "2018-06-12"
+ },
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 9,
+ "ProductName": "Mishi Kobe Niku",
+ "SupplierID": 4,
+ "CategoryID": 6,
+ "QuantityPerUnit": "18 - 500 g pkgs.",
+ "UnitPrice": 97,
+ "UnitsInStock": 29,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": true,
+ "OrderDate": "2010-02-17",
+ "Rating": 4,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 10,
+ "ProductName": "Ikura",
+ "SupplierID": 4,
+ "CategoryID": 8,
+ "QuantityPerUnit": "12 - 200 ml jars",
+ "UnitPrice": 31,
+ "UnitsInStock": 31,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2008-05-17",
+ "Rating": 3,
+ "Locations": [
+ {
+ "Shop": "Wall Market",
+ "LastInventory": "2018-12-06"
+ }
+ ]
+ },
+ {
+ "ProductID": 11,
+ "ProductName": "Queso Cabrales",
+ "SupplierID": 5,
+ "CategoryID": 4,
+ "QuantityPerUnit": "1 kg pkg.",
+ "UnitPrice": 21,
+ "UnitsInStock": 22,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 30,
+ "Discontinued": false,
+ "OrderDate": "2009-01-17",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Fun-Tasty Co.",
+ "LastInventory": "2018-06-12"
+ },
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 12,
+ "ProductName": "Queso Manchego La Pastora",
+ "SupplierID": 5,
+ "CategoryID": 4,
+ "QuantityPerUnit": "10 - 500 g pkgs.",
+ "UnitPrice": 38,
+ "UnitsInStock": 86,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2015-11-17",
+ "Rating": 3,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 13,
+ "ProductName": "Konbu",
+ "SupplierID": 6,
+ "CategoryID": 8,
+ "QuantityPerUnit": "2 kg box",
+ "UnitPrice": 6,
+ "UnitsInStock": 24,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 5,
+ "Discontinued": false,
+ "OrderDate": "2015-03-17",
+ "Rating": 2,
+ "Locations": [
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ },
+ {
+ "ProductID": 14,
+ "ProductName": "Tofu",
+ "SupplierID": 6,
+ "CategoryID": 7,
+ "QuantityPerUnit": "40 - 100 g pkgs.",
+ "UnitPrice": 23.25,
+ "UnitsInStock": 35,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2017-06-17",
+ "Rating": 4,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ },
+ {
+ "Shop": "Street Market",
+ "LastInventory": "2018-12-12"
+ }
+ ]
+ },
+ {
+ "ProductID": 15,
+ "ProductName": "Genen Shouyu",
+ "SupplierID": 6,
+ "CategoryID": 2,
+ "QuantityPerUnit": "24 - 250 ml bottles",
+ "UnitPrice": 15.5,
+ "UnitsInStock": 39,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 5,
+ "Discontinued": false,
+ "OrderDate": "2014-03-17",
+ "Rating": 4,
+ "Locations": [
+ {
+ "Shop": "Local Market",
+ "LastInventory": "2018-07-03"
+ },
+ {
+ "Shop": "Wall Market",
+ "LastInventory": "2018-12-06"
+ }
+ ]
+ },
+ {
+ "ProductID": 16,
+ "ProductName": "Pavlova",
+ "SupplierID": 7,
+ "CategoryID": 3,
+ "QuantityPerUnit": "32 - 500 g boxes",
+ "UnitPrice": 17.45,
+ "UnitsInStock": 29,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 10,
+ "Discontinued": false,
+ "OrderDate": "2018-03-28",
+ "Rating": 2,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ },
+ {
+ "Shop": "Street Market",
+ "LastInventory": "2018-12-12"
+ },
+ {
+ "Shop": "24/7 Market",
+ "LastInventory": "2018-11-11"
+ }
+ ]
+ },
+ {
+ "ProductID": 17,
+ "ProductName": "Alice Mutton",
+ "SupplierID": 7,
+ "CategoryID": 6,
+ "QuantityPerUnit": "20 - 1 kg tins",
+ "UnitPrice": 39,
+ "UnitsInStock": 0,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": true,
+ "OrderDate": "2015-08-17",
+ "Rating": 2,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 18,
+ "ProductName": "Carnarvon Tigers",
+ "SupplierID": 7,
+ "CategoryID": 8,
+ "QuantityPerUnit": "16 kg pkg.",
+ "UnitPrice": 62.5,
+ "UnitsInStock": 42,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2005-09-27",
+ "Rating": 2,
+ "Locations": [
+ {
+ "Shop": "24/7 Market",
+ "LastInventory": "2018-11-11"
+ },
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ },
+ {
+ "ProductID": 19,
+ "ProductName": "Teatime Chocolate Biscuits",
+ "SupplierID": 8,
+ "CategoryID": 3,
+ "QuantityPerUnit": "",
+ "UnitPrice": 9.2,
+ "UnitsInStock": 25,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 5,
+ "Discontinued": false,
+ "OrderDate": "2001-03-17",
+ "Rating": 2,
+ "Locations": [
+ {
+ "Shop": "Local Market",
+ "LastInventory": "2018-07-03"
+ }
+ ]
+ },
+ {
+ "ProductID": 20,
+ "ProductName": "Sir Rodneys Marmalade",
+ "SupplierID": 8,
+ "CategoryID": 3,
+ "QuantityPerUnit": "4 - 100 ml jars",
+ "UnitPrice": 4.5,
+ "UnitsInStock": 40,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2005-03-17",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/samples/grids/grid/remote-virtualization/src/RemoteService.ts b/samples/grids/grid/remote-virtualization/src/RemoteService.ts
new file mode 100644
index 0000000000..17e934cc5a
--- /dev/null
+++ b/samples/grids/grid/remote-virtualization/src/RemoteService.ts
@@ -0,0 +1,32 @@
+const DATA_URL = 'https://services.odata.org/V4/Northwind/Northwind.svc/Products';
+
+export class RemoteService {
+ private static cachedData: any[] = [];
+ private static totalCount: number = 0;
+
+ public static async getData(skip: number = 0, take: number = 50): Promise {
+ if (this.cachedData.length === 0) {
+ const res = await fetch(`${DATA_URL}?$count=true&$skip=0&$top=1`);
+ const json = await res.json();
+ this.totalCount = json['@odata.count'];
+ this.cachedData = new Array(this.totalCount).fill({ emptyRec: true });
+ }
+
+ const slice = this.cachedData.slice(skip, skip + take);
+ const allLoaded = slice.every(row => row.emptyRec !== true);
+ if (allLoaded) return slice;
+
+ const res = await fetch(`${DATA_URL}?$count=true&$skip=${skip}&$top=${take}`);
+ const json = await res.json();
+
+ for (let i = 0; i < json.value.length; i++) {
+ this.cachedData[skip + i] = json.value[i];
+ }
+
+ return this.cachedData.slice(skip, skip + take);
+ }
+
+ public static getTotalCount(): number {
+ return this.totalCount;
+ }
+}
diff --git a/samples/grids/grid/remote-virtualization/src/index.css b/samples/grids/grid/remote-virtualization/src/index.css
new file mode 100644
index 0000000000..98682b8543
--- /dev/null
+++ b/samples/grids/grid/remote-virtualization/src/index.css
@@ -0,0 +1,2 @@
+/* shared styles are loaded from: */
+/* https://static.infragistics.com/xplatform/css/samples */
diff --git a/samples/grids/grid/remote-virtualization/src/index.tsx b/samples/grids/grid/remote-virtualization/src/index.tsx
new file mode 100644
index 0000000000..e2320e371c
--- /dev/null
+++ b/samples/grids/grid/remote-virtualization/src/index.tsx
@@ -0,0 +1,51 @@
+import React, { useEffect, useState } from 'react';
+import ReactDOM from 'react-dom/client';
+import { RemoteService } from './RemoteService';
+import { IgrGrid, IgrColumn } from 'igniteui-react-grids';
+import 'igniteui-react-grids/grids/themes/light/bootstrap.css';
+
+const RemoteVirtualizationGrid = () => {
+ const [data, setData] = useState([]);
+ const [isLoading, setIsLoading] = useState(false);
+
+ const fetchData = async (skip: number = 0, take: number = 50) => {
+ setIsLoading(true);
+ const result = await RemoteService.getData(skip, take);
+ setData(result);
+ setIsLoading(false);
+ };
+
+ const handleDataPreLoad = (event: any) => {
+ const { startIndex, chunkSize } = event.detail;
+ fetchData(startIndex, chunkSize);
+ };
+
+ useEffect(() => {
+ fetchData(); // initial load
+ }, []);
+
+ return (
+
+
Remote Virtualization Grid
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render();
diff --git a/samples/grids/grid/remote-virtualization/src/react-app-env.d.ts b/samples/grids/grid/remote-virtualization/src/react-app-env.d.ts
new file mode 100644
index 0000000000..6431bc5fc6
--- /dev/null
+++ b/samples/grids/grid/remote-virtualization/src/react-app-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/samples/grids/grid/remote-virtualization/tsconfig.json b/samples/grids/grid/remote-virtualization/tsconfig.json
new file mode 100644
index 0000000000..8c0d146f95
--- /dev/null
+++ b/samples/grids/grid/remote-virtualization/tsconfig.json
@@ -0,0 +1,44 @@
+{
+ "compilerOptions": {
+ "resolveJsonModule": true,
+ "esModuleInterop": true,
+ "baseUrl": ".",
+ "outDir": "build/dist",
+ "module": "esnext",
+ "target": "es5",
+ "lib": [
+ "es6",
+ "dom"
+ ],
+ "sourceMap": true,
+ "allowJs": true,
+ "jsx": "react-jsx",
+ "moduleResolution": "node",
+ "rootDir": "src",
+ "forceConsistentCasingInFileNames": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": true,
+ "noImplicitAny": true,
+ "noUnusedLocals": false,
+ "importHelpers": true,
+ "allowSyntheticDefaultImports": true,
+ "skipLibCheck": true,
+ "strict": false,
+ "isolatedModules": true,
+ "noEmit": true
+ },
+ "exclude": [
+ "node_modules",
+ "build",
+ "scripts",
+ "acceptance-tests",
+ "webpack",
+ "jest",
+ "src/setupTests.ts",
+ "**/odatajs-4.0.0.js",
+ "config-overrides.js"
+ ],
+ "include": [
+ "src"
+ ]
+}
diff --git a/samples/grids/grid/unique-columns-value/.eslintrc.js b/samples/grids/grid/unique-columns-value/.eslintrc.js
new file mode 100644
index 0000000000..7168b71441
--- /dev/null
+++ b/samples/grids/grid/unique-columns-value/.eslintrc.js
@@ -0,0 +1,78 @@
+// https://www.robertcooper.me/using-eslint-and-prettier-in-a-typescript-project
+module.exports = {
+ parser: "@typescript-eslint/parser", // Specifies the ESLint parser
+ parserOptions: {
+ ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
+ sourceType: "module", // Allows for the use of imports
+ ecmaFeatures: {
+ jsx: true // Allows for the parsing of JSX
+ }
+ },
+ settings: {
+ react: {
+ version: "999.999.999" // Tells eslint-plugin-react to automatically detect the version of React to use
+ }
+ },
+ extends: [
+ "eslint:recommended",
+ "plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react
+ "plugin:@typescript-eslint/recommended" // Uses the recommended rules from @typescript-eslint/eslint-plugin
+ ],
+ rules: {
+ // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
+ "default-case": "off",
+ "jsx-a11y/alt-text": "off",
+ "jsx-a11y/iframe-has-title": "off",
+ "no-undef": "off",
+ "no-unused-vars": "off",
+ "no-extend-native": "off",
+ "no-throw-literal": "off",
+ "no-useless-concat": "off",
+ "no-mixed-operators": "off",
+ "no-prototype-builtins": "off",
+ "no-mixed-spaces-and-tabs": 0,
+ "prefer-const": "off",
+ "prefer-rest-params": "off",
+ "@typescript-eslint/no-unused-vars": "off",
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-inferrable-types": "off",
+ "@typescript-eslint/no-useless-constructor": "off",
+ "@typescript-eslint/no-use-before-define": "off",
+ "@typescript-eslint/no-non-null-assertion": "off",
+ "@typescript-eslint/interface-name-prefix": "off",
+ "@typescript-eslint/prefer-namespace-keyword": "off",
+ "@typescript-eslint/explicit-function-return-type": "off",
+ "@typescript-eslint/explicit-module-boundary-types": "off"
+ },
+ "overrides": [
+ {
+ "files": ["*.ts", "*.tsx"],
+ "rules": {
+ "default-case": "off",
+ "jsx-a11y/alt-text": "off",
+ "jsx-a11y/iframe-has-title": "off",
+ "no-var": "off",
+ "no-undef": "off",
+ "no-unused-vars": "off",
+ "no-extend-native": "off",
+ "no-throw-literal": "off",
+ "no-useless-concat": "off",
+ "no-mixed-operators": "off",
+ "no-mixed-spaces-and-tabs": 0,
+ "no-prototype-builtins": "off",
+ "prefer-const": "off",
+ "prefer-rest-params": "off",
+ "@typescript-eslint/no-unused-vars": "off",
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-inferrable-types": "off",
+ "@typescript-eslint/no-useless-constructor": "off",
+ "@typescript-eslint/no-use-before-define": "off",
+ "@typescript-eslint/no-non-null-assertion": "off",
+ "@typescript-eslint/interface-name-prefix": "off",
+ "@typescript-eslint/prefer-namespace-keyword": "off",
+ "@typescript-eslint/explicit-function-return-type": "off",
+ "@typescript-eslint/explicit-module-boundary-types": "off"
+ }
+ }
+ ]
+ };
\ No newline at end of file
diff --git a/samples/grids/grid/unique-columns-value/ReadMe.md b/samples/grids/grid/unique-columns-value/ReadMe.md
new file mode 100644
index 0000000000..6d7315e7ac
--- /dev/null
+++ b/samples/grids/grid/unique-columns-value/ReadMe.md
@@ -0,0 +1,56 @@
+
+
+
+This folder contains implementation of React application with example of Filtering Options feature using [Grid](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html) component.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Branches
+
+> **_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.
+
+## Instructions
+
+Follow these instructions to run this example:
+
+
+```
+git clone https://github.com/IgniteUI/igniteui-react-examples.git
+git checkout master
+cd ./igniteui-react-examples
+cd ./samples/grids/grid/filtering-options
+```
+
+open above folder in VS Code or type:
+```
+code .
+```
+
+In terminal window, run:
+```
+npm install --legacy-peer-deps
+npm run-script start
+```
+
+Then open http://localhost:4200/ in your browser
+
+
+## Learn More
+
+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).
diff --git a/samples/grids/grid/unique-columns-value/package.json b/samples/grids/grid/unique-columns-value/package.json
new file mode 100644
index 0000000000..02dbc47aea
--- /dev/null
+++ b/samples/grids/grid/unique-columns-value/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "example-ignite-ui-react",
+ "description": "This project provides example of using Ignite UI for React components",
+ "author": "Infragistics",
+ "version": "1.4.0",
+ "license": "",
+ "homepage": ".",
+ "private": true,
+ "scripts": {
+ "start": "set PORT=4200 && react-scripts --max_old_space_size=10240 start",
+ "build": "react-scripts --max_old_space_size=10240 build ",
+ "test": "react-scripts test --env=jsdom",
+ "eject": "react-scripts eject",
+ "lint": "eslint ./src/**/*.{ts,tsx}"
+ },
+ "dependencies": {
+ "igniteui-dockmanager": "1.16.1",
+ "igniteui-react": "19.0.2",
+ "igniteui-react-core": "19.0.0",
+ "igniteui-react-grids": "19.0.2",
+ "igniteui-react-inputs": "19.0.0",
+ "igniteui-react-layouts": "19.0.0",
+ "igniteui-webcomponents": "6.0.0",
+ "lit-html": "^3.2.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-scripts": "^5.0.1",
+ "tslib": "^2.4.0"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.2.0",
+ "@types/node": "^18.11.7",
+ "@types/react": "^18.0.24",
+ "@types/react-dom": "^18.0.8",
+ "eslint": "^8.33.0",
+ "eslint-config-react": "^1.1.7",
+ "eslint-plugin-react": "^7.20.0",
+ "react-app-rewired": "^2.2.1",
+ "typescript": "^4.8.4",
+ "worker-loader": "^3.0.8"
+ },
+ "browserslist": [
+ ">0.2%",
+ "not dead",
+ "not ie <= 11",
+ "not op_mini all"
+ ]
+}
diff --git a/samples/grids/grid/unique-columns-value/public/index.html b/samples/grids/grid/unique-columns-value/public/index.html
new file mode 100644
index 0000000000..e2d3265576
--- /dev/null
+++ b/samples/grids/grid/unique-columns-value/public/index.html
@@ -0,0 +1,11 @@
+
+
+
+ Sample | Ignite UI | React | infragistics
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/grids/grid/unique-columns-value/sandbox.config.json b/samples/grids/grid/unique-columns-value/sandbox.config.json
new file mode 100644
index 0000000000..07f53508eb
--- /dev/null
+++ b/samples/grids/grid/unique-columns-value/sandbox.config.json
@@ -0,0 +1,5 @@
+{
+ "infiniteLoopProtection": false,
+ "hardReloadOnChange": false,
+ "view": "browser"
+}
\ No newline at end of file
diff --git a/samples/grids/grid/unique-columns-value/src/NwindData.json b/samples/grids/grid/unique-columns-value/src/NwindData.json
new file mode 100644
index 0000000000..c00b03ec8d
--- /dev/null
+++ b/samples/grids/grid/unique-columns-value/src/NwindData.json
@@ -0,0 +1,458 @@
+[
+ {
+ "ProductID": 1,
+ "ProductName": "Chai",
+ "SupplierID": 1,
+ "CategoryID": 1,
+ "QuantityPerUnit": "10 boxes x 20 bags",
+ "UnitPrice": 18,
+ "UnitsInStock": 39,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 10,
+ "Discontinued": false,
+ "OrderDate": "2012-02-12",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Fun-Tasty Co.",
+ "LastInventory": "2018-06-12"
+ },
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 2,
+ "ProductName": "Chang",
+ "SupplierID": 1,
+ "CategoryID": 1,
+ "QuantityPerUnit": "24 - 12 oz bottles",
+ "UnitPrice": 19,
+ "UnitsInStock": 17,
+ "UnitsOnOrder": 40,
+ "ReorderLevel": 25,
+ "Discontinued": true,
+ "OrderDate": "2003-03-17",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ },
+ {
+ "ProductID": 3,
+ "ProductName": "Aniseed Syrup",
+ "SupplierID": 1,
+ "CategoryID": 2,
+ "QuantityPerUnit": "12 - 550 ml bottles",
+ "UnitPrice": 10,
+ "UnitsInStock": 13,
+ "UnitsOnOrder": 70,
+ "ReorderLevel": 25,
+ "Discontinued": false,
+ "OrderDate": "2006-03-17",
+ "Rating": 3,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ },
+ {
+ "Shop": "Street Market",
+ "LastInventory": "2018-12-12"
+ },
+ {
+ "Shop": "24/7 Market",
+ "LastInventory": "2018-11-11"
+ }
+ ]
+ },
+ {
+ "ProductID": 4,
+ "ProductName": "Chef Antons Cajun Seasoning",
+ "SupplierID": 2,
+ "CategoryID": 2,
+ "QuantityPerUnit": "48 - 6 oz jars",
+ "UnitPrice": 22,
+ "UnitsInStock": 53,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2016-03-17",
+ "Rating": 3,
+ "Locations": [
+ {
+ "Shop": "Fun-Tasty Co.",
+ "LastInventory": "2018-06-12"
+ },
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ },
+ {
+ "Shop": "Street Market",
+ "LastInventory": "2018-12-12"
+ }
+ ]
+ },
+ {
+ "ProductID": 5,
+ "ProductName": "Chef Antons Gumbo Mix",
+ "SupplierID": 2,
+ "CategoryID": 2,
+ "QuantityPerUnit": "36 boxes",
+ "UnitPrice": 21.35,
+ "UnitsInStock": 0,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": true,
+ "OrderDate": "2011-11-11",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ },
+ {
+ "ProductID": 6,
+ "ProductName": "Grandmas Boysenberry Spread",
+ "SupplierID": 3,
+ "CategoryID": 2,
+ "QuantityPerUnit": "12 - 8 oz jars",
+ "UnitPrice": 25,
+ "UnitsInStock": 0,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 25,
+ "Discontinued": false,
+ "OrderDate": "2017-12-17",
+ "Rating": 4,
+ "Locations": [
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ },
+ {
+ "ProductID": 7,
+ "ProductName": "Uncle Bobs Organic Dried Pears",
+ "SupplierID": 3,
+ "CategoryID": 7,
+ "QuantityPerUnit": "12 - 1 lb pkgs.",
+ "UnitPrice": 30,
+ "UnitsInStock": 150,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 10,
+ "Discontinued": false,
+ "OrderDate": "2016-07-17",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Fun-Tasty Co.",
+ "LastInventory": "2018-06-12"
+ },
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ },
+ {
+ "Shop": "Street Market",
+ "LastInventory": "2018-12-12"
+ }
+ ]
+ },
+ {
+ "ProductID": 8,
+ "ProductName": "Northwoods Cranberry Sauce",
+ "SupplierID": 3,
+ "CategoryID": 2,
+ "QuantityPerUnit": "12 - 12 oz jars",
+ "UnitPrice": 40,
+ "UnitsInStock": 6,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2018-01-17",
+ "Rating": 4,
+ "Locations": [
+ {
+ "Shop": "Fun-Tasty Co.",
+ "LastInventory": "2018-06-12"
+ },
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 9,
+ "ProductName": "Mishi Kobe Niku",
+ "SupplierID": 4,
+ "CategoryID": 6,
+ "QuantityPerUnit": "18 - 500 g pkgs.",
+ "UnitPrice": 97,
+ "UnitsInStock": 29,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": true,
+ "OrderDate": "2010-02-17",
+ "Rating": 4,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 10,
+ "ProductName": "Ikura",
+ "SupplierID": 4,
+ "CategoryID": 8,
+ "QuantityPerUnit": "12 - 200 ml jars",
+ "UnitPrice": 31,
+ "UnitsInStock": 31,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2008-05-17",
+ "Rating": 3,
+ "Locations": [
+ {
+ "Shop": "Wall Market",
+ "LastInventory": "2018-12-06"
+ }
+ ]
+ },
+ {
+ "ProductID": 11,
+ "ProductName": "Queso Cabrales",
+ "SupplierID": 5,
+ "CategoryID": 4,
+ "QuantityPerUnit": "1 kg pkg.",
+ "UnitPrice": 21,
+ "UnitsInStock": 22,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 30,
+ "Discontinued": false,
+ "OrderDate": "2009-01-17",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Fun-Tasty Co.",
+ "LastInventory": "2018-06-12"
+ },
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 12,
+ "ProductName": "Queso Manchego La Pastora",
+ "SupplierID": 5,
+ "CategoryID": 4,
+ "QuantityPerUnit": "10 - 500 g pkgs.",
+ "UnitPrice": 38,
+ "UnitsInStock": 86,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2015-11-17",
+ "Rating": 3,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 13,
+ "ProductName": "Konbu",
+ "SupplierID": 6,
+ "CategoryID": 8,
+ "QuantityPerUnit": "2 kg box",
+ "UnitPrice": 6,
+ "UnitsInStock": 24,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 5,
+ "Discontinued": false,
+ "OrderDate": "2015-03-17",
+ "Rating": 2,
+ "Locations": [
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ },
+ {
+ "ProductID": 14,
+ "ProductName": "Tofu",
+ "SupplierID": 6,
+ "CategoryID": 7,
+ "QuantityPerUnit": "40 - 100 g pkgs.",
+ "UnitPrice": 23.25,
+ "UnitsInStock": 35,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2017-06-17",
+ "Rating": 4,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ },
+ {
+ "Shop": "Street Market",
+ "LastInventory": "2018-12-12"
+ }
+ ]
+ },
+ {
+ "ProductID": 15,
+ "ProductName": "Genen Shouyu",
+ "SupplierID": 6,
+ "CategoryID": 2,
+ "QuantityPerUnit": "24 - 250 ml bottles",
+ "UnitPrice": 15.5,
+ "UnitsInStock": 39,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 5,
+ "Discontinued": false,
+ "OrderDate": "2014-03-17",
+ "Rating": 4,
+ "Locations": [
+ {
+ "Shop": "Local Market",
+ "LastInventory": "2018-07-03"
+ },
+ {
+ "Shop": "Wall Market",
+ "LastInventory": "2018-12-06"
+ }
+ ]
+ },
+ {
+ "ProductID": 16,
+ "ProductName": "Pavlova",
+ "SupplierID": 7,
+ "CategoryID": 3,
+ "QuantityPerUnit": "32 - 500 g boxes",
+ "UnitPrice": 17.45,
+ "UnitsInStock": 29,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 10,
+ "Discontinued": false,
+ "OrderDate": "2018-03-28",
+ "Rating": 2,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ },
+ {
+ "Shop": "Street Market",
+ "LastInventory": "2018-12-12"
+ },
+ {
+ "Shop": "24/7 Market",
+ "LastInventory": "2018-11-11"
+ }
+ ]
+ },
+ {
+ "ProductID": 17,
+ "ProductName": "Alice Mutton",
+ "SupplierID": 7,
+ "CategoryID": 6,
+ "QuantityPerUnit": "20 - 1 kg tins",
+ "UnitPrice": 39,
+ "UnitsInStock": 0,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": true,
+ "OrderDate": "2015-08-17",
+ "Rating": 2,
+ "Locations": [
+ {
+ "Shop": "Farmer Market",
+ "LastInventory": "2018-04-04"
+ }
+ ]
+ },
+ {
+ "ProductID": 18,
+ "ProductName": "Carnarvon Tigers",
+ "SupplierID": 7,
+ "CategoryID": 8,
+ "QuantityPerUnit": "16 kg pkg.",
+ "UnitPrice": 62.5,
+ "UnitsInStock": 42,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2005-09-27",
+ "Rating": 2,
+ "Locations": [
+ {
+ "Shop": "24/7 Market",
+ "LastInventory": "2018-11-11"
+ },
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ },
+ {
+ "ProductID": 19,
+ "ProductName": "Teatime Chocolate Biscuits",
+ "SupplierID": 8,
+ "CategoryID": 3,
+ "QuantityPerUnit": "",
+ "UnitPrice": 9.2,
+ "UnitsInStock": 25,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 5,
+ "Discontinued": false,
+ "OrderDate": "2001-03-17",
+ "Rating": 2,
+ "Locations": [
+ {
+ "Shop": "Local Market",
+ "LastInventory": "2018-07-03"
+ }
+ ]
+ },
+ {
+ "ProductID": 20,
+ "ProductName": "Sir Rodneys Marmalade",
+ "SupplierID": 8,
+ "CategoryID": 3,
+ "QuantityPerUnit": "4 - 100 ml jars",
+ "UnitPrice": 4.5,
+ "UnitsInStock": 40,
+ "UnitsOnOrder": 30,
+ "ReorderLevel": 0,
+ "Discontinued": false,
+ "OrderDate": "2005-03-17",
+ "Rating": 5,
+ "Locations": [
+ {
+ "Shop": "Super Market",
+ "LastInventory": "2018-09-09"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/samples/grids/grid/unique-columns-value/src/RemoteService.ts b/samples/grids/grid/unique-columns-value/src/RemoteService.ts
new file mode 100644
index 0000000000..ce412e4124
--- /dev/null
+++ b/samples/grids/grid/unique-columns-value/src/RemoteService.ts
@@ -0,0 +1,160 @@
+import { IgrColumn, IgrFilteringExpressionsTree, IgrFilteringStrategy } from "igniteui-react-grids";
+
+const DATA_URL = 'https://services.odata.org/V4/Northwind/Northwind.svc/Products';
+const EMPTY_STRING = '';
+const NULL_VALUE: null = null;
+
+export enum FILTER_OPERATION {
+ CONTAINS = 'contains',
+ STARTS_WITH = 'startswith',
+ ENDS_WITH = 'endswith',
+ EQUALS = 'eq',
+ DOES_NOT_EQUAL = 'ne',
+ GREATER_THAN = 'gt',
+ LESS_THAN = 'lt',
+ LESS_THAN_EQUAL = 'le',
+ GREATER_THAN_EQUAL = 'ge'
+}
+
+export class RemoteService {
+ public static _filteringStrategy: IgrFilteringStrategy;
+
+ public static getData(
+ filteringArgs?: any,
+ sortingArgs?: any
+ ): Promise {
+ const url = this.buildDataUrl(filteringArgs, sortingArgs);
+ return fetch(url)
+ .then((res) => res.json())
+ .then((data) => data.value); // only return the actual items
+ }
+
+ private static buildDataUrl(filteringArgs: any, sortingArgs: any): string {
+ let baseQuery = `${DATA_URL}?$count=true&$top=1000`;
+ const parts: string[] = [];
+
+ const sortExpr = this.buildSortExpression(sortingArgs);
+ if (sortExpr) parts.push(sortExpr);
+
+ const filterExpr = this.buildFilterExpression(filteringArgs);
+ if (filterExpr) parts.push(filterExpr);
+
+ return `${baseQuery}&${parts.join('&')}`;
+ }
+
+ public static getColumnData( column: IgrColumn, columnExpTree: IgrFilteringExpressionsTree, done: (values: any[]) => void): void {
+ setTimeout(async () => {
+ const data = await RemoteService.getData();
+ const filteredData = this._filteringStrategy.filter(data, columnExpTree, null, null);
+ const columnValues = filteredData.map(record => record[column.field]);
+ done(columnValues);
+ }, 1000);
+ }
+
+ private static buildFilterExpression(filteringArgs: any): string {
+ if (!filteringArgs?.filteringOperands?.length) return '';
+
+ const expression = this.buildAdvancedFilterExpression(
+ filteringArgs.filteringOperands,
+ filteringArgs.operator
+ );
+
+ return expression ? `$filter=${expression}` : '';
+ }
+
+ private static buildAdvancedFilterExpression(operands: any[], operator: any): string {
+ let filterExpression = '';
+
+ operands.forEach((operand, index) => {
+ if (operand.filteringOperands) {
+ const subExpr = this.buildAdvancedFilterExpression(
+ operand.filteringOperands,
+ operand.operator
+ );
+ if (subExpr) {
+ if (index > 0) filterExpression += ` ${this.getFilteringLogic(operator)} `;
+ filterExpression += subExpr;
+ }
+ return;
+ }
+
+ const { fieldName, searchVal, condition } = operand;
+ if (searchVal === undefined || condition === undefined) return;
+
+ const isNumber = typeof searchVal === 'number';
+ const filterValue = isNumber ? searchVal : `'${searchVal}'`;
+ let filterPart = '';
+
+ if (index > 0) filterExpression += ` ${this.getFilteringLogic(operator)} `;
+
+ switch (condition.name) {
+ case 'contains':
+ filterPart = `contains(${fieldName}, ${filterValue})`;
+ break;
+ case 'startsWith':
+ filterPart = `startswith(${fieldName}, ${filterValue})`;
+ break;
+ case 'endsWith':
+ filterPart = `endswith(${fieldName}, ${filterValue})`;
+ break;
+ case 'equals':
+ filterPart = `${fieldName} eq ${filterValue}`;
+ break;
+ case 'doesNotEqual':
+ filterPart = `${fieldName} ne ${filterValue}`;
+ break;
+ case 'greaterThan':
+ filterPart = `${fieldName} gt ${filterValue}`;
+ break;
+ case 'greaterThanOrEqualTo':
+ filterPart = `${fieldName} ge ${filterValue}`;
+ break;
+ case 'lessThan':
+ filterPart = `${fieldName} lt ${filterValue}`;
+ break;
+ case 'lessThanOrEqualTo':
+ filterPart = `${fieldName} le ${filterValue}`;
+ break;
+ case 'null':
+ filterPart = `${fieldName} eq null`;
+ break;
+ case 'notNull':
+ filterPart = `${fieldName} ne null`;
+ break;
+ case 'empty':
+ filterPart = `length(${fieldName}) eq 0`;
+ break;
+ case 'notEmpty':
+ filterPart = `length(${fieldName}) gt 0`;
+ break;
+ }
+
+ filterExpression += filterPart;
+ });
+
+ return filterExpression;
+ }
+
+ private static buildSortExpression(sortingArgs: any[]): string {
+ if (!sortingArgs || sortingArgs.length === 0) return '';
+
+ const sortStrings = sortingArgs.map(sort => {
+ const dir = sort.dir === 2 ? 'desc' : 'asc'; // 1 = asc, 2 = desc
+ return `${sort.fieldName} ${dir}`;
+ });
+
+ return sortStrings.length > 0 ? `$orderby=${sortStrings.join(',')}` : '';
+ }
+
+ private static getFilteringLogic(operator: any): string {
+ switch (operator) {
+ case 0: return 'and';
+ case 1: return 'or';
+ default: return 'and';
+ }
+ }
+
+
+
+
+}
diff --git a/samples/grids/grid/unique-columns-value/src/index.css b/samples/grids/grid/unique-columns-value/src/index.css
new file mode 100644
index 0000000000..98682b8543
--- /dev/null
+++ b/samples/grids/grid/unique-columns-value/src/index.css
@@ -0,0 +1,2 @@
+/* shared styles are loaded from: */
+/* https://static.infragistics.com/xplatform/css/samples */
diff --git a/samples/grids/grid/unique-columns-value/src/index.tsx b/samples/grids/grid/unique-columns-value/src/index.tsx
new file mode 100644
index 0000000000..666ec568ef
--- /dev/null
+++ b/samples/grids/grid/unique-columns-value/src/index.tsx
@@ -0,0 +1,82 @@
+import React, { useEffect, useState, useRef } from 'react';
+import ReactDOM from 'react-dom/client';
+import { RemoteService } from './RemoteService'
+import { IgrGrid, IgrColumn, IgrFilteringExpressionsTree } from 'igniteui-react-grids';
+import { IgrSortingExpressionEventArgs, IgrFilteringExpressionsTreeEventArgs } from 'igniteui-react-grids';
+import 'igniteui-react-grids/grids/themes/light/bootstrap.css';
+
+const RemoteFilteringGrid = () => {
+ const [data, setData] = useState([]);
+ const [isLoading, setIsLoading] = useState(false);
+ const debounceRef = useRef(null);
+
+ const fetchData = async (filterExpressions: any = null, sortExpressions: any[] = []) => {
+ try {
+ setIsLoading(true);
+ const result = await RemoteService.getData(filterExpressions, sortExpressions);
+ setData(result);
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleSortingExpressionsChange = (event: IgrSortingExpressionEventArgs) => {
+ const sortExpressions = event.detail;
+
+ if (debounceRef.current) clearTimeout(debounceRef.current);
+ debounceRef.current = setTimeout(() => {
+ fetchData(null, sortExpressions);
+ }, 300);
+ };
+
+ const handleFilteringExpressionsTreeChange = (event: IgrFilteringExpressionsTreeEventArgs) => {
+ const filterExpressions = event.detail;
+
+ if (debounceRef.current) clearTimeout(debounceRef.current);
+ debounceRef.current = setTimeout(() => {
+ fetchData(filterExpressions, []);
+ }, 500);
+ };
+
+ const columnValuesStrategy = (column: IgrColumn, columnExpTree: IgrFilteringExpressionsTree, done: (values: any[]) => void) => {
+ RemoteService.getColumnData(column, columnExpTree, done);
+ };
+
+ useEffect(() => {
+ fetchData();
+ }, []);
+
+ return (
+
+
Remote Filtering & Sorting Grid
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+// rendering above function to the React DOM
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render();
+
+export default RemoteFilteringGrid;
diff --git a/samples/grids/grid/unique-columns-value/src/react-app-env.d.ts b/samples/grids/grid/unique-columns-value/src/react-app-env.d.ts
new file mode 100644
index 0000000000..6431bc5fc6
--- /dev/null
+++ b/samples/grids/grid/unique-columns-value/src/react-app-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/samples/grids/grid/unique-columns-value/tsconfig.json b/samples/grids/grid/unique-columns-value/tsconfig.json
new file mode 100644
index 0000000000..8c0d146f95
--- /dev/null
+++ b/samples/grids/grid/unique-columns-value/tsconfig.json
@@ -0,0 +1,44 @@
+{
+ "compilerOptions": {
+ "resolveJsonModule": true,
+ "esModuleInterop": true,
+ "baseUrl": ".",
+ "outDir": "build/dist",
+ "module": "esnext",
+ "target": "es5",
+ "lib": [
+ "es6",
+ "dom"
+ ],
+ "sourceMap": true,
+ "allowJs": true,
+ "jsx": "react-jsx",
+ "moduleResolution": "node",
+ "rootDir": "src",
+ "forceConsistentCasingInFileNames": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": true,
+ "noImplicitAny": true,
+ "noUnusedLocals": false,
+ "importHelpers": true,
+ "allowSyntheticDefaultImports": true,
+ "skipLibCheck": true,
+ "strict": false,
+ "isolatedModules": true,
+ "noEmit": true
+ },
+ "exclude": [
+ "node_modules",
+ "build",
+ "scripts",
+ "acceptance-tests",
+ "webpack",
+ "jest",
+ "src/setupTests.ts",
+ "**/odatajs-4.0.0.js",
+ "config-overrides.js"
+ ],
+ "include": [
+ "src"
+ ]
+}