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/RemoteService.ts b/samples/grids/grid/filtering-remote/src/RemoteService.ts
new file mode 100644
index 0000000000..a1692e5076
--- /dev/null
+++ b/samples/grids/grid/filtering-remote/src/RemoteService.ts
@@ -0,0 +1,188 @@
+const DATA_URL = 'https://services.odata.org/V4/Northwind/Northwind.svc/Products';
+
+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 enum LOGICAL_OPERATOR {
+ AND = 0,
+ OR = 1
+}
+
+export enum SORT_DIRECTION {
+ ASC = 1,
+ DESC = 2
+}
+
+interface FilterCondition {
+ name: string;
+}
+
+interface FilterOperand {
+ fieldName: string;
+ searchVal: string | number;
+ condition: FilterCondition;
+ filteringOperands?: FilterOperand[];
+ operator?: LOGICAL_OPERATOR;
+}
+
+interface FilteringArgs {
+ filteringOperands: FilterOperand[];
+ operator: LOGICAL_OPERATOR;
+}
+
+interface SortingArgs {
+ fieldName: string;
+ dir: SORT_DIRECTION;
+}
+
+export class RemoteService {
+ public static getData(
+ filteringArgs?: FilteringArgs,
+ sortingArgs?: SortingArgs[]
+ ): Promise {
+ const url = this.buildDataUrl(filteringArgs, sortingArgs);
+ return fetch(url)
+ .then((response) => {
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ return response.json();
+ })
+ .then((data) => data.value || [])
+ .catch((error): any[] => {
+ console.error('Error fetching data:', error);
+ return [];
+ });
+ }
+
+ private static buildDataUrl(filteringArgs?: FilteringArgs, sortingArgs?: SortingArgs[]): string {
+ const baseQuery = `${DATA_URL}?$count=true&$top=1000`;
+ const parts: string[] = [];
+
+ if (sortingArgs && sortingArgs.length > 0) {
+ const sortExpr = this.buildSortExpression(sortingArgs);
+ if (sortExpr) parts.push(sortExpr);
+ }
+
+ if (filteringArgs?.filteringOperands?.length) {
+ const filterExpr = this.buildFilterExpression(filteringArgs);
+ if (filterExpr) parts.push(filterExpr);
+ }
+
+ return parts.length > 0 ? `${baseQuery}&${parts.join('&')}` : baseQuery;
+ }
+
+ private static buildFilterExpression(filteringArgs: FilteringArgs): string {
+ if (!filteringArgs?.filteringOperands?.length) return '';
+
+ const expression = this.buildAdvancedFilterExpression(
+ filteringArgs.filteringOperands,
+ filteringArgs.operator
+ );
+
+ return expression ? `$filter=${expression}` : '';
+ }
+
+ private static buildAdvancedFilterExpression(operands: FilterOperand[], operator: LOGICAL_OPERATOR): string {
+ const filterParts: string[] = [];
+
+ operands.forEach((operand) => {
+ if (operand.filteringOperands && operand.filteringOperands.length > 0) {
+ const subExpr = this.buildAdvancedFilterExpression(
+ operand.filteringOperands,
+ operand.operator || LOGICAL_OPERATOR.AND
+ );
+ if (subExpr) {
+ filterParts.push(`(${subExpr})`);
+ }
+ return;
+ }
+
+ const { fieldName, searchVal, condition } = operand;
+ if (searchVal === undefined || condition === undefined || !fieldName) return;
+
+ const filterPart = this.buildSingleFilterExpression(fieldName, searchVal, condition.name);
+ if (filterPart) {
+ filterParts.push(filterPart);
+ }
+ });
+
+ const logicalOp = this.getFilteringLogic(operator);
+ return filterParts.join(` ${logicalOp} `);
+ }
+
+ private static buildSingleFilterExpression(fieldName: string, searchVal: string | number, conditionName: string): string {
+ // Input validation
+ if (!fieldName || searchVal === null || searchVal === undefined) {
+ return '';
+ }
+
+ const isNumber = typeof searchVal === 'number';
+ const filterValue = isNumber ? searchVal : `'${String(searchVal).replace(/'/g, "''")}'`;
+
+ switch (conditionName) {
+ case 'contains':
+ return `${FILTER_OPERATION.CONTAINS}(${fieldName}, ${filterValue})`;
+ case 'startsWith':
+ return `${FILTER_OPERATION.STARTS_WITH}(${fieldName}, ${filterValue})`;
+ case 'endsWith':
+ return `${FILTER_OPERATION.ENDS_WITH}(${fieldName}, ${filterValue})`;
+ case 'equals':
+ return `${fieldName} ${FILTER_OPERATION.EQUALS} ${filterValue}`;
+ case 'doesNotEqual':
+ return `${fieldName} ${FILTER_OPERATION.DOES_NOT_EQUAL} ${filterValue}`;
+ case 'greaterThan':
+ return `${fieldName} ${FILTER_OPERATION.GREATER_THAN} ${filterValue}`;
+ case 'greaterThanOrEqualTo':
+ return `${fieldName} ${FILTER_OPERATION.GREATER_THAN_EQUAL} ${filterValue}`;
+ case 'lessThan':
+ return `${fieldName} ${FILTER_OPERATION.LESS_THAN} ${filterValue}`;
+ case 'lessThanOrEqualTo':
+ return `${fieldName} ${FILTER_OPERATION.LESS_THAN_EQUAL} ${filterValue}`;
+ case 'null':
+ return `${fieldName} ${FILTER_OPERATION.EQUALS} null`;
+ case 'notNull':
+ return `${fieldName} ${FILTER_OPERATION.DOES_NOT_EQUAL} null`;
+ case 'empty':
+ return `length(${fieldName}) ${FILTER_OPERATION.EQUALS} 0`;
+ case 'notEmpty':
+ return `length(${fieldName}) ${FILTER_OPERATION.GREATER_THAN} 0`;
+ default:
+ console.warn(`Unknown filter condition: ${conditionName}`);
+ return '';
+ }
+ }
+
+ private static buildSortExpression(sortingArgs: SortingArgs[]): string {
+ if (!sortingArgs || sortingArgs.length === 0) return '';
+
+ const sortStrings = sortingArgs
+ .filter(sort => sort.fieldName) // filter out invalid entries
+ .map(sort => {
+ const dir = sort.dir === SORT_DIRECTION.DESC ? 'desc' : 'asc';
+ return `${sort.fieldName} ${dir}`;
+ });
+
+ return sortStrings.length > 0 ? `$orderby=${sortStrings.join(',')}` : '';
+ }
+
+ private static getFilteringLogic(operator: LOGICAL_OPERATOR): string {
+ switch (operator) {
+ case LOGICAL_OPERATOR.AND:
+ return 'and';
+ case LOGICAL_OPERATOR.OR:
+ 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..712ee43e4a
--- /dev/null
+++ b/samples/grids/grid/filtering-remote/src/index.tsx
@@ -0,0 +1,138 @@
+import React, { useEffect, useState, useRef, useCallback } from 'react';
+import ReactDOM from 'react-dom/client';
+import { RemoteService } from './RemoteService'
+import { IgrGrid, IgrColumn, GridColumnDataType } from 'igniteui-react-grids';
+import { IgrSortingExpressionEventArgs, IgrFilteringExpressionsTreeEventArgs } from 'igniteui-react-grids';
+import 'igniteui-react-grids/grids/themes/light/bootstrap.css';
+
+interface ProductData {
+ ProductID: number;
+ ProductName: string;
+ SupplierID: number;
+ CategoryID: number;
+ QuantityPerUnit: string;
+ UnitPrice: number;
+ UnitsInStock: number;
+ UnitsOnOrder: number;
+ ReorderLevel: number;
+ Discontinued: boolean;
+}
+
+interface ColumnConfig {
+ field: string;
+ header: string;
+ dataType: GridColumnDataType;
+}
+
+interface GridState {
+ filterExpressions: any;
+ sortExpressions: any[];
+}
+
+// Column configuration outside of component to avoid recreation on every render
+const columnConfig: ColumnConfig[] = [
+ { field: 'ProductID', header: 'Product ID', dataType: 'number' as GridColumnDataType },
+ { field: 'ProductName', header: 'Product Name', dataType: 'string' as GridColumnDataType },
+ { field: 'SupplierID', header: 'Supplier ID', dataType: 'number' as GridColumnDataType },
+ { field: 'CategoryID', header: 'Category ID', dataType: 'number' as GridColumnDataType },
+ { field: 'QuantityPerUnit', header: 'Quantity Per Unit', dataType: 'string' as GridColumnDataType },
+ { field: 'UnitPrice', header: 'Unit Price', dataType: 'number' as GridColumnDataType },
+ { field: 'UnitsInStock', header: 'Units In Stock', dataType: 'number' as GridColumnDataType },
+ { field: 'UnitsOnOrder', header: 'Units On Order', dataType: 'number' as GridColumnDataType },
+ { field: 'ReorderLevel', header: 'Reorder Level', dataType: 'number' as GridColumnDataType },
+ { field: 'Discontinued', header: 'Discontinued', dataType: 'boolean' as GridColumnDataType }
+];
+
+const RemoteFilteringGrid = () => {
+ const [data, setData] = useState([]);
+ const [isLoading, setIsLoading] = useState(false);
+ const [gridState, setGridState] = useState({
+ filterExpressions: null,
+ sortExpressions: []
+ });
+ const debounceRef = useRef(null);
+
+ const fetchData = useCallback((newGridState?: Partial) => {
+ const currentState = newGridState ? { ...gridState, ...newGridState } : gridState;
+
+ setIsLoading(true);
+ RemoteService.getData(
+ currentState.filterExpressions,
+ currentState.sortExpressions
+ )
+ .then((result) => {
+ setData(result);
+
+ if (newGridState) {
+ setGridState(currentState);
+ }
+ })
+ .catch((error) => {
+ console.error('Error fetching data:', error);
+ setData([]);
+ })
+ .finally(() => {
+ setIsLoading(false);
+ });
+ }, [gridState]);
+
+ const debouncedFetchData = useCallback((newGridState: Partial) => {
+ if (debounceRef.current) clearTimeout(debounceRef.current);
+ debounceRef.current = window.setTimeout(() => {
+ fetchData(newGridState);
+ }, 400);
+ }, [fetchData]);
+
+ const handleSortingExpressionsChange = useCallback((event: IgrSortingExpressionEventArgs) => {
+ const sortExpressions = event.detail;
+ debouncedFetchData({ sortExpressions });
+ }, [debouncedFetchData]);
+
+ const handleFilteringExpressionsTreeChange = useCallback((event: IgrFilteringExpressionsTreeEventArgs) => {
+ const filterExpressions = event.detail;
+ debouncedFetchData({ filterExpressions });
+ }, [debouncedFetchData]);
+
+ useEffect(() => {
+ fetchData();
+ }, [fetchData]);
+
+ useEffect(() => {
+ return () => {
+ if (debounceRef.current) {
+ clearTimeout(debounceRef.current);
+ }
+ };
+ }, []);
+
+ return (
+
+
Remote Filtering & Sorting Grid
+
+ {columnConfig.map((col) => (
+
+ ))}
+
+
+ );
+};
+
+// 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..90358fd948
--- /dev/null
+++ b/samples/grids/grid/remote-virtualization/src/RemoteService.ts
@@ -0,0 +1,115 @@
+const DATA_URL = 'https://services.odata.org/V4/Northwind/Northwind.svc/Products';
+
+interface CachedDataItem {
+ emptyRec?: boolean;
+ [key: string]: any;
+}
+
+interface ODataResponse {
+ '@odata.count': number;
+ value: any[];
+}
+
+export class RemoteService {
+ private static cachedData: CachedDataItem[] = [];
+ private static totalCount: number = 0;
+ private static isInitialized: boolean = false;
+
+ public static getData(skip: number = 0, take: number = 50): Promise {
+ try {
+ if (skip < 0 || take <= 0) {
+ console.warn('Invalid parameters: skip must be >= 0 and take must be > 0');
+ return Promise.resolve([]);
+ }
+
+ if (!this.isInitialized) {
+ return this.initializeCache().then(() => this.fetchDataFromCache(skip, take));
+ }
+
+ return this.fetchDataFromCache(skip, take);
+
+ } catch (error) {
+ console.error('Error in getData:', error);
+ return Promise.resolve([]);
+ }
+ }
+
+ public static getTotalCount(): number {
+ return this.totalCount;
+ }
+
+ public static clearCache(): void {
+ this.cachedData = [];
+ this.totalCount = 0;
+ this.isInitialized = false;
+ }
+
+ private static fetchDataFromCache(skip: number, take: number): Promise {
+ const slice = this.cachedData.slice(skip, skip + take);
+ const allLoaded = slice.every(row => row.emptyRec !== true);
+
+ if (allLoaded) {
+ return Promise.resolve(slice);
+ }
+ return this.fetchAndCacheData(skip, take);
+ }
+
+ private static initializeCache(): Promise {
+ const url = `${DATA_URL}?$count=true&$skip=0&$top=1`;
+
+ return fetch(url)
+ .then(response => {
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ return response.json();
+ })
+ .then((json: ODataResponse) => {
+ if (typeof json['@odata.count'] !== 'number') {
+ throw new Error('Invalid response: missing or invalid count');
+ }
+
+ this.totalCount = json['@odata.count'];
+ this.cachedData = new Array(this.totalCount).fill({ emptyRec: true });
+ this.isInitialized = true;
+ })
+ .catch(error => {
+ console.error('Error initializing cache:', error);
+ this.totalCount = 0;
+ this.cachedData = [];
+ this.isInitialized = false;
+ throw error;
+ });
+ }
+
+ private static fetchAndCacheData(skip: number, take: number): Promise {
+ const url = `${DATA_URL}?$count=true&$skip=${skip}&$top=${take}`;
+
+ return fetch(url)
+ .then(response => {
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ return response.json();
+ })
+ .then((json: ODataResponse) => {
+ if (!Array.isArray(json.value)) {
+ throw new Error('Invalid response: missing or invalid data array');
+ }
+
+ // Cache the fetched data
+ for (let i = 0; i < json.value.length; i++) {
+ const cacheIndex = skip + i;
+ if (cacheIndex < this.cachedData.length) {
+ this.cachedData[cacheIndex] = json.value[i];
+ }
+ }
+
+ return this.cachedData.slice(skip, skip + take);
+ })
+ .catch(error => {
+ console.error(`Error fetching data (skip: ${skip}, take: ${take}):`, error);
+ return this.cachedData.slice(skip, skip + take);
+ });
+ }
+}
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..80c0819083
--- /dev/null
+++ b/samples/grids/grid/remote-virtualization/src/index.tsx
@@ -0,0 +1,61 @@
+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 [totalCount, setTotalCount] = useState(0);
+ const [isLoading, setIsLoading] = useState(false);
+
+ const fetchData = (skip: number = 0, take: number = 50) => {
+ setIsLoading(true);
+ RemoteService.getData(skip, take)
+ .then(result => {
+ setData(result);
+ const count = RemoteService.getTotalCount();
+ setTotalCount(count);
+ setIsLoading(false);
+ })
+ .catch(error => {
+ console.error('Error fetching data:', error);
+ setIsLoading(false);
+ });
+ };
+
+ const handleDataPreLoad = (event: any) => {
+ const { startIndex, chunkSize } = event.detail;
+ fetchData(startIndex, chunkSize);
+ };
+
+ useEffect(() => {
+ fetchData(); // initial load
+ }, []);
+
+ return (
+
+
Remote Virtualization Grid ({totalCount.toLocaleString()} records)
+
+
+
+
+
+
+
+
+
+ );
+};
+
+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..48b446a113
--- /dev/null
+++ b/samples/grids/grid/unique-columns-value/src/RemoteService.ts
@@ -0,0 +1,236 @@
+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 enum LOGICAL_OPERATOR {
+ AND = 0,
+ OR = 1
+}
+
+export enum SORT_DIRECTION {
+ ASC = 1,
+ DESC = 2
+}
+
+interface FilterCondition {
+ name: string;
+}
+
+interface FilterOperand {
+ fieldName: string;
+ searchVal: string | number;
+ condition: FilterCondition;
+ filteringOperands?: FilterOperand[];
+ operator?: LOGICAL_OPERATOR;
+}
+
+interface FilteringArgs {
+ filteringOperands: FilterOperand[];
+ operator: LOGICAL_OPERATOR;
+}
+
+interface SortingArgs {
+ fieldName: string;
+ dir: SORT_DIRECTION;
+}
+
+interface ODataResponse {
+ '@odata.count'?: number;
+ value: any[];
+}
+
+/**
+ * RemoteService - Template for future uniqueColumnValuesStrategy functionality
+ *
+ * This service contains placeholder implementations that will be replaced
+ * when the official uniqueColumnValuesStrategy becomes available from
+ * the Infragistics package.
+ */
+export class RemoteService {
+ public static _filteringStrategy: IgrFilteringStrategy;
+
+ public static getData(
+ filteringArgs?: FilteringArgs,
+ sortingArgs?: SortingArgs[]
+ ): Promise {
+ const url = this.buildDataUrl(filteringArgs, sortingArgs);
+ return fetch(url)
+ .then((response) => {
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ return response.json();
+ })
+ .then((data: ODataResponse) => {
+ if (!Array.isArray(data.value)) {
+ throw new Error('Invalid response: missing or invalid data array');
+ }
+ return data.value;
+ })
+ .catch((error): any[] => {
+ console.error('Error fetching data:', error);
+ return [];
+ });
+ }
+
+ private static buildDataUrl(filteringArgs?: FilteringArgs, sortingArgs?: SortingArgs[]): string {
+ const baseQuery = `${DATA_URL}?$count=true&$top=1000`;
+ const parts: string[] = [];
+
+ if (sortingArgs && sortingArgs.length > 0) {
+ const sortExpr = this.buildSortExpression(sortingArgs);
+ if (sortExpr) parts.push(sortExpr);
+ }
+
+ if (filteringArgs?.filteringOperands?.length) {
+ const filterExpr = this.buildFilterExpression(filteringArgs);
+ if (filterExpr) parts.push(filterExpr);
+ }
+
+ return parts.length > 0 ? `${baseQuery}&${parts.join('&')}` : baseQuery;
+ }
+
+
+ public static getColumnData(column: IgrColumn, columnExpTree: IgrFilteringExpressionsTree, done: (values: any[]) => void): void {
+ setTimeout(() => {
+ RemoteService.getData()
+ .then(data => {
+ // TODO: Replace this with uniqueColumnValuesStrategy when available
+
+ if (!this._filteringStrategy) {
+ console.warn('Filtering strategy not initialized');
+ done([]);
+ return;
+ }
+ const filteredData = this._filteringStrategy.filter(data, columnExpTree, null, null);
+ const columnValues = filteredData.map((record: any) => record[column.field]);
+
+ const uniqueValues = Array.from(new Set(columnValues));
+ done(uniqueValues);
+ })
+ .catch(error => {
+ console.error('Error fetching column data:', error);
+ done([]);
+ });
+ }, 1000);
+ }
+
+ private static buildFilterExpression(filteringArgs: FilteringArgs): string {
+ if (!filteringArgs?.filteringOperands?.length) return '';
+
+ const expression = this.buildAdvancedFilterExpression(
+ filteringArgs.filteringOperands,
+ filteringArgs.operator
+ );
+
+ return expression ? `$filter=${expression}` : '';
+ }
+
+ private static buildAdvancedFilterExpression(operands: FilterOperand[], operator: LOGICAL_OPERATOR): string {
+ const filterParts: string[] = [];
+
+ operands.forEach((operand) => {
+ if (operand.filteringOperands && operand.filteringOperands.length > 0) {
+ const subExpr = this.buildAdvancedFilterExpression(
+ operand.filteringOperands,
+ operand.operator || LOGICAL_OPERATOR.AND
+ );
+ if (subExpr) {
+ filterParts.push(`(${subExpr})`);
+ }
+ return;
+ }
+
+ const { fieldName, searchVal, condition } = operand;
+ if (searchVal === undefined || condition === undefined || !fieldName) return;
+
+ const filterPart = this.buildSingleFilterExpression(fieldName, searchVal, condition.name);
+ if (filterPart) {
+ filterParts.push(filterPart);
+ }
+ });
+
+ const logicalOp = this.getFilteringLogic(operator);
+ return filterParts.join(` ${logicalOp} `);
+ }
+
+ private static buildSingleFilterExpression(fieldName: string, searchVal: string | number, conditionName: string): string {
+ // Input validation
+ if (!fieldName || searchVal === null || searchVal === undefined) {
+ return '';
+ }
+
+ const isNumber = typeof searchVal === 'number';
+ const filterValue = isNumber ? searchVal : `'${String(searchVal).replace(/'/g, "''")}'`;
+
+ switch (conditionName) {
+ case 'contains':
+ return `${FILTER_OPERATION.CONTAINS}(${fieldName}, ${filterValue})`;
+ case 'startsWith':
+ return `${FILTER_OPERATION.STARTS_WITH}(${fieldName}, ${filterValue})`;
+ case 'endsWith':
+ return `${FILTER_OPERATION.ENDS_WITH}(${fieldName}, ${filterValue})`;
+ case 'equals':
+ return `${fieldName} ${FILTER_OPERATION.EQUALS} ${filterValue}`;
+ case 'doesNotEqual':
+ return `${fieldName} ${FILTER_OPERATION.DOES_NOT_EQUAL} ${filterValue}`;
+ case 'greaterThan':
+ return `${fieldName} ${FILTER_OPERATION.GREATER_THAN} ${filterValue}`;
+ case 'greaterThanOrEqualTo':
+ return `${fieldName} ${FILTER_OPERATION.GREATER_THAN_EQUAL} ${filterValue}`;
+ case 'lessThan':
+ return `${fieldName} ${FILTER_OPERATION.LESS_THAN} ${filterValue}`;
+ case 'lessThanOrEqualTo':
+ return `${fieldName} ${FILTER_OPERATION.LESS_THAN_EQUAL} ${filterValue}`;
+ case 'null':
+ return `${fieldName} ${FILTER_OPERATION.EQUALS} null`;
+ case 'notNull':
+ return `${fieldName} ${FILTER_OPERATION.DOES_NOT_EQUAL} null`;
+ case 'empty':
+ return `length(${fieldName}) ${FILTER_OPERATION.EQUALS} 0`;
+ case 'notEmpty':
+ return `length(${fieldName}) ${FILTER_OPERATION.GREATER_THAN} 0`;
+ default:
+ console.warn(`Unknown filter condition: ${conditionName}`);
+ return '';
+ }
+ }
+
+ private static buildSortExpression(sortingArgs: SortingArgs[]): string {
+ if (!sortingArgs || sortingArgs.length === 0) return '';
+
+ const sortStrings = sortingArgs
+ .filter(sort => sort.fieldName) // filter out invalid entries
+ .map(sort => {
+ const dir = sort.dir === SORT_DIRECTION.DESC ? 'desc' : 'asc';
+ return `${sort.fieldName} ${dir}`;
+ });
+
+ return sortStrings.length > 0 ? `$orderby=${sortStrings.join(',')}` : '';
+ }
+
+ private static getFilteringLogic(operator: LOGICAL_OPERATOR): string {
+ switch (operator) {
+ case LOGICAL_OPERATOR.AND:
+ return 'and';
+ case LOGICAL_OPERATOR.OR:
+ 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..1d2aaeb653
--- /dev/null
+++ b/samples/grids/grid/unique-columns-value/src/index.tsx
@@ -0,0 +1,95 @@
+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 { IgrFilteringStrategy } 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);
+
+ // Initialize the filtering strategy for RemoteService
+ useEffect(() => {
+ RemoteService._filteringStrategy = new IgrFilteringStrategy();
+ }, []);
+
+ const fetchData = (filterExpressions: any = null, sortExpressions: any[] = []) => {
+ setIsLoading(true);
+
+ RemoteService.getData(filterExpressions, sortExpressions)
+ .then(result => {
+ setData(result);
+ setIsLoading(false);
+ })
+ .catch(error => {
+ console.error('Error fetching data:', error);
+ 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 with Unique Column Values
+
+ This sample demonstrates remote filtering and sorting. The uniqueColumnValuesStrategy
+ is commented out as it's a future feature from the Infragistics package.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+// 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"
+ ]
+}