From 182c3570b2f3f901bda576b6853897e9330bafe2 Mon Sep 17 00:00:00 2001 From: ttonev Date: Fri, 11 Jul 2025 09:57:19 +0300 Subject: [PATCH 1/5] Remote filtering initial sample --- samples/grids/grid/filtering-options.7z | Bin 0 -> 5050 bytes .../grids/grid/filtering-remote/.eslintrc.js | 78 +++ samples/grids/grid/filtering-remote/ReadMe.md | 56 +++ .../grids/grid/filtering-remote/package.json | 48 ++ .../grid/filtering-remote/public/index.html | 11 + .../grid/filtering-remote/sandbox.config.json | 5 + .../grid/filtering-remote/src/NwindData.json | 458 ++++++++++++++++++ .../filtering-remote/src/RemoteService.ts | 18 + .../grids/grid/filtering-remote/src/index.css | 2 + .../grids/grid/filtering-remote/src/index.tsx | 54 +++ .../filtering-remote/src/react-app-env.d.ts | 1 + .../grids/grid/filtering-remote/tsconfig.json | 45 ++ 12 files changed, 776 insertions(+) create mode 100644 samples/grids/grid/filtering-options.7z create mode 100644 samples/grids/grid/filtering-remote/.eslintrc.js create mode 100644 samples/grids/grid/filtering-remote/ReadMe.md create mode 100644 samples/grids/grid/filtering-remote/package.json create mode 100644 samples/grids/grid/filtering-remote/public/index.html create mode 100644 samples/grids/grid/filtering-remote/sandbox.config.json create mode 100644 samples/grids/grid/filtering-remote/src/NwindData.json create mode 100644 samples/grids/grid/filtering-remote/src/RemoteService.ts create mode 100644 samples/grids/grid/filtering-remote/src/index.css create mode 100644 samples/grids/grid/filtering-remote/src/index.tsx create mode 100644 samples/grids/grid/filtering-remote/src/react-app-env.d.ts create mode 100644 samples/grids/grid/filtering-remote/tsconfig.json diff --git a/samples/grids/grid/filtering-options.7z b/samples/grids/grid/filtering-options.7z new file mode 100644 index 0000000000000000000000000000000000000000..323f415422451c93fb627ff61c979b0d05adaf2a GIT binary patch literal 5050 zcmV;r6GiMddc3bE8~_AL2y=sX6951J0000Z000000002I$77J-RP7QHT>uy0e9&~j z2RR(PEhS0SyGaTNrSY(gB@+}9UMrm7NJ!=78dwcvyB!=+i)nztG!f(??{7g%QJkO< z20!?v_|fNm5Vrh$ROc`gWyr#btSzZT)Wb}w zap&qnJOL_#-YFu1(qEwwD{^Mt#9y@Lx|%R1U8{V`-Tg)tidW9LEI9y&wP@-qF*sXV z8xTKhW@?hE+38Cm5QK?V51TP=8SWP?{IVxgj$z#f`=aDc;y`X$5AiZh!?xLB&b-mD z7{vhRyxgsb$in@sP!0p5h9N{FroN*u9k~ltWS&@oRU{J5w%2qH*<6voe19y!#2JQ9a z%Mi1X=+d2wrZio8nL3x{Hh`5mQhZt^MCR4x4DWe7&R$KTZ<47Ia|9u7mic3^37}7R z*Vv>J9NPc5^aG-wgm&B`7ME}boO|e1lhT$GtA*sLMED`AC?<@)8%@Ps_J1Y9Mkhh6 zC=Vt-{Ae`FUgVB}WT@&A&P6$*SPf%&ifYrj`xFO}(=_7MscS_kZ<5GYq#$1wAwS(V zde81MGMGC?w;yY7kySl{2Z+;-GCCK|aqk~}DwMNfT-Qo1fB#xqyTfI|ED;K`r)@|* z?)})npqEN`=i3c;ohYX8{Z+U~0Pw4GgYElQW)82Y(PC#}Y@FqyCk6HAd93Ph2C)ib zlgP6i#PO$)uQC=A`3+P6~h0N3{FU$?3UxS6`gzkDH`Mj6mu>CZjkW1 zyl$9CeJl1av}q`I)Bcb+0%A{;KvtI(?@0SJxK+7VgbBvEI0IU2*5DEb0^8E6hIX%3E)dzh8xevt4N)$eVR;4CX2=7I ze-~BXQhTb}U9Ep%@FaMrcMiNV{b-xvk7sNf6j_=quh4<2cgB<)|v_S^l7%GvH_&(GRgh-W5ItF$1U>1fyxtFCLTU(OUk5#jKx( z&g%ar*>z&cd37kT>N>BN96?N@Cg$bsvrj#Q#M=n9?g*f%8q zKUS%yP@ObSk6KTqXELrQv6Bgb9A;3Xkr&1__N5e|7r7jAN2M$zDz*Bqo5d!7Cj6Cb z9l3dAehG>57@qh3h&$5Oyy%#d#uIBnk>YoQ6Ktu1WpP0l{8bDCg#u)PCzC@~?sicUYE%VYv&t4r9XE6PyLj7&?H;98AYD)@~A{j zm^2Z>0H;K**C`lHPvR608ncFvU5Oagl3>ctTS9k_)f(jPN(|p&RSi7Rmm~C}q3E-g zGOq%ArJE_JbT=Bd;emC$^}ns<0?qHPArdVel|)X(Z*R%2 zKC!TZfWpS&<)v6%S9XE$lX{{kfK9s)D_5YW<{FH{hlj#fVQTOEIUup&cz~eR7Z&qS@Ep3&0s5y*I0e$e|Pf54g)~{{%ntUrahTl zIIqULh;}JmDxxiulLIpVBHEJR(C zv>Pd95fnj%x9aR=n*CGf@XGz&drCaTu2d^Sc@YO)m(uQyo4>MI_mJN|hcNJec@@9J z=J8U*6z6ghH`kxP{mEl7l#;x((Zv$DD-}kiqLm~g)UD;1Hp9O+J_Ha5!!s&z$Vn#t z^U^b>VU%P#IkC|1r3TAtUUL2Ri95wjS19*@QA-8G;=ICt?&g}71{Od!Q>tSW?>i2BB1c5ukFBf6S;ZR<*+(KTas7-uL5>cf#3pvMB~zdg5ypBAOr{;OZ(8u&$Ym>^u0$&E{Rs6@sqNxg^p1o14Pn!d`~`!|hE>g1+z|uDd$8D*s{* z_JR=rq6mJ-klfTkDA6=EpU-W3NwJjkqVk*ii2^=|Px;v$j2?%snl~r`xX|!}6UVfo zad!t{VDwTSw2&;{PAj|%Ku4bER`V+@Vd2|E0022&PPjD$==1TgvMo``7*B}UNnM#- z4)jPYWEMB9k*mJs=qFVChfQa$X*7oNQqI-+Bc|SOFtIJst?Rhnz9<-GjztDv^G7h& z)8Ufenp8sLs#}4_?}+y=rV~oEfD1O;IViZ>0ZOKRi$+fux)t-W!vm8GYYURQJ3uh5;pZ!bE=*B$6_?}3Erw|$TxE@|L`bwJXdZSHF>QFou>M6f zB&AY*1n-p~nLLSQvMsF%h8r0v|96`jcMf8JB%i3T+`NY2kELA8A8)#&+HoVga4f_v z5HX+qV(NrE1`>V?OT(7SuX;;Y);0wM zxdiDGc9U-u+(%P&SR-)G~W$qV( zU-bl{?=lwfW-FR_naZQ^St@N>{djB4D&$}OuBy{1O7k($aMB4w{<>hn2J zRrf9XQq=|$Ei{$+rqWBc_@p~fbyDEtmlS8ld0l1dhF4_XJ_ue!*q4aJ15x1aL@z@1 zbRT%5dTxPh@06_nM-&%BR#&h|zqrP9O`5zWpLkRN4LdwfqQ@7G%w>`Y$cA*;1U;Zr z_8@SI3vQ^cS&B?NSXc00nnfM82*<@?-%k)-socLbbd(#CZdfyGZf}3_&0h)y6l}uO z(#bv#GvpYrD4Edeq_0B>MlGv&ph+CHbW>0@2AZpv|51Q|(b;E z%DwEptyZR}r7FWq}xogZqs+t|xp=0duRZ`<5e*2~x-wa0$H zoFdrFe#06GRhbaMYbM?SEvWo5{QqvbVRg;lm9|0iuN5S9vh_F-Fx>@A3EF}|=IM%a z3^+1PS589JdDD_u;TxWTW-xcpB?4<9xI* z3}05ZGWKi*nzEu9Cq6g%?Ry+d*_*2pouf_IqSW^UtDSr^vR{*8Kf>?-nM@ZiBZ94r z{jBi&12iN&P($T4jjo!l7N?%IE5UR#agawu3|yGCOX025PX*CDP60UfZ;7j3fia;k160g;3yokPD3{I7mU6&(F2Y#eyqqRe5Gj0=i|zEVr+);S*2 z`cedw_dA*zM1Pl$9Z@&~=GdhZoA3=YO2V{Jv-a6#S-lBKCpbxfpHJw46E7t;gGR!bxbEZx?110oV$W81!L=$aSIp+?vl*h=3SzPkPCs> ze))O+9p%lH-D_%|qRaUfEoMY_O_s{piOU>ty1*qlS8;lO)*ZO$@S_fK=*bg~C~GAVuyhNqsUmZLI-B$Jd)@==ny_Tw^|hW(RYW2; zhlQ0*$29GpXd{R7|JLh0vB-9=@!~EU%|< zf%nx&)Q-c8$C)QPZ1we&&Of$^a-ydJgi96V>*R#H?4le4M_Bjd97rb-3#!o=#UCIL z2m+!_W-e$MjA!ly@fhLYJ2Yqo-q%a7-3t&cBy5?i@&Uyslmvp@W}OP+i!A9V+C+u_ z1Z0T6)~BiLiK(nylJA)p`e)o@4JwJm7Ql#VG&}Sm%Gu}^-?`?-<)EIoW4ZxNhQ!U{ z&t^9Y1rkZv7wcPD6C=GfhYuVFBC7Z_*780av0vd&G6z>3VjIR_TnGsQ5)Nl z%>g~|rdk1bItNhp3WeOims4Bv7MQW#Jp%DPOFWH0CJ-~c`T}9>ZEoz+25w*n4gV>6 z;4r`X5c1^5|P?5;5Q?j&zkJx4z0)}>QCE^bb z@d=1{2*lH=4c#%5856`M#0AX7%f5I+sKL5u`+8gVSJsmc(t#EeOw?GKihj#MD#AG6 zbOJ;2)vL+LJN?u>5B!6BDq*P>8L;MgsPA%$>#$qb~I{9fbxG20kC=d_3MEeH>MY#G$>0001iGY75@ z)$?u7xyEMNjq~l$ij|4PB$*%D@WtJoPgyD*vSw`zK_A0r=WU8oK}qe*@k4&Aq^kDv zY9W2J;?c-P#7}I$mqb&=@$l$VCrMo$PIV?3lV(uiT8N@3!dcM>YWJnYp6o505SA3V z^XzCX?p5{xBL8%PZ{LVkX*-?sI<-W|&RxeIX2Luzw8`i6v30o-vFjU?44I0W$$ymr z2jeLM#FOC6hio`XMpd8OHhfitISfC_)}JC!mwl(ZAhX2T#w?1Vu2e8xaG`GDa?jS( z2`&SWFVb29OHm8z`}P=X#<+hvI``M5w2xADHxsc4T7g!c0`K9dlsarc6C}fVAYcGsdM~6lt@$imVTk$jwmDvzj8FT?<%R@+PRd zJxgRK(5)lD=aSedtb*NatH6@GIDo){bn*f__s$mvk{SUCfn5Lx3jqKDBLe{e1zi9T Q000bxG715-H_d4P0OTpRL;wH) literal 0 HcmV?d00001 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..daa575de4f --- /dev/null +++ b/samples/grids/grid/filtering-remote/src/RemoteService.ts @@ -0,0 +1,18 @@ +const CUSTOMERS_URL = `https://data-northwind.indigo.design/Customers/GetCustomersWithPage`; + +export class RemoteService { + public static getDataWithFilter(filterText: string = '') { + const url = this.buildUrl(CUSTOMERS_URL, filterText); + return fetch(url).then((res) => res.json()); + } + + private static buildUrl(baseUrl: string, filterText?: string) { + const params = new URLSearchParams(); + + if (filterText) { + params.append("name", filterText); // Adjust "name" if your backend expects a different filter key + } + + return `${baseUrl}?${params.toString()}`; + } +} 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..ceffbd9c19 --- /dev/null +++ b/samples/grids/grid/filtering-remote/src/index.tsx @@ -0,0 +1,54 @@ +import React, { useEffect, useState, useRef } from 'react'; +import { RemoteService } from './RemoteService' + +import { IgrGrid, IgrColumn } from 'igniteui-react-grids'; + +import 'igniteui-react-grids/grids/themes/light/bootstrap.css'; + +const RemoteFilteringGrid = () => { + const [data, setData] = useState([]); + const [filterText, setFilterText] = useState(''); + const debounceRef = useRef(null); + + useEffect(() => { + fetchData(''); + }, []); + + useEffect(() => { + if (debounceRef.current) clearTimeout(debounceRef.current); + debounceRef.current = setTimeout(() => { + fetchData(filterText); + }, 500); + }, [filterText]); + + const fetchData = async (filter: string) => { + try { + const result = await RemoteService.getDataWithFilter(filter); + setData(result); + } catch (error) { + console.error('Error fetching filtered data:', error); + } + }; + + return ( +
+

Remote Filtering Grid

+ setFilterText(e.target.value)} + style={{ padding: '8px', marginBottom: '10px', width: '300px' }} + /> + + + + + + + +
+ ); +}; + +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" + ] +} From fde6f3506709209d0c0c792bdc5cc2fb8e0d8b37 Mon Sep 17 00:00:00 2001 From: ttonev Date: Thu, 17 Jul 2025 11:43:18 +0300 Subject: [PATCH 2/5] added extra remote samples --- .../filtering-remote/src/RemoteService.ts | 147 +++++- .../grids/grid/filtering-remote/src/index.tsx | 85 ++-- .../remote-paging-grid-templated/.eslintrc.js | 76 +++ .../remote-paging-grid-templated/ReadMe.md | 56 +++ .../remote-paging-grid-templated/package.json | 50 ++ .../public/index.html | 11 + .../sandbox.config.json | 5 + .../src/CustomersWithPageResponseModel.ts | 7 + .../src/NwindData.json | 458 ++++++++++++++++++ .../src/RemotePagingService.ts | 28 ++ .../src/index.css | 2 + .../src/index.tsx | 78 +++ .../src/react-app-env.d.ts | 1 + .../tsconfig.json | 44 ++ .../grid/remote-virtualization/.eslintrc.js | 78 +++ .../grid/remote-virtualization/ReadMe.md | 56 +++ .../grid/remote-virtualization/package.json | 48 ++ .../remote-virtualization/public/index.html | 11 + .../remote-virtualization/sandbox.config.json | 5 + .../remote-virtualization/src/NwindData.json | 458 ++++++++++++++++++ .../src/RemoteService.ts | 32 ++ .../grid/remote-virtualization/src/index.css | 2 + .../grid/remote-virtualization/src/index.tsx | 51 ++ .../src/react-app-env.d.ts | 1 + .../grid/remote-virtualization/tsconfig.json | 45 ++ 25 files changed, 1793 insertions(+), 42 deletions(-) create mode 100644 samples/grids/grid/remote-paging-grid-templated/.eslintrc.js create mode 100644 samples/grids/grid/remote-paging-grid-templated/ReadMe.md create mode 100644 samples/grids/grid/remote-paging-grid-templated/package.json create mode 100644 samples/grids/grid/remote-paging-grid-templated/public/index.html create mode 100644 samples/grids/grid/remote-paging-grid-templated/sandbox.config.json create mode 100644 samples/grids/grid/remote-paging-grid-templated/src/CustomersWithPageResponseModel.ts create mode 100644 samples/grids/grid/remote-paging-grid-templated/src/NwindData.json create mode 100644 samples/grids/grid/remote-paging-grid-templated/src/RemotePagingService.ts create mode 100644 samples/grids/grid/remote-paging-grid-templated/src/index.css create mode 100644 samples/grids/grid/remote-paging-grid-templated/src/index.tsx create mode 100644 samples/grids/grid/remote-paging-grid-templated/src/react-app-env.d.ts create mode 100644 samples/grids/grid/remote-paging-grid-templated/tsconfig.json create mode 100644 samples/grids/grid/remote-virtualization/.eslintrc.js create mode 100644 samples/grids/grid/remote-virtualization/ReadMe.md create mode 100644 samples/grids/grid/remote-virtualization/package.json create mode 100644 samples/grids/grid/remote-virtualization/public/index.html create mode 100644 samples/grids/grid/remote-virtualization/sandbox.config.json create mode 100644 samples/grids/grid/remote-virtualization/src/NwindData.json create mode 100644 samples/grids/grid/remote-virtualization/src/RemoteService.ts create mode 100644 samples/grids/grid/remote-virtualization/src/index.css create mode 100644 samples/grids/grid/remote-virtualization/src/index.tsx create mode 100644 samples/grids/grid/remote-virtualization/src/react-app-env.d.ts create mode 100644 samples/grids/grid/remote-virtualization/tsconfig.json diff --git a/samples/grids/grid/filtering-remote/src/RemoteService.ts b/samples/grids/grid/filtering-remote/src/RemoteService.ts index daa575de4f..1fc3ae35f8 100644 --- a/samples/grids/grid/filtering-remote/src/RemoteService.ts +++ b/samples/grids/grid/filtering-remote/src/RemoteService.ts @@ -1,18 +1,143 @@ -const CUSTOMERS_URL = `https://data-northwind.indigo.design/Customers/GetCustomersWithPage`; +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 getDataWithFilter(filterText: string = '') { - const url = this.buildUrl(CUSTOMERS_URL, filterText); - return fetch(url).then((res) => res.json()); - } + 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); - private static buildUrl(baseUrl: string, filterText?: string) { - const params = new URLSearchParams(); + return `${baseQuery}&${parts.join('&')}`; + } + + private static buildFilterExpression(filteringArgs: any): string { + if (!filteringArgs?.filteringOperands?.length) return ''; + + const expression = this.buildAdvancedFilterExpression( + filteringArgs.filteringOperands, + filteringArgs.operator + ); - if (filterText) { - params.append("name", filterText); // Adjust "name" if your backend expects a different filter key + return expression ? `$filter=${expression}` : ''; } - return `${baseUrl}?${params.toString()}`; - } + 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.tsx b/samples/grids/grid/filtering-remote/src/index.tsx index ceffbd9c19..6c8fd62a70 100644 --- a/samples/grids/grid/filtering-remote/src/index.tsx +++ b/samples/grids/grid/filtering-remote/src/index.tsx @@ -1,54 +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 [filterText, setFilterText] = useState(''); + const [isLoading, setIsLoading] = useState(false); const debounceRef = useRef(null); - useEffect(() => { - fetchData(''); - }, []); - - useEffect(() => { - if (debounceRef.current) clearTimeout(debounceRef.current); - debounceRef.current = setTimeout(() => { - fetchData(filterText); - }, 500); - }, [filterText]); - - const fetchData = async (filter: string) => { + const fetchData = async (filterExpressions: any = null, sortExpressions: any[] = []) => { try { - const result = await RemoteService.getDataWithFilter(filter); + setIsLoading(true); + const result = await RemoteService.getData(filterExpressions, sortExpressions); setData(result); } catch (error) { - console.error('Error fetching filtered data:', 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 Grid

- setFilterText(e.target.value)} - style={{ padding: '8px', marginBottom: '10px', width: '300px' }} - /> - - - - - - +

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/remote-paging-grid-templated/.eslintrc.js b/samples/grids/grid/remote-paging-grid-templated/.eslintrc.js new file mode 100644 index 0000000000..9d283f6407 --- /dev/null +++ b/samples/grids/grid/remote-paging-grid-templated/.eslintrc.js @@ -0,0 +1,76 @@ +// 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", + "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-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-paging-grid-templated/ReadMe.md b/samples/grids/grid/remote-paging-grid-templated/ReadMe.md new file mode 100644 index 0000000000..5e1cd1f53e --- /dev/null +++ b/samples/grids/grid/remote-paging-grid-templated/ReadMe.md @@ -0,0 +1,56 @@ + + + +This folder contains implementation of React application with example of Remote Paging 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/remote-paging-grid +``` + +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-paging-grid-templated/package.json b/samples/grids/grid/remote-paging-grid-templated/package.json new file mode 100644 index 0000000000..20922f0db4 --- /dev/null +++ b/samples/grids/grid/remote-paging-grid-templated/package.json @@ -0,0 +1,50 @@ +{ + "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-datasources": "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", + "rxjs": "^7.8.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-paging-grid-templated/public/index.html b/samples/grids/grid/remote-paging-grid-templated/public/index.html new file mode 100644 index 0000000000..e2d3265576 --- /dev/null +++ b/samples/grids/grid/remote-paging-grid-templated/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-paging-grid-templated/sandbox.config.json b/samples/grids/grid/remote-paging-grid-templated/sandbox.config.json new file mode 100644 index 0000000000..07f53508eb --- /dev/null +++ b/samples/grids/grid/remote-paging-grid-templated/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-paging-grid-templated/src/CustomersWithPageResponseModel.ts b/samples/grids/grid/remote-paging-grid-templated/src/CustomersWithPageResponseModel.ts new file mode 100644 index 0000000000..6976b77c4d --- /dev/null +++ b/samples/grids/grid/remote-paging-grid-templated/src/CustomersWithPageResponseModel.ts @@ -0,0 +1,7 @@ +export interface CustomersWithPageResponseModel { + items: any[]; + totalRecordsCount: number; + pageSize: number; + pageNumber: number; + totalPages: number; +} \ No newline at end of file diff --git a/samples/grids/grid/remote-paging-grid-templated/src/NwindData.json b/samples/grids/grid/remote-paging-grid-templated/src/NwindData.json new file mode 100644 index 0000000000..c00b03ec8d --- /dev/null +++ b/samples/grids/grid/remote-paging-grid-templated/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-paging-grid-templated/src/RemotePagingService.ts b/samples/grids/grid/remote-paging-grid-templated/src/RemotePagingService.ts new file mode 100644 index 0000000000..8a80591226 --- /dev/null +++ b/samples/grids/grid/remote-paging-grid-templated/src/RemotePagingService.ts @@ -0,0 +1,28 @@ +const CUSTOMERS_URL = `https://data-northwind.indigo.design/Customers/GetCustomersWithPage`; + +export class RemoteService { + + public static getDataWithPaging(pageIndex?: number, pageSize?: number) { + return fetch(this.buildUrl(CUSTOMERS_URL, pageIndex, pageSize)) + .then((result) => result.json()); + } + + private static buildUrl(baseUrl: string, pageIndex?: number, pageSize?: number) { + let qS = ""; + if (baseUrl) { + qS += `${baseUrl}`; + } + + // Add pageIndex and size to the query string if they are defined + if (pageIndex !== undefined) { + qS += `?pageIndex=${pageIndex}`; + if (pageSize !== undefined) { + qS += `&size=${pageSize}`; + } + } else if (pageSize !== undefined) { + qS += `?perPage=${pageSize}`; + } + + return `${qS}`; + } +} \ No newline at end of file diff --git a/samples/grids/grid/remote-paging-grid-templated/src/index.css b/samples/grids/grid/remote-paging-grid-templated/src/index.css new file mode 100644 index 0000000000..98682b8543 --- /dev/null +++ b/samples/grids/grid/remote-paging-grid-templated/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-paging-grid-templated/src/index.tsx b/samples/grids/grid/remote-paging-grid-templated/src/index.tsx new file mode 100644 index 0000000000..617662ac10 --- /dev/null +++ b/samples/grids/grid/remote-paging-grid-templated/src/index.tsx @@ -0,0 +1,78 @@ +import React, { useEffect, useRef, useState } from "react"; +import ReactDOM from "react-dom/client"; +import { IgrGrid, IgrPaginator } from "igniteui-react-grids"; +import { IgrColumn } from "igniteui-react-grids"; +import "igniteui-react-grids/grids/combined"; +import "igniteui-react-grids/grids/themes/light/bootstrap.css"; +import { RemoteService } from "./RemotePagingService"; +import { CustomersWithPageResponseModel } from "./CustomersWithPageResponseModel"; +import { IgrNumberEventArgs } from "igniteui-react"; + +export default function App() { + const grid = useRef(null); + const paginator = useRef(null); + const [data, setData] = useState([]); + const [page, setPage] = useState(0); + const [perPage, setPerPage] = useState(15); + + useEffect(() => { + loadGridData(page, perPage); + }, [page, perPage]); + + function loadGridData(pageIndex?: number, pageSize?: number) { + grid.current.isLoading = true; + + RemoteService.getDataWithPaging(pageIndex, pageSize) + .then((response: CustomersWithPageResponseModel) => { + setData(response.items); + grid.current.isLoading = false; + paginator.current.totalRecords = response.totalRecordsCount; + }) + .catch((error) => { + console.error(error.message); + setData([]); + grid.current.isLoading = false; + }); + } + + function onPageNumberChange(args: IgrNumberEventArgs) { + setPage(args.detail); + } + + function onPageSizeChange(args: IgrNumberEventArgs) { + setPerPage(args.detail); + } + + return ( +
+
+ + + + + + + + + + + +
+
+ ); +} + +// Render the component +const root = ReactDOM.createRoot(document.getElementById("root")); +root.render(); diff --git a/samples/grids/grid/remote-paging-grid-templated/src/react-app-env.d.ts b/samples/grids/grid/remote-paging-grid-templated/src/react-app-env.d.ts new file mode 100644 index 0000000000..6431bc5fc6 --- /dev/null +++ b/samples/grids/grid/remote-paging-grid-templated/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/samples/grids/grid/remote-paging-grid-templated/tsconfig.json b/samples/grids/grid/remote-paging-grid-templated/tsconfig.json new file mode 100644 index 0000000000..8c0d146f95 --- /dev/null +++ b/samples/grids/grid/remote-paging-grid-templated/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/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..42c6ace1da --- /dev/null +++ b/samples/grids/grid/remote-virtualization/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" + ] +} From f1eb93b858970607a162626354465782e482f846 Mon Sep 17 00:00:00 2001 From: ttonev Date: Mon, 21 Jul 2025 19:02:29 +0300 Subject: [PATCH 3/5] removed paginator template sample --- .../src/CustomersWithPageResponseModel.ts | 7 - .../src/RemotePagingService.ts | 28 --- .../src/index.tsx | 78 --------- .../grid/remote-virtualization/tsconfig.json | 1 - .../.eslintrc.js | 2 + .../ReadMe.md | 8 +- .../package.json | 2 - .../public/index.html | 0 .../sandbox.config.json | 0 .../src/NwindData.json | 0 .../unique-columns-value/src/RemoteService.ts | 160 ++++++++++++++++++ .../src/index.css | 0 .../grid/unique-columns-value/src/index.tsx | 82 +++++++++ .../src/react-app-env.d.ts | 0 .../tsconfig.json | 0 15 files changed, 248 insertions(+), 120 deletions(-) delete mode 100644 samples/grids/grid/remote-paging-grid-templated/src/CustomersWithPageResponseModel.ts delete mode 100644 samples/grids/grid/remote-paging-grid-templated/src/RemotePagingService.ts delete mode 100644 samples/grids/grid/remote-paging-grid-templated/src/index.tsx rename samples/grids/grid/{remote-paging-grid-templated => unique-columns-value}/.eslintrc.js (97%) rename samples/grids/grid/{remote-paging-grid-templated => unique-columns-value}/ReadMe.md (86%) rename samples/grids/grid/{remote-paging-grid-templated => unique-columns-value}/package.json (95%) rename samples/grids/grid/{remote-paging-grid-templated => unique-columns-value}/public/index.html (100%) rename samples/grids/grid/{remote-paging-grid-templated => unique-columns-value}/sandbox.config.json (100%) rename samples/grids/grid/{remote-paging-grid-templated => unique-columns-value}/src/NwindData.json (100%) create mode 100644 samples/grids/grid/unique-columns-value/src/RemoteService.ts rename samples/grids/grid/{remote-paging-grid-templated => unique-columns-value}/src/index.css (100%) create mode 100644 samples/grids/grid/unique-columns-value/src/index.tsx rename samples/grids/grid/{remote-paging-grid-templated => unique-columns-value}/src/react-app-env.d.ts (100%) rename samples/grids/grid/{remote-paging-grid-templated => unique-columns-value}/tsconfig.json (100%) diff --git a/samples/grids/grid/remote-paging-grid-templated/src/CustomersWithPageResponseModel.ts b/samples/grids/grid/remote-paging-grid-templated/src/CustomersWithPageResponseModel.ts deleted file mode 100644 index 6976b77c4d..0000000000 --- a/samples/grids/grid/remote-paging-grid-templated/src/CustomersWithPageResponseModel.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface CustomersWithPageResponseModel { - items: any[]; - totalRecordsCount: number; - pageSize: number; - pageNumber: number; - totalPages: number; -} \ No newline at end of file diff --git a/samples/grids/grid/remote-paging-grid-templated/src/RemotePagingService.ts b/samples/grids/grid/remote-paging-grid-templated/src/RemotePagingService.ts deleted file mode 100644 index 8a80591226..0000000000 --- a/samples/grids/grid/remote-paging-grid-templated/src/RemotePagingService.ts +++ /dev/null @@ -1,28 +0,0 @@ -const CUSTOMERS_URL = `https://data-northwind.indigo.design/Customers/GetCustomersWithPage`; - -export class RemoteService { - - public static getDataWithPaging(pageIndex?: number, pageSize?: number) { - return fetch(this.buildUrl(CUSTOMERS_URL, pageIndex, pageSize)) - .then((result) => result.json()); - } - - private static buildUrl(baseUrl: string, pageIndex?: number, pageSize?: number) { - let qS = ""; - if (baseUrl) { - qS += `${baseUrl}`; - } - - // Add pageIndex and size to the query string if they are defined - if (pageIndex !== undefined) { - qS += `?pageIndex=${pageIndex}`; - if (pageSize !== undefined) { - qS += `&size=${pageSize}`; - } - } else if (pageSize !== undefined) { - qS += `?perPage=${pageSize}`; - } - - return `${qS}`; - } -} \ No newline at end of file diff --git a/samples/grids/grid/remote-paging-grid-templated/src/index.tsx b/samples/grids/grid/remote-paging-grid-templated/src/index.tsx deleted file mode 100644 index 617662ac10..0000000000 --- a/samples/grids/grid/remote-paging-grid-templated/src/index.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useEffect, useRef, useState } from "react"; -import ReactDOM from "react-dom/client"; -import { IgrGrid, IgrPaginator } from "igniteui-react-grids"; -import { IgrColumn } from "igniteui-react-grids"; -import "igniteui-react-grids/grids/combined"; -import "igniteui-react-grids/grids/themes/light/bootstrap.css"; -import { RemoteService } from "./RemotePagingService"; -import { CustomersWithPageResponseModel } from "./CustomersWithPageResponseModel"; -import { IgrNumberEventArgs } from "igniteui-react"; - -export default function App() { - const grid = useRef(null); - const paginator = useRef(null); - const [data, setData] = useState([]); - const [page, setPage] = useState(0); - const [perPage, setPerPage] = useState(15); - - useEffect(() => { - loadGridData(page, perPage); - }, [page, perPage]); - - function loadGridData(pageIndex?: number, pageSize?: number) { - grid.current.isLoading = true; - - RemoteService.getDataWithPaging(pageIndex, pageSize) - .then((response: CustomersWithPageResponseModel) => { - setData(response.items); - grid.current.isLoading = false; - paginator.current.totalRecords = response.totalRecordsCount; - }) - .catch((error) => { - console.error(error.message); - setData([]); - grid.current.isLoading = false; - }); - } - - function onPageNumberChange(args: IgrNumberEventArgs) { - setPage(args.detail); - } - - function onPageSizeChange(args: IgrNumberEventArgs) { - setPerPage(args.detail); - } - - return ( -
-
- - - - - - - - - - - -
-
- ); -} - -// Render the component -const root = ReactDOM.createRoot(document.getElementById("root")); -root.render(); diff --git a/samples/grids/grid/remote-virtualization/tsconfig.json b/samples/grids/grid/remote-virtualization/tsconfig.json index 42c6ace1da..8c0d146f95 100644 --- a/samples/grids/grid/remote-virtualization/tsconfig.json +++ b/samples/grids/grid/remote-virtualization/tsconfig.json @@ -21,7 +21,6 @@ "noImplicitAny": true, "noUnusedLocals": false, "importHelpers": true, - "suppressImplicitAnyIndexErrors": true, "allowSyntheticDefaultImports": true, "skipLibCheck": true, "strict": false, diff --git a/samples/grids/grid/remote-paging-grid-templated/.eslintrc.js b/samples/grids/grid/unique-columns-value/.eslintrc.js similarity index 97% rename from samples/grids/grid/remote-paging-grid-templated/.eslintrc.js rename to samples/grids/grid/unique-columns-value/.eslintrc.js index 9d283f6407..7168b71441 100644 --- a/samples/grids/grid/remote-paging-grid-templated/.eslintrc.js +++ b/samples/grids/grid/unique-columns-value/.eslintrc.js @@ -30,6 +30,7 @@ module.exports = { "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", @@ -57,6 +58,7 @@ module.exports = { "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", diff --git a/samples/grids/grid/remote-paging-grid-templated/ReadMe.md b/samples/grids/grid/unique-columns-value/ReadMe.md similarity index 86% rename from samples/grids/grid/remote-paging-grid-templated/ReadMe.md rename to samples/grids/grid/unique-columns-value/ReadMe.md index 5e1cd1f53e..6d7315e7ac 100644 --- a/samples/grids/grid/remote-paging-grid-templated/ReadMe.md +++ b/samples/grids/grid/unique-columns-value/ReadMe.md @@ -1,7 +1,7 @@ -This folder contains implementation of React application with example of Remote Paging feature using [Grid](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html) component. +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. @@ -12,10 +12,10 @@ This folder contains implementation of React application with example of Remote View Code - + Run Sample - + Run Sample @@ -34,7 +34,7 @@ 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/remote-paging-grid +cd ./samples/grids/grid/filtering-options ``` open above folder in VS Code or type: diff --git a/samples/grids/grid/remote-paging-grid-templated/package.json b/samples/grids/grid/unique-columns-value/package.json similarity index 95% rename from samples/grids/grid/remote-paging-grid-templated/package.json rename to samples/grids/grid/unique-columns-value/package.json index 20922f0db4..02dbc47aea 100644 --- a/samples/grids/grid/remote-paging-grid-templated/package.json +++ b/samples/grids/grid/unique-columns-value/package.json @@ -17,7 +17,6 @@ "igniteui-dockmanager": "1.16.1", "igniteui-react": "19.0.2", "igniteui-react-core": "19.0.0", - "igniteui-react-datasources": "19.0.0", "igniteui-react-grids": "19.0.2", "igniteui-react-inputs": "19.0.0", "igniteui-react-layouts": "19.0.0", @@ -26,7 +25,6 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "^5.0.1", - "rxjs": "^7.8.1", "tslib": "^2.4.0" }, "devDependencies": { diff --git a/samples/grids/grid/remote-paging-grid-templated/public/index.html b/samples/grids/grid/unique-columns-value/public/index.html similarity index 100% rename from samples/grids/grid/remote-paging-grid-templated/public/index.html rename to samples/grids/grid/unique-columns-value/public/index.html diff --git a/samples/grids/grid/remote-paging-grid-templated/sandbox.config.json b/samples/grids/grid/unique-columns-value/sandbox.config.json similarity index 100% rename from samples/grids/grid/remote-paging-grid-templated/sandbox.config.json rename to samples/grids/grid/unique-columns-value/sandbox.config.json diff --git a/samples/grids/grid/remote-paging-grid-templated/src/NwindData.json b/samples/grids/grid/unique-columns-value/src/NwindData.json similarity index 100% rename from samples/grids/grid/remote-paging-grid-templated/src/NwindData.json rename to samples/grids/grid/unique-columns-value/src/NwindData.json 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/remote-paging-grid-templated/src/index.css b/samples/grids/grid/unique-columns-value/src/index.css similarity index 100% rename from samples/grids/grid/remote-paging-grid-templated/src/index.css rename to samples/grids/grid/unique-columns-value/src/index.css 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/remote-paging-grid-templated/src/react-app-env.d.ts b/samples/grids/grid/unique-columns-value/src/react-app-env.d.ts similarity index 100% rename from samples/grids/grid/remote-paging-grid-templated/src/react-app-env.d.ts rename to samples/grids/grid/unique-columns-value/src/react-app-env.d.ts diff --git a/samples/grids/grid/remote-paging-grid-templated/tsconfig.json b/samples/grids/grid/unique-columns-value/tsconfig.json similarity index 100% rename from samples/grids/grid/remote-paging-grid-templated/tsconfig.json rename to samples/grids/grid/unique-columns-value/tsconfig.json From a466a70b49c1b59b13a3b15278f2bd6ed742d380 Mon Sep 17 00:00:00 2001 From: tishko0 Date: Mon, 6 Oct 2025 17:22:19 +0300 Subject: [PATCH 4/5] fix(sample): updated sample to target issues around fetching data and used enums for types --- .../grid/filtering-remote/src/NwindData.json | 458 ------------------ .../filtering-remote/src/RemoteService.ts | 207 ++++---- .../grids/grid/filtering-remote/src/index.tsx | 133 +++-- 3 files changed, 223 insertions(+), 575 deletions(-) delete mode 100644 samples/grids/grid/filtering-remote/src/NwindData.json diff --git a/samples/grids/grid/filtering-remote/src/NwindData.json b/samples/grids/grid/filtering-remote/src/NwindData.json deleted file mode 100644 index c00b03ec8d..0000000000 --- a/samples/grids/grid/filtering-remote/src/NwindData.json +++ /dev/null @@ -1,458 +0,0 @@ -[ - { - "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 index 1fc3ae35f8..a1692e5076 100644 --- a/samples/grids/grid/filtering-remote/src/RemoteService.ts +++ b/samples/grids/grid/filtering-remote/src/RemoteService.ts @@ -1,6 +1,4 @@ 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', @@ -14,31 +12,76 @@ export enum FILTER_OPERATION { 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?: any, - sortingArgs?: any + filteringArgs?: FilteringArgs, + sortingArgs?: SortingArgs[] ): Promise { const url = this.buildDataUrl(filteringArgs, sortingArgs); return fetch(url) - .then((res) => res.json()) - .then((data) => data.value); // only return the actual items + .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: any, sortingArgs: any): string { - let baseQuery = `${DATA_URL}?$count=true&$top=1000`; + private static buildDataUrl(filteringArgs?: FilteringArgs, sortingArgs?: SortingArgs[]): string { + const baseQuery = `${DATA_URL}?$count=true&$top=1000`; const parts: string[] = []; - const sortExpr = this.buildSortExpression(sortingArgs); - if (sortExpr) parts.push(sortExpr); + if (sortingArgs && sortingArgs.length > 0) { + const sortExpr = this.buildSortExpression(sortingArgs); + if (sortExpr) parts.push(sortExpr); + } - const filterExpr = this.buildFilterExpression(filteringArgs); - if (filterExpr) parts.push(filterExpr); + if (filteringArgs?.filteringOperands?.length) { + const filterExpr = this.buildFilterExpression(filteringArgs); + if (filterExpr) parts.push(filterExpr); + } - return `${baseQuery}&${parts.join('&')}`; + return parts.length > 0 ? `${baseQuery}&${parts.join('&')}` : baseQuery; } - private static buildFilterExpression(filteringArgs: any): string { + private static buildFilterExpression(filteringArgs: FilteringArgs): string { if (!filteringArgs?.filteringOperands?.length) return ''; const expression = this.buildAdvancedFilterExpression( @@ -49,95 +92,97 @@ export class RemoteService { return expression ? `$filter=${expression}` : ''; } - private static buildAdvancedFilterExpression(operands: any[], operator: any): string { - let filterExpression = ''; + private static buildAdvancedFilterExpression(operands: FilterOperand[], operator: LOGICAL_OPERATOR): string { + const filterParts: string[] = []; - operands.forEach((operand, index) => { - if (operand.filteringOperands) { + operands.forEach((operand) => { + if (operand.filteringOperands && operand.filteringOperands.length > 0) { const subExpr = this.buildAdvancedFilterExpression( operand.filteringOperands, - operand.operator + operand.operator || LOGICAL_OPERATOR.AND ); if (subExpr) { - if (index > 0) filterExpression += ` ${this.getFilteringLogic(operator)} `; - filterExpression += subExpr; + filterParts.push(`(${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; - } + if (searchVal === undefined || condition === undefined || !fieldName) return; - filterExpression += filterPart; + const filterPart = this.buildSingleFilterExpression(fieldName, searchVal, condition.name); + if (filterPart) { + filterParts.push(filterPart); + } }); - return filterExpression; + const logicalOp = this.getFilteringLogic(operator); + return filterParts.join(` ${logicalOp} `); } - private static buildSortExpression(sortingArgs: any[]): string { + 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.map(sort => { - const dir = sort.dir === 2 ? 'desc' : 'asc'; // 1 = asc, 2 = desc - return `${sort.fieldName} ${dir}`; - }); + 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: any): string { + private static getFilteringLogic(operator: LOGICAL_OPERATOR): string { switch (operator) { - case 0: return 'and'; - case 1: return 'or'; - default: return 'and'; + 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.tsx b/samples/grids/grid/filtering-remote/src/index.tsx index 6c8fd62a70..712ee43e4a 100644 --- a/samples/grids/grid/filtering-remote/src/index.tsx +++ b/samples/grids/grid/filtering-remote/src/index.tsx @@ -1,47 +1,108 @@ -import React, { useEffect, useState, useRef } from 'react'; +import React, { useEffect, useState, useRef, useCallback } from 'react'; import ReactDOM from 'react-dom/client'; import { RemoteService } from './RemoteService' -import { IgrGrid, IgrColumn } from 'igniteui-react-grids'; +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 [data, setData] = useState([]); const [isLoading, setIsLoading] = useState(false); - const debounceRef = useRef(null); + const [gridState, setGridState] = useState({ + filterExpressions: null, + sortExpressions: [] + }); + const debounceRef = useRef(null); - const fetchData = async (filterExpressions: any = null, sortExpressions: any[] = []) => { - try { - setIsLoading(true); - const result = await RemoteService.getData(filterExpressions, sortExpressions); + const fetchData = useCallback((newGridState?: Partial) => { + const currentState = newGridState ? { ...gridState, ...newGridState } : gridState; + + setIsLoading(true); + RemoteService.getData( + currentState.filterExpressions, + currentState.sortExpressions + ) + .then((result) => { setData(result); - } catch (error) { + + if (newGridState) { + setGridState(currentState); + } + }) + .catch((error) => { console.error('Error fetching data:', error); - } finally { + setData([]); + }) + .finally(() => { setIsLoading(false); - } - }; + }); + }, [gridState]); - const handleSortingExpressionsChange = (event: IgrSortingExpressionEventArgs) => { - const sortExpressions = event.detail; - + const debouncedFetchData = useCallback((newGridState: Partial) => { if (debounceRef.current) clearTimeout(debounceRef.current); - debounceRef.current = setTimeout(() => { - fetchData(null, sortExpressions); - }, 300); - }; + debounceRef.current = window.setTimeout(() => { + fetchData(newGridState); + }, 400); + }, [fetchData]); - const handleFilteringExpressionsTreeChange = (event: IgrFilteringExpressionsTreeEventArgs) => { + const handleSortingExpressionsChange = useCallback((event: IgrSortingExpressionEventArgs) => { + const sortExpressions = event.detail; + debouncedFetchData({ sortExpressions }); + }, [debouncedFetchData]); + + const handleFilteringExpressionsTreeChange = useCallback((event: IgrFilteringExpressionsTreeEventArgs) => { const filterExpressions = event.detail; - - if (debounceRef.current) clearTimeout(debounceRef.current); - debounceRef.current = setTimeout(() => { - fetchData(filterExpressions, []); - }, 500); - }; + debouncedFetchData({ filterExpressions }); + }, [debouncedFetchData]); useEffect(() => { fetchData(); + }, [fetchData]); + + useEffect(() => { + return () => { + if (debounceRef.current) { + clearTimeout(debounceRef.current); + } + }; }, []); return ( @@ -55,16 +116,16 @@ const RemoteFilteringGrid = () => { onFilteringExpressionsTreeChange={handleFilteringExpressionsTreeChange} allowFiltering={true} > - - - - - - - - - - + {columnConfig.map((col) => ( + + ))}
); From 9e394e72f92b5c0751b2231ec85e4266c86056a7 Mon Sep 17 00:00:00 2001 From: tishko0 Date: Mon, 13 Oct 2025 10:37:00 +0300 Subject: [PATCH 5/5] fix(remote): Updated remote services fetchdata issues --- .../src/RemoteService.ts | 117 ++++++-- .../grid/remote-virtualization/src/index.tsx | 22 +- .../unique-columns-value/src/RemoteService.ts | 254 ++++++++++++------ .../grid/unique-columns-value/src/index.tsx | 37 ++- 4 files changed, 306 insertions(+), 124 deletions(-) diff --git a/samples/grids/grid/remote-virtualization/src/RemoteService.ts b/samples/grids/grid/remote-virtualization/src/RemoteService.ts index 17e934cc5a..90358fd948 100644 --- a/samples/grids/grid/remote-virtualization/src/RemoteService.ts +++ b/samples/grids/grid/remote-virtualization/src/RemoteService.ts @@ -1,32 +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: any[] = []; + 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)); + } - 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 }); + return this.fetchDataFromCache(skip, take); + + } catch (error) { + console.error('Error in getData:', error); + return Promise.resolve([]); } + } - const slice = this.cachedData.slice(skip, skip + take); - const allLoaded = slice.every(row => row.emptyRec !== true); - if (allLoaded) return slice; + public static getTotalCount(): number { + return this.totalCount; + } - const res = await fetch(`${DATA_URL}?$count=true&$skip=${skip}&$top=${take}`); - const json = await res.json(); + public static clearCache(): void { + this.cachedData = []; + this.totalCount = 0; + this.isInitialized = false; + } - for (let i = 0; i < json.value.length; i++) { - this.cachedData[skip + i] = json.value[i]; + 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); + } - return this.cachedData.slice(skip, 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; + }); } - public static getTotalCount(): number { - return this.totalCount; + 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.tsx b/samples/grids/grid/remote-virtualization/src/index.tsx index e2320e371c..80c0819083 100644 --- a/samples/grids/grid/remote-virtualization/src/index.tsx +++ b/samples/grids/grid/remote-virtualization/src/index.tsx @@ -6,13 +6,22 @@ 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 = async (skip: number = 0, take: number = 50) => { + const fetchData = (skip: number = 0, take: number = 50) => { setIsLoading(true); - const result = await RemoteService.getData(skip, take); - setData(result); - setIsLoading(false); + 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) => { @@ -26,19 +35,20 @@ const RemoteVirtualizationGrid = () => { return (
-

Remote Virtualization Grid

+

Remote Virtualization Grid ({totalCount.toLocaleString()} records)

- + diff --git a/samples/grids/grid/unique-columns-value/src/RemoteService.ts b/samples/grids/grid/unique-columns-value/src/RemoteService.ts index ce412e4124..48b446a113 100644 --- a/samples/grids/grid/unique-columns-value/src/RemoteService.ts +++ b/samples/grids/grid/unique-columns-value/src/RemoteService.ts @@ -16,42 +16,120 @@ export enum FILTER_OPERATION { 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?: any, - sortingArgs?: any + filteringArgs?: FilteringArgs, + sortingArgs?: SortingArgs[] ): Promise { const url = this.buildDataUrl(filteringArgs, sortingArgs); return fetch(url) - .then((res) => res.json()) - .then((data) => data.value); // only return the actual items + .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: any, sortingArgs: any): string { - let baseQuery = `${DATA_URL}?$count=true&$top=1000`; + private static buildDataUrl(filteringArgs?: FilteringArgs, sortingArgs?: SortingArgs[]): string { + const baseQuery = `${DATA_URL}?$count=true&$top=1000`; const parts: string[] = []; - const sortExpr = this.buildSortExpression(sortingArgs); - if (sortExpr) parts.push(sortExpr); + if (sortingArgs && sortingArgs.length > 0) { + const sortExpr = this.buildSortExpression(sortingArgs); + if (sortExpr) parts.push(sortExpr); + } - const filterExpr = this.buildFilterExpression(filteringArgs); - if (filterExpr) parts.push(filterExpr); + if (filteringArgs?.filteringOperands?.length) { + const filterExpr = this.buildFilterExpression(filteringArgs); + if (filterExpr) parts.push(filterExpr); + } - return `${baseQuery}&${parts.join('&')}`; + return parts.length > 0 ? `${baseQuery}&${parts.join('&')}` : baseQuery; } - 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); + + 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: any): string { + private static buildFilterExpression(filteringArgs: FilteringArgs): string { if (!filteringArgs?.filteringOperands?.length) return ''; const expression = this.buildAdvancedFilterExpression( @@ -62,99 +140,97 @@ export class RemoteService { return expression ? `$filter=${expression}` : ''; } - private static buildAdvancedFilterExpression(operands: any[], operator: any): string { - let filterExpression = ''; + private static buildAdvancedFilterExpression(operands: FilterOperand[], operator: LOGICAL_OPERATOR): string { + const filterParts: string[] = []; - operands.forEach((operand, index) => { - if (operand.filteringOperands) { + operands.forEach((operand) => { + if (operand.filteringOperands && operand.filteringOperands.length > 0) { const subExpr = this.buildAdvancedFilterExpression( operand.filteringOperands, - operand.operator + operand.operator || LOGICAL_OPERATOR.AND ); if (subExpr) { - if (index > 0) filterExpression += ` ${this.getFilteringLogic(operator)} `; - filterExpression += subExpr; + filterParts.push(`(${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; - } + if (searchVal === undefined || condition === undefined || !fieldName) return; - filterExpression += filterPart; + const filterPart = this.buildSingleFilterExpression(fieldName, searchVal, condition.name); + if (filterPart) { + filterParts.push(filterPart); + } }); - return filterExpression; + 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: any[]): string { + private static buildSortExpression(sortingArgs: SortingArgs[]): 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}`; - }); + 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: any): string { + private static getFilteringLogic(operator: LOGICAL_OPERATOR): string { switch (operator) { - case 0: return 'and'; - case 1: return 'or'; - default: return 'and'; + 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.tsx b/samples/grids/grid/unique-columns-value/src/index.tsx index 666ec568ef..1d2aaeb653 100644 --- a/samples/grids/grid/unique-columns-value/src/index.tsx +++ b/samples/grids/grid/unique-columns-value/src/index.tsx @@ -3,23 +3,31 @@ 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); + 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); - } + // 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) => { @@ -50,7 +58,11 @@ const RemoteFilteringGrid = () => { return (
-

Remote Filtering & Sorting Grid

+

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. +
{ onSortingExpressionsChange={handleSortingExpressionsChange} onFilteringExpressionsTreeChange={handleFilteringExpressionsTreeChange} allowFiltering={true} + // TODO: Uncomment when uniqueColumnValuesStrategy becomes available // uniqueColumnValuesStrategy={columnValuesStrategy} >