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. + + + + + + View Docs + + + View Code + + + Run Sample + + + Run Sample + + + + +## 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. + + + + + + View Docs + + + View Code + + + Run Sample + + + Run Sample + + + + +## 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. + + + + + + View Docs + + + View Code + + + Run Sample + + + Run Sample + + + + +## 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" + ] +}