diff --git a/samples/grids/grid/grid-batch-editing-remote/.devcontainer/devcontainer.json b/samples/grids/grid/grid-batch-editing-remote/.devcontainer/devcontainer.json
new file mode 100644
index 0000000000..ff434ecd44
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing-remote/.devcontainer/devcontainer.json
@@ -0,0 +1,4 @@
+{
+ "name": "Node.js",
+ "image": "mcr.microsoft.com/devcontainers/javascript-node:22"
+}
diff --git a/samples/grids/grid/grid-batch-editing-remote/.eslintrc.js b/samples/grids/grid/grid-batch-editing-remote/.eslintrc.js
new file mode 100644
index 0000000000..0280480e75
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing-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"
+ }
+ }
+ ]
+ };
diff --git a/samples/grids/grid/grid-batch-editing-remote/ReadMe.md b/samples/grids/grid/grid-batch-editing-remote/ReadMe.md
new file mode 100644
index 0000000000..fb264b9e0d
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing-remote/ReadMe.md
@@ -0,0 +1,18 @@
+# Grid Batch Editing with Remote Paging Sample
+
+This sample demonstrates how to use batch editing with remote paging in the Ignite UI for React Grid component.
+
+## Features
+
+- Remote paging with simulated network delay
+- Batch editing (add, edit, delete rows)
+- Undo/Redo support
+- Transaction dialog showing pending changes
+- Commit/Discard functionality
+
+## Running the Sample
+
+```bash
+npm install
+npm start
+```
diff --git a/samples/grids/grid/grid-batch-editing-remote/index.html b/samples/grids/grid/grid-batch-editing-remote/index.html
new file mode 100644
index 0000000000..272184ef96
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing-remote/index.html
@@ -0,0 +1,12 @@
+
+
+
+ Sample | Ignite UI | React | infragistics
+
+
+
+
+
+
+
+
diff --git a/samples/grids/grid/grid-batch-editing-remote/package.json b/samples/grids/grid/grid-batch-editing-remote/package.json
new file mode 100644
index 0000000000..777c424c5b
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing-remote/package.json
@@ -0,0 +1,51 @@
+{
+ "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": "vite --port 4200",
+ "build": "tsc && node --max-old-space-size=4096 node_modules/vite/bin/vite build",
+ "preview": "vite preview",
+ "test": "vitest",
+ "lint": "eslint ./src/**/*.{ts,tsx}"
+ },
+ "dependencies": {
+ "igniteui-dockmanager": "^1.17.0",
+ "igniteui-react": "^19.4.0",
+ "igniteui-react-core": "19.3.1",
+ "igniteui-react-grids": "^19.5.0-beta.0",
+ "igniteui-react-inputs": "19.3.1",
+ "igniteui-react-layouts": "19.3.1",
+ "igniteui-webcomponents": "^6.3.0",
+ "lit-html": "^3.2.0",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "tslib": "^2.4.0"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.2.0",
+ "@types/node": "^24.7.1",
+ "@types/react": "^18.0.24",
+ "@types/react-dom": "^18.0.8",
+ "@vitejs/plugin-react": "^5.0.4",
+ "@vitest/browser": "^3.2.4",
+ "eslint": "^8.33.0",
+ "eslint-config-react": "^1.1.7",
+ "eslint-plugin-react": "^7.20.0",
+ "typescript": "^4.8.4",
+ "vite": "^7.1.9",
+ "vitest": "^3.2.4",
+ "vitest-canvas-mock": "^0.3.3",
+ "worker-loader": "^3.0.8"
+ },
+ "browserslist": [
+ ">0.2%",
+ "not dead",
+ "not ie <= 11",
+ "not op_mini all"
+ ]
+}
diff --git a/samples/grids/grid/grid-batch-editing-remote/sandbox.config.json b/samples/grids/grid/grid-batch-editing-remote/sandbox.config.json
new file mode 100644
index 0000000000..49a80d1d8b
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing-remote/sandbox.config.json
@@ -0,0 +1,5 @@
+{
+ "infiniteLoopProtection": false,
+ "hardReloadOnChange": false,
+ "view": "browser"
+}
diff --git a/samples/grids/grid/grid-batch-editing-remote/src/ProductsWithPageResponseModel.ts b/samples/grids/grid/grid-batch-editing-remote/src/ProductsWithPageResponseModel.ts
new file mode 100644
index 0000000000..ef0db00cd8
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing-remote/src/ProductsWithPageResponseModel.ts
@@ -0,0 +1,7 @@
+export interface ProductsWithPageResponseModel {
+ items: any[];
+ totalRecordsCount: number;
+ pageSize: number;
+ pageNumber: number;
+ totalPages: number;
+}
diff --git a/samples/grids/grid/grid-batch-editing-remote/src/RemotePagingService.ts b/samples/grids/grid/grid-batch-editing-remote/src/RemotePagingService.ts
new file mode 100644
index 0000000000..dae0d22586
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing-remote/src/RemotePagingService.ts
@@ -0,0 +1,54 @@
+import { ProductsWithPageResponseModel } from './ProductsWithPageResponseModel';
+
+// Simulated remote data - in a real app, this would come from an API
+const ALL_PRODUCTS = [
+ { ProductID: 1, ProductName: 'Chai', SupplierID: 1, CategoryID: 1, QuantityPerUnit: '10 boxes x 20 bags', UnitPrice: 18, UnitsInStock: 39, UnitsOnOrder: 30, ReorderLevel: 10, Discontinued: false },
+ { ProductID: 2, ProductName: 'Chang', SupplierID: 1, CategoryID: 1, QuantityPerUnit: '24 - 12 oz bottles', UnitPrice: 19, UnitsInStock: 17, UnitsOnOrder: 40, ReorderLevel: 25, Discontinued: true },
+ { ProductID: 3, ProductName: 'Aniseed Syrup', SupplierID: 1, CategoryID: 2, QuantityPerUnit: '12 - 550 ml bottles', UnitPrice: 10, UnitsInStock: 13, UnitsOnOrder: 70, ReorderLevel: 25, Discontinued: false },
+ { ProductID: 4, ProductName: 'Chef Anton\'s Cajun Seasoning', SupplierID: 2, CategoryID: 2, QuantityPerUnit: '48 - 6 oz jars', UnitPrice: 22, UnitsInStock: 53, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: false },
+ { ProductID: 5, ProductName: 'Chef Anton\'s Gumbo Mix', SupplierID: 2, CategoryID: 2, QuantityPerUnit: '36 boxes', UnitPrice: 21.35, UnitsInStock: 0, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: true },
+ { ProductID: 6, ProductName: 'Grandma\'s Boysenberry Spread', SupplierID: 3, CategoryID: 2, QuantityPerUnit: '12 - 8 oz jars', UnitPrice: 25, UnitsInStock: 120, UnitsOnOrder: 0, ReorderLevel: 25, Discontinued: false },
+ { ProductID: 7, ProductName: 'Uncle Bob\'s Organic Dried Pears', SupplierID: 3, CategoryID: 7, QuantityPerUnit: '12 - 1 lb pkgs.', UnitPrice: 30, UnitsInStock: 15, UnitsOnOrder: 0, ReorderLevel: 10, Discontinued: false },
+ { ProductID: 8, ProductName: 'Northwoods Cranberry Sauce', SupplierID: 3, CategoryID: 2, QuantityPerUnit: '12 - 12 oz jars', UnitPrice: 40, UnitsInStock: 6, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: false },
+ { ProductID: 9, ProductName: 'Mishi Kobe Niku', SupplierID: 4, CategoryID: 6, QuantityPerUnit: '18 - 500 g pkgs.', UnitPrice: 97, UnitsInStock: 29, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: true },
+ { ProductID: 10, ProductName: 'Ikura', SupplierID: 4, CategoryID: 8, QuantityPerUnit: '12 - 200 ml jars', UnitPrice: 31, UnitsInStock: 31, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: false },
+ { ProductID: 11, ProductName: 'Queso Cabrales', SupplierID: 5, CategoryID: 4, QuantityPerUnit: '1 kg pkg.', UnitPrice: 21, UnitsInStock: 22, UnitsOnOrder: 30, ReorderLevel: 30, Discontinued: false },
+ { ProductID: 12, ProductName: 'Queso Manchego La Pastora', SupplierID: 5, CategoryID: 4, QuantityPerUnit: '10 - 500 g pkgs.', UnitPrice: 38, UnitsInStock: 86, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: false },
+ { ProductID: 13, ProductName: 'Konbu', SupplierID: 6, CategoryID: 8, QuantityPerUnit: '2 kg box', UnitPrice: 6, UnitsInStock: 24, UnitsOnOrder: 0, ReorderLevel: 5, Discontinued: false },
+ { ProductID: 14, ProductName: 'Tofu', SupplierID: 6, CategoryID: 7, QuantityPerUnit: '40 - 100 g pkgs.', UnitPrice: 23.25, UnitsInStock: 35, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: false },
+ { ProductID: 15, ProductName: 'Genen Shouyu', SupplierID: 6, CategoryID: 2, QuantityPerUnit: '24 - 250 ml bottles', UnitPrice: 15.5, UnitsInStock: 39, UnitsOnOrder: 0, ReorderLevel: 5, Discontinued: false },
+ { ProductID: 16, ProductName: 'Pavlova', SupplierID: 7, CategoryID: 3, QuantityPerUnit: '32 - 500 g boxes', UnitPrice: 17.45, UnitsInStock: 29, UnitsOnOrder: 0, ReorderLevel: 10, Discontinued: false },
+ { ProductID: 17, ProductName: 'Alice Mutton', SupplierID: 7, CategoryID: 6, QuantityPerUnit: '20 - 1 kg tins', UnitPrice: 39, UnitsInStock: 0, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: true },
+ { ProductID: 18, ProductName: 'Carnarvon Tigers', SupplierID: 7, CategoryID: 8, QuantityPerUnit: '16 kg pkg.', UnitPrice: 62.5, UnitsInStock: 42, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: false },
+ { ProductID: 19, ProductName: 'Teatime Chocolate Biscuits', SupplierID: 8, CategoryID: 3, QuantityPerUnit: '10 boxes x 12 pieces', UnitPrice: 9.2, UnitsInStock: 25, UnitsOnOrder: 0, ReorderLevel: 5, Discontinued: false },
+ { ProductID: 20, ProductName: 'Sir Rodney\'s Marmalade', SupplierID: 8, CategoryID: 3, QuantityPerUnit: '30 gift boxes', UnitPrice: 81, UnitsInStock: 40, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: false },
+];
+
+export class RemotePagingService {
+ private static delay(ms: number): Promise {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+
+ public static async getDataWithPaging(pageIndex: number = 0, pageSize: number = 10): Promise {
+ // Simulate network delay
+ await this.delay(300);
+
+ const totalRecords = ALL_PRODUCTS.length;
+ const totalPages = Math.ceil(totalRecords / pageSize);
+ const startIndex = pageIndex * pageSize;
+ const endIndex = Math.min(startIndex + pageSize, totalRecords);
+ const items = ALL_PRODUCTS.slice(startIndex, endIndex);
+
+ return {
+ items,
+ totalRecordsCount: totalRecords,
+ pageSize,
+ pageNumber: pageIndex,
+ totalPages
+ };
+ }
+
+ public static getTotalRecords(): number {
+ return ALL_PRODUCTS.length;
+ }
+}
diff --git a/samples/grids/grid/grid-batch-editing-remote/src/index.css b/samples/grids/grid/grid-batch-editing-remote/src/index.css
new file mode 100644
index 0000000000..17966fb590
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing-remote/src/index.css
@@ -0,0 +1,44 @@
+/* shared styles are loaded from: */
+/* https://dl.infragistics.com/x/css/samples/shared.v8.css */
+
+.buttons-wrapper {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ padding: 10px 0;
+}
+
+.buttons-right {
+ display: flex;
+ gap: 8px;
+}
+
+.dialog-buttons {
+ display: flex;
+ gap: 8px;
+ justify-content: flex-end;
+}
+
+.transaction--add {
+ color: #6b3;
+ font-weight: 600;
+}
+
+.transaction--update {
+ color: #4a71b9;
+ font-weight: 600;
+}
+
+.transaction--delete {
+ color: #ee4920;
+ font-weight: 600;
+}
+
+.error-banner {
+ background-color: #fee;
+ border: 1px solid #fcc;
+ border-radius: 4px;
+ color: #c00;
+ padding: 10px 15px;
+ margin-bottom: 10px;
+}
diff --git a/samples/grids/grid/grid-batch-editing-remote/src/index.tsx b/samples/grids/grid/grid-batch-editing-remote/src/index.tsx
new file mode 100644
index 0000000000..30d71f94c3
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing-remote/src/index.tsx
@@ -0,0 +1,291 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './index.css';
+
+import { IgrGrid, IgrPaginator, IgrColumn, IgrCellTemplateContext } from 'igniteui-react-grids';
+import { IgrButton, IgrDialog, IgrNumberEventArgs } from 'igniteui-react';
+import { RemotePagingService } from './RemotePagingService';
+import { ProductsWithPageResponseModel } from './ProductsWithPageResponseModel';
+
+import 'igniteui-react-grids/grids/themes/light/bootstrap.css';
+import 'igniteui-webcomponents/themes/light/bootstrap.css';
+
+interface SampleState {
+ undoDisabled: boolean;
+ redoDisabled: boolean;
+ commitDisabled: boolean;
+ discardDisabled: boolean;
+ transactionData: any[];
+ data: any[];
+ page: number;
+ perPage: number;
+ totalRecords: number;
+}
+
+export default class Sample extends React.Component, SampleState> {
+ private grid: IgrGrid | null = null;
+ private paginator: IgrPaginator | null = null;
+ private dialog: IgrDialog | null = null;
+ private addProductId: number = 1000;
+ private stateUpdateSubscription: (() => void) | null = null;
+ private isUpdatingTransactionState: boolean = false;
+
+ constructor(props: Record) {
+ super(props);
+ this.state = {
+ undoDisabled: true,
+ redoDisabled: true,
+ commitDisabled: true,
+ discardDisabled: true,
+ transactionData: [],
+ data: [],
+ page: 0,
+ perPage: 10,
+ totalRecords: 0
+ };
+ }
+
+ public componentDidMount(): void {
+ this.loadGridData(this.state.page, this.state.perPage);
+ }
+
+ public componentWillUnmount(): void {
+ if (this.stateUpdateSubscription) {
+ this.stateUpdateSubscription();
+ }
+ }
+
+ private loadGridData(pageIndex: number, pageSize: number): void {
+ if (this.grid) {
+ this.grid.isLoading = true;
+ }
+
+ RemotePagingService.getDataWithPaging(pageIndex, pageSize)
+ .then((response: ProductsWithPageResponseModel) => {
+ this.setState({
+ data: response.items,
+ totalRecords: response.totalRecordsCount
+ });
+ if (this.grid) {
+ this.grid.isLoading = false;
+ }
+ if (this.paginator) {
+ this.paginator.totalRecords = response.totalRecordsCount;
+ }
+ })
+ .catch((error) => {
+ console.error(error.message);
+ this.setState({ data: [] });
+ if (this.grid) {
+ this.grid.isLoading = false;
+ }
+ });
+ }
+
+ public render() {
+ const { undoDisabled, redoDisabled, commitDisabled, discardDisabled, transactionData, data, perPage } = this.state;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add Row
+
+
+
+ Undo
+
+
+ Redo
+
+
+ Discard
+
+
+ Commit
+
+
+
+
+
+
+
+
+
+
+
+
+ Commit
+
+
+ Discard
+
+
+ Cancel
+
+
+
+
+ );
+ }
+
+ private gridRef = (r: IgrGrid) => {
+ if (!r) { return; }
+ this.grid = r;
+ (this.grid as any).batchEditing = true;
+
+ this.stateUpdateSubscription = this.grid.transactions.onStateUpdate.subscribe(() => {
+ if (!this.grid) { return; }
+ const hasChanges = this.grid.transactions.getAggregatedChanges(false).length > 0;
+ this.isUpdatingTransactionState = true;
+ this.setState({
+ undoDisabled: !this.grid.transactions.canUndo,
+ redoDisabled: !this.grid.transactions.canRedo,
+ commitDisabled: !hasChanges,
+ discardDisabled: !hasChanges
+ }, () => {
+ this.isUpdatingTransactionState = false;
+ });
+ });
+ }
+
+ private paginatorRef = (r: IgrPaginator) => {
+ this.paginator = r;
+ if (this.paginator && this.state.totalRecords > 0) {
+ this.paginator.totalRecords = this.state.totalRecords;
+ }
+ }
+
+ private dialogRef = (r: IgrDialog) => {
+ this.dialog = r;
+ }
+
+ private onPageChange = (args: IgrNumberEventArgs) => {
+ // Ignore page changes triggered by transaction state updates
+ if (this.isUpdatingTransactionState) {
+ return;
+ }
+ const newPage = args.detail;
+ // Only load data if page actually changed
+ if (newPage !== this.state.page) {
+ this.setState({ page: newPage });
+ this.loadGridData(newPage, this.state.perPage);
+ }
+ }
+
+ private onPerPageChange = (args: IgrNumberEventArgs) => {
+ const newPerPage = args.detail;
+ this.setState({ perPage: newPerPage, page: 0 });
+ this.loadGridData(0, newPerPage);
+ }
+
+ private randomInt(min: number, max: number): number {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ }
+
+ public onAddRow = () => {
+ this.grid?.addRow({
+ CategoryID: this.randomInt(1, 10),
+ Discontinued: this.randomInt(1, 10) % 2 === 0,
+ ProductID: this.addProductId++,
+ ProductName: `Product with index ${this.randomInt(0, 20)}`,
+ QuantityPerUnit: `${this.randomInt(1, 10) * 10} pcs.`,
+ ReorderLevel: this.randomInt(10, 20),
+ SupplierID: this.randomInt(1, 20),
+ UnitPrice: this.randomInt(10, 1000),
+ UnitsInStock: this.randomInt(1, 100),
+ UnitsOnOrder: this.randomInt(1, 20)
+ });
+ }
+
+ public onUndo = () => {
+ this.grid?.endEdit(true);
+ this.grid?.transactions.undo();
+ }
+
+ public onRedo = () => {
+ this.grid?.endEdit(true);
+ this.grid?.transactions.redo();
+ }
+
+ public onOpenCommitDialog = () => {
+ if (!this.grid) { return; }
+ this.setState({
+ transactionData: this.grid.transactions.getAggregatedChanges(true)
+ });
+ this.dialog?.show();
+ }
+
+ public onCommit = () => {
+ if (!this.grid) { return; }
+ this.grid.transactions.commit(this.grid.data);
+ this.dialog?.hide();
+ }
+
+ public onDiscard = () => {
+ this.grid?.transactions.clear();
+ this.dialog?.hide();
+ }
+
+ public onCancel = () => {
+ this.dialog?.hide();
+ }
+
+ public deleteRowCellTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ const id = e.dataContext.cell.id.rowID;
+ return (
+ this.grid?.deleteRow(id)}>
+ Delete
+
+ );
+ }
+
+ public typeColumnTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ const type = e.dataContext.cell.value as string;
+ return {type.toUpperCase()};
+ }
+
+ public valueColumnTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ return {JSON.stringify(e.dataContext.cell.value)};
+ }
+}
+
+// rendering above component in the React DOM
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render();
diff --git a/samples/grids/grid/grid-batch-editing-remote/tsconfig.json b/samples/grids/grid/grid-batch-editing-remote/tsconfig.json
new file mode 100644
index 0000000000..8c0d146f95
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing-remote/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/grid-batch-editing-remote/vite.config.js b/samples/grids/grid/grid-batch-editing-remote/vite.config.js
new file mode 100644
index 0000000000..4fc593892f
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing-remote/vite.config.js
@@ -0,0 +1,12 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+ build: {
+ outDir: 'build'
+ },
+ server: {
+ open: false
+ },
+});
diff --git a/samples/grids/grid/grid-batch-editing/.devcontainer/devcontainer.json b/samples/grids/grid/grid-batch-editing/.devcontainer/devcontainer.json
new file mode 100644
index 0000000000..e0b8e9c925
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing/.devcontainer/devcontainer.json
@@ -0,0 +1,4 @@
+{
+ "name": "Node.js",
+ "image": "mcr.microsoft.com/devcontainers/javascript-node:22"
+}
\ No newline at end of file
diff --git a/samples/grids/grid/grid-batch-editing/.eslintrc.js b/samples/grids/grid/grid-batch-editing/.eslintrc.js
new file mode 100644
index 0000000000..7168b71441
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing/.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/grid-batch-editing/ReadMe.md b/samples/grids/grid/grid-batch-editing/ReadMe.md
new file mode 100644
index 0000000000..2b91d5b2df
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing/ReadMe.md
@@ -0,0 +1,56 @@
+
+
+
+This folder contains implementation of React application with example of Data Batch Editing Actions feature using [Grid](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html) component.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Branches
+
+> **_NOTE:_** You should use [master](https://github.com/IgniteUI/igniteui-react-examples/tree/master) branch of this repository if you want to run samples on your computer. Use the [vnext](https://github.com/IgniteUI/igniteui-react-examples/tree/vnext) branch only when you want to contribute new samples to this repository.
+
+## Instructions
+
+Follow these instructions to run this example:
+
+
+```
+git clone https://github.com/IgniteUI/igniteui-react-examples.git
+git checkout master
+cd ./igniteui-react-examples
+cd ./samples/grids/grid/data-batch-editing-actions
+```
+
+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/grid-batch-editing/index.html b/samples/grids/grid/grid-batch-editing/index.html
new file mode 100644
index 0000000000..7ccd41dace
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing/index.html
@@ -0,0 +1,12 @@
+
+
+
+ Sample | Ignite UI | React | infragistics
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/grids/grid/grid-batch-editing/package.json b/samples/grids/grid/grid-batch-editing/package.json
new file mode 100644
index 0000000000..777c424c5b
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing/package.json
@@ -0,0 +1,51 @@
+{
+ "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": "vite --port 4200",
+ "build": "tsc && node --max-old-space-size=4096 node_modules/vite/bin/vite build",
+ "preview": "vite preview",
+ "test": "vitest",
+ "lint": "eslint ./src/**/*.{ts,tsx}"
+ },
+ "dependencies": {
+ "igniteui-dockmanager": "^1.17.0",
+ "igniteui-react": "^19.4.0",
+ "igniteui-react-core": "19.3.1",
+ "igniteui-react-grids": "^19.5.0-beta.0",
+ "igniteui-react-inputs": "19.3.1",
+ "igniteui-react-layouts": "19.3.1",
+ "igniteui-webcomponents": "^6.3.0",
+ "lit-html": "^3.2.0",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "tslib": "^2.4.0"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.2.0",
+ "@types/node": "^24.7.1",
+ "@types/react": "^18.0.24",
+ "@types/react-dom": "^18.0.8",
+ "@vitejs/plugin-react": "^5.0.4",
+ "@vitest/browser": "^3.2.4",
+ "eslint": "^8.33.0",
+ "eslint-config-react": "^1.1.7",
+ "eslint-plugin-react": "^7.20.0",
+ "typescript": "^4.8.4",
+ "vite": "^7.1.9",
+ "vitest": "^3.2.4",
+ "vitest-canvas-mock": "^0.3.3",
+ "worker-loader": "^3.0.8"
+ },
+ "browserslist": [
+ ">0.2%",
+ "not dead",
+ "not ie <= 11",
+ "not op_mini all"
+ ]
+}
diff --git a/samples/grids/grid/grid-batch-editing/sandbox.config.json b/samples/grids/grid/grid-batch-editing/sandbox.config.json
new file mode 100644
index 0000000000..07f53508eb
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing/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/grid-batch-editing/src/NwindData.json b/samples/grids/grid/grid-batch-editing/src/NwindData.json
new file mode 100644
index 0000000000..c00b03ec8d
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing/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/grid-batch-editing/src/index.css b/samples/grids/grid/grid-batch-editing/src/index.css
new file mode 100644
index 0000000000..7a4d6b21d7
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing/src/index.css
@@ -0,0 +1,35 @@
+/* shared styles are loaded from: */
+/* https://dl.infragistics.com/x/css/samples/shared.v8.css */
+
+.buttons-wrapper {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ padding: 10px 0;
+}
+
+.buttons-right {
+ display: flex;
+ gap: 8px;
+}
+
+.dialog-buttons {
+ display: flex;
+ gap: 8px;
+ justify-content: flex-end;
+}
+
+.transaction--add {
+ color: #6b3;
+ font-weight: 600;
+}
+
+.transaction--update {
+ color: #4a71b9;
+ font-weight: 600;
+}
+
+.transaction--delete {
+ color: #ee4920;
+ font-weight: 600;
+}
diff --git a/samples/grids/grid/grid-batch-editing/src/index.tsx b/samples/grids/grid/grid-batch-editing/src/index.tsx
new file mode 100644
index 0000000000..7e14c1c8d2
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing/src/index.tsx
@@ -0,0 +1,222 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './index.css';
+
+import { IgrGrid, IgrColumn, IgrCellTemplateContext } from 'igniteui-react-grids';
+import { IgrButton, IgrDialog } from 'igniteui-react';
+import NwindData from './NwindData.json';
+
+import 'igniteui-react-grids/grids/themes/light/bootstrap.css';
+import 'igniteui-webcomponents/themes/light/bootstrap.css';
+
+interface SampleState {
+ undoDisabled: boolean;
+ redoDisabled: boolean;
+ commitDisabled: boolean;
+ discardDisabled: boolean;
+ transactionData: any[];
+}
+
+export default class Sample extends React.Component, SampleState> {
+ private grid: IgrGrid | null = null;
+ private dialog: IgrDialog | null = null;
+ private addProductId: number = 1000;
+ private stateUpdateSubscription: (() => void) | null = null;
+
+ constructor(props: Record) {
+ super(props);
+ this.state = {
+ undoDisabled: true,
+ redoDisabled: true,
+ commitDisabled: true,
+ discardDisabled: true,
+ transactionData: []
+ };
+ }
+
+ public componentWillUnmount(): void {
+ // Clean up subscription to prevent memory leaks
+ if (this.stateUpdateSubscription) {
+ this.stateUpdateSubscription();
+ }
+ }
+
+ public render() {
+ const { undoDisabled, redoDisabled, commitDisabled, discardDisabled, transactionData } = this.state;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add Row
+
+
+
+ Undo
+
+
+ Redo
+
+
+ Discard
+
+
+ Commit
+
+
+
+
+
+
+
+
+
+
+
+
+ Commit
+
+
+ Discard
+
+
+ Cancel
+
+
+
+
+ );
+ }
+
+ private _nwindData: any[] = NwindData;
+ public get nwindData(): any[] {
+ return this._nwindData;
+ }
+
+ private gridRef = (r: IgrGrid) => {
+ if (!r) { return; }
+ this.grid = r;
+ (this.grid as any).batchEditing = true;
+
+ // Store unsubscribe function for cleanup
+ this.stateUpdateSubscription = this.grid.transactions.onStateUpdate.subscribe(() => {
+ if (!this.grid) { return; }
+ const hasChanges = this.grid.transactions.getAggregatedChanges(false).length > 0;
+ this.setState({
+ undoDisabled: !this.grid.transactions.canUndo,
+ redoDisabled: !this.grid.transactions.canRedo,
+ commitDisabled: !hasChanges,
+ discardDisabled: !hasChanges
+ });
+ });
+ }
+
+ private dialogRef = (r: IgrDialog) => {
+ this.dialog = r;
+ }
+
+ private randomInt(min: number, max: number): number {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ }
+
+ public onAddRow = () => {
+ this.grid?.addRow({
+ CategoryID: this.randomInt(1, 10),
+ Discontinued: this.randomInt(1, 10) % 2 === 0,
+ OrderDate: new Date(this.randomInt(2000, 2050),
+ this.randomInt(0, 11), this.randomInt(1, 25))
+ .toISOString().slice(0, 10),
+ ProductID: this.addProductId++,
+ ProductName: `Product with index ${this.randomInt(0, 20)}`,
+ QuantityPerUnit: `${this.randomInt(1, 10) * 10} pcs.`,
+ ReorderLevel: this.randomInt(10, 20),
+ SupplierID: this.randomInt(1, 20),
+ UnitPrice: this.randomInt(10, 1000),
+ UnitsInStock: this.randomInt(1, 100),
+ UnitsOnOrder: this.randomInt(1, 20)
+ });
+ }
+
+ public onUndo = () => {
+ this.grid?.endEdit(true);
+ this.grid?.transactions.undo();
+ }
+
+ public onRedo = () => {
+ this.grid?.endEdit(true);
+ this.grid?.transactions.redo();
+ }
+
+ public onOpenCommitDialog = () => {
+ if (!this.grid) { return; }
+ this.setState({
+ transactionData: this.grid.transactions.getAggregatedChanges(true)
+ });
+ this.dialog?.show();
+ }
+
+ public onCommit = () => {
+ if (!this.grid) { return; }
+ this.grid.transactions.commit(this.grid.data);
+ this.dialog?.hide();
+ }
+
+ public onDiscard = () => {
+ this.grid?.transactions.clear();
+ this.dialog?.hide();
+ }
+
+ public onCancel = () => {
+ this.dialog?.hide();
+ }
+
+ public deleteRowCellTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ const id = e.dataContext.cell.id.rowID;
+ return (
+ this.grid?.deleteRow(id)}>
+ Delete
+
+ );
+ }
+
+ public typeColumnTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ const type = e.dataContext.cell.value as string;
+ return {type.toUpperCase()};
+ }
+
+ public valueColumnTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ return {JSON.stringify(e.dataContext.cell.value)};
+ }
+}
+
+// rendering above component in the React DOM
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render();
diff --git a/samples/grids/grid/grid-batch-editing/tsconfig.json b/samples/grids/grid/grid-batch-editing/tsconfig.json
new file mode 100644
index 0000000000..8c0d146f95
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing/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/grid-batch-editing/vite.config.js b/samples/grids/grid/grid-batch-editing/vite.config.js
new file mode 100644
index 0000000000..1744dbc719
--- /dev/null
+++ b/samples/grids/grid/grid-batch-editing/vite.config.js
@@ -0,0 +1,12 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+ build: {
+ outDir: 'build'
+ },
+ server: {
+ open: false
+ },
+});
\ No newline at end of file
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing-remote/.devcontainer/devcontainer.json b/samples/grids/hierarchical-grid/grid-batch-editing-remote/.devcontainer/devcontainer.json
new file mode 100644
index 0000000000..ff434ecd44
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing-remote/.devcontainer/devcontainer.json
@@ -0,0 +1,4 @@
+{
+ "name": "Node.js",
+ "image": "mcr.microsoft.com/devcontainers/javascript-node:22"
+}
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing-remote/.eslintrc.js b/samples/grids/hierarchical-grid/grid-batch-editing-remote/.eslintrc.js
new file mode 100644
index 0000000000..0280480e75
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing-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"
+ }
+ }
+ ]
+ };
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing-remote/ReadMe.md b/samples/grids/hierarchical-grid/grid-batch-editing-remote/ReadMe.md
new file mode 100644
index 0000000000..fca94406e8
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing-remote/ReadMe.md
@@ -0,0 +1,19 @@
+# Hierarchical Grid Batch Editing with Remote Paging Sample
+
+This sample demonstrates how to use batch editing with remote paging in the Ignite UI for React Hierarchical Grid component.
+
+## Features
+
+- Remote paging with simulated network delay
+- Hierarchical data with nested Albums/Songs and Tours
+- Batch editing (add, edit, delete rows)
+- Undo/Redo support
+- Transaction dialog showing pending changes
+- Commit/Discard functionality
+
+## Running the Sample
+
+```bash
+npm install
+npm start
+```
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing-remote/index.html b/samples/grids/hierarchical-grid/grid-batch-editing-remote/index.html
new file mode 100644
index 0000000000..272184ef96
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing-remote/index.html
@@ -0,0 +1,12 @@
+
+
+
+ Sample | Ignite UI | React | infragistics
+
+
+
+
+
+
+
+
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing-remote/package.json b/samples/grids/hierarchical-grid/grid-batch-editing-remote/package.json
new file mode 100644
index 0000000000..777c424c5b
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing-remote/package.json
@@ -0,0 +1,51 @@
+{
+ "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": "vite --port 4200",
+ "build": "tsc && node --max-old-space-size=4096 node_modules/vite/bin/vite build",
+ "preview": "vite preview",
+ "test": "vitest",
+ "lint": "eslint ./src/**/*.{ts,tsx}"
+ },
+ "dependencies": {
+ "igniteui-dockmanager": "^1.17.0",
+ "igniteui-react": "^19.4.0",
+ "igniteui-react-core": "19.3.1",
+ "igniteui-react-grids": "^19.5.0-beta.0",
+ "igniteui-react-inputs": "19.3.1",
+ "igniteui-react-layouts": "19.3.1",
+ "igniteui-webcomponents": "^6.3.0",
+ "lit-html": "^3.2.0",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "tslib": "^2.4.0"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.2.0",
+ "@types/node": "^24.7.1",
+ "@types/react": "^18.0.24",
+ "@types/react-dom": "^18.0.8",
+ "@vitejs/plugin-react": "^5.0.4",
+ "@vitest/browser": "^3.2.4",
+ "eslint": "^8.33.0",
+ "eslint-config-react": "^1.1.7",
+ "eslint-plugin-react": "^7.20.0",
+ "typescript": "^4.8.4",
+ "vite": "^7.1.9",
+ "vitest": "^3.2.4",
+ "vitest-canvas-mock": "^0.3.3",
+ "worker-loader": "^3.0.8"
+ },
+ "browserslist": [
+ ">0.2%",
+ "not dead",
+ "not ie <= 11",
+ "not op_mini all"
+ ]
+}
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing-remote/sandbox.config.json b/samples/grids/hierarchical-grid/grid-batch-editing-remote/sandbox.config.json
new file mode 100644
index 0000000000..49a80d1d8b
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing-remote/sandbox.config.json
@@ -0,0 +1,5 @@
+{
+ "infiniteLoopProtection": false,
+ "hardReloadOnChange": false,
+ "view": "browser"
+}
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing-remote/src/RemotePagingService.ts b/samples/grids/hierarchical-grid/grid-batch-editing-remote/src/RemotePagingService.ts
new file mode 100644
index 0000000000..479d505814
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing-remote/src/RemotePagingService.ts
@@ -0,0 +1,201 @@
+import { SingersWithPageResponseModel } from './SingersWithPageResponseModel';
+
+// Simulated remote data - in a real app, this would come from an API
+const ALL_SINGERS = [
+ {
+ ID: 0,
+ Artist: 'Naomà Yepes',
+ Debut: 2011,
+ GrammyNominations: 6,
+ GrammyAwards: 0,
+ HasGrammyAward: false,
+ Tours: [
+ { Tour: 'Faithful Tour', StartedOn: 'Sep 12', Location: 'Worldwide', Headliner: 'NO' },
+ { Tour: 'City Jam Sessions', StartedOn: 'Aug 13', Location: 'North America', Headliner: 'YES' }
+ ],
+ Albums: [
+ {
+ Album: 'Initiation',
+ LaunchDate: '2013-09-03',
+ BillboardReview: 86,
+ USBillboard200: 1,
+ Songs: [
+ { Number: 1, Title: 'Lonely Falling', Released: '2019-05-01', Genre: 'Pop' },
+ { Number: 2, Title: 'Bright Breaking', Released: '2019-07-25', Genre: 'Electropop' }
+ ]
+ }
+ ]
+ },
+ {
+ ID: 1,
+ Artist: 'Babila Ebwélé',
+ Debut: 2009,
+ GrammyNominations: 0,
+ GrammyAwards: 11,
+ HasGrammyAward: true,
+ Tours: [
+ { Tour: 'The BIG Tour', StartedOn: 'May 10', Location: 'Worldwide', Headliner: 'YES' }
+ ],
+ Albums: [
+ {
+ Album: 'Fahrenheit',
+ LaunchDate: '2010-05-15',
+ BillboardReview: 92,
+ USBillboard200: 3,
+ Songs: [
+ { Number: 1, Title: 'Heat Wave', Released: '2010-05-01', Genre: 'R&B' }
+ ]
+ }
+ ]
+ },
+ {
+ ID: 2,
+ Artist: 'Chetana Kadarapu',
+ Debut: 2003,
+ GrammyNominations: 3,
+ GrammyAwards: 2,
+ HasGrammyAward: true,
+ Tours: [
+ { Tour: 'Eastern Winds', StartedOn: 'Mar 04', Location: 'Asia', Headliner: 'YES' }
+ ],
+ Albums: []
+ },
+ {
+ ID: 3,
+ Artist: 'Xin Zhuang',
+ Debut: 2015,
+ GrammyNominations: 2,
+ GrammyAwards: 1,
+ HasGrammyAward: true,
+ Tours: [],
+ Albums: [
+ {
+ Album: 'New Horizons',
+ LaunchDate: '2016-02-20',
+ BillboardReview: 88,
+ USBillboard200: 5,
+ Songs: [
+ { Number: 1, Title: 'Dawn', Released: '2016-02-01', Genre: 'Electronic' }
+ ]
+ }
+ ]
+ },
+ {
+ ID: 4,
+ Artist: 'Elena Volkova',
+ Debut: 2012,
+ GrammyNominations: 4,
+ GrammyAwards: 0,
+ HasGrammyAward: false,
+ Tours: [
+ { Tour: 'Winter Sessions', StartedOn: 'Dec 13', Location: 'Europe', Headliner: 'NO' }
+ ],
+ Albums: []
+ },
+ {
+ ID: 5,
+ Artist: 'Marcus Chen',
+ Debut: 2008,
+ GrammyNominations: 8,
+ GrammyAwards: 3,
+ HasGrammyAward: true,
+ Tours: [
+ { Tour: 'Global Sounds', StartedOn: 'Jun 09', Location: 'Worldwide', Headliner: 'YES' }
+ ],
+ Albums: [
+ {
+ Album: 'Fusion',
+ LaunchDate: '2009-03-10',
+ BillboardReview: 90,
+ USBillboard200: 2,
+ Songs: [
+ { Number: 1, Title: 'Blend', Released: '2009-03-01', Genre: 'Jazz Fusion' }
+ ]
+ }
+ ]
+ },
+ {
+ ID: 6,
+ Artist: 'Sofia Martinez',
+ Debut: 2014,
+ GrammyNominations: 5,
+ GrammyAwards: 2,
+ HasGrammyAward: true,
+ Tours: [],
+ Albums: []
+ },
+ {
+ ID: 7,
+ Artist: 'James Wilson',
+ Debut: 2010,
+ GrammyNominations: 1,
+ GrammyAwards: 0,
+ HasGrammyAward: false,
+ Tours: [
+ { Tour: 'Acoustic Sessions', StartedOn: 'Apr 11', Location: 'North America', Headliner: 'NO' }
+ ],
+ Albums: []
+ },
+ {
+ ID: 8,
+ Artist: 'Aisha Patel',
+ Debut: 2016,
+ GrammyNominations: 7,
+ GrammyAwards: 4,
+ HasGrammyAward: true,
+ Tours: [
+ { Tour: 'Rhythm of Life', StartedOn: 'Jan 17', Location: 'Worldwide', Headliner: 'YES' }
+ ],
+ Albums: [
+ {
+ Album: 'Essence',
+ LaunchDate: '2017-06-15',
+ BillboardReview: 95,
+ USBillboard200: 1,
+ Songs: [
+ { Number: 1, Title: 'Soul', Released: '2017-06-01', Genre: 'Soul' },
+ { Number: 2, Title: 'Spirit', Released: '2017-07-15', Genre: 'R&B' }
+ ]
+ }
+ ]
+ },
+ {
+ ID: 9,
+ Artist: 'Oliver Brown',
+ Debut: 2007,
+ GrammyNominations: 2,
+ GrammyAwards: 1,
+ HasGrammyAward: true,
+ Tours: [],
+ Albums: []
+ }
+];
+
+export class RemotePagingService {
+ private static delay(ms: number): Promise {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+
+ public static async getDataWithPaging(pageIndex: number = 0, pageSize: number = 10): Promise {
+ // Simulate network delay
+ await this.delay(300);
+
+ const totalRecords = ALL_SINGERS.length;
+ const totalPages = Math.ceil(totalRecords / pageSize);
+ const startIndex = pageIndex * pageSize;
+ const endIndex = Math.min(startIndex + pageSize, totalRecords);
+ const items = ALL_SINGERS.slice(startIndex, endIndex);
+
+ return {
+ items,
+ totalRecordsCount: totalRecords,
+ pageSize,
+ pageNumber: pageIndex,
+ totalPages
+ };
+ }
+
+ public static getTotalRecords(): number {
+ return ALL_SINGERS.length;
+ }
+}
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing-remote/src/SingersWithPageResponseModel.ts b/samples/grids/hierarchical-grid/grid-batch-editing-remote/src/SingersWithPageResponseModel.ts
new file mode 100644
index 0000000000..248df0825c
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing-remote/src/SingersWithPageResponseModel.ts
@@ -0,0 +1,7 @@
+export interface SingersWithPageResponseModel {
+ items: any[];
+ totalRecordsCount: number;
+ pageSize: number;
+ pageNumber: number;
+ totalPages: number;
+}
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing-remote/src/index.css b/samples/grids/hierarchical-grid/grid-batch-editing-remote/src/index.css
new file mode 100644
index 0000000000..17966fb590
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing-remote/src/index.css
@@ -0,0 +1,44 @@
+/* shared styles are loaded from: */
+/* https://dl.infragistics.com/x/css/samples/shared.v8.css */
+
+.buttons-wrapper {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ padding: 10px 0;
+}
+
+.buttons-right {
+ display: flex;
+ gap: 8px;
+}
+
+.dialog-buttons {
+ display: flex;
+ gap: 8px;
+ justify-content: flex-end;
+}
+
+.transaction--add {
+ color: #6b3;
+ font-weight: 600;
+}
+
+.transaction--update {
+ color: #4a71b9;
+ font-weight: 600;
+}
+
+.transaction--delete {
+ color: #ee4920;
+ font-weight: 600;
+}
+
+.error-banner {
+ background-color: #fee;
+ border: 1px solid #fcc;
+ border-radius: 4px;
+ color: #c00;
+ padding: 10px 15px;
+ margin-bottom: 10px;
+}
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing-remote/src/index.tsx b/samples/grids/hierarchical-grid/grid-batch-editing-remote/src/index.tsx
new file mode 100644
index 0000000000..0bdf017834
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing-remote/src/index.tsx
@@ -0,0 +1,314 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './index.css';
+
+import { IgrHierarchicalGrid, IgrPaginator, IgrColumn, IgrRowIsland, IgrGrid, IgrCellTemplateContext } from 'igniteui-react-grids';
+import { IgrButton, IgrDialog, IgrNumberEventArgs } from 'igniteui-react';
+import { RemotePagingService } from './RemotePagingService';
+import { SingersWithPageResponseModel } from './SingersWithPageResponseModel';
+
+import 'igniteui-react-grids/grids/themes/light/bootstrap.css';
+import 'igniteui-webcomponents/themes/light/bootstrap.css';
+
+interface SampleState {
+ undoDisabled: boolean;
+ redoDisabled: boolean;
+ commitDisabled: boolean;
+ discardDisabled: boolean;
+ transactionData: any[];
+ data: any[];
+ page: number;
+ perPage: number;
+ totalRecords: number;
+}
+
+export default class Sample extends React.Component, SampleState> {
+ private grid: IgrHierarchicalGrid | null = null;
+ private paginator: IgrPaginator | null = null;
+ private dialog: IgrDialog | null = null;
+ private addId: number = 1000;
+ private stateUpdateSubscription: (() => void) | null = null;
+ private isUpdatingTransactionState: boolean = false;
+
+ constructor(props: Record) {
+ super(props);
+ this.state = {
+ undoDisabled: true,
+ redoDisabled: true,
+ commitDisabled: true,
+ discardDisabled: true,
+ transactionData: [],
+ data: [],
+ page: 0,
+ perPage: 10,
+ totalRecords: 0
+ };
+ }
+
+ public componentDidMount(): void {
+ this.loadGridData(this.state.page, this.state.perPage);
+ }
+
+ public componentWillUnmount(): void {
+ if (this.stateUpdateSubscription) {
+ this.stateUpdateSubscription();
+ }
+ }
+
+ private loadGridData(pageIndex: number, pageSize: number): void {
+ if (this.grid) {
+ this.grid.isLoading = true;
+ }
+
+ RemotePagingService.getDataWithPaging(pageIndex, pageSize)
+ .then((response: SingersWithPageResponseModel) => {
+ this.setState({
+ data: response.items,
+ totalRecords: response.totalRecordsCount
+ });
+ if (this.grid) {
+ this.grid.isLoading = false;
+ }
+ if (this.paginator) {
+ this.paginator.totalRecords = response.totalRecordsCount;
+ }
+ })
+ .catch((error) => {
+ console.error(error.message);
+ this.setState({ data: [] });
+ if (this.grid) {
+ this.grid.isLoading = false;
+ }
+ });
+ }
+
+ public render() {
+ const { undoDisabled, redoDisabled, commitDisabled, discardDisabled, transactionData, data, perPage } = this.state;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add Row
+
+
+
+ Undo
+
+
+ Redo
+
+
+ Discard
+
+
+ Commit
+
+
+
+
+
+
+
+
+
+
+
+
+ Commit
+
+
+ Discard
+
+
+ Cancel
+
+
+
+
+ );
+ }
+
+ private gridRef = (r: IgrHierarchicalGrid) => {
+ if (!r) { return; }
+ this.grid = r;
+ (this.grid as any).batchEditing = true;
+
+ this.stateUpdateSubscription = this.grid.transactions.onStateUpdate.subscribe(() => {
+ if (!this.grid) { return; }
+ const hasChanges = this.grid.transactions.getAggregatedChanges(false).length > 0;
+ this.isUpdatingTransactionState = true;
+ this.setState({
+ undoDisabled: !this.grid.transactions.canUndo,
+ redoDisabled: !this.grid.transactions.canRedo,
+ commitDisabled: !hasChanges,
+ discardDisabled: !hasChanges
+ }, () => {
+ this.isUpdatingTransactionState = false;
+ });
+ });
+ }
+
+ private paginatorRef = (r: IgrPaginator) => {
+ this.paginator = r;
+ if (this.paginator && this.state.totalRecords > 0) {
+ this.paginator.totalRecords = this.state.totalRecords;
+ }
+ }
+
+ private dialogRef = (r: IgrDialog) => {
+ this.dialog = r;
+ }
+
+ private onPageChange = (args: IgrNumberEventArgs) => {
+ // Ignore page changes triggered by transaction state updates
+ if (this.isUpdatingTransactionState) {
+ return;
+ }
+ const newPage = args.detail;
+ // Only load data if page actually changed
+ if (newPage !== this.state.page) {
+ this.setState({ page: newPage });
+ this.loadGridData(newPage, this.state.perPage);
+ }
+ }
+
+ private onPerPageChange = (args: IgrNumberEventArgs) => {
+ const newPerPage = args.detail;
+ this.setState({ perPage: newPerPage, page: 0 });
+ this.loadGridData(0, newPerPage);
+ }
+
+ private randomInt(min: number, max: number): number {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ }
+
+ public onAddRow = () => {
+ this.grid?.addRow({
+ ID: this.addId++,
+ Artist: `New Artist ${this.randomInt(1, 100)}`,
+ Debut: this.randomInt(1990, 2025),
+ GrammyNominations: this.randomInt(0, 20),
+ GrammyAwards: this.randomInt(0, 10),
+ HasGrammyAward: this.randomInt(0, 1) === 1,
+ Albums: [],
+ Tours: []
+ });
+ }
+
+ public onUndo = () => {
+ this.grid?.endEdit(true);
+ this.grid?.transactions.undo();
+ }
+
+ public onRedo = () => {
+ this.grid?.endEdit(true);
+ this.grid?.transactions.redo();
+ }
+
+ public onOpenCommitDialog = () => {
+ if (!this.grid) { return; }
+ this.setState({
+ transactionData: this.grid.transactions.getAggregatedChanges(true)
+ });
+ this.dialog?.show();
+ }
+
+ public onCommit = () => {
+ if (!this.grid) { return; }
+ this.grid.transactions.commit(this.grid.data);
+ this.dialog?.hide();
+ }
+
+ public onDiscard = () => {
+ this.grid?.transactions.clear();
+ this.dialog?.hide();
+ }
+
+ public onCancel = () => {
+ this.dialog?.hide();
+ }
+
+ public deleteRowCellTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ const id = e.dataContext.cell.id.rowID;
+ return (
+ this.grid?.deleteRow(id)}>
+ Delete
+
+ );
+ }
+
+ public typeColumnTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ const type = e.dataContext.cell.value as string;
+ return {type.toUpperCase()};
+ }
+
+ public valueColumnTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ return {JSON.stringify(e.dataContext.cell.value)};
+ }
+}
+
+// rendering above component in the React DOM
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render();
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing-remote/tsconfig.json b/samples/grids/hierarchical-grid/grid-batch-editing-remote/tsconfig.json
new file mode 100644
index 0000000000..8c0d146f95
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing-remote/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/hierarchical-grid/grid-batch-editing-remote/vite.config.js b/samples/grids/hierarchical-grid/grid-batch-editing-remote/vite.config.js
new file mode 100644
index 0000000000..4fc593892f
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing-remote/vite.config.js
@@ -0,0 +1,12 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+ build: {
+ outDir: 'build'
+ },
+ server: {
+ open: false
+ },
+});
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing/.devcontainer/devcontainer.json b/samples/grids/hierarchical-grid/grid-batch-editing/.devcontainer/devcontainer.json
new file mode 100644
index 0000000000..e0b8e9c925
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing/.devcontainer/devcontainer.json
@@ -0,0 +1,4 @@
+{
+ "name": "Node.js",
+ "image": "mcr.microsoft.com/devcontainers/javascript-node:22"
+}
\ No newline at end of file
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing/.eslintrc.js b/samples/grids/hierarchical-grid/grid-batch-editing/.eslintrc.js
new file mode 100644
index 0000000000..7168b71441
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing/.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/hierarchical-grid/grid-batch-editing/ReadMe.md b/samples/grids/hierarchical-grid/grid-batch-editing/ReadMe.md
new file mode 100644
index 0000000000..96882387ba
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing/ReadMe.md
@@ -0,0 +1,56 @@
+
+
+
+This folder contains implementation of React application with example of Editing Events feature using [Hierarchical Grid](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html) component.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Branches
+
+> **_NOTE:_** You should use [master](https://github.com/IgniteUI/igniteui-react-examples/tree/master) branch of this repository if you want to run samples on your computer. Use the [vnext](https://github.com/IgniteUI/igniteui-react-examples/tree/vnext) branch only when you want to contribute new samples to this repository.
+
+## Instructions
+
+Follow these instructions to run this example:
+
+
+```
+git clone https://github.com/IgniteUI/igniteui-react-examples.git
+git checkout master
+cd ./igniteui-react-examples
+cd ./samples/grids/hierarchical-grid/editing-events
+```
+
+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/hierarchical-grid/grid-batch-editing/index.html b/samples/grids/hierarchical-grid/grid-batch-editing/index.html
new file mode 100644
index 0000000000..7ccd41dace
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing/index.html
@@ -0,0 +1,12 @@
+
+
+
+ Sample | Ignite UI | React | infragistics
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing/package.json b/samples/grids/hierarchical-grid/grid-batch-editing/package.json
new file mode 100644
index 0000000000..c2fc0d47eb
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing/package.json
@@ -0,0 +1,49 @@
+{
+ "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": "vite --port 4200",
+ "build": "tsc && node --max-old-space-size=4096 node_modules/vite/bin/vite build",
+ "preview": "vite preview",
+ "test": "vitest",
+ "lint": "eslint ./src/**/*.{ts,tsx}"
+ },
+ "dependencies": {
+ "igniteui-dockmanager": "^1.17.0",
+ "igniteui-react": "^19.4.0",
+ "igniteui-react-core": "19.3.2",
+ "igniteui-react-grids": "^19.5.0-beta.0",
+ "igniteui-webcomponents": "^6.3.0",
+ "lit-html": "^3.2.0",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "tslib": "^2.4.0"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.2.0",
+ "@types/node": "^24.7.1",
+ "@types/react": "^18.0.24",
+ "@types/react-dom": "^18.0.8",
+ "@vitejs/plugin-react": "^5.0.4",
+ "@vitest/browser": "^3.2.4",
+ "eslint": "^8.33.0",
+ "eslint-config-react": "^1.1.7",
+ "eslint-plugin-react": "^7.20.0",
+ "typescript": "^4.8.4",
+ "vite": "^7.1.9",
+ "vitest": "^3.2.4",
+ "vitest-canvas-mock": "^0.3.3",
+ "worker-loader": "^3.0.8"
+ },
+ "browserslist": [
+ ">0.2%",
+ "not dead",
+ "not ie <= 11",
+ "not op_mini all"
+ ]
+}
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing/sandbox.config.json b/samples/grids/hierarchical-grid/grid-batch-editing/sandbox.config.json
new file mode 100644
index 0000000000..07f53508eb
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing/sandbox.config.json
@@ -0,0 +1,5 @@
+{
+ "infiniteLoopProtection": false,
+ "hardReloadOnChange": false,
+ "view": "browser"
+}
\ No newline at end of file
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing/src/NwindData.json b/samples/grids/hierarchical-grid/grid-batch-editing/src/NwindData.json
new file mode 100644
index 0000000000..c00b03ec8d
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing/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/hierarchical-grid/grid-batch-editing/src/SingersData.json b/samples/grids/hierarchical-grid/grid-batch-editing/src/SingersData.json
new file mode 100644
index 0000000000..b0c61e1499
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing/src/SingersData.json
@@ -0,0 +1,2480 @@
+[
+ {
+ "ID": 0,
+ "Artist": "Naomà Yepes",
+ "Photo": "https://dl.infragistics.com/x/img/people/names/naomi.png",
+ "Debut": 2011,
+ "GrammyNominations": 6,
+ "GrammyAwards": 0,
+ "HasGrammyAward": false,
+ "Tours": [
+ {
+ "Tour": "Faithful Tour",
+ "StartedOn": "Sep 12",
+ "Location": "Worldwide",
+ "Headliner": "NO",
+ "TouredBy": "Naomà Yepes"
+ },
+ {
+ "Tour": "City Jam Sessions",
+ "StartedOn": "Aug 13",
+ "Location": "North America",
+ "Headliner": "YES",
+ "TouredBy": "Naomà Yepes"
+ },
+ {
+ "Tour": "Christmas NYC 2013",
+ "StartedOn": "Dec 13",
+ "Location": "United States",
+ "Headliner": "NO",
+ "TouredBy": "Naomà Yepes"
+ },
+ {
+ "Tour": "Christmas NYC 2014",
+ "StartedOn": "Dec 14",
+ "Location": "North America",
+ "Headliner": "NO",
+ "TouredBy": "Naomà Yepes"
+ },
+ {
+ "Tour": "Watermelon Tour",
+ "StartedOn": "Feb 15",
+ "Location": "Worldwide",
+ "Headliner": "YES",
+ "TouredBy": "Naomà Yepes"
+ },
+ {
+ "Tour": "Christmas NYC 2016",
+ "StartedOn": "Dec 16",
+ "Location": "United States",
+ "Headliner": "NO",
+ "TouredBy": "Naomà Yepes"
+ },
+ {
+ "Tour": "The Dragon Tour",
+ "StartedOn": "Feb 17",
+ "Location": "Worldwide",
+ "Headliner": "NO",
+ "TouredBy": "Naomà Yepes"
+ },
+ {
+ "Tour": "Organic Sessions",
+ "StartedOn": "Aug 18",
+ "Location": "United States, England",
+ "Headliner": "YES",
+ "TouredBy": "Naomà Yepes"
+ },
+ {
+ "Tour": "Hope World Tour",
+ "StartedOn": "Mar 19",
+ "Location": "Worldwide",
+ "Headliner": "NO",
+ "TouredBy": "Naomà Yepes"
+ }
+ ],
+ "Albums": [
+ {
+ "Album": "Initiation",
+ "LaunchDate": "2013-09-03",
+ "BillboardReview": 86,
+ "USBillboard200": 1,
+ "Artist": "Naomà Yepes",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Lonely Falling",
+ "Released": "2019-05-01",
+ "Genre": "*",
+ "Album": "Initiation"
+ },
+ {
+ "Number": 2,
+ "Title": "Bright Breaking",
+ "Released": "2019-07-25",
+ "Genre": "Electro house Electropop",
+ "Album": "Initiation"
+ },
+ {
+ "Number": 3,
+ "Title": "River of Whisper",
+ "Released": "2020-01-29",
+ "Genre": "Electro house Electropop",
+ "Album": "Initiation"
+ },
+ {
+ "Number": 4,
+ "Title": "Sky of Storm",
+ "Released": "2019-05-07",
+ "Genre": "Electro house Electropop",
+ "Album": "Initiation"
+ },
+ {
+ "Number": 5,
+ "Title": "Electric River",
+ "Released": "2020-01-20",
+ "Genre": "R&B",
+ "Album": "Initiation"
+ },
+ {
+ "Number": 6,
+ "Title": "Storm of Storm",
+ "Released": "2019-09-01",
+ "Genre": "Crunk reggaeton",
+ "Album": "Initiation"
+ },
+ {
+ "Number": 7,
+ "Title": "Fire of Dream",
+ "Released": "2019-09-12",
+ "Genre": "Electro house Electropop",
+ "Album": "Initiation"
+ },
+ {
+ "Number": 8,
+ "Title": "Burning in the Light",
+ "Released": "2019-04-15",
+ "Genre": "*",
+ "Album": "Initiation"
+ },
+ {
+ "Number": 9,
+ "Title": "Burning in the Storm",
+ "Released": "2019-09-10",
+ "Genre": "Electro house Electropop",
+ "Album": "Initiation"
+ },
+ {
+ "Number": 10,
+ "Title": "Shadow of Whisper",
+ "Released": "2019-01-06",
+ "Genre": "Crunk reggaeton",
+ "Album": "Initiation"
+ }
+ ]
+ },
+ {
+ "Album": "Dream Driven",
+ "LaunchDate": "2014-08-25",
+ "BillboardReview": 81,
+ "USBillboard200": 1,
+ "Artist": "Naomà Yepes",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Intro",
+ "Released": "2019-09-10",
+ "Genre": "*",
+ "Album": "Dream Driven"
+ },
+ {
+ "Number": 2,
+ "Title": "Ferocious",
+ "Released": "2014-04-28",
+ "Genre": "Dance-pop R&B",
+ "Album": "Dream Driven"
+ },
+ {
+ "Number": 3,
+ "Title": "Going crazy",
+ "Released": "2015-02-10",
+ "Genre": "Dance-pop EDM",
+ "Album": "Dream Driven"
+ },
+ {
+ "Number": 4,
+ "Title": "Future past",
+ "Released": "2011-09-20",
+ "Genre": "*",
+ "Album": "Dream Driven"
+ },
+ {
+ "Number": 5,
+ "Title": "Roaming like them",
+ "Released": "2014-07-02",
+ "Genre": "Electro house Electropop",
+ "Album": "Dream Driven"
+ },
+ {
+ "Number": 6,
+ "Title": "Last Wishes",
+ "Released": "2014-08-12",
+ "Genre": "R&B",
+ "Album": "Dream Driven"
+ },
+ {
+ "Number": 7,
+ "Title": "Stay where you are",
+ "Released": "2013-09-14",
+ "Genre": "*",
+ "Album": "Dream Driven"
+ },
+ {
+ "Number": 8,
+ "Title": "Imaginarium",
+ "Released": "2021-09-20",
+ "Genre": "*",
+ "Album": "Dream Driven"
+ },
+ {
+ "Number": 9,
+ "Title": "Tell me",
+ "Released": "2014-09-30",
+ "Genre": "Synth-pop R&B",
+ "Album": "Dream Driven"
+ },
+ {
+ "Number": 10,
+ "Title": "Shredded into pieces",
+ "Released": "2015-09-11",
+ "Genre": "*",
+ "Album": "Dream Driven"
+ },
+ {
+ "Number": 11,
+ "Title": "Capture this moment",
+ "Released": "2016-09-13",
+ "Genre": "*",
+ "Album": "Dream Driven"
+ },
+ {
+ "Number": 12,
+ "Title": "Dream Driven",
+ "Released": "2014-09-14",
+ "Genre": "*",
+ "Album": "Dream Driven"
+ }
+ ]
+ },
+ {
+ "Album": "The dragon journey",
+ "LaunchDate": "2016-05-20",
+ "BillboardReview": 60,
+ "USBillboard200": 2,
+ "Artist": "Naomà Yepes",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Calling in the Storm",
+ "Released": "2019-03-12",
+ "Genre": "Electro house Electropop",
+ "Album": "The dragon journey"
+ },
+ {
+ "Number": 2,
+ "Title": "Hiding in the Dream",
+ "Released": "2019-03-23",
+ "Genre": "R&B",
+ "Album": "The dragon journey"
+ },
+ {
+ "Number": 3,
+ "Title": "Electric Heart",
+ "Released": "2019-03-17",
+ "Genre": "ethno-tunes",
+ "Album": "The dragon journey"
+ },
+ {
+ "Number": 4,
+ "Title": "Shadow of Echo",
+ "Released": "2019-02-20",
+ "Genre": "ethno-tunes",
+ "Album": "The dragon journey"
+ },
+ {
+ "Number": 5,
+ "Title": "Flying in the Storm",
+ "Released": "2019-04-08",
+ "Genre": "R&B",
+ "Album": "The dragon journey"
+ },
+ {
+ "Number": 6,
+ "Title": "Dark Waiting",
+ "Released": "2019-10-20",
+ "Genre": "Synth-pop R&B",
+ "Album": "The dragon journey"
+ },
+ {
+ "Number": 7,
+ "Title": "Fire of River",
+ "Released": "2019-02-20",
+ "Genre": "Synth-pop R&B",
+ "Album": "The dragon journey"
+ },
+ {
+ "Number": 8,
+ "Title": "Wild Crying",
+ "Released": "2019-06-14",
+ "Genre": "R&B",
+ "Album": "The dragon journey"
+ },
+ {
+ "Number": 9,
+ "Title": "Bright Dancing",
+ "Released": "2019-03-14",
+ "Genre": "Electro house Electropop",
+ "Album": "The dragon journey"
+ },
+ {
+ "Number": 10,
+ "Title": "Golden Waiting",
+ "Released": "2019-09-12",
+ "Genre": "Synth-pop R&B",
+ "Album": "The dragon journey"
+ }
+ ]
+ },
+ {
+ "Album": "Organic me",
+ "LaunchDate": "2018-08-17",
+ "BillboardReview": 82,
+ "USBillboard200": 1,
+ "Artist": "Naomà Yepes",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "I Love",
+ "Released": "2019-05-11",
+ "Genre": "Crunk reggaeton",
+ "Album": "Organic me"
+ },
+ {
+ "Number": 2,
+ "Title": "Early Morning Compass",
+ "Released": "2020-01-15",
+ "Genre": "mystical parody-bap ",
+ "Album": "Organic me"
+ },
+ {
+ "Number": 3,
+ "Title": "Key Fields Forever",
+ "Released": "2020-01-02",
+ "Genre": "Dance-pop EDM",
+ "Album": "Organic me"
+ },
+ {
+ "Number": 4,
+ "Title": "Stand by Your Goblins",
+ "Released": "2019-11-20",
+ "Genre": "*",
+ "Album": "Organic me"
+ },
+ {
+ "Number": 5,
+ "Title": "Mad to Walk",
+ "Released": "2019-05-12",
+ "Genre": "Electro house Electropop",
+ "Album": "Organic me"
+ },
+ {
+ "Number": 6,
+ "Title": "Alice's Waiting",
+ "Released": "2020-01-28",
+ "Genre": "R&B",
+ "Album": "Organic me"
+ },
+ {
+ "Number": 7,
+ "Title": "We Shall Kiss",
+ "Released": "2019-10-30",
+ "Genre": "*",
+ "Album": "Organic me"
+ },
+ {
+ "Number": 8,
+ "Title": "Behind Single Ants",
+ "Released": "2019-10-02",
+ "Genre": "*",
+ "Album": "Organic me"
+ },
+ {
+ "Number": 9,
+ "Title": "Soap Autopsy",
+ "Released": "2019-08-08",
+ "Genre": "Synth-pop R&B",
+ "Album": "Organic me"
+ },
+ {
+ "Number": 10,
+ "Title": "Have You Met Rich?",
+ "Released": "2019-07-01",
+ "Genre": "ethno-tunes",
+ "Album": "Organic me"
+ },
+ {
+ "Number": 11,
+ "Title": "Livin' on a Banana",
+ "Released": "2019-11-22",
+ "Genre": "Crunk reggaeton",
+ "Album": "Organic me"
+ }
+ ]
+ },
+ {
+ "Album": "Curiosity",
+ "LaunchDate": "2019-12-07",
+ "BillboardReview": 75,
+ "USBillboard200": 12,
+ "Artist": "Naomà Yepes",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Storm of Dream",
+ "Released": "2019-01-05",
+ "Genre": "*",
+ "Album": "Curiosity"
+ },
+ {
+ "Number": 2,
+ "Title": "Echo of Dream",
+ "Released": "2019-01-28",
+ "Genre": "Synth-pop R&B",
+ "Album": "Curiosity"
+ },
+ {
+ "Number": 3,
+ "Title": "Light of Shadow",
+ "Released": "2019-02-07",
+ "Genre": "Synth-pop R&B",
+ "Album": "Curiosity"
+ },
+ {
+ "Number": 4,
+ "Title": "Storm of Heart",
+ "Released": "2020-01-05",
+ "Genre": "Electro house Electropop",
+ "Album": "Curiosity"
+ },
+ {
+ "Number": 5,
+ "Title": "Shadow of River",
+ "Released": "2019-02-27",
+ "Genre": "*",
+ "Album": "Curiosity"
+ },
+ {
+ "Number": 6,
+ "Title": "Wicked Dancing",
+ "Released": "2020-01-17",
+ "Genre": "ethno-tunes",
+ "Album": "Curiosity"
+ },
+ {
+ "Number": 7,
+ "Title": "River of Light",
+ "Released": "2019-02-22",
+ "Genre": "R&B",
+ "Album": "Curiosity"
+ },
+ {
+ "Number": 8,
+ "Title": "Lonely Breaking",
+ "Released": "2019-09-09",
+ "Genre": "ethno-tunes",
+ "Album": "Curiosity"
+ },
+ {
+ "Number": 9,
+ "Title": "Furious Flying",
+ "Released": "2019-06-08",
+ "Genre": "R&B",
+ "Album": "Curiosity"
+ },
+ {
+ "Number": 10,
+ "Title": "Hiding in the Storm",
+ "Released": "2019-05-27",
+ "Genre": "Electro house Electropop",
+ "Album": "Curiosity"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": 1,
+ "Artist": "Babila Ebwélé",
+ "Photo": "https://dl.infragistics.com/x/img/people/names/babila.png",
+ "Debut": 2009,
+ "GrammyNominations": 0,
+ "GrammyAwards": 11,
+ "HasGrammyAward": true,
+ "Tours": [
+ {
+ "Tour": "The last straw",
+ "StartedOn": "May 09",
+ "Location": "Europe, Asia",
+ "Headliner": "NO",
+ "TouredBy": "Babila Ebwélé"
+ },
+ {
+ "Tour": "No foundations",
+ "StartedOn": "Jun 04",
+ "Location": "United States, Europe",
+ "Headliner": "YES",
+ "TouredBy": "Babila Ebwélé"
+ },
+ {
+ "Tour": "Crazy eyes",
+ "StartedOn": "Jun 08",
+ "Location": "North America",
+ "Headliner": "NO",
+ "TouredBy": "Babila Ebwélé"
+ },
+ {
+ "Tour": "Zero gravity",
+ "StartedOn": "Apr 19",
+ "Location": "United States",
+ "Headliner": "NO",
+ "TouredBy": "Babila Ebwélé"
+ },
+ {
+ "Tour": "Battle with myself",
+ "StartedOn": "Mar 08",
+ "Location": "North America",
+ "Headliner": "YES",
+ "TouredBy": "Babila Ebwélé"
+ }
+ ],
+ "Albums": [
+ {
+ "Album": "Pushing up daisies",
+ "LaunchDate": "2000-05-31",
+ "BillboardReview": 86,
+ "USBillboard200": 42,
+ "Artist": "Babila Ebwélé",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Wood Shavings Forever",
+ "Released": "2019-06-09",
+ "Genre": "*",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 2,
+ "Title": "Early Morning Drive",
+ "Released": "2019-05-20",
+ "Genre": "*",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 3,
+ "Title": "Don't Natter",
+ "Released": "2019-06-10",
+ "Genre": "adult calypso-industrial",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 4,
+ "Title": "Stairway to Balloons",
+ "Released": "2019-06-18",
+ "Genre": "calypso and mariachi",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 5,
+ "Title": "The Number of your Apple",
+ "Released": "2019-10-29",
+ "Genre": "*",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 6,
+ "Title": "Your Delightful Heart",
+ "Released": "2019-02-24",
+ "Genre": "*",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 7,
+ "Title": "Nice Weather For Balloons",
+ "Released": "2019-08-01",
+ "Genre": "rap-hop",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 8,
+ "Title": "The Girl From Cornwall",
+ "Released": "2019-05-04",
+ "Genre": "enigmatic rock-and-roll",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 9,
+ "Title": "Here Without Jack",
+ "Released": "2019-10-24",
+ "Genre": "*",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 10,
+ "Title": "Born Rancid",
+ "Released": "2019-03-19",
+ "Genre": "*",
+ "Album": "Pushing up daisies"
+ }
+ ]
+ },
+ {
+ "Album": "Death's dead",
+ "LaunchDate": "2016-06-08",
+ "BillboardReview": 85,
+ "USBillboard200": 95,
+ "Artist": "Babila Ebwélé",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Men Sound Better With You",
+ "Released": "2019-10-20",
+ "Genre": "rap-hop",
+ "Album": "Death's dead"
+ },
+ {
+ "Number": 2,
+ "Title": "Ghost in My Rod",
+ "Released": "2019-10-05",
+ "Genre": "enigmatic rock-and-roll",
+ "Album": "Death's dead"
+ },
+ {
+ "Number": 3,
+ "Title": "Bed of Men",
+ "Released": "2019-11-14",
+ "Genre": "whimsical comedy-grass ",
+ "Album": "Death's dead"
+ },
+ {
+ "Number": 4,
+ "Title": "Don't Push",
+ "Released": "2020-01-02",
+ "Genre": "unblack electronic-trip-hop",
+ "Album": "Death's dead"
+ },
+ {
+ "Number": 5,
+ "Title": "Nice Weather For Men",
+ "Released": "2019-12-18",
+ "Genre": "*",
+ "Album": "Death's dead"
+ },
+ {
+ "Number": 6,
+ "Title": "Rancid Rhapsody",
+ "Released": "2019-03-10",
+ "Genre": "*",
+ "Album": "Death's dead"
+ },
+ {
+ "Number": 7,
+ "Title": "Push, Push, Push!",
+ "Released": "2019-02-21",
+ "Genre": "*",
+ "Album": "Death's dead"
+ },
+ {
+ "Number": 8,
+ "Title": "My Name is Sarah",
+ "Released": "2019-11-15",
+ "Genre": "*",
+ "Album": "Death's dead"
+ },
+ {
+ "Number": 9,
+ "Title": "The Girl From My Hotel",
+ "Released": "2019-11-06",
+ "Genre": "*",
+ "Album": "Death's dead"
+ },
+ {
+ "Number": 10,
+ "Title": "Free Box",
+ "Released": "2019-04-18",
+ "Genre": "splitter-funk",
+ "Album": "Death's dead"
+ },
+ {
+ "Number": 11,
+ "Title": "Hotel Cardiff",
+ "Released": "2019-12-30",
+ "Genre": "guilty pleasure ebm",
+ "Album": "Death's dead"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": 2,
+ "Artist": "Ahmad Nazeri",
+ "Photo": "https://dl.infragistics.com/x/img/people/names/ahmad.png",
+ "Debut": 2004,
+ "GrammyNominations": 3,
+ "GrammyAwards": 1,
+ "HasGrammyAward": true,
+ "Tours": [],
+ "Albums": [
+ {
+ "Album": "Emergency",
+ "LaunchDate": "2004-03-06",
+ "BillboardReview": 98,
+ "USBillboard200": 69,
+ "Artist": "Ahmad Nazeri",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Gentle Falling",
+ "Released": "2019-04-26",
+ "Genre": "Crunk reggaeton",
+ "Album": "Emergency"
+ },
+ {
+ "Number": 2,
+ "Title": "Calling in the Fire",
+ "Released": "2019-09-03",
+ "Genre": "ethno-tunes",
+ "Album": "Emergency"
+ },
+ {
+ "Number": 3,
+ "Title": "Fire of Shadow",
+ "Released": "2019-01-05",
+ "Genre": "ethno-tunes",
+ "Album": "Emergency"
+ },
+ {
+ "Number": 4,
+ "Title": "Dancing in the Dream",
+ "Released": "2019-04-15",
+ "Genre": "R&B",
+ "Album": "Emergency"
+ },
+ {
+ "Number": 5,
+ "Title": "Calling in the Shadow",
+ "Released": "2019-10-09",
+ "Genre": "R&B",
+ "Album": "Emergency"
+ },
+ {
+ "Number": 6,
+ "Title": "Falling in the Sky",
+ "Released": "2019-03-08",
+ "Genre": "ethno-tunes",
+ "Album": "Emergency"
+ },
+ {
+ "Number": 7,
+ "Title": "Calling in the Storm",
+ "Released": "2019-12-05",
+ "Genre": "ethno-tunes",
+ "Album": "Emergency"
+ },
+ {
+ "Number": 8,
+ "Title": "Falling in the River",
+ "Released": "2019-08-19",
+ "Genre": "Electro house Electropop",
+ "Album": "Emergency"
+ },
+ {
+ "Number": 9,
+ "Title": "Electric Fire",
+ "Released": "2019-11-30",
+ "Genre": "Crunk reggaeton",
+ "Album": "Emergency"
+ },
+ {
+ "Number": 10,
+ "Title": "Lonely River",
+ "Released": "2019-11-11",
+ "Genre": "Electro house Electropop",
+ "Album": "Emergency"
+ }
+ ]
+ },
+ {
+ "Album": "Bursting bubbles",
+ "LaunchDate": "2006-04-17",
+ "BillboardReview": 69,
+ "USBillboard200": 39,
+ "Artist": "Ahmad Nazeri",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Lonely Dream",
+ "Released": "2019-12-11",
+ "Genre": "ethno-tunes",
+ "Album": "Bursting bubbles"
+ },
+ {
+ "Number": 2,
+ "Title": "Fire of River",
+ "Released": "2019-08-01",
+ "Genre": "Synth-pop R&B",
+ "Album": "Bursting bubbles"
+ },
+ {
+ "Number": 3,
+ "Title": "Wicked Falling",
+ "Released": "2019-01-25",
+ "Genre": "*",
+ "Album": "Bursting bubbles"
+ },
+ {
+ "Number": 4,
+ "Title": "Crying in the Shadow",
+ "Released": "2019-01-04",
+ "Genre": "Synth-pop R&B",
+ "Album": "Bursting bubbles"
+ },
+ {
+ "Number": 5,
+ "Title": "Wild Burning",
+ "Released": "2019-05-10",
+ "Genre": "ethno-tunes",
+ "Album": "Bursting bubbles"
+ },
+ {
+ "Number": 6,
+ "Title": "Waiting in the Heart",
+ "Released": "2019-08-07",
+ "Genre": "ethno-tunes",
+ "Album": "Bursting bubbles"
+ },
+ {
+ "Number": 7,
+ "Title": "Fire of Fire",
+ "Released": "2019-05-16",
+ "Genre": "Electro house Electropop",
+ "Album": "Bursting bubbles"
+ },
+ {
+ "Number": 8,
+ "Title": "Bright Heart",
+ "Released": "2019-03-14",
+ "Genre": "Synth-pop R&B",
+ "Album": "Bursting bubbles"
+ },
+ {
+ "Number": 9,
+ "Title": "Lonely Fire",
+ "Released": "2019-10-15",
+ "Genre": "R&B",
+ "Album": "Bursting bubbles"
+ },
+ {
+ "Number": 10,
+ "Title": "Sky of Dream",
+ "Released": "2019-06-20",
+ "Genre": "ethno-tunes",
+ "Album": "Bursting bubbles"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": 3,
+ "Artist": "Kimmy McIlmorie",
+ "Photo": "https://dl.infragistics.com/x/img/people/names/kimmy.png",
+ "Debut": 2007,
+ "GrammyNominations": 21,
+ "GrammyAwards": 3,
+ "HasGrammyAward": true,
+ "Tours": [],
+ "Albums": [
+ {
+ "Album": "Here we go again",
+ "LaunchDate": "2017-11-18",
+ "BillboardReview": 68,
+ "USBillboard200": 1,
+ "Artist": "Kimmy McIlmorie",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Storm of Sky",
+ "Released": "2019-03-04",
+ "Genre": "ethno-tunes",
+ "Album": "Here we go again"
+ },
+ {
+ "Number": 2,
+ "Title": "Dream of Shadow",
+ "Released": "2019-01-03",
+ "Genre": "ethno-tunes",
+ "Album": "Here we go again"
+ },
+ {
+ "Number": 3,
+ "Title": "Dream of Shadow",
+ "Released": "2019-12-19",
+ "Genre": "Electro house Electropop",
+ "Album": "Here we go again"
+ },
+ {
+ "Number": 4,
+ "Title": "Golden Fire",
+ "Released": "2019-01-20",
+ "Genre": "R&B",
+ "Album": "Here we go again"
+ },
+ {
+ "Number": 5,
+ "Title": "Running in the Light",
+ "Released": "2020-01-03",
+ "Genre": "Synth-pop R&B",
+ "Album": "Here we go again"
+ },
+ {
+ "Number": 6,
+ "Title": "Flying in the Heart",
+ "Released": "2019-01-17",
+ "Genre": "*",
+ "Album": "Here we go again"
+ },
+ {
+ "Number": 7,
+ "Title": "Fire of Storm",
+ "Released": "2019-01-26",
+ "Genre": "ethno-tunes",
+ "Album": "Here we go again"
+ },
+ {
+ "Number": 8,
+ "Title": "Calling in the Sky",
+ "Released": "2019-10-28",
+ "Genre": "Synth-pop R&B",
+ "Album": "Here we go again"
+ },
+ {
+ "Number": 9,
+ "Title": "Flying in the Shadow",
+ "Released": "2019-03-30",
+ "Genre": "ethno-tunes",
+ "Album": "Here we go again"
+ },
+ {
+ "Number": 10,
+ "Title": "Golden Dancing",
+ "Released": "2019-10-12",
+ "Genre": "ethno-tunes",
+ "Album": "Here we go again"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": 4,
+ "Artist": "Mar Rueda",
+ "Photo": "https://dl.infragistics.com/x/img/people/names/mar.png",
+ "Debut": 1996,
+ "GrammyNominations": 14,
+ "GrammyAwards": 2,
+ "HasGrammyAward": true,
+ "Tours": [],
+ "Albums": []
+ },
+ {
+ "ID": 5,
+ "Artist": "Izabella Tabakova",
+ "Photo": "https://dl.infragistics.com/x/img/people/names/izabella.png",
+ "Debut": 2017,
+ "GrammyNominations": 7,
+ "GrammyAwards": 11,
+ "HasGrammyAward": true,
+ "Tours": [
+ {
+ "Tour": "Final breath",
+ "StartedOn": "Jun 13",
+ "Location": "Europe",
+ "Headliner": "YES",
+ "TouredBy": "Izabella Tabakova"
+ },
+ {
+ "Tour": "Once bitten",
+ "StartedOn": "Dec 18",
+ "Location": "Australia, United States",
+ "Headliner": "NO",
+ "TouredBy": "Izabella Tabakova"
+ },
+ {
+ "Tour": "Code word",
+ "StartedOn": "Sep 19",
+ "Location": "United States, Europe",
+ "Headliner": "NO",
+ "TouredBy": "Izabella Tabakova"
+ },
+ {
+ "Tour": "Final draft",
+ "StartedOn": "Sep 17",
+ "Location": "United States, Europe",
+ "Headliner": "YES",
+ "TouredBy": "Izabella Tabakova"
+ }
+ ],
+ "Albums": [
+ {
+ "Album": "Once bitten",
+ "LaunchDate": "2007-07-16",
+ "BillboardReview": 79,
+ "USBillboard200": 53,
+ "Artist": "Izabella Tabakova",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Whole Lotta Super Cats",
+ "Released": "2019-05-21",
+ "Genre": "*",
+ "Album": "Once bitten"
+ },
+ {
+ "Number": 2,
+ "Title": "Enter Becky",
+ "Released": "2020-01-16",
+ "Genre": "*",
+ "Album": "Once bitten"
+ },
+ {
+ "Number": 3,
+ "Title": "Your Cheatin' Flamingo",
+ "Released": "2020-01-14",
+ "Genre": "*",
+ "Album": "Once bitten"
+ },
+ {
+ "Number": 4,
+ "Title": "Mad to Kiss",
+ "Released": "2019-11-06",
+ "Genre": "Synth-pop R&B",
+ "Album": "Once bitten"
+ },
+ {
+ "Number": 5,
+ "Title": "Hotel Prague",
+ "Released": "2019-10-20",
+ "Genre": "ethno-tunes",
+ "Album": "Once bitten"
+ },
+ {
+ "Number": 6,
+ "Title": "Jail on My Mind",
+ "Released": "2019-05-31",
+ "Genre": "Crunk reggaeton",
+ "Album": "Once bitten"
+ },
+ {
+ "Number": 7,
+ "Title": "Amazing Blues",
+ "Released": "2019-05-29",
+ "Genre": "mystical parody-bap ",
+ "Album": "Once bitten"
+ },
+ {
+ "Number": 8,
+ "Title": "Goody Two Iron Filings",
+ "Released": "2019-07-04",
+ "Genre": "Electro house Electropop",
+ "Album": "Once bitten"
+ },
+ {
+ "Number": 9,
+ "Title": "I Love in Your Arms",
+ "Released": "2019-06-07",
+ "Genre": "R&B",
+ "Album": "Once bitten"
+ },
+ {
+ "Number": 10,
+ "Title": "Truly Madly Amazing",
+ "Released": "2019-09-12",
+ "Genre": "ethno-tunes",
+ "Album": "Once bitten"
+ }
+ ]
+ },
+ {
+ "Album": "Your graciousness",
+ "LaunchDate": "2004-11-17",
+ "BillboardReview": 69,
+ "USBillboard200": 30,
+ "Artist": "Izabella Tabakova",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "We Shall Tickle",
+ "Released": "2019-08-31",
+ "Genre": "old emo-garage ",
+ "Album": "Your graciousness"
+ },
+ {
+ "Number": 2,
+ "Title": "Snail Boogie",
+ "Released": "2019-06-14",
+ "Genre": "*",
+ "Album": "Your graciousness"
+ },
+ {
+ "Number": 3,
+ "Title": "Amazing Liz",
+ "Released": "2019-10-15",
+ "Genre": "*",
+ "Album": "Your graciousness"
+ },
+ {
+ "Number": 4,
+ "Title": "When Sexy Aardvarks Cry",
+ "Released": "2019-10-01",
+ "Genre": "whimsical comedy-grass ",
+ "Album": "Your graciousness"
+ },
+ {
+ "Number": 5,
+ "Title": "Stand By Dave",
+ "Released": "2019-08-18",
+ "Genre": "unblack electronic-trip-hop",
+ "Album": "Your graciousness"
+ },
+ {
+ "Number": 6,
+ "Title": "The Golf Course is Your Land",
+ "Released": "2019-04-02",
+ "Genre": "*",
+ "Album": "Your graciousness"
+ },
+ {
+ "Number": 7,
+ "Title": "Where Have All the Men Gone?",
+ "Released": "2019-04-29",
+ "Genre": "*",
+ "Album": "Your graciousness"
+ },
+ {
+ "Number": 8,
+ "Title": "Rhythm of the Leg",
+ "Released": "2019-08-05",
+ "Genre": "ethno-tunes",
+ "Album": "Your graciousness"
+ },
+ {
+ "Number": 9,
+ "Title": "Baby, I Need Your Hats",
+ "Released": "2019-12-05",
+ "Genre": "neuro-tunes",
+ "Album": "Your graciousness"
+ },
+ {
+ "Number": 10,
+ "Title": "Stand by Your Cat",
+ "Released": "2019-07-25",
+ "Genre": "*",
+ "Album": "Your graciousness"
+ }
+ ]
+ },
+ {
+ "Album": "Dark matters",
+ "LaunchDate": "2002-11-03",
+ "BillboardReview": 79,
+ "USBillboard200": 85,
+ "Artist": "Izabella Tabakova",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Hiding in the Light",
+ "Released": "2019-01-24",
+ "Genre": "Synth-pop R&B",
+ "Album": "Dark matters"
+ },
+ {
+ "Number": 2,
+ "Title": "Furious River",
+ "Released": "2020-01-13",
+ "Genre": "Electro house Electropop",
+ "Album": "Dark matters"
+ },
+ {
+ "Number": 3,
+ "Title": "Wild Crying",
+ "Released": "2019-02-27",
+ "Genre": "Electro house Electropop",
+ "Album": "Dark matters"
+ },
+ {
+ "Number": 4,
+ "Title": "Light of Dream",
+ "Released": "2019-06-01",
+ "Genre": "Crunk reggaeton",
+ "Album": "Dark matters"
+ },
+ {
+ "Number": 5,
+ "Title": "Light of Dream",
+ "Released": "2019-08-24",
+ "Genre": "*",
+ "Album": "Dark matters"
+ },
+ {
+ "Number": 6,
+ "Title": "Storm of Light",
+ "Released": "2019-02-26",
+ "Genre": "*",
+ "Album": "Dark matters"
+ },
+ {
+ "Number": 7,
+ "Title": "Dark Storm",
+ "Released": "2020-01-18",
+ "Genre": "R&B",
+ "Album": "Dark matters"
+ },
+ {
+ "Number": 8,
+ "Title": "Dark Calling",
+ "Released": "2019-03-20",
+ "Genre": "Crunk reggaeton",
+ "Album": "Dark matters"
+ },
+ {
+ "Number": 9,
+ "Title": "Sky of Whisper",
+ "Released": "2019-01-30",
+ "Genre": "ethno-tunes",
+ "Album": "Dark matters"
+ },
+ {
+ "Number": 10,
+ "Title": "Dancing in the Light",
+ "Released": "2019-11-28",
+ "Genre": "Synth-pop R&B",
+ "Album": "Dark matters"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": 6,
+ "Artist": "Nguyễn Diệp Chi",
+ "Photo": "https://dl.infragistics.com/x/img/people/names/nguyen.png",
+ "Debut": 1992,
+ "GrammyNominations": 4,
+ "GrammyAwards": 2,
+ "HasGrammyAward": true,
+ "Tours": [],
+ "Albums": [
+ {
+ "Album": "Library of liberty",
+ "LaunchDate": "2003-12-22",
+ "BillboardReview": 93,
+ "USBillboard200": 5,
+ "Artist": "Nguyễn Diệp Chi",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Echo of River",
+ "Released": "2019-03-05",
+ "Genre": "Synth-pop R&B",
+ "Album": "Library of liberty"
+ },
+ {
+ "Number": 2,
+ "Title": "Heart of River",
+ "Released": "2020-01-12",
+ "Genre": "Electro house Electropop",
+ "Album": "Library of liberty"
+ },
+ {
+ "Number": 3,
+ "Title": "Dark Light",
+ "Released": "2019-08-09",
+ "Genre": "Electro house Electropop",
+ "Album": "Library of liberty"
+ },
+ {
+ "Number": 4,
+ "Title": "Dark Fire",
+ "Released": "2019-06-22",
+ "Genre": "R&B",
+ "Album": "Library of liberty"
+ },
+ {
+ "Number": 5,
+ "Title": "Flying in the Fire",
+ "Released": "2019-07-22",
+ "Genre": "*",
+ "Album": "Library of liberty"
+ },
+ {
+ "Number": 6,
+ "Title": "Shadow of Heart",
+ "Released": "2020-01-02",
+ "Genre": "*",
+ "Album": "Library of liberty"
+ },
+ {
+ "Number": 7,
+ "Title": "Fire of Fire",
+ "Released": "2019-01-27",
+ "Genre": "*",
+ "Album": "Library of liberty"
+ },
+ {
+ "Number": 8,
+ "Title": "Falling in the River",
+ "Released": "2019-08-05",
+ "Genre": "Crunk reggaeton",
+ "Album": "Library of liberty"
+ },
+ {
+ "Number": 9,
+ "Title": "Fire of Light",
+ "Released": "2019-12-31",
+ "Genre": "ethno-tunes",
+ "Album": "Library of liberty"
+ },
+ {
+ "Number": 10,
+ "Title": "Bright Flying",
+ "Released": "2019-01-24",
+ "Genre": "*",
+ "Album": "Library of liberty"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": 7,
+ "Artist": "Eva Lee",
+ "Photo": "https://dl.infragistics.com/x/img/people/names/eva.png",
+ "Debut": 2008,
+ "GrammyNominations": 2,
+ "GrammyAwards": 0,
+ "HasGrammyAward": false,
+ "Tours": [],
+ "Albums": [
+ {
+ "Album": "Just a tease",
+ "LaunchDate": "2001-05-03",
+ "BillboardReview": 91,
+ "USBillboard200": 29,
+ "Artist": "Eva Lee",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Dancing in the Shadow",
+ "Released": "2019-08-02",
+ "Genre": "ethno-tunes",
+ "Album": "Just a tease"
+ },
+ {
+ "Number": 2,
+ "Title": "Silent Whisper",
+ "Released": "2019-07-09",
+ "Genre": "*",
+ "Album": "Just a tease"
+ },
+ {
+ "Number": 3,
+ "Title": "Crying in the Whisper",
+ "Released": "2019-05-30",
+ "Genre": "Crunk reggaeton",
+ "Album": "Just a tease"
+ },
+ {
+ "Number": 4,
+ "Title": "River of Light",
+ "Released": "2019-01-10",
+ "Genre": "Electro house Electropop",
+ "Album": "Just a tease"
+ },
+ {
+ "Number": 5,
+ "Title": "Golden River",
+ "Released": "2019-11-15",
+ "Genre": "*",
+ "Album": "Just a tease"
+ },
+ {
+ "Number": 6,
+ "Title": "Burning in the Shadow",
+ "Released": "2019-04-18",
+ "Genre": "Crunk reggaeton",
+ "Album": "Just a tease"
+ },
+ {
+ "Number": 7,
+ "Title": "Shadow of Sky",
+ "Released": "2019-09-06",
+ "Genre": "Crunk reggaeton",
+ "Album": "Just a tease"
+ },
+ {
+ "Number": 8,
+ "Title": "Gentle Waiting",
+ "Released": "2019-12-05",
+ "Genre": "R&B",
+ "Album": "Just a tease"
+ },
+ {
+ "Number": 9,
+ "Title": "Bright River",
+ "Released": "2020-01-27",
+ "Genre": "R&B",
+ "Album": "Just a tease"
+ },
+ {
+ "Number": 10,
+ "Title": "Heart of Storm",
+ "Released": "2019-01-07",
+ "Genre": "Synth-pop R&B",
+ "Album": "Just a tease"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": 8,
+ "Artist": "Siri Jakobsson",
+ "Photo": "https://dl.infragistics.com/x/img/people/names/siri.png",
+ "Debut": 1990,
+ "GrammyNominations": 2,
+ "GrammyAwards": 8,
+ "HasGrammyAward": true,
+ "Tours": [
+ {
+ "Tour": "Basket case",
+ "StartedOn": "Jan 07",
+ "Location": "Europe, Asia",
+ "Headliner": "NO",
+ "TouredBy": "Siri Jakobsson"
+ },
+ {
+ "Tour": "The bigger fish",
+ "StartedOn": "Dec 07",
+ "Location": "United States, Europe",
+ "Headliner": "YES",
+ "TouredBy": "Siri Jakobsson"
+ },
+ {
+ "Tour": "Missed the boat",
+ "StartedOn": "Jun 09",
+ "Location": "Europe, Asia",
+ "Headliner": "NO",
+ "TouredBy": "Siri Jakobsson"
+ },
+ {
+ "Tour": "Equivalent exchange",
+ "StartedOn": "Feb 06",
+ "Location": "United States, Europe",
+ "Headliner": "YES",
+ "TouredBy": "Siri Jakobsson"
+ },
+ {
+ "Tour": "Damage control",
+ "StartedOn": "Oct 11",
+ "Location": "Australia, United States",
+ "Headliner": "NO",
+ "TouredBy": "Siri Jakobsson"
+ }
+ ],
+ "Albums": [
+ {
+ "Album": "Under the bus",
+ "LaunchDate": "2000-05-14",
+ "BillboardReview": 67,
+ "USBillboard200": 67,
+ "Artist": "Siri Jakobsson",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Jack Broke My Heart At Tesco's",
+ "Released": "2020-01-19",
+ "Genre": "*",
+ "Album": "Under the bus"
+ },
+ {
+ "Number": 2,
+ "Title": "Cat Deep, Hats High",
+ "Released": "2019-12-05",
+ "Genre": "*",
+ "Album": "Under the bus"
+ },
+ {
+ "Number": 3,
+ "Title": "In Snail We Trust",
+ "Released": "2019-05-31",
+ "Genre": "hardcore opera",
+ "Album": "Under the bus"
+ },
+ {
+ "Number": 4,
+ "Title": "Liz's Waiting",
+ "Released": "2019-07-22",
+ "Genre": "emotional C-jam ",
+ "Album": "Under the bus"
+ },
+ {
+ "Number": 5,
+ "Title": "Lifeless Blues",
+ "Released": "2019-06-14",
+ "Genre": "*",
+ "Album": "Under the bus"
+ },
+ {
+ "Number": 6,
+ "Title": "I Spin",
+ "Released": "2019-03-26",
+ "Genre": "*",
+ "Album": "Under the bus"
+ },
+ {
+ "Number": 7,
+ "Title": "Ring of Rock",
+ "Released": "2019-12-12",
+ "Genre": "*",
+ "Album": "Under the bus"
+ },
+ {
+ "Number": 8,
+ "Title": "Livin' on a Rock",
+ "Released": "2019-04-17",
+ "Genre": "*",
+ "Album": "Under the bus"
+ },
+ {
+ "Number": 9,
+ "Title": "Your Lifeless Heart",
+ "Released": "2019-09-15",
+ "Genre": "adult calypso-industrial",
+ "Album": "Under the bus"
+ },
+ {
+ "Number": 10,
+ "Title": "The High Street on My Mind",
+ "Released": "2019-11-11",
+ "Genre": "calypso and mariachi",
+ "Album": "Under the bus"
+ },
+ {
+ "Number": 11,
+ "Title": "Behind Ugly Curtains",
+ "Released": "2019-05-08",
+ "Genre": "*",
+ "Album": "Under the bus"
+ },
+ {
+ "Number": 12,
+ "Title": "Where Have All the Curtains Gone?",
+ "Released": "2019-06-28",
+ "Genre": "*",
+ "Album": "Under the bus"
+ },
+ {
+ "Number": 13,
+ "Title": "Ghost in My Apple",
+ "Released": "2019-12-14",
+ "Genre": "*",
+ "Album": "Under the bus"
+ },
+ {
+ "Number": 14,
+ "Title": "I Chatter",
+ "Released": "2019-11-30",
+ "Genre": "*",
+ "Album": "Under the bus"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": 9,
+ "Artist": "Pablo Cambeiro",
+ "Photo": "https://dl.infragistics.com/x/img/people/names/pablo.png",
+ "Debut": 2011,
+ "GrammyNominations": 5,
+ "GrammyAwards": 0,
+ "HasGrammyAward": false,
+ "Tours": [
+ {
+ "Tour": "Beads",
+ "StartedOn": "May 11",
+ "Location": "Worldwide",
+ "Headliner": "NO",
+ "TouredBy": "Pablo Cambeiro"
+ },
+ {
+ "Tour": "Concept art",
+ "StartedOn": "Dec 18",
+ "Location": "United States",
+ "Headliner": "YES",
+ "TouredBy": "Pablo Cambeiro"
+ },
+ {
+ "Tour": "Glass shoe",
+ "StartedOn": "Jan 20",
+ "Location": "Worldwide",
+ "Headliner": "YES",
+ "TouredBy": "Pablo Cambeiro"
+ },
+ {
+ "Tour": "Pushing buttons",
+ "StartedOn": "Feb 15",
+ "Location": "Europe, Asia",
+ "Headliner": "NO",
+ "TouredBy": "Pablo Cambeiro"
+ },
+ {
+ "Tour": "Dark matters",
+ "StartedOn": "Jan 04",
+ "Location": "Australia, United States",
+ "Headliner": "YES",
+ "TouredBy": "Pablo Cambeiro"
+ },
+ {
+ "Tour": "Greener grass",
+ "StartedOn": "Sep 09",
+ "Location": "United States, Europe",
+ "Headliner": "NO",
+ "TouredBy": "Pablo Cambeiro"
+ },
+ {
+ "Tour": "Apparatus",
+ "StartedOn": "Nov 16",
+ "Location": "Europe",
+ "Headliner": "NO",
+ "TouredBy": "Pablo Cambeiro"
+ }
+ ],
+ "Albums": [
+ {
+ "Album": "Fluke",
+ "LaunchDate": "2017-08-04",
+ "BillboardReview": 93,
+ "USBillboard200": 98,
+ "Artist": "Pablo Cambeiro",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Dancing in the Echo",
+ "Released": "2019-10-03",
+ "Genre": "Crunk reggaeton",
+ "Album": "Fluke"
+ },
+ {
+ "Number": 2,
+ "Title": "Dream of Dream",
+ "Released": "2019-03-03",
+ "Genre": "*",
+ "Album": "Fluke"
+ },
+ {
+ "Number": 3,
+ "Title": "Calling in the Echo",
+ "Released": "2019-09-16",
+ "Genre": "*",
+ "Album": "Fluke"
+ },
+ {
+ "Number": 4,
+ "Title": "Light of Light",
+ "Released": "2019-05-25",
+ "Genre": "Electro house Electropop",
+ "Album": "Fluke"
+ },
+ {
+ "Number": 5,
+ "Title": "Bright Light",
+ "Released": "2019-03-21",
+ "Genre": "R&B",
+ "Album": "Fluke"
+ },
+ {
+ "Number": 6,
+ "Title": "Storm of Echo",
+ "Released": "2019-07-17",
+ "Genre": "Synth-pop R&B",
+ "Album": "Fluke"
+ },
+ {
+ "Number": 7,
+ "Title": "Lonely Calling",
+ "Released": "2019-04-10",
+ "Genre": "ethno-tunes",
+ "Album": "Fluke"
+ },
+ {
+ "Number": 8,
+ "Title": "Gentle Falling",
+ "Released": "2019-11-28",
+ "Genre": "Synth-pop R&B",
+ "Album": "Fluke"
+ },
+ {
+ "Number": 9,
+ "Title": "Wild Flying",
+ "Released": "2019-11-26",
+ "Genre": "Crunk reggaeton",
+ "Album": "Fluke"
+ },
+ {
+ "Number": 10,
+ "Title": "Sky of Dream",
+ "Released": "2019-05-29",
+ "Genre": "R&B",
+ "Album": "Fluke"
+ }
+ ]
+ },
+ {
+ "Album": "Crowd control",
+ "LaunchDate": "2003-08-26",
+ "BillboardReview": 68,
+ "USBillboard200": 84,
+ "Artist": "Pablo Cambeiro",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "My Bed on My Mind",
+ "Released": "2019-03-25",
+ "Genre": "ethno-tunes",
+ "Album": "Crowd control"
+ },
+ {
+ "Number": 2,
+ "Title": "Bright Blues",
+ "Released": "2019-09-28",
+ "Genre": "neuro-tunes",
+ "Album": "Crowd control"
+ },
+ {
+ "Number": 3,
+ "Title": "Sail, Sail, Sail!",
+ "Released": "2019-03-05",
+ "Genre": "*",
+ "Album": "Crowd control"
+ },
+ {
+ "Number": 4,
+ "Title": "Hotel My Bed",
+ "Released": "2019-03-22",
+ "Genre": "*",
+ "Album": "Crowd control"
+ },
+ {
+ "Number": 5,
+ "Title": "Gonna Make You Mash",
+ "Released": "2019-05-18",
+ "Genre": "*",
+ "Album": "Crowd control"
+ },
+ {
+ "Number": 6,
+ "Title": "Straight Outta America",
+ "Released": "2020-01-16",
+ "Genre": "hardcore opera",
+ "Album": "Crowd control"
+ },
+ {
+ "Number": 7,
+ "Title": "I Drive",
+ "Released": "2019-02-23",
+ "Genre": "emotional C-jam ",
+ "Album": "Crowd control"
+ },
+ {
+ "Number": 8,
+ "Title": "Like a Teddy",
+ "Released": "2019-08-31",
+ "Genre": "*",
+ "Album": "Crowd control"
+ },
+ {
+ "Number": 9,
+ "Title": "Teddy Boogie",
+ "Released": "2019-11-30",
+ "Genre": "*",
+ "Album": "Crowd control"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": 10,
+ "Artist": "Athar Malakooti",
+ "Photo": "https://dl.infragistics.com/x/img/people/names/athar.png",
+ "Debut": 2017,
+ "GrammyNominations": 0,
+ "GrammyAwards": 0,
+ "HasGrammyAward": false,
+ "Tours": [],
+ "Albums": [
+ {
+ "Album": "Pushing up daisies",
+ "LaunchDate": "2016-02-24",
+ "BillboardReview": 74,
+ "USBillboard200": 77,
+ "Artist": "Athar Malakooti",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Hiding in the Whisper",
+ "Released": "2019-04-03",
+ "Genre": "R&B",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 2,
+ "Title": "Wicked Light",
+ "Released": "2019-08-21",
+ "Genre": "R&B",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 3,
+ "Title": "Flying in the River",
+ "Released": "2020-02-03",
+ "Genre": "Synth-pop R&B",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 4,
+ "Title": "Wicked Hiding",
+ "Released": "2019-09-15",
+ "Genre": "Synth-pop R&B",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 5,
+ "Title": "Lonely Light",
+ "Released": "2019-05-13",
+ "Genre": "Electro house Electropop",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 6,
+ "Title": "Bright Dancing",
+ "Released": "2019-04-10",
+ "Genre": "Synth-pop R&B",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 7,
+ "Title": "Gentle Dream",
+ "Released": "2019-05-21",
+ "Genre": "*",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 8,
+ "Title": "Sky of Echo",
+ "Released": "2019-06-09",
+ "Genre": "Synth-pop R&B",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 9,
+ "Title": "Breaking in the Sky",
+ "Released": "2019-12-27",
+ "Genre": "Crunk reggaeton",
+ "Album": "Pushing up daisies"
+ },
+ {
+ "Number": 10,
+ "Title": "Whisper of Shadow",
+ "Released": "2019-01-04",
+ "Genre": "ethno-tunes",
+ "Album": "Pushing up daisies"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": 11,
+ "Artist": "Marti Valencia",
+ "Photo": "https://dl.infragistics.com/x/img/people/names/marti.png",
+ "Debut": 2004,
+ "GrammyNominations": 1,
+ "GrammyAwards": 1,
+ "HasGrammyAward": true,
+ "Tours": [
+ {
+ "Tour": "Cat eat cat world",
+ "StartedOn": "Sep 00",
+ "Location": "Worldwide",
+ "Headliner": "YES",
+ "TouredBy": "Marti Valencia"
+ },
+ {
+ "Tour": "Final straw",
+ "StartedOn": "Sep 06",
+ "Location": "United States, Europe",
+ "Headliner": "NO",
+ "TouredBy": "Marti Valencia"
+ }
+ ],
+ "Albums": [
+ {
+ "Album": "Nemesis",
+ "LaunchDate": "2004-06-30",
+ "BillboardReview": 94,
+ "USBillboard200": 9,
+ "Artist": "Marti Valencia",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Hiding in the Sky",
+ "Released": "2019-11-26",
+ "Genre": "Synth-pop R&B",
+ "Album": "Nemesis"
+ },
+ {
+ "Number": 2,
+ "Title": "Waiting in the Echo",
+ "Released": "2019-07-10",
+ "Genre": "ethno-tunes",
+ "Album": "Nemesis"
+ },
+ {
+ "Number": 3,
+ "Title": "Wicked Shadow",
+ "Released": "2019-07-29",
+ "Genre": "Synth-pop R&B",
+ "Album": "Nemesis"
+ },
+ {
+ "Number": 4,
+ "Title": "Crying in the Whisper",
+ "Released": "2019-04-09",
+ "Genre": "*",
+ "Album": "Nemesis"
+ },
+ {
+ "Number": 5,
+ "Title": "Echo of Storm",
+ "Released": "2019-11-19",
+ "Genre": "Crunk reggaeton",
+ "Album": "Nemesis"
+ },
+ {
+ "Number": 6,
+ "Title": "Shadow of Sky",
+ "Released": "2019-07-24",
+ "Genre": "Crunk reggaeton",
+ "Album": "Nemesis"
+ },
+ {
+ "Number": 7,
+ "Title": "Golden Hiding",
+ "Released": "2019-12-12",
+ "Genre": "Electro house Electropop",
+ "Album": "Nemesis"
+ },
+ {
+ "Number": 8,
+ "Title": "Wild Dancing",
+ "Released": "2019-08-17",
+ "Genre": "Synth-pop R&B",
+ "Album": "Nemesis"
+ },
+ {
+ "Number": 9,
+ "Title": "Bright Burning",
+ "Released": "2019-08-30",
+ "Genre": "Electro house Electropop",
+ "Album": "Nemesis"
+ },
+ {
+ "Number": 10,
+ "Title": "Flying in the River",
+ "Released": "2019-09-02",
+ "Genre": "*",
+ "Album": "Nemesis"
+ }
+ ]
+ },
+ {
+ "Album": "First chance",
+ "LaunchDate": "2019-01-07",
+ "BillboardReview": 96,
+ "USBillboard200": 19,
+ "Artist": "Marti Valencia",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "My Name is Jason",
+ "Released": "2019-07-12",
+ "Genre": "*",
+ "Album": "First chance"
+ },
+ {
+ "Number": 2,
+ "Title": "Amazing Andy",
+ "Released": "2019-03-05",
+ "Genre": "*",
+ "Album": "First chance"
+ },
+ {
+ "Number": 3,
+ "Title": "The Number of your Knight",
+ "Released": "2019-12-04",
+ "Genre": "*",
+ "Album": "First chance"
+ },
+ {
+ "Number": 4,
+ "Title": "I Sail",
+ "Released": "2019-03-03",
+ "Genre": "*",
+ "Album": "First chance"
+ },
+ {
+ "Number": 5,
+ "Title": "Goody Two Hands",
+ "Released": "2019-10-11",
+ "Genre": "Electro house Electropop",
+ "Album": "First chance"
+ },
+ {
+ "Number": 6,
+ "Title": "Careful With That Knife",
+ "Released": "2019-12-18",
+ "Genre": "R&B",
+ "Album": "First chance"
+ },
+ {
+ "Number": 7,
+ "Title": "Four Single Ants",
+ "Released": "2020-01-18",
+ "Genre": "*",
+ "Album": "First chance"
+ },
+ {
+ "Number": 8,
+ "Title": "Kiss Forever",
+ "Released": "2019-08-10",
+ "Genre": "*",
+ "Album": "First chance"
+ },
+ {
+ "Number": 9,
+ "Title": "Rich's Waiting",
+ "Released": "2019-03-15",
+ "Genre": "Synth-pop R&B",
+ "Album": "First chance"
+ },
+ {
+ "Number": 10,
+ "Title": "Japan is Your Land",
+ "Released": "2019-03-07",
+ "Genre": "ethno-tunes",
+ "Album": "First chance"
+ },
+ {
+ "Number": 11,
+ "Title": "Pencils in My Banana",
+ "Released": "2019-06-21",
+ "Genre": "Crunk reggaeton",
+ "Album": "First chance"
+ },
+ {
+ "Number": 12,
+ "Title": "I Sail in Your Arms",
+ "Released": "2019-04-30",
+ "Genre": "Synth-pop R&B",
+ "Album": "First chance"
+ }
+ ]
+ },
+ {
+ "Album": "God's advocate",
+ "LaunchDate": "2007-04-29",
+ "BillboardReview": 66,
+ "USBillboard200": 37,
+ "Artist": "Marti Valencia",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Wild River",
+ "Released": "2019-01-11",
+ "Genre": "*",
+ "Album": "God's advocate"
+ },
+ {
+ "Number": 2,
+ "Title": "Wicked Whisper",
+ "Released": "2019-02-16",
+ "Genre": "Electro house Electropop",
+ "Album": "God's advocate"
+ },
+ {
+ "Number": 3,
+ "Title": "Storm of Heart",
+ "Released": "2019-08-11",
+ "Genre": "*",
+ "Album": "God's advocate"
+ },
+ {
+ "Number": 4,
+ "Title": "Golden Dancing",
+ "Released": "2019-03-02",
+ "Genre": "Crunk reggaeton",
+ "Album": "God's advocate"
+ },
+ {
+ "Number": 5,
+ "Title": "Calling in the Sky",
+ "Released": "2019-09-10",
+ "Genre": "Electro house Electropop",
+ "Album": "God's advocate"
+ },
+ {
+ "Number": 6,
+ "Title": "Calling in the Heart",
+ "Released": "2019-01-12",
+ "Genre": "ethno-tunes",
+ "Album": "God's advocate"
+ },
+ {
+ "Number": 7,
+ "Title": "Running in the Storm",
+ "Released": "2019-11-10",
+ "Genre": "Synth-pop R&B",
+ "Album": "God's advocate"
+ },
+ {
+ "Number": 8,
+ "Title": "Wild Sky",
+ "Released": "2019-04-10",
+ "Genre": "R&B",
+ "Album": "God's advocate"
+ },
+ {
+ "Number": 9,
+ "Title": "Crying in the Shadow",
+ "Released": "2019-03-02",
+ "Genre": "R&B",
+ "Album": "God's advocate"
+ },
+ {
+ "Number": 10,
+ "Title": "Whisper of River",
+ "Released": "2019-05-12",
+ "Genre": "*",
+ "Album": "God's advocate"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": 12,
+ "Artist": "Alicia Stanger",
+ "Photo": "https://dl.infragistics.com/x/img/people/names/alicia.png",
+ "Debut": 2010,
+ "GrammyNominations": 1,
+ "GrammyAwards": 0,
+ "HasGrammyAward": false,
+ "Tours": [],
+ "Albums": [
+ {
+ "Album": "Forever alone",
+ "LaunchDate": "2005-11-03",
+ "BillboardReview": 82,
+ "USBillboard200": 7,
+ "Artist": "Alicia Stanger",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Shadow of Light",
+ "Released": "2019-03-24",
+ "Genre": "ethno-tunes",
+ "Album": "Forever alone"
+ },
+ {
+ "Number": 2,
+ "Title": "Running in the Echo",
+ "Released": "2019-05-03",
+ "Genre": "Crunk reggaeton",
+ "Album": "Forever alone"
+ },
+ {
+ "Number": 3,
+ "Title": "Gentle Dream",
+ "Released": "2019-08-24",
+ "Genre": "Crunk reggaeton",
+ "Album": "Forever alone"
+ },
+ {
+ "Number": 4,
+ "Title": "Furious River",
+ "Released": "2019-04-24",
+ "Genre": "ethno-tunes",
+ "Album": "Forever alone"
+ },
+ {
+ "Number": 5,
+ "Title": "Wild Whisper",
+ "Released": "2019-03-09",
+ "Genre": "ethno-tunes",
+ "Album": "Forever alone"
+ },
+ {
+ "Number": 6,
+ "Title": "Whisper of Sky",
+ "Released": "2019-07-24",
+ "Genre": "Crunk reggaeton",
+ "Album": "Forever alone"
+ },
+ {
+ "Number": 7,
+ "Title": "Lonely Storm",
+ "Released": "2019-05-01",
+ "Genre": "Crunk reggaeton",
+ "Album": "Forever alone"
+ },
+ {
+ "Number": 8,
+ "Title": "Dancing in the River",
+ "Released": "2019-12-17",
+ "Genre": "*",
+ "Album": "Forever alone"
+ },
+ {
+ "Number": 9,
+ "Title": "Electric Fire",
+ "Released": "2019-10-17",
+ "Genre": "Electro house Electropop",
+ "Album": "Forever alone"
+ },
+ {
+ "Number": 10,
+ "Title": "Electric Sky",
+ "Released": "2019-09-25",
+ "Genre": "ethno-tunes",
+ "Album": "Forever alone"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": 13,
+ "Artist": "Peter Taylor",
+ "Photo": "https://dl.infragistics.com/x/img/people/names/peter.png",
+ "Debut": 2005,
+ "GrammyNominations": 0,
+ "GrammyAwards": 2,
+ "HasGrammyAward": true,
+ "Tours": [
+ {
+ "Tour": "Love",
+ "StartedOn": "Jun 04",
+ "Location": "Europe, Asia",
+ "Headliner": "YES",
+ "TouredBy": "Peter Taylor"
+ },
+ {
+ "Tour": "Fault of treasures",
+ "StartedOn": "Oct 13",
+ "Location": "North America",
+ "Headliner": "NO",
+ "TouredBy": "Peter Taylor"
+ },
+ {
+ "Tour": "For eternity",
+ "StartedOn": "Mar 05",
+ "Location": "United States",
+ "Headliner": "YES",
+ "TouredBy": "Peter Taylor"
+ },
+ {
+ "Tour": "Time flies",
+ "StartedOn": "Jun 03",
+ "Location": "North America",
+ "Headliner": "NO",
+ "TouredBy": "Peter Taylor"
+ },
+ {
+ "Tour": "Highest difficulty",
+ "StartedOn": "Nov 01",
+ "Location": "Worldwide",
+ "Headliner": "YES",
+ "TouredBy": "Peter Taylor"
+ },
+ {
+ "Tour": "Sleeping dogs",
+ "StartedOn": "May 04",
+ "Location": "United States, Europe",
+ "Headliner": "NO",
+ "TouredBy": "Peter Taylor"
+ }
+ ],
+ "Albums": [
+ {
+ "Album": "Decisions decisions",
+ "LaunchDate": "2008-04-10",
+ "BillboardReview": 85,
+ "USBillboard200": 35,
+ "Artist": "Peter Taylor",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Calling in the Dream",
+ "Released": "2019-08-01",
+ "Genre": "R&B",
+ "Album": "Decisions decisions"
+ },
+ {
+ "Number": 2,
+ "Title": "Electric Burning",
+ "Released": "2019-09-10",
+ "Genre": "Electro house Electropop",
+ "Album": "Decisions decisions"
+ },
+ {
+ "Number": 3,
+ "Title": "Dark Flying",
+ "Released": "2019-04-28",
+ "Genre": "*",
+ "Album": "Decisions decisions"
+ },
+ {
+ "Number": 4,
+ "Title": "Gentle Sky",
+ "Released": "2019-11-20",
+ "Genre": "ethno-tunes",
+ "Album": "Decisions decisions"
+ },
+ {
+ "Number": 5,
+ "Title": "Gentle Calling",
+ "Released": "2019-01-13",
+ "Genre": "Crunk reggaeton",
+ "Album": "Decisions decisions"
+ },
+ {
+ "Number": 6,
+ "Title": "Golden Falling",
+ "Released": "2019-02-14",
+ "Genre": "Crunk reggaeton",
+ "Album": "Decisions decisions"
+ },
+ {
+ "Number": 7,
+ "Title": "Silent River",
+ "Released": "2019-02-13",
+ "Genre": "R&B",
+ "Album": "Decisions decisions"
+ },
+ {
+ "Number": 8,
+ "Title": "Furious Calling",
+ "Released": "2019-06-11",
+ "Genre": "Synth-pop R&B",
+ "Album": "Decisions decisions"
+ },
+ {
+ "Number": 9,
+ "Title": "Running in the Echo",
+ "Released": "2019-11-06",
+ "Genre": "Electro house Electropop",
+ "Album": "Decisions decisions"
+ },
+ {
+ "Number": 10,
+ "Title": "Furious River",
+ "Released": "2019-03-12",
+ "Genre": "*",
+ "Album": "Decisions decisions"
+ }
+ ]
+ },
+ {
+ "Album": "Climate changed",
+ "LaunchDate": "2015-06-20",
+ "BillboardReview": 66,
+ "USBillboard200": 89,
+ "Artist": "Peter Taylor",
+ "Songs": [
+ {
+ "Number": 1,
+ "Title": "Dark Crying",
+ "Released": "2019-04-27",
+ "Genre": "Electro house Electropop",
+ "Album": "Climate changed"
+ },
+ {
+ "Number": 2,
+ "Title": "Dark Waiting",
+ "Released": "2019-11-14",
+ "Genre": "Synth-pop R&B",
+ "Album": "Climate changed"
+ },
+ {
+ "Number": 3,
+ "Title": "Furious Waiting",
+ "Released": "2019-05-23",
+ "Genre": "*",
+ "Album": "Climate changed"
+ },
+ {
+ "Number": 4,
+ "Title": "Running in the Echo",
+ "Released": "2019-11-29",
+ "Genre": "Crunk reggaeton",
+ "Album": "Climate changed"
+ },
+ {
+ "Number": 5,
+ "Title": "Dream of Sky",
+ "Released": "2019-10-31",
+ "Genre": "Crunk reggaeton",
+ "Album": "Climate changed"
+ },
+ {
+ "Number": 6,
+ "Title": "Hiding in the Heart",
+ "Released": "2019-08-09",
+ "Genre": "R&B",
+ "Album": "Climate changed"
+ },
+ {
+ "Number": 7,
+ "Title": "Sky of Storm",
+ "Released": "2019-06-01",
+ "Genre": "R&B",
+ "Album": "Climate changed"
+ },
+ {
+ "Number": 8,
+ "Title": "Light of Storm",
+ "Released": "2020-01-17",
+ "Genre": "ethno-tunes",
+ "Album": "Climate changed"
+ },
+ {
+ "Number": 9,
+ "Title": "Light of Sky",
+ "Released": "2019-05-26",
+ "Genre": "*",
+ "Album": "Climate changed"
+ },
+ {
+ "Number": 10,
+ "Title": "Golden River",
+ "Released": "2019-06-19",
+ "Genre": "*",
+ "Album": "Climate changed"
+ }
+ ]
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing/src/index.css b/samples/grids/hierarchical-grid/grid-batch-editing/src/index.css
new file mode 100644
index 0000000000..7a4d6b21d7
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing/src/index.css
@@ -0,0 +1,35 @@
+/* shared styles are loaded from: */
+/* https://dl.infragistics.com/x/css/samples/shared.v8.css */
+
+.buttons-wrapper {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ padding: 10px 0;
+}
+
+.buttons-right {
+ display: flex;
+ gap: 8px;
+}
+
+.dialog-buttons {
+ display: flex;
+ gap: 8px;
+ justify-content: flex-end;
+}
+
+.transaction--add {
+ color: #6b3;
+ font-weight: 600;
+}
+
+.transaction--update {
+ color: #4a71b9;
+ font-weight: 600;
+}
+
+.transaction--delete {
+ color: #ee4920;
+ font-weight: 600;
+}
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing/src/index.tsx b/samples/grids/hierarchical-grid/grid-batch-editing/src/index.tsx
new file mode 100644
index 0000000000..f8d6eb347d
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing/src/index.tsx
@@ -0,0 +1,241 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './index.css';
+
+import { IgrHierarchicalGrid, IgrColumn, IgrRowIsland, IgrGrid, IgrCellTemplateContext } from 'igniteui-react-grids';
+import { IgrButton, IgrDialog } from 'igniteui-react';
+import SingersData from './SingersData.json';
+
+import 'igniteui-react-grids/grids/themes/light/bootstrap.css';
+import 'igniteui-webcomponents/themes/light/bootstrap.css';
+
+interface SampleState {
+ undoDisabled: boolean;
+ redoDisabled: boolean;
+ commitDisabled: boolean;
+ discardDisabled: boolean;
+ transactionData: any[];
+}
+
+export default class Sample extends React.Component, SampleState> {
+ private grid: IgrHierarchicalGrid | null = null;
+ private dialog: IgrDialog | null = null;
+ private addId: number = 1000;
+
+ constructor(props: Record) {
+ super(props);
+ this.state = {
+ undoDisabled: true,
+ redoDisabled: true,
+ commitDisabled: true,
+ discardDisabled: true,
+ transactionData: []
+ };
+ }
+
+ public componentWillUnmount(): void {
+ if (this.stateUpdateSubscription) {
+ this.stateUpdateSubscription();
+ }
+ }
+
+ private stateUpdateSubscription: (() => void) | null = null;
+
+ public render() {
+ const { undoDisabled, redoDisabled, commitDisabled, discardDisabled, transactionData } = this.state;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add Row
+
+
+
+ Undo
+
+
+ Redo
+
+
+ Discard
+
+
+ Commit
+
+
+
+
+
+
+
+
+
+
+
+
+ Commit
+
+
+ Discard
+
+
+ Cancel
+
+
+
+
+ );
+ }
+
+ private _singersData: any[] = SingersData;
+ public get singersData(): any[] {
+ return this._singersData;
+ }
+
+ private gridRef = (r: IgrHierarchicalGrid) => {
+ if (!r) { return; }
+ this.grid = r;
+ (this.grid as any).batchEditing = true;
+
+ this.stateUpdateSubscription = this.grid.transactions.onStateUpdate.subscribe(() => {
+ if (!this.grid) { return; }
+ const hasChanges = this.grid.transactions.getAggregatedChanges(false).length > 0;
+ this.setState({
+ undoDisabled: !this.grid.transactions.canUndo,
+ redoDisabled: !this.grid.transactions.canRedo,
+ commitDisabled: !hasChanges,
+ discardDisabled: !hasChanges
+ });
+ });
+ }
+
+ private dialogRef = (r: IgrDialog) => {
+ this.dialog = r;
+ }
+
+ private randomInt(min: number, max: number): number {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ }
+
+ public onAddRow = () => {
+ this.grid?.addRow({
+ ID: this.addId++,
+ Artist: `New Artist ${this.randomInt(1, 100)}`,
+ Debut: this.randomInt(1990, 2025),
+ GrammyNominations: this.randomInt(0, 20),
+ GrammyAwards: this.randomInt(0, 10),
+ HasGrammyAward: this.randomInt(0, 1) === 1,
+ Albums: [],
+ Tours: []
+ });
+ }
+
+ public onUndo = () => {
+ this.grid?.endEdit(true);
+ this.grid?.transactions.undo();
+ }
+
+ public onRedo = () => {
+ this.grid?.endEdit(true);
+ this.grid?.transactions.redo();
+ }
+
+ public onOpenCommitDialog = () => {
+ if (!this.grid) { return; }
+ this.setState({
+ transactionData: this.grid.transactions.getAggregatedChanges(true)
+ });
+ this.dialog?.show();
+ }
+
+ public onCommit = () => {
+ if (!this.grid) { return; }
+ this.grid.transactions.commit(this.grid.data);
+ this.dialog?.hide();
+ }
+
+ public onDiscard = () => {
+ this.grid?.transactions.clear();
+ this.dialog?.hide();
+ }
+
+ public onCancel = () => {
+ this.dialog?.hide();
+ }
+
+ public deleteRowCellTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ const id = e.dataContext.cell.id.rowID;
+ return (
+ this.grid?.deleteRow(id)}>
+ Delete
+
+ );
+ }
+
+ public typeColumnTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ const type = e.dataContext.cell.value as string;
+ return {type.toUpperCase()};
+ }
+
+ public valueColumnTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ return {JSON.stringify(e.dataContext.cell.value)};
+ }
+}
+
+// rendering above component in the React DOM
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render();
diff --git a/samples/grids/hierarchical-grid/grid-batch-editing/tsconfig.json b/samples/grids/hierarchical-grid/grid-batch-editing/tsconfig.json
new file mode 100644
index 0000000000..8c0d146f95
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing/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/hierarchical-grid/grid-batch-editing/vite.config.js b/samples/grids/hierarchical-grid/grid-batch-editing/vite.config.js
new file mode 100644
index 0000000000..1744dbc719
--- /dev/null
+++ b/samples/grids/hierarchical-grid/grid-batch-editing/vite.config.js
@@ -0,0 +1,12 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+ build: {
+ outDir: 'build'
+ },
+ server: {
+ open: false
+ },
+});
\ No newline at end of file
diff --git a/samples/grids/tree-grid/grid-batch-editing-remote/.devcontainer/devcontainer.json b/samples/grids/tree-grid/grid-batch-editing-remote/.devcontainer/devcontainer.json
new file mode 100644
index 0000000000..e0b8e9c925
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing-remote/.devcontainer/devcontainer.json
@@ -0,0 +1,4 @@
+{
+ "name": "Node.js",
+ "image": "mcr.microsoft.com/devcontainers/javascript-node:22"
+}
\ No newline at end of file
diff --git a/samples/grids/tree-grid/grid-batch-editing-remote/.eslintrc.js b/samples/grids/tree-grid/grid-batch-editing-remote/.eslintrc.js
new file mode 100644
index 0000000000..7168b71441
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing-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/tree-grid/grid-batch-editing-remote/ReadMe.md b/samples/grids/tree-grid/grid-batch-editing-remote/ReadMe.md
new file mode 100644
index 0000000000..31d6ecb845
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing-remote/ReadMe.md
@@ -0,0 +1,56 @@
+
+
+
+This folder contains implementation of React application with example of Editing Events feature using [Tree Grid](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html) component.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Branches
+
+> **_NOTE:_** You should use [master](https://github.com/IgniteUI/igniteui-react-examples/tree/master) branch of this repository if you want to run samples on your computer. Use the [vnext](https://github.com/IgniteUI/igniteui-react-examples/tree/vnext) branch only when you want to contribute new samples to this repository.
+
+## Instructions
+
+Follow these instructions to run this example:
+
+
+```
+git clone https://github.com/IgniteUI/igniteui-react-examples.git
+git checkout master
+cd ./igniteui-react-examples
+cd ./samples/grids/tree-grid/editing-events
+```
+
+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/tree-grid/grid-batch-editing-remote/index.html b/samples/grids/tree-grid/grid-batch-editing-remote/index.html
new file mode 100644
index 0000000000..7ccd41dace
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing-remote/index.html
@@ -0,0 +1,12 @@
+
+
+
+ Sample | Ignite UI | React | infragistics
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/grids/tree-grid/grid-batch-editing-remote/package.json b/samples/grids/tree-grid/grid-batch-editing-remote/package.json
new file mode 100644
index 0000000000..c2fc0d47eb
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing-remote/package.json
@@ -0,0 +1,49 @@
+{
+ "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": "vite --port 4200",
+ "build": "tsc && node --max-old-space-size=4096 node_modules/vite/bin/vite build",
+ "preview": "vite preview",
+ "test": "vitest",
+ "lint": "eslint ./src/**/*.{ts,tsx}"
+ },
+ "dependencies": {
+ "igniteui-dockmanager": "^1.17.0",
+ "igniteui-react": "^19.4.0",
+ "igniteui-react-core": "19.3.2",
+ "igniteui-react-grids": "^19.5.0-beta.0",
+ "igniteui-webcomponents": "^6.3.0",
+ "lit-html": "^3.2.0",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "tslib": "^2.4.0"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.2.0",
+ "@types/node": "^24.7.1",
+ "@types/react": "^18.0.24",
+ "@types/react-dom": "^18.0.8",
+ "@vitejs/plugin-react": "^5.0.4",
+ "@vitest/browser": "^3.2.4",
+ "eslint": "^8.33.0",
+ "eslint-config-react": "^1.1.7",
+ "eslint-plugin-react": "^7.20.0",
+ "typescript": "^4.8.4",
+ "vite": "^7.1.9",
+ "vitest": "^3.2.4",
+ "vitest-canvas-mock": "^0.3.3",
+ "worker-loader": "^3.0.8"
+ },
+ "browserslist": [
+ ">0.2%",
+ "not dead",
+ "not ie <= 11",
+ "not op_mini all"
+ ]
+}
diff --git a/samples/grids/tree-grid/grid-batch-editing-remote/sandbox.config.json b/samples/grids/tree-grid/grid-batch-editing-remote/sandbox.config.json
new file mode 100644
index 0000000000..07f53508eb
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing-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/tree-grid/grid-batch-editing-remote/src/EmployeesWithPageResponseModel.ts b/samples/grids/tree-grid/grid-batch-editing-remote/src/EmployeesWithPageResponseModel.ts
new file mode 100644
index 0000000000..522812133f
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing-remote/src/EmployeesWithPageResponseModel.ts
@@ -0,0 +1,7 @@
+export interface EmployeesWithPageResponseModel {
+ items: any[];
+ totalRecordsCount: number;
+ pageSize: number;
+ pageNumber: number;
+ totalPages: number;
+}
diff --git a/samples/grids/tree-grid/grid-batch-editing-remote/src/RemotePagingService.ts b/samples/grids/tree-grid/grid-batch-editing-remote/src/RemotePagingService.ts
new file mode 100644
index 0000000000..a660338334
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing-remote/src/RemotePagingService.ts
@@ -0,0 +1,53 @@
+import { EmployeesNestedTreeDataItem } from './EmployeesNestedTreeData';
+import { EmployeesWithPageResponseModel } from './EmployeesWithPageResponseModel';
+
+// Simulated remote data - in a real app, this would come from an API
+const ALL_EMPLOYEES: EmployeesNestedTreeDataItem[] = [
+ { Age: 55, HireDate: '2008-03-20', ID: 1, Name: 'Johnathan Winchester', Phone: '0251-031259', OnPTO: false, ParentID: -1, Title: 'Development Manager' },
+ { Age: 42, HireDate: '2014-01-22', ID: 4, Name: 'Ana Sanders', Phone: '(21) 555-0091', OnPTO: true, ParentID: -1, Title: 'CEO' },
+ { Age: 49, HireDate: '2014-01-22', ID: 18, Name: 'Victoria Lincoln', Phone: '(071) 23 67 22 20', OnPTO: true, ParentID: -1, Title: 'Accounting Manager' },
+ { Age: 61, HireDate: '2010-01-01', ID: 10, Name: 'Yang Wang', Phone: '(21) 555-0091', OnPTO: false, ParentID: -1, Title: 'Localization Manager' },
+ { Age: 43, HireDate: '2011-06-03', ID: 3, Name: 'Michael Burke', Phone: '0452-076545', OnPTO: true, ParentID: 1, Title: 'Senior Software Developer' },
+ { Age: 29, HireDate: '2009-06-19', ID: 2, Name: 'Thomas Anderson', Phone: '(14) 555-8122', OnPTO: false, ParentID: 1, Title: 'Senior Software Developer' },
+ { Age: 31, HireDate: '2014-08-18', ID: 11, Name: 'Monica Reyes', Phone: '7675-3425', OnPTO: false, ParentID: 1, Title: 'Software Development Team Lead' },
+ { Age: 35, HireDate: '2015-09-17', ID: 6, Name: 'Roland Mendel', Phone: '(505) 555-5939', OnPTO: false, ParentID: 11, Title: 'Senior Software Developer' },
+ { Age: 44, HireDate: '2009-10-11', ID: 12, Name: 'Sven Cooper', Phone: '0695-34 67 21', OnPTO: true, ParentID: 11, Title: 'Senior Software Developer' },
+ { Age: 44, HireDate: '2014-04-04', ID: 14, Name: 'Laurence Johnson', Phone: '981-443655', OnPTO: false, ParentID: 4, Title: 'Director' },
+ { Age: 25, HireDate: '2017-11-09', ID: 5, Name: 'Elizabeth Richards', Phone: '(2) 283-2951', OnPTO: true, ParentID: 4, Title: 'Vice President' },
+ { Age: 39, HireDate: '2010-03-22', ID: 13, Name: 'Trevor Ashworth', Phone: '981-443655', OnPTO: true, ParentID: 5, Title: 'Director' },
+ { Age: 44, HireDate: '2014-04-04', ID: 17, Name: 'Antonio Moreno', Phone: '(505) 555-5939', OnPTO: false, ParentID: 18, Title: 'Senior Accountant' },
+ { Age: 50, HireDate: '2007-11-18', ID: 7, Name: 'Pedro Rodriguez', Phone: '035-640230', OnPTO: false, ParentID: 10, Title: 'Senior Localization Developer' },
+ { Age: 27, HireDate: '2016-02-19', ID: 8, Name: 'Casey Harper', Phone: '0342-023176', OnPTO: true, ParentID: 10, Title: 'Senior Localization' },
+ { Age: 25, HireDate: '2017-11-09', ID: 15, Name: 'Patricia Simpson', Phone: '069-0245984', OnPTO: false, ParentID: 7, Title: 'Localization Intern' },
+ { Age: 39, HireDate: '2010-03-22', ID: 9, Name: 'Francisco Chang', Phone: '(91) 745 6200', OnPTO: false, ParentID: 7, Title: 'Localization Intern' },
+ { Age: 25, HireDate: '2018-03-18', ID: 16, Name: 'Peter Lewis', Phone: '069-0245984', OnPTO: true, ParentID: 7, Title: 'Localization Intern' },
+];
+
+export class RemotePagingService {
+ private static delay(ms: number): Promise {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+
+ public static async getDataWithPaging(pageIndex: number = 0, pageSize: number = 10): Promise {
+ // Simulate network delay
+ await this.delay(300);
+
+ const totalRecords = ALL_EMPLOYEES.length;
+ const totalPages = Math.ceil(totalRecords / pageSize);
+ const startIndex = pageIndex * pageSize;
+ const endIndex = Math.min(startIndex + pageSize, totalRecords);
+ const items = ALL_EMPLOYEES.slice(startIndex, endIndex);
+
+ return {
+ items,
+ totalRecordsCount: totalRecords,
+ pageSize,
+ pageNumber: pageIndex,
+ totalPages
+ };
+ }
+
+ public static getTotalRecords(): number {
+ return ALL_EMPLOYEES.length;
+ }
+}
diff --git a/samples/grids/tree-grid/grid-batch-editing-remote/src/index.css b/samples/grids/tree-grid/grid-batch-editing-remote/src/index.css
new file mode 100644
index 0000000000..17966fb590
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing-remote/src/index.css
@@ -0,0 +1,44 @@
+/* shared styles are loaded from: */
+/* https://dl.infragistics.com/x/css/samples/shared.v8.css */
+
+.buttons-wrapper {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ padding: 10px 0;
+}
+
+.buttons-right {
+ display: flex;
+ gap: 8px;
+}
+
+.dialog-buttons {
+ display: flex;
+ gap: 8px;
+ justify-content: flex-end;
+}
+
+.transaction--add {
+ color: #6b3;
+ font-weight: 600;
+}
+
+.transaction--update {
+ color: #4a71b9;
+ font-weight: 600;
+}
+
+.transaction--delete {
+ color: #ee4920;
+ font-weight: 600;
+}
+
+.error-banner {
+ background-color: #fee;
+ border: 1px solid #fcc;
+ border-radius: 4px;
+ color: #c00;
+ padding: 10px 15px;
+ margin-bottom: 10px;
+}
diff --git a/samples/grids/tree-grid/grid-batch-editing-remote/src/index.tsx b/samples/grids/tree-grid/grid-batch-editing-remote/src/index.tsx
new file mode 100644
index 0000000000..1abeef309b
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing-remote/src/index.tsx
@@ -0,0 +1,289 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './index.css';
+
+import { IgrTreeGrid, IgrPaginator, IgrColumn, IgrGrid, IgrCellTemplateContext } from 'igniteui-react-grids';
+import { IgrButton, IgrDialog, IgrNumberEventArgs } from 'igniteui-react';
+import { RemotePagingService } from './RemotePagingService';
+import { EmployeesWithPageResponseModel } from './EmployeesWithPageResponseModel';
+
+import 'igniteui-react-grids/grids/themes/light/bootstrap.css';
+import 'igniteui-webcomponents/themes/light/bootstrap.css';
+
+interface SampleState {
+ undoDisabled: boolean;
+ redoDisabled: boolean;
+ commitDisabled: boolean;
+ discardDisabled: boolean;
+ transactionData: any[];
+ data: any[];
+ page: number;
+ perPage: number;
+ totalRecords: number;
+}
+
+export default class Sample extends React.Component, SampleState> {
+ private grid: IgrTreeGrid | null = null;
+ private paginator: IgrPaginator | null = null;
+ private dialog: IgrDialog | null = null;
+ private addId: number = 1000;
+ private stateUpdateSubscription: (() => void) | null = null;
+ private isUpdatingTransactionState: boolean = false;
+
+ constructor(props: Record) {
+ super(props);
+ this.state = {
+ undoDisabled: true,
+ redoDisabled: true,
+ commitDisabled: true,
+ discardDisabled: true,
+ transactionData: [],
+ data: [],
+ page: 0,
+ perPage: 10,
+ totalRecords: 0
+ };
+ }
+
+ public componentDidMount(): void {
+ this.loadGridData(this.state.page, this.state.perPage);
+ }
+
+ public componentWillUnmount(): void {
+ if (this.stateUpdateSubscription) {
+ this.stateUpdateSubscription();
+ }
+ }
+
+ private loadGridData(pageIndex: number, pageSize: number): void {
+ if (this.grid) {
+ this.grid.isLoading = true;
+ }
+
+ RemotePagingService.getDataWithPaging(pageIndex, pageSize)
+ .then((response: EmployeesWithPageResponseModel) => {
+ this.setState({
+ data: response.items,
+ totalRecords: response.totalRecordsCount
+ });
+ if (this.grid) {
+ this.grid.isLoading = false;
+ }
+ if (this.paginator) {
+ this.paginator.totalRecords = response.totalRecordsCount;
+ }
+ })
+ .catch((error) => {
+ console.error(error.message);
+ this.setState({ data: [] });
+ if (this.grid) {
+ this.grid.isLoading = false;
+ }
+ });
+ }
+
+ public render() {
+ const { undoDisabled, redoDisabled, commitDisabled, discardDisabled, transactionData, data, perPage } = this.state;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add Row
+
+
+
+ Undo
+
+
+ Redo
+
+
+ Discard
+
+
+ Commit
+
+
+
+
+
+
+
+
+
+
+
+
+ Commit
+
+
+ Discard
+
+
+ Cancel
+
+
+
+
+ );
+ }
+
+ private gridRef = (r: IgrTreeGrid) => {
+ if (!r) { return; }
+ this.grid = r;
+ (this.grid as any).batchEditing = true;
+
+ this.stateUpdateSubscription = this.grid.transactions.onStateUpdate.subscribe(() => {
+ if (!this.grid) { return; }
+ const hasChanges = this.grid.transactions.getAggregatedChanges(false).length > 0;
+ this.isUpdatingTransactionState = true;
+ this.setState({
+ undoDisabled: !this.grid.transactions.canUndo,
+ redoDisabled: !this.grid.transactions.canRedo,
+ commitDisabled: !hasChanges,
+ discardDisabled: !hasChanges
+ }, () => {
+ this.isUpdatingTransactionState = false;
+ });
+ });
+ }
+
+ private paginatorRef = (r: IgrPaginator) => {
+ this.paginator = r;
+ if (this.paginator && this.state.totalRecords > 0) {
+ this.paginator.totalRecords = this.state.totalRecords;
+ }
+ }
+
+ private dialogRef = (r: IgrDialog) => {
+ this.dialog = r;
+ }
+
+ private onPageChange = (args: IgrNumberEventArgs) => {
+ // Ignore page changes triggered by transaction state updates
+ if (this.isUpdatingTransactionState) {
+ return;
+ }
+ const newPage = args.detail;
+ // Only load data if page actually changed
+ if (newPage !== this.state.page) {
+ this.setState({ page: newPage });
+ this.loadGridData(newPage, this.state.perPage);
+ }
+ }
+
+ private onPerPageChange = (args: IgrNumberEventArgs) => {
+ const newPerPage = args.detail;
+ this.setState({ perPage: newPerPage, page: 0 });
+ this.loadGridData(0, newPerPage);
+ }
+
+ private randomInt(min: number, max: number): number {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ }
+
+ public onAddRow = () => {
+ this.grid?.addRow({
+ ID: this.addId++,
+ ParentID: -1,
+ Name: `New Employee ${this.randomInt(1, 100)}`,
+ Title: 'Employee',
+ Age: this.randomInt(20, 60),
+ HireDate: new Date(this.randomInt(2000, 2025),
+ this.randomInt(0, 11), this.randomInt(1, 25))
+ .toISOString().slice(0, 10),
+ Phone: `555-${this.randomInt(1000, 9999)}`,
+ OnPTO: this.randomInt(0, 1) === 1
+ });
+ }
+
+ public onUndo = () => {
+ this.grid?.endEdit(true);
+ this.grid?.transactions.undo();
+ }
+
+ public onRedo = () => {
+ this.grid?.endEdit(true);
+ this.grid?.transactions.redo();
+ }
+
+ public onOpenCommitDialog = () => {
+ if (!this.grid) { return; }
+ this.setState({
+ transactionData: this.grid.transactions.getAggregatedChanges(true)
+ });
+ this.dialog?.show();
+ }
+
+ public onCommit = () => {
+ if (!this.grid) { return; }
+ this.grid.transactions.commit(this.grid.data);
+ this.dialog?.hide();
+ }
+
+ public onDiscard = () => {
+ this.grid?.transactions.clear();
+ this.dialog?.hide();
+ }
+
+ public onCancel = () => {
+ this.dialog?.hide();
+ }
+
+ public deleteRowCellTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ const id = e.dataContext.cell.id.rowID;
+ return (
+ this.grid?.deleteRow(id)}>
+ Delete
+
+ );
+ }
+
+ public typeColumnTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ const type = e.dataContext.cell.value as string;
+ return {type.toUpperCase()};
+ }
+
+ public valueColumnTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ return {JSON.stringify(e.dataContext.cell.value)};
+ }
+}
+
+// rendering above component in the React DOM
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render();
diff --git a/samples/grids/tree-grid/grid-batch-editing-remote/tsconfig.json b/samples/grids/tree-grid/grid-batch-editing-remote/tsconfig.json
new file mode 100644
index 0000000000..8c0d146f95
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing-remote/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/tree-grid/grid-batch-editing-remote/vite.config.js b/samples/grids/tree-grid/grid-batch-editing-remote/vite.config.js
new file mode 100644
index 0000000000..1744dbc719
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing-remote/vite.config.js
@@ -0,0 +1,12 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+ build: {
+ outDir: 'build'
+ },
+ server: {
+ open: false
+ },
+});
\ No newline at end of file
diff --git a/samples/grids/tree-grid/grid-batch-editing/.devcontainer/devcontainer.json b/samples/grids/tree-grid/grid-batch-editing/.devcontainer/devcontainer.json
new file mode 100644
index 0000000000..e0b8e9c925
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing/.devcontainer/devcontainer.json
@@ -0,0 +1,4 @@
+{
+ "name": "Node.js",
+ "image": "mcr.microsoft.com/devcontainers/javascript-node:22"
+}
\ No newline at end of file
diff --git a/samples/grids/tree-grid/grid-batch-editing/.eslintrc.js b/samples/grids/tree-grid/grid-batch-editing/.eslintrc.js
new file mode 100644
index 0000000000..7168b71441
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing/.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/tree-grid/grid-batch-editing/ReadMe.md b/samples/grids/tree-grid/grid-batch-editing/ReadMe.md
new file mode 100644
index 0000000000..31d6ecb845
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing/ReadMe.md
@@ -0,0 +1,56 @@
+
+
+
+This folder contains implementation of React application with example of Editing Events feature using [Tree Grid](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html) component.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Branches
+
+> **_NOTE:_** You should use [master](https://github.com/IgniteUI/igniteui-react-examples/tree/master) branch of this repository if you want to run samples on your computer. Use the [vnext](https://github.com/IgniteUI/igniteui-react-examples/tree/vnext) branch only when you want to contribute new samples to this repository.
+
+## Instructions
+
+Follow these instructions to run this example:
+
+
+```
+git clone https://github.com/IgniteUI/igniteui-react-examples.git
+git checkout master
+cd ./igniteui-react-examples
+cd ./samples/grids/tree-grid/editing-events
+```
+
+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/tree-grid/grid-batch-editing/index.html b/samples/grids/tree-grid/grid-batch-editing/index.html
new file mode 100644
index 0000000000..7ccd41dace
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing/index.html
@@ -0,0 +1,12 @@
+
+
+
+ Sample | Ignite UI | React | infragistics
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/grids/tree-grid/grid-batch-editing/package.json b/samples/grids/tree-grid/grid-batch-editing/package.json
new file mode 100644
index 0000000000..c2fc0d47eb
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing/package.json
@@ -0,0 +1,49 @@
+{
+ "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": "vite --port 4200",
+ "build": "tsc && node --max-old-space-size=4096 node_modules/vite/bin/vite build",
+ "preview": "vite preview",
+ "test": "vitest",
+ "lint": "eslint ./src/**/*.{ts,tsx}"
+ },
+ "dependencies": {
+ "igniteui-dockmanager": "^1.17.0",
+ "igniteui-react": "^19.4.0",
+ "igniteui-react-core": "19.3.2",
+ "igniteui-react-grids": "^19.5.0-beta.0",
+ "igniteui-webcomponents": "^6.3.0",
+ "lit-html": "^3.2.0",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "tslib": "^2.4.0"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.2.0",
+ "@types/node": "^24.7.1",
+ "@types/react": "^18.0.24",
+ "@types/react-dom": "^18.0.8",
+ "@vitejs/plugin-react": "^5.0.4",
+ "@vitest/browser": "^3.2.4",
+ "eslint": "^8.33.0",
+ "eslint-config-react": "^1.1.7",
+ "eslint-plugin-react": "^7.20.0",
+ "typescript": "^4.8.4",
+ "vite": "^7.1.9",
+ "vitest": "^3.2.4",
+ "vitest-canvas-mock": "^0.3.3",
+ "worker-loader": "^3.0.8"
+ },
+ "browserslist": [
+ ">0.2%",
+ "not dead",
+ "not ie <= 11",
+ "not op_mini all"
+ ]
+}
diff --git a/samples/grids/tree-grid/grid-batch-editing/sandbox.config.json b/samples/grids/tree-grid/grid-batch-editing/sandbox.config.json
new file mode 100644
index 0000000000..07f53508eb
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing/sandbox.config.json
@@ -0,0 +1,5 @@
+{
+ "infiniteLoopProtection": false,
+ "hardReloadOnChange": false,
+ "view": "browser"
+}
\ No newline at end of file
diff --git a/samples/grids/tree-grid/grid-batch-editing/src/EmployeesNestedTreeData.ts b/samples/grids/tree-grid/grid-batch-editing/src/EmployeesNestedTreeData.ts
new file mode 100644
index 0000000000..a51b020df3
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing/src/EmployeesNestedTreeData.ts
@@ -0,0 +1,44 @@
+export class EmployeesNestedTreeDataItem {
+ public constructor(init: Partial) {
+ Object.assign(this, init);
+ }
+
+ public Age: number;
+ public HireDate: string;
+ public ID: number;
+ public Name: string;
+ public Phone: string;
+ public OnPTO: boolean;
+ public ParentID: number;
+ public Title: string;
+
+}
+export class EmployeesNestedTreeData extends Array {
+ public constructor(items: Array | number = -1) {
+ if (Array.isArray(items)) {
+ super(...items);
+ } else {
+ const newItems = [
+ new EmployeesNestedTreeDataItem({ Age: 55, HireDate: `2008-03-20`, ID: 1, Name: `Johnathan Winchester`, Phone: `0251-031259`, OnPTO: false, ParentID: -1, Title: `Development Manager` }),
+ new EmployeesNestedTreeDataItem({ Age: 42, HireDate: `2014-01-22`, ID: 4, Name: `Ana Sanders`, Phone: `(21) 555-0091`, OnPTO: true, ParentID: -1, Title: `CEO` }),
+ new EmployeesNestedTreeDataItem({ Age: 49, HireDate: `2014-01-22`, ID: 18, Name: `Victoria Lincoln`, Phone: `(071) 23 67 22 20`, OnPTO: true, ParentID: -1, Title: `Accounting Manager` }),
+ new EmployeesNestedTreeDataItem({ Age: 61, HireDate: `2010-01-01`, ID: 10, Name: `Yang Wang`, Phone: `(21) 555-0091`, OnPTO: false, ParentID: -1, Title: `Localization Manager` }),
+ new EmployeesNestedTreeDataItem({ Age: 43, HireDate: `2011-06-03`, ID: 3, Name: `Michael Burke`, Phone: `0452-076545`, OnPTO: true, ParentID: 1, Title: `Senior Software Developer` }),
+ new EmployeesNestedTreeDataItem({ Age: 29, HireDate: `2009-06-19`, ID: 2, Name: `Thomas Anderson`, Phone: `(14) 555-8122`, OnPTO: false, ParentID: 1, Title: `Senior Software Developer` }),
+ new EmployeesNestedTreeDataItem({ Age: 31, HireDate: `2014-08-18`, ID: 11, Name: `Monica Reyes`, Phone: `7675-3425`, OnPTO: false, ParentID: 1, Title: `Software Development Team Lead` }),
+ new EmployeesNestedTreeDataItem({ Age: 35, HireDate: `2015-09-17`, ID: 6, Name: `Roland Mendel`, Phone: `(505) 555-5939`, OnPTO: false, ParentID: 11, Title: `Senior Software Developer` }),
+ new EmployeesNestedTreeDataItem({ Age: 44, HireDate: `2009-10-11`, ID: 12, Name: `Sven Cooper`, Phone: `0695-34 67 21`, OnPTO: true, ParentID: 11, Title: `Senior Software Developer` }),
+ new EmployeesNestedTreeDataItem({ Age: 44, HireDate: `2014-04-04`, ID: 14, Name: `Laurence Johnson`, Phone: `981-443655`, OnPTO: false, ParentID: 4, Title: `Director` }),
+ new EmployeesNestedTreeDataItem({ Age: 25, HireDate: `2017-11-09`, ID: 5, Name: `Elizabeth Richards`, Phone: `(2) 283-2951`, OnPTO: true, ParentID: 4, Title: `Vice President` }),
+ new EmployeesNestedTreeDataItem({ Age: 39, HireDate: `2010-03-22`, ID: 13, Name: `Trevor Ashworth`, Phone: `981-443655`, OnPTO: true, ParentID: 5, Title: `Director` }),
+ new EmployeesNestedTreeDataItem({ Age: 44, HireDate: `2014-04-04`, ID: 17, Name: `Antonio Moreno`, Phone: `(505) 555-5939`, OnPTO: false, ParentID: 18, Title: `Senior Accountant` }),
+ new EmployeesNestedTreeDataItem({ Age: 50, HireDate: `2007-11-18`, ID: 7, Name: `Pedro Rodriguez`, Phone: `035-640230`, OnPTO: false, ParentID: 10, Title: `Senior Localization Developer` }),
+ new EmployeesNestedTreeDataItem({ Age: 27, HireDate: `2016-02-19`, ID: 8, Name: `Casey Harper`, Phone: `0342-023176`, OnPTO: true, ParentID: 10, Title: `Senior Localization` }),
+ new EmployeesNestedTreeDataItem({ Age: 25, HireDate: `2017-11-09`, ID: 15, Name: `Patricia Simpson`, Phone: `069-0245984`, OnPTO: false, ParentID: 7, Title: `Localization Intern` }),
+ new EmployeesNestedTreeDataItem({ Age: 39, HireDate: `2010-03-22`, ID: 9, Name: `Francisco Chang`, Phone: `(91) 745 6200`, OnPTO: false, ParentID: 7, Title: `Localization Intern` }),
+ new EmployeesNestedTreeDataItem({ Age: 25, HireDate: `2018-03-18`, ID: 16, Name: `Peter Lewis`, Phone: `069-0245984`, OnPTO: true, ParentID: 7, Title: `Localization Intern` }),
+ ];
+ super(...newItems.slice(0));
+ }
+ }
+}
diff --git a/samples/grids/tree-grid/grid-batch-editing/src/index.css b/samples/grids/tree-grid/grid-batch-editing/src/index.css
new file mode 100644
index 0000000000..7a4d6b21d7
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing/src/index.css
@@ -0,0 +1,35 @@
+/* shared styles are loaded from: */
+/* https://dl.infragistics.com/x/css/samples/shared.v8.css */
+
+.buttons-wrapper {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ padding: 10px 0;
+}
+
+.buttons-right {
+ display: flex;
+ gap: 8px;
+}
+
+.dialog-buttons {
+ display: flex;
+ gap: 8px;
+ justify-content: flex-end;
+}
+
+.transaction--add {
+ color: #6b3;
+ font-weight: 600;
+}
+
+.transaction--update {
+ color: #4a71b9;
+ font-weight: 600;
+}
+
+.transaction--delete {
+ color: #ee4920;
+ font-weight: 600;
+}
diff --git a/samples/grids/tree-grid/grid-batch-editing/src/index.tsx b/samples/grids/tree-grid/grid-batch-editing/src/index.tsx
new file mode 100644
index 0000000000..7b4fcad93c
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing/src/index.tsx
@@ -0,0 +1,218 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './index.css';
+
+import { IgrTreeGrid, IgrColumn, IgrGrid, IgrCellTemplateContext } from 'igniteui-react-grids';
+import { IgrButton, IgrDialog } from 'igniteui-react';
+import { EmployeesNestedTreeData } from './EmployeesNestedTreeData';
+
+import 'igniteui-react-grids/grids/themes/light/bootstrap.css';
+import 'igniteui-webcomponents/themes/light/bootstrap.css';
+
+interface SampleState {
+ undoDisabled: boolean;
+ redoDisabled: boolean;
+ commitDisabled: boolean;
+ discardDisabled: boolean;
+ transactionData: any[];
+}
+
+export default class Sample extends React.Component, SampleState> {
+ private grid: IgrTreeGrid | null = null;
+ private dialog: IgrDialog | null = null;
+ private addId: number = 1000;
+ private stateUpdateSubscription: (() => void) | null = null;
+
+ constructor(props: Record) {
+ super(props);
+ this.state = {
+ undoDisabled: true,
+ redoDisabled: true,
+ commitDisabled: true,
+ discardDisabled: true,
+ transactionData: []
+ };
+ }
+
+ public componentWillUnmount(): void {
+ if (this.stateUpdateSubscription) {
+ this.stateUpdateSubscription();
+ }
+ }
+
+ public render() {
+ const { undoDisabled, redoDisabled, commitDisabled, discardDisabled, transactionData } = this.state;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add Row
+
+
+
+ Undo
+
+
+ Redo
+
+
+ Discard
+
+
+ Commit
+
+
+
+
+
+
+
+
+
+
+
+
+ Commit
+
+
+ Discard
+
+
+ Cancel
+
+
+
+
+ );
+ }
+
+ private _employeesData: EmployeesNestedTreeData | null = null;
+ public get employeesData(): EmployeesNestedTreeData {
+ if (this._employeesData == null) {
+ this._employeesData = new EmployeesNestedTreeData();
+ }
+ return this._employeesData;
+ }
+
+ private gridRef = (r: IgrTreeGrid) => {
+ if (!r) { return; }
+ this.grid = r;
+ (this.grid as any).batchEditing = true;
+
+ this.stateUpdateSubscription = this.grid.transactions.onStateUpdate.subscribe(() => {
+ if (!this.grid) { return; }
+ const hasChanges = this.grid.transactions.getAggregatedChanges(false).length > 0;
+ this.setState({
+ undoDisabled: !this.grid.transactions.canUndo,
+ redoDisabled: !this.grid.transactions.canRedo,
+ commitDisabled: !hasChanges,
+ discardDisabled: !hasChanges
+ });
+ });
+ }
+
+ private dialogRef = (r: IgrDialog) => {
+ this.dialog = r;
+ }
+
+ private randomInt(min: number, max: number): number {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ }
+
+ public onAddRow = () => {
+ this.grid?.addRow({
+ ID: this.addId++,
+ ParentID: -1,
+ Name: `New Employee ${this.randomInt(1, 100)}`,
+ Title: 'Employee',
+ Age: this.randomInt(20, 60),
+ HireDate: new Date(this.randomInt(2000, 2025),
+ this.randomInt(0, 11), this.randomInt(1, 25))
+ .toISOString().slice(0, 10),
+ Phone: `555-${this.randomInt(1000, 9999)}`,
+ OnPTO: this.randomInt(0, 1) === 1
+ });
+ }
+
+ public onUndo = () => {
+ this.grid?.endEdit(true);
+ this.grid?.transactions.undo();
+ }
+
+ public onRedo = () => {
+ this.grid?.endEdit(true);
+ this.grid?.transactions.redo();
+ }
+
+ public onOpenCommitDialog = () => {
+ if (!this.grid) { return; }
+ this.setState({
+ transactionData: this.grid.transactions.getAggregatedChanges(true)
+ });
+ this.dialog?.show();
+ }
+
+ public onCommit = () => {
+ if (!this.grid) { return; }
+ this.grid.transactions.commit(this.grid.data);
+ this.dialog?.hide();
+ }
+
+ public onDiscard = () => {
+ this.grid?.transactions.clear();
+ this.dialog?.hide();
+ }
+
+ public onCancel = () => {
+ this.dialog?.hide();
+ }
+
+ public deleteRowCellTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ const id = e.dataContext.cell.id.rowID;
+ return (
+ this.grid?.deleteRow(id)}>
+ Delete
+
+ );
+ }
+
+ public typeColumnTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ const type = e.dataContext.cell.value as string;
+ return {type.toUpperCase()};
+ }
+
+ public valueColumnTemplate = (e: { dataContext: IgrCellTemplateContext }) => {
+ return {JSON.stringify(e.dataContext.cell.value)};
+ }
+}
+
+// rendering above component in the React DOM
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render();
diff --git a/samples/grids/tree-grid/grid-batch-editing/tsconfig.json b/samples/grids/tree-grid/grid-batch-editing/tsconfig.json
new file mode 100644
index 0000000000..8c0d146f95
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing/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/tree-grid/grid-batch-editing/vite.config.js b/samples/grids/tree-grid/grid-batch-editing/vite.config.js
new file mode 100644
index 0000000000..1744dbc719
--- /dev/null
+++ b/samples/grids/tree-grid/grid-batch-editing/vite.config.js
@@ -0,0 +1,12 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+ build: {
+ outDir: 'build'
+ },
+ server: {
+ open: false
+ },
+});
\ No newline at end of file