diff --git a/examples/kendo-react-redux-undo/.gitignore b/examples/kendo-react-redux-undo/.gitignore
new file mode 100644
index 00000000..aae8560f
--- /dev/null
+++ b/examples/kendo-react-redux-undo/.gitignore
@@ -0,0 +1,27 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+**/kendo-ui-license**
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# package-lock file
+package-lock.json
\ No newline at end of file
diff --git a/examples/kendo-react-redux-undo/LICENSE b/examples/kendo-react-redux-undo/LICENSE
new file mode 100644
index 00000000..01893653
--- /dev/null
+++ b/examples/kendo-react-redux-undo/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Telerik
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/examples/kendo-react-redux-undo/README.md b/examples/kendo-react-redux-undo/README.md
new file mode 100644
index 00000000..7f168c4b
--- /dev/null
+++ b/examples/kendo-react-redux-undo/README.md
@@ -0,0 +1,13 @@
+# kendo-react-redux-undo
+Kendo UI Grid using [Redux](https://redux.js.org/) for state management and [Redux Undo](https://github.com/omnidan/redux-undo) for history management.
+
+## Build Setup
+
+```bash
+# install dependencies
+npm install
+# serve with hot reload at localhost:3000
+npm start
+# build for production with minification
+npm run build
+
diff --git a/examples/kendo-react-redux-undo/package.json b/examples/kendo-react-redux-undo/package.json
new file mode 100644
index 00000000..5f07671c
--- /dev/null
+++ b/examples/kendo-react-redux-undo/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "kendo-redux-timetravel-editing",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "@progress/kendo-data-query": "^1.7.1",
+ "@progress/kendo-drawing": "^1.21.2",
+ "@progress/kendo-react-dateinputs": "^11.0.0",
+ "@progress/kendo-react-dropdowns": "^11.0.0",
+ "@progress/kendo-react-grid": "^11.0.0",
+ "@progress/kendo-react-inputs": "^11.0.0",
+ "@progress/kendo-react-intl": "^11.0.0",
+ "@progress/kendo-react-pdf": "^11.0.0",
+ "@progress/kendo-react-data-tools": "^11.0.0",
+ "@progress/kendo-theme-default": "^11.0.2",
+ "react": "^18",
+ "react-dom": "^18",
+ "react-redux": "^9.2.0",
+ "react-scripts": "5.0.1",
+ "redux": "^5.0.1",
+ "@redux-devtools/extension": "^3.3.0",
+ "@reduxjs/toolkit": "^2.8.2",
+ "redux-thunk": "^3.1.0",
+ "redux-undo": "^1.1.0",
+ "prop-types": "15.8.1"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test --env=jsdom",
+ "eject": "react-scripts eject"
+ }
+}
diff --git a/examples/kendo-react-redux-undo/public/favicon.ico b/examples/kendo-react-redux-undo/public/favicon.ico
new file mode 100644
index 00000000..a11777cc
Binary files /dev/null and b/examples/kendo-react-redux-undo/public/favicon.ico differ
diff --git a/examples/kendo-react-redux-undo/public/index.html b/examples/kendo-react-redux-undo/public/index.html
new file mode 100644
index 00000000..ed0ebafa
--- /dev/null
+++ b/examples/kendo-react-redux-undo/public/index.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+ React App
+
+
+
+ You need to enable JavaScript to run this app.
+
+
+
+
+
diff --git a/examples/kendo-react-redux-undo/public/manifest.json b/examples/kendo-react-redux-undo/public/manifest.json
new file mode 100644
index 00000000..ef19ec24
--- /dev/null
+++ b/examples/kendo-react-redux-undo/public/manifest.json
@@ -0,0 +1,15 @@
+{
+ "short_name": "React App",
+ "name": "Create React App Sample",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "64x64 32x32 24x24 16x16",
+ "type": "image/x-icon"
+ }
+ ],
+ "start_url": "./index.html",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
diff --git a/examples/kendo-react-redux-undo/src/App.css b/examples/kendo-react-redux-undo/src/App.css
new file mode 100644
index 00000000..c5c6e8a6
--- /dev/null
+++ b/examples/kendo-react-redux-undo/src/App.css
@@ -0,0 +1,28 @@
+.App {
+ text-align: center;
+}
+
+.App-logo {
+ animation: App-logo-spin infinite 20s linear;
+ height: 80px;
+}
+
+.App-header {
+ background-color: #222;
+ height: 150px;
+ padding: 20px;
+ color: white;
+}
+
+.App-title {
+ font-size: 1.5em;
+}
+
+.App-intro {
+ font-size: large;
+}
+
+@keyframes App-logo-spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+}
diff --git a/examples/kendo-react-redux-undo/src/App.js b/examples/kendo-react-redux-undo/src/App.js
new file mode 100644
index 00000000..90b3a7ed
--- /dev/null
+++ b/examples/kendo-react-redux-undo/src/App.js
@@ -0,0 +1,17 @@
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import './App.css';
+import GridContainer from './components/GridContainer';
+import '@progress/kendo-theme-default/dist/all.css';
+
+class App extends Component {
+ render() {
+ return (
+
+
+
+ );
+ }
+}
+
+export default connect()(App);
diff --git a/examples/kendo-react-redux-undo/src/App.test.js b/examples/kendo-react-redux-undo/src/App.test.js
new file mode 100644
index 00000000..fb712258
--- /dev/null
+++ b/examples/kendo-react-redux-undo/src/App.test.js
@@ -0,0 +1,10 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+
+it('renders without crashing', () => {
+ const div = document.createElement('div');
+ const root = ReactDOM.createRoot(div);
+ root.render( );
+ root.unmount();
+});
diff --git a/examples/kendo-react-redux-undo/src/actions/actions.js b/examples/kendo-react-redux-undo/src/actions/actions.js
new file mode 100644
index 00000000..a4d2bba3
--- /dev/null
+++ b/examples/kendo-react-redux-undo/src/actions/actions.js
@@ -0,0 +1,23 @@
+export const ADD_PRODUCT = 'ADD_PRODUCT'
+export const UPDATE_PRODUCT = 'UPDATE_PRODUCT'
+export const DELETE_PRODUCT = 'DELETE_PRODUCT'
+export const CHANGE_EDIT = 'CHANGE_EDIT'
+
+export const DATASTATE_CHANGE = 'DATASTATE_CHANGE'
+
+export function addProduct(payload) {
+ return { type: ADD_PRODUCT, payload }
+}
+export function updateProduct(payload) {
+ return { type: UPDATE_PRODUCT, payload }
+}
+export function deleteProduct(payload) {
+ return { type: DELETE_PRODUCT, payload }
+}
+export function changeEdit(payload) {
+ return { type: CHANGE_EDIT, payload }
+}
+
+export function datastateChange(payload) {
+ return { type: DATASTATE_CHANGE, payload }
+}
diff --git a/examples/kendo-react-redux-undo/src/components/GridContainer.js b/examples/kendo-react-redux-undo/src/components/GridContainer.js
new file mode 100644
index 00000000..0d736095
--- /dev/null
+++ b/examples/kendo-react-redux-undo/src/components/GridContainer.js
@@ -0,0 +1,110 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { ActionCreators as UndoActionCreators } from 'redux-undo';
+import { addProduct, updateProduct, deleteProduct, changeEdit, datastateChange } from '../actions/actions';
+import { Grid, GridColumn as Column, GridToolbar, GridToolbarSort, GridToolbarFilter } from '@progress/kendo-react-grid';
+import { Button } from '@progress/kendo-react-buttons';
+import { gearIcon } from '@progress/kendo-svg-icons';
+import { process } from '@progress/kendo-data-query'
+import MyCommandCell from './MyCommandCell'
+
+class GridContainer extends React.Component {
+ constructor(props) {
+ super(props);
+ this.CommandCell = MyCommandCell(this.remove);
+ }
+
+ addItem = () => {
+ this.props.addProduct();
+ }
+
+ handleItemChange = (dataItem) => {
+ this.props.updateProduct(dataItem)
+ }
+
+ handleStateChange = (event) => {
+ this.props.datastateChange(event)
+ }
+
+ remove = (dataItem) => {
+ this.props.deleteProduct(dataItem);
+ }
+
+ rowClick = (event) => {
+ this.props.changeEdit(event.dataItem);
+ }
+
+ render() {
+ const processedProducts = process(this.props.products, this.props.dataState)
+ console.log(this.props.products.length)
+ return (
+
+
+
+
+
+
+ Add new
+
+
+ Back
+
+
+ Forward
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onUndo: () => dispatch(UndoActionCreators.undo()),
+ onRedo: () => dispatch(UndoActionCreators.redo()),
+ addProduct: () => dispatch(addProduct()),
+ updateProduct: (dataItem) => dispatch(updateProduct(dataItem)),
+ deleteProduct: (dataItem) => dispatch(deleteProduct(dataItem)),
+ changeEdit: (row) => dispatch(changeEdit(row)),
+ datastateChange: (event) => dispatch(datastateChange(event))
+ }
+ }
+
+function mapStateToProps(state) {
+ return {
+ products: state.products.present.products,
+ dataState: state.dataState.present,
+ canUndo: state.products.past.length > 0,
+ canRedo: state.products.future.length > 0,
+ dataState: state.dataState.present
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(GridContainer);
diff --git a/examples/kendo-react-redux-undo/src/components/MyCommandCell.js b/examples/kendo-react-redux-undo/src/components/MyCommandCell.js
new file mode 100644
index 00000000..69329c91
--- /dev/null
+++ b/examples/kendo-react-redux-undo/src/components/MyCommandCell.js
@@ -0,0 +1,17 @@
+import React from 'react';
+
+export default function MyCommandCell(remove) {
+ return class extends React.Component {
+ render() {
+ return (
+
+ window.confirm('Confirm deleting: ' + this.props.dataItem.ProductName) && remove(this.props.dataItem)}>
+ Remove
+
+
+ );
+ }
+ }
+};
\ No newline at end of file
diff --git a/examples/kendo-react-redux-undo/src/data/products.js b/examples/kendo-react-redux-undo/src/data/products.js
new file mode 100644
index 00000000..f603e1b4
--- /dev/null
+++ b/examples/kendo-react-redux-undo/src/data/products.js
@@ -0,0 +1,1233 @@
+export const sampleProducts = [{
+ "ProductID" : 1,
+ "ProductName" : "Chai",
+ "SupplierID" : 1,
+ "CategoryID" : 1,
+ "QuantityPerUnit" : "10 boxes x 20 bags",
+ "UnitPrice" : 18.0000,
+ "UnitsInStock" : 39,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 10,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 1,
+ "CategoryName" : "Beverages",
+ "Description" : "Soft drinks, coffees, teas, beers, and ales"
+ }
+}, {
+ "ProductID" : 2,
+ "ProductName" : "Chang",
+ "SupplierID" : 1,
+ "CategoryID" : 1,
+ "QuantityPerUnit" : "24 - 12 oz bottles",
+ "UnitPrice" : 19.0000,
+ "UnitsInStock" : 17,
+ "UnitsOnOrder" : 40,
+ "ReorderLevel" : 25,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 1,
+ "CategoryName" : "Beverages",
+ "Description" : "Soft drinks, coffees, teas, beers, and ales"
+ }
+}, {
+ "ProductID" : 3,
+ "ProductName" : "Aniseed Syrup",
+ "SupplierID" : 1,
+ "CategoryID" : 2,
+ "QuantityPerUnit" : "12 - 550 ml bottles",
+ "UnitPrice" : 10.0000,
+ "UnitsInStock" : 13,
+ "UnitsOnOrder" : 70,
+ "ReorderLevel" : 25,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 2,
+ "CategoryName" : "Condiments",
+ "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings"
+ }
+}, {
+ "ProductID" : 4,
+ "ProductName" : "Chef Anton's Cajun Seasoning",
+ "SupplierID" : 2,
+ "CategoryID" : 2,
+ "QuantityPerUnit" : "48 - 6 oz jars",
+ "UnitPrice" : 22.0000,
+ "UnitsInStock" : 53,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 2,
+ "CategoryName" : "Condiments",
+ "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings"
+ }
+}, {
+ "ProductID" : 5,
+ "ProductName" : "Chef Anton's Gumbo Mix",
+ "SupplierID" : 2,
+ "CategoryID" : 2,
+ "QuantityPerUnit" : "36 boxes",
+ "UnitPrice" : 21.3500,
+ "UnitsInStock" : 0,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : true,
+ "Category" : {
+ "CategoryID" : 2,
+ "CategoryName" : "Condiments",
+ "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings"
+ }
+}, {
+ "ProductID" : 6,
+ "ProductName" : "Grandma's Boysenberry Spread",
+ "SupplierID" : 3,
+ "CategoryID" : 2,
+ "QuantityPerUnit" : "12 - 8 oz jars",
+ "UnitPrice" : 25.0000,
+ "UnitsInStock" : 120,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 25,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 2,
+ "CategoryName" : "Condiments",
+ "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings"
+ }
+}, {
+ "ProductID" : 7,
+ "ProductName" : "Uncle Bob's Organic Dried Pears",
+ "SupplierID" : 3,
+ "CategoryID" : 7,
+ "QuantityPerUnit" : "12 - 1 lb pkgs.",
+ "UnitPrice" : 30.0000,
+ "UnitsInStock" : 15,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 10,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 7,
+ "CategoryName" : "Produce",
+ "Description" : "Dried fruit and bean curd"
+ }
+}, {
+ "ProductID" : 8,
+ "ProductName" : "Northwoods Cranberry Sauce",
+ "SupplierID" : 3,
+ "CategoryID" : 2,
+ "QuantityPerUnit" : "12 - 12 oz jars",
+ "UnitPrice" : 40.0000,
+ "UnitsInStock" : 6,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 2,
+ "CategoryName" : "Condiments",
+ "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings"
+ }
+}, {
+ "ProductID" : 9,
+ "ProductName" : "Mishi Kobe Niku",
+ "SupplierID" : 4,
+ "CategoryID" : 6,
+ "QuantityPerUnit" : "18 - 500 g pkgs.",
+ "UnitPrice" : 97.0000,
+ "UnitsInStock" : 29,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : true,
+ "Category" : {
+ "CategoryID" : 6,
+ "CategoryName" : "Meat/Poultry",
+ "Description" : "Prepared meats"
+ }
+}, {
+ "ProductID" : 10,
+ "ProductName" : "Ikura",
+ "SupplierID" : 4,
+ "CategoryID" : 8,
+ "QuantityPerUnit" : "12 - 200 ml jars",
+ "UnitPrice" : 31.0000,
+ "UnitsInStock" : 31,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 8,
+ "CategoryName" : "Seafood",
+ "Description" : "Seaweed and fish"
+ }
+}, {
+ "ProductID" : 11,
+ "ProductName" : "Queso Cabrales",
+ "SupplierID" : 5,
+ "CategoryID" : 4,
+ "QuantityPerUnit" : "1 kg pkg.",
+ "UnitPrice" : 21.0000,
+ "UnitsInStock" : 22,
+ "UnitsOnOrder" : 30,
+ "ReorderLevel" : 30,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 4,
+ "CategoryName" : "Dairy Products",
+ "Description" : "Cheeses"
+ }
+}, {
+ "ProductID" : 12,
+ "ProductName" : "Queso Manchego La Pastora",
+ "SupplierID" : 5,
+ "CategoryID" : 4,
+ "QuantityPerUnit" : "10 - 500 g pkgs.",
+ "UnitPrice" : 38.0000,
+ "UnitsInStock" : 86,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 4,
+ "CategoryName" : "Dairy Products",
+ "Description" : "Cheeses"
+ }
+}, {
+ "ProductID" : 13,
+ "ProductName" : "Konbu",
+ "SupplierID" : 6,
+ "CategoryID" : 8,
+ "QuantityPerUnit" : "2 kg box",
+ "UnitPrice" : 6.0000,
+ "UnitsInStock" : 24,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 5,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 8,
+ "CategoryName" : "Seafood",
+ "Description" : "Seaweed and fish"
+ }
+}, {
+ "ProductID" : 14,
+ "ProductName" : "Tofu",
+ "SupplierID" : 6,
+ "CategoryID" : 7,
+ "QuantityPerUnit" : "40 - 100 g pkgs.",
+ "UnitPrice" : 23.2500,
+ "UnitsInStock" : 35,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 7,
+ "CategoryName" : "Produce",
+ "Description" : "Dried fruit and bean curd"
+ }
+}, {
+ "ProductID" : 15,
+ "ProductName" : "Genen Shouyu",
+ "SupplierID" : 6,
+ "CategoryID" : 2,
+ "QuantityPerUnit" : "24 - 250 ml bottles",
+ "UnitPrice" : 15.5000,
+ "UnitsInStock" : 39,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 5,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 2,
+ "CategoryName" : "Condiments",
+ "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings"
+ }
+}, {
+ "ProductID" : 16,
+ "ProductName" : "Pavlova",
+ "SupplierID" : 7,
+ "CategoryID" : 3,
+ "QuantityPerUnit" : "32 - 500 g boxes",
+ "UnitPrice" : 17.4500,
+ "UnitsInStock" : 29,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 10,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 3,
+ "CategoryName" : "Confections",
+ "Description" : "Desserts, candies, and sweet breads"
+ }
+}, {
+ "ProductID" : 17,
+ "ProductName" : "Alice Mutton",
+ "SupplierID" : 7,
+ "CategoryID" : 6,
+ "QuantityPerUnit" : "20 - 1 kg tins",
+ "UnitPrice" : 39.0000,
+ "UnitsInStock" : 0,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : true,
+ "Category" : {
+ "CategoryID" : 6,
+ "CategoryName" : "Meat/Poultry",
+ "Description" : "Prepared meats"
+ }
+}, {
+ "ProductID" : 18,
+ "ProductName" : "Carnarvon Tigers",
+ "SupplierID" : 7,
+ "CategoryID" : 8,
+ "QuantityPerUnit" : "16 kg pkg.",
+ "UnitPrice" : 62.5000,
+ "UnitsInStock" : 42,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 8,
+ "CategoryName" : "Seafood",
+ "Description" : "Seaweed and fish"
+ }
+}, {
+ "ProductID" : 19,
+ "ProductName" : "Teatime Chocolate Biscuits",
+ "SupplierID" : 8,
+ "CategoryID" : 3,
+ "QuantityPerUnit" : "10 boxes x 12 pieces",
+ "UnitPrice" : 9.2000,
+ "UnitsInStock" : 25,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 5,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 3,
+ "CategoryName" : "Confections",
+ "Description" : "Desserts, candies, and sweet breads"
+ }
+}, {
+ "ProductID" : 20,
+ "ProductName" : "Sir Rodney's Marmalade",
+ "SupplierID" : 8,
+ "CategoryID" : 3,
+ "QuantityPerUnit" : "30 gift boxes",
+ "UnitPrice" : 81.0000,
+ "UnitsInStock" : 40,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 3,
+ "CategoryName" : "Confections",
+ "Description" : "Desserts, candies, and sweet breads"
+ }
+}, {
+ "ProductID" : 21,
+ "ProductName" : "Sir Rodney's Scones",
+ "SupplierID" : 8,
+ "CategoryID" : 3,
+ "QuantityPerUnit" : "24 pkgs. x 4 pieces",
+ "UnitPrice" : 10.0000,
+ "UnitsInStock" : 3,
+ "UnitsOnOrder" : 40,
+ "ReorderLevel" : 5,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 3,
+ "CategoryName" : "Confections",
+ "Description" : "Desserts, candies, and sweet breads"
+ }
+}, {
+ "ProductID" : 22,
+ "ProductName" : "Gustaf's Knäckebröd",
+ "SupplierID" : 9,
+ "CategoryID" : 5,
+ "QuantityPerUnit" : "24 - 500 g pkgs.",
+ "UnitPrice" : 21.0000,
+ "UnitsInStock" : 104,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 25,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 5,
+ "CategoryName" : "Grains/Cereals",
+ "Description" : "Breads, crackers, pasta, and cereal"
+ }
+}, {
+ "ProductID" : 23,
+ "ProductName" : "Tunnbröd",
+ "SupplierID" : 9,
+ "CategoryID" : 5,
+ "QuantityPerUnit" : "12 - 250 g pkgs.",
+ "UnitPrice" : 9.0000,
+ "UnitsInStock" : 61,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 25,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 5,
+ "CategoryName" : "Grains/Cereals",
+ "Description" : "Breads, crackers, pasta, and cereal"
+ }
+}, {
+ "ProductID" : 24,
+ "ProductName" : "Guaraná Fantástica",
+ "SupplierID" : 10,
+ "CategoryID" : 1,
+ "QuantityPerUnit" : "12 - 355 ml cans",
+ "UnitPrice" : 4.5000,
+ "UnitsInStock" : 20,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : true,
+ "Category" : {
+ "CategoryID" : 1,
+ "CategoryName" : "Beverages",
+ "Description" : "Soft drinks, coffees, teas, beers, and ales"
+ }
+}, {
+ "ProductID" : 25,
+ "ProductName" : "NuNuCa Nuß-Nougat-Creme",
+ "SupplierID" : 11,
+ "CategoryID" : 3,
+ "QuantityPerUnit" : "20 - 450 g glasses",
+ "UnitPrice" : 14.0000,
+ "UnitsInStock" : 76,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 30,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 3,
+ "CategoryName" : "Confections",
+ "Description" : "Desserts, candies, and sweet breads"
+ }
+}, {
+ "ProductID" : 26,
+ "ProductName" : "Gumbär Gummibärchen",
+ "SupplierID" : 11,
+ "CategoryID" : 3,
+ "QuantityPerUnit" : "100 - 250 g bags",
+ "UnitPrice" : 31.2300,
+ "UnitsInStock" : 15,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 3,
+ "CategoryName" : "Confections",
+ "Description" : "Desserts, candies, and sweet breads"
+ }
+}, {
+ "ProductID" : 27,
+ "ProductName" : "Schoggi Schokolade",
+ "SupplierID" : 11,
+ "CategoryID" : 3,
+ "QuantityPerUnit" : "100 - 100 g pieces",
+ "UnitPrice" : 43.9000,
+ "UnitsInStock" : 49,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 30,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 3,
+ "CategoryName" : "Confections",
+ "Description" : "Desserts, candies, and sweet breads"
+ }
+}, {
+ "ProductID" : 28,
+ "ProductName" : "Rössle Sauerkraut",
+ "SupplierID" : 12,
+ "CategoryID" : 7,
+ "QuantityPerUnit" : "25 - 825 g cans",
+ "UnitPrice" : 45.6000,
+ "UnitsInStock" : 26,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : true,
+ "Category" : {
+ "CategoryID" : 7,
+ "CategoryName" : "Produce",
+ "Description" : "Dried fruit and bean curd"
+ }
+}, {
+ "ProductID" : 29,
+ "ProductName" : "Thüringer Rostbratwurst",
+ "SupplierID" : 12,
+ "CategoryID" : 6,
+ "QuantityPerUnit" : "50 bags x 30 sausgs.",
+ "UnitPrice" : 123.7900,
+ "UnitsInStock" : 0,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : true,
+ "Category" : {
+ "CategoryID" : 6,
+ "CategoryName" : "Meat/Poultry",
+ "Description" : "Prepared meats"
+ }
+}, {
+ "ProductID" : 30,
+ "ProductName" : "Nord-Ost Matjeshering",
+ "SupplierID" : 13,
+ "CategoryID" : 8,
+ "QuantityPerUnit" : "10 - 200 g glasses",
+ "UnitPrice" : 25.8900,
+ "UnitsInStock" : 10,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 15,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 8,
+ "CategoryName" : "Seafood",
+ "Description" : "Seaweed and fish"
+ }
+}, {
+ "ProductID" : 31,
+ "ProductName" : "Gorgonzola Telino",
+ "SupplierID" : 14,
+ "CategoryID" : 4,
+ "QuantityPerUnit" : "12 - 100 g pkgs",
+ "UnitPrice" : 12.5000,
+ "UnitsInStock" : 0,
+ "UnitsOnOrder" : 70,
+ "ReorderLevel" : 20,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 4,
+ "CategoryName" : "Dairy Products",
+ "Description" : "Cheeses"
+ }
+}, {
+ "ProductID" : 32,
+ "ProductName" : "Mascarpone Fabioli",
+ "SupplierID" : 14,
+ "CategoryID" : 4,
+ "QuantityPerUnit" : "24 - 200 g pkgs.",
+ "UnitPrice" : 32.0000,
+ "UnitsInStock" : 9,
+ "UnitsOnOrder" : 40,
+ "ReorderLevel" : 25,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 4,
+ "CategoryName" : "Dairy Products",
+ "Description" : "Cheeses"
+ }
+}, {
+ "ProductID" : 33,
+ "ProductName" : "Geitost",
+ "SupplierID" : 15,
+ "CategoryID" : 4,
+ "QuantityPerUnit" : "500 g",
+ "UnitPrice" : 2.5000,
+ "UnitsInStock" : 112,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 20,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 4,
+ "CategoryName" : "Dairy Products",
+ "Description" : "Cheeses"
+ }
+}, {
+ "ProductID" : 34,
+ "ProductName" : "Sasquatch Ale",
+ "SupplierID" : 16,
+ "CategoryID" : 1,
+ "QuantityPerUnit" : "24 - 12 oz bottles",
+ "UnitPrice" : 14.0000,
+ "UnitsInStock" : 111,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 15,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 1,
+ "CategoryName" : "Beverages",
+ "Description" : "Soft drinks, coffees, teas, beers, and ales"
+ }
+}, {
+ "ProductID" : 35,
+ "ProductName" : "Steeleye Stout",
+ "SupplierID" : 16,
+ "CategoryID" : 1,
+ "QuantityPerUnit" : "24 - 12 oz bottles",
+ "UnitPrice" : 18.0000,
+ "UnitsInStock" : 20,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 15,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 1,
+ "CategoryName" : "Beverages",
+ "Description" : "Soft drinks, coffees, teas, beers, and ales"
+ }
+}, {
+ "ProductID" : 36,
+ "ProductName" : "Inlagd Sill",
+ "SupplierID" : 17,
+ "CategoryID" : 8,
+ "QuantityPerUnit" : "24 - 250 g jars",
+ "UnitPrice" : 19.0000,
+ "UnitsInStock" : 112,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 20,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 8,
+ "CategoryName" : "Seafood",
+ "Description" : "Seaweed and fish"
+ }
+}, {
+ "ProductID" : 37,
+ "ProductName" : "Gravad lax",
+ "SupplierID" : 17,
+ "CategoryID" : 8,
+ "QuantityPerUnit" : "12 - 500 g pkgs.",
+ "UnitPrice" : 26.0000,
+ "UnitsInStock" : 11,
+ "UnitsOnOrder" : 50,
+ "ReorderLevel" : 25,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 8,
+ "CategoryName" : "Seafood",
+ "Description" : "Seaweed and fish"
+ }
+}, {
+ "ProductID" : 38,
+ "ProductName" : "Côte de Blaye",
+ "SupplierID" : 18,
+ "CategoryID" : 1,
+ "QuantityPerUnit" : "12 - 75 cl bottles",
+ "UnitPrice" : 263.5000,
+ "UnitsInStock" : 17,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 15,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 1,
+ "CategoryName" : "Beverages",
+ "Description" : "Soft drinks, coffees, teas, beers, and ales"
+ }
+}, {
+ "ProductID" : 39,
+ "ProductName" : "Chartreuse verte",
+ "SupplierID" : 18,
+ "CategoryID" : 1,
+ "QuantityPerUnit" : "750 cc per bottle",
+ "UnitPrice" : 18.0000,
+ "UnitsInStock" : 69,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 5,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 1,
+ "CategoryName" : "Beverages",
+ "Description" : "Soft drinks, coffees, teas, beers, and ales"
+ }
+}, {
+ "ProductID" : 40,
+ "ProductName" : "Boston Crab Meat",
+ "SupplierID" : 19,
+ "CategoryID" : 8,
+ "QuantityPerUnit" : "24 - 4 oz tins",
+ "UnitPrice" : 18.4000,
+ "UnitsInStock" : 123,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 30,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 8,
+ "CategoryName" : "Seafood",
+ "Description" : "Seaweed and fish"
+ }
+}, {
+ "ProductID" : 41,
+ "ProductName" : "Jack's New England Clam Chowder",
+ "SupplierID" : 19,
+ "CategoryID" : 8,
+ "QuantityPerUnit" : "12 - 12 oz cans",
+ "UnitPrice" : 9.6500,
+ "UnitsInStock" : 85,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 10,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 8,
+ "CategoryName" : "Seafood",
+ "Description" : "Seaweed and fish"
+ }
+}, {
+ "ProductID" : 42,
+ "ProductName" : "Singaporean Hokkien Fried Mee",
+ "SupplierID" : 20,
+ "CategoryID" : 5,
+ "QuantityPerUnit" : "32 - 1 kg pkgs.",
+ "UnitPrice" : 14.0000,
+ "UnitsInStock" : 26,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : true,
+ "Category" : {
+ "CategoryID" : 5,
+ "CategoryName" : "Grains/Cereals",
+ "Description" : "Breads, crackers, pasta, and cereal"
+ }
+}, {
+ "ProductID" : 43,
+ "ProductName" : "Ipoh Coffee",
+ "SupplierID" : 20,
+ "CategoryID" : 1,
+ "QuantityPerUnit" : "16 - 500 g tins",
+ "UnitPrice" : 46.0000,
+ "UnitsInStock" : 17,
+ "UnitsOnOrder" : 10,
+ "ReorderLevel" : 25,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 1,
+ "CategoryName" : "Beverages",
+ "Description" : "Soft drinks, coffees, teas, beers, and ales"
+ }
+}, {
+ "ProductID" : 44,
+ "ProductName" : "Gula Malacca",
+ "SupplierID" : 20,
+ "CategoryID" : 2,
+ "QuantityPerUnit" : "20 - 2 kg bags",
+ "UnitPrice" : 19.4500,
+ "UnitsInStock" : 27,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 15,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 2,
+ "CategoryName" : "Condiments",
+ "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings"
+ }
+}, {
+ "ProductID" : 45,
+ "ProductName" : "Rogede sild",
+ "SupplierID" : 21,
+ "CategoryID" : 8,
+ "QuantityPerUnit" : "1k pkg.",
+ "UnitPrice" : 9.5000,
+ "UnitsInStock" : 5,
+ "UnitsOnOrder" : 70,
+ "ReorderLevel" : 15,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 8,
+ "CategoryName" : "Seafood",
+ "Description" : "Seaweed and fish"
+ }
+}, {
+ "ProductID" : 46,
+ "ProductName" : "Spegesild",
+ "SupplierID" : 21,
+ "CategoryID" : 8,
+ "QuantityPerUnit" : "4 - 450 g glasses",
+ "UnitPrice" : 12.0000,
+ "UnitsInStock" : 95,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 8,
+ "CategoryName" : "Seafood",
+ "Description" : "Seaweed and fish"
+ }
+}, {
+ "ProductID" : 47,
+ "ProductName" : "Zaanse koeken",
+ "SupplierID" : 22,
+ "CategoryID" : 3,
+ "QuantityPerUnit" : "10 - 4 oz boxes",
+ "UnitPrice" : 9.5000,
+ "UnitsInStock" : 36,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 3,
+ "CategoryName" : "Confections",
+ "Description" : "Desserts, candies, and sweet breads"
+ }
+}, {
+ "ProductID" : 48,
+ "ProductName" : "Chocolade",
+ "SupplierID" : 22,
+ "CategoryID" : 3,
+ "QuantityPerUnit" : "10 pkgs.",
+ "UnitPrice" : 12.7500,
+ "UnitsInStock" : 15,
+ "UnitsOnOrder" : 70,
+ "ReorderLevel" : 25,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 3,
+ "CategoryName" : "Confections",
+ "Description" : "Desserts, candies, and sweet breads"
+ }
+}, {
+ "ProductID" : 49,
+ "ProductName" : "Maxilaku",
+ "SupplierID" : 23,
+ "CategoryID" : 3,
+ "QuantityPerUnit" : "24 - 50 g pkgs.",
+ "UnitPrice" : 20.0000,
+ "UnitsInStock" : 10,
+ "UnitsOnOrder" : 60,
+ "ReorderLevel" : 15,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 3,
+ "CategoryName" : "Confections",
+ "Description" : "Desserts, candies, and sweet breads"
+ }
+}, {
+ "ProductID" : 50,
+ "ProductName" : "Valkoinen suklaa",
+ "SupplierID" : 23,
+ "CategoryID" : 3,
+ "QuantityPerUnit" : "12 - 100 g bars",
+ "UnitPrice" : 16.2500,
+ "UnitsInStock" : 65,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 30,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 3,
+ "CategoryName" : "Confections",
+ "Description" : "Desserts, candies, and sweet breads"
+ }
+}, {
+ "ProductID" : 51,
+ "ProductName" : "Manjimup Dried Apples",
+ "SupplierID" : 24,
+ "CategoryID" : 7,
+ "QuantityPerUnit" : "50 - 300 g pkgs.",
+ "UnitPrice" : 53.0000,
+ "UnitsInStock" : 20,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 10,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 7,
+ "CategoryName" : "Produce",
+ "Description" : "Dried fruit and bean curd"
+ }
+}, {
+ "ProductID" : 52,
+ "ProductName" : "Filo Mix",
+ "SupplierID" : 24,
+ "CategoryID" : 5,
+ "QuantityPerUnit" : "16 - 2 kg boxes",
+ "UnitPrice" : 7.0000,
+ "UnitsInStock" : 38,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 25,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 5,
+ "CategoryName" : "Grains/Cereals",
+ "Description" : "Breads, crackers, pasta, and cereal"
+ }
+}, {
+ "ProductID" : 53,
+ "ProductName" : "Perth Pasties",
+ "SupplierID" : 24,
+ "CategoryID" : 6,
+ "QuantityPerUnit" : "48 pieces",
+ "UnitPrice" : 32.8000,
+ "UnitsInStock" : 0,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : true,
+ "Category" : {
+ "CategoryID" : 6,
+ "CategoryName" : "Meat/Poultry",
+ "Description" : "Prepared meats"
+ }
+}, {
+ "ProductID" : 54,
+ "ProductName" : "Tourtière",
+ "SupplierID" : 25,
+ "CategoryID" : 6,
+ "QuantityPerUnit" : "16 pies",
+ "UnitPrice" : 7.4500,
+ "UnitsInStock" : 21,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 10,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 6,
+ "CategoryName" : "Meat/Poultry",
+ "Description" : "Prepared meats"
+ }
+}, {
+ "ProductID" : 55,
+ "ProductName" : "Pâté chinois",
+ "SupplierID" : 25,
+ "CategoryID" : 6,
+ "QuantityPerUnit" : "24 boxes x 2 pies",
+ "UnitPrice" : 24.0000,
+ "UnitsInStock" : 115,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 20,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 6,
+ "CategoryName" : "Meat/Poultry",
+ "Description" : "Prepared meats"
+ }
+}, {
+ "ProductID" : 56,
+ "ProductName" : "Gnocchi di nonna Alice",
+ "SupplierID" : 26,
+ "CategoryID" : 5,
+ "QuantityPerUnit" : "24 - 250 g pkgs.",
+ "UnitPrice" : 38.0000,
+ "UnitsInStock" : 21,
+ "UnitsOnOrder" : 10,
+ "ReorderLevel" : 30,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 5,
+ "CategoryName" : "Grains/Cereals",
+ "Description" : "Breads, crackers, pasta, and cereal"
+ }
+}, {
+ "ProductID" : 57,
+ "ProductName" : "Ravioli Angelo",
+ "SupplierID" : 26,
+ "CategoryID" : 5,
+ "QuantityPerUnit" : "24 - 250 g pkgs.",
+ "UnitPrice" : 19.5000,
+ "UnitsInStock" : 36,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 20,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 5,
+ "CategoryName" : "Grains/Cereals",
+ "Description" : "Breads, crackers, pasta, and cereal"
+ }
+}, {
+ "ProductID" : 58,
+ "ProductName" : "Escargots de Bourgogne",
+ "SupplierID" : 27,
+ "CategoryID" : 8,
+ "QuantityPerUnit" : "24 pieces",
+ "UnitPrice" : 13.2500,
+ "UnitsInStock" : 62,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 20,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 8,
+ "CategoryName" : "Seafood",
+ "Description" : "Seaweed and fish"
+ }
+}, {
+ "ProductID" : 59,
+ "ProductName" : "Raclette Courdavault",
+ "SupplierID" : 28,
+ "CategoryID" : 4,
+ "QuantityPerUnit" : "5 kg pkg.",
+ "UnitPrice" : 55.0000,
+ "UnitsInStock" : 79,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 4,
+ "CategoryName" : "Dairy Products",
+ "Description" : "Cheeses"
+ }
+}, {
+ "ProductID" : 60,
+ "ProductName" : "Camembert Pierrot",
+ "SupplierID" : 28,
+ "CategoryID" : 4,
+ "QuantityPerUnit" : "15 - 300 g rounds",
+ "UnitPrice" : 34.0000,
+ "UnitsInStock" : 19,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 4,
+ "CategoryName" : "Dairy Products",
+ "Description" : "Cheeses"
+ }
+}, {
+ "ProductID" : 61,
+ "ProductName" : "Sirop d'érable",
+ "SupplierID" : 29,
+ "CategoryID" : 2,
+ "QuantityPerUnit" : "24 - 500 ml bottles",
+ "UnitPrice" : 28.5000,
+ "UnitsInStock" : 113,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 25,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 2,
+ "CategoryName" : "Condiments",
+ "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings"
+ }
+}, {
+ "ProductID" : 62,
+ "ProductName" : "Tarte au sucre",
+ "SupplierID" : 29,
+ "CategoryID" : 3,
+ "QuantityPerUnit" : "48 pies",
+ "UnitPrice" : 49.3000,
+ "UnitsInStock" : 17,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 3,
+ "CategoryName" : "Confections",
+ "Description" : "Desserts, candies, and sweet breads"
+ }
+}, {
+ "ProductID" : 63,
+ "ProductName" : "Vegie-spread",
+ "SupplierID" : 7,
+ "CategoryID" : 2,
+ "QuantityPerUnit" : "15 - 625 g jars",
+ "UnitPrice" : 43.9000,
+ "UnitsInStock" : 24,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 5,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 2,
+ "CategoryName" : "Condiments",
+ "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings"
+ }
+}, {
+ "ProductID" : 64,
+ "ProductName" : "Wimmers gute Semmelknödel",
+ "SupplierID" : 12,
+ "CategoryID" : 5,
+ "QuantityPerUnit" : "20 bags x 4 pieces",
+ "UnitPrice" : 33.2500,
+ "UnitsInStock" : 22,
+ "UnitsOnOrder" : 80,
+ "ReorderLevel" : 30,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 5,
+ "CategoryName" : "Grains/Cereals",
+ "Description" : "Breads, crackers, pasta, and cereal"
+ }
+}, {
+ "ProductID" : 65,
+ "ProductName" : "Louisiana Fiery Hot Pepper Sauce",
+ "SupplierID" : 2,
+ "CategoryID" : 2,
+ "QuantityPerUnit" : "32 - 8 oz bottles",
+ "UnitPrice" : 21.0500,
+ "UnitsInStock" : 76,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 2,
+ "CategoryName" : "Condiments",
+ "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings"
+ }
+}, {
+ "ProductID" : 66,
+ "ProductName" : "Louisiana Hot Spiced Okra",
+ "SupplierID" : 2,
+ "CategoryID" : 2,
+ "QuantityPerUnit" : "24 - 8 oz jars",
+ "UnitPrice" : 17.0000,
+ "UnitsInStock" : 4,
+ "UnitsOnOrder" : 100,
+ "ReorderLevel" : 20,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 2,
+ "CategoryName" : "Condiments",
+ "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings"
+ }
+}, {
+ "ProductID" : 67,
+ "ProductName" : "Laughing Lumberjack Lager",
+ "SupplierID" : 16,
+ "CategoryID" : 1,
+ "QuantityPerUnit" : "24 - 12 oz bottles",
+ "UnitPrice" : 14.0000,
+ "UnitsInStock" : 52,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 10,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 1,
+ "CategoryName" : "Beverages",
+ "Description" : "Soft drinks, coffees, teas, beers, and ales"
+ }
+}, {
+ "ProductID" : 68,
+ "ProductName" : "Scottish Longbreads",
+ "SupplierID" : 8,
+ "CategoryID" : 3,
+ "QuantityPerUnit" : "10 boxes x 8 pieces",
+ "UnitPrice" : 12.5000,
+ "UnitsInStock" : 6,
+ "UnitsOnOrder" : 10,
+ "ReorderLevel" : 15,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 3,
+ "CategoryName" : "Confections",
+ "Description" : "Desserts, candies, and sweet breads"
+ }
+}, {
+ "ProductID" : 69,
+ "ProductName" : "Gudbrandsdalsost",
+ "SupplierID" : 15,
+ "CategoryID" : 4,
+ "QuantityPerUnit" : "10 kg pkg.",
+ "UnitPrice" : 36.0000,
+ "UnitsInStock" : 26,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 15,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 4,
+ "CategoryName" : "Dairy Products",
+ "Description" : "Cheeses"
+ }
+}, {
+ "ProductID" : 70,
+ "ProductName" : "Outback Lager",
+ "SupplierID" : 7,
+ "CategoryID" : 1,
+ "QuantityPerUnit" : "24 - 355 ml bottles",
+ "UnitPrice" : 15.0000,
+ "UnitsInStock" : 15,
+ "UnitsOnOrder" : 10,
+ "ReorderLevel" : 30,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 1,
+ "CategoryName" : "Beverages",
+ "Description" : "Soft drinks, coffees, teas, beers, and ales"
+ }
+}, {
+ "ProductID" : 71,
+ "ProductName" : "Flotemysost",
+ "SupplierID" : 15,
+ "CategoryID" : 4,
+ "QuantityPerUnit" : "10 - 500 g pkgs.",
+ "UnitPrice" : 21.5000,
+ "UnitsInStock" : 26,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 4,
+ "CategoryName" : "Dairy Products",
+ "Description" : "Cheeses"
+ }
+}, {
+ "ProductID" : 72,
+ "ProductName" : "Mozzarella di Giovanni",
+ "SupplierID" : 14,
+ "CategoryID" : 4,
+ "QuantityPerUnit" : "24 - 200 g pkgs.",
+ "UnitPrice" : 34.8000,
+ "UnitsInStock" : 14,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 0,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 4,
+ "CategoryName" : "Dairy Products",
+ "Description" : "Cheeses"
+ }
+}, {
+ "ProductID" : 73,
+ "ProductName" : "Röd Kaviar",
+ "SupplierID" : 17,
+ "CategoryID" : 8,
+ "QuantityPerUnit" : "24 - 150 g jars",
+ "UnitPrice" : 15.0000,
+ "UnitsInStock" : 101,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 5,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 8,
+ "CategoryName" : "Seafood",
+ "Description" : "Seaweed and fish"
+ }
+}, {
+ "ProductID" : 74,
+ "ProductName" : "Longlife Tofu",
+ "SupplierID" : 4,
+ "CategoryID" : 7,
+ "QuantityPerUnit" : "5 kg pkg.",
+ "UnitPrice" : 10.0000,
+ "UnitsInStock" : 4,
+ "UnitsOnOrder" : 20,
+ "ReorderLevel" : 5,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 7,
+ "CategoryName" : "Produce",
+ "Description" : "Dried fruit and bean curd"
+ }
+}, {
+ "ProductID" : 75,
+ "ProductName" : "Rhönbräu Klosterbier",
+ "SupplierID" : 12,
+ "CategoryID" : 1,
+ "QuantityPerUnit" : "24 - 0.5 l bottles",
+ "UnitPrice" : 7.7500,
+ "UnitsInStock" : 125,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 25,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 1,
+ "CategoryName" : "Beverages",
+ "Description" : "Soft drinks, coffees, teas, beers, and ales"
+ }
+}, {
+ "ProductID" : 76,
+ "ProductName" : "Lakkalikööri",
+ "SupplierID" : 23,
+ "CategoryID" : 1,
+ "QuantityPerUnit" : "500 ml",
+ "UnitPrice" : 18.0000,
+ "UnitsInStock" : 57,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 20,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 1,
+ "CategoryName" : "Beverages",
+ "Description" : "Soft drinks, coffees, teas, beers, and ales"
+ }
+}, {
+ "ProductID" : 77,
+ "ProductName" : "Original Frankfurter grüne Soße",
+ "SupplierID" : 12,
+ "CategoryID" : 2,
+ "QuantityPerUnit" : "12 boxes",
+ "UnitPrice" : 13.0000,
+ "UnitsInStock" : 32,
+ "UnitsOnOrder" : 0,
+ "ReorderLevel" : 15,
+ "Discontinued" : false,
+ "Category" : {
+ "CategoryID" : 2,
+ "CategoryName" : "Condiments",
+ "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings"
+ }
+}];
diff --git a/examples/kendo-react-redux-undo/src/index.css b/examples/kendo-react-redux-undo/src/index.css
new file mode 100644
index 00000000..6c39d0eb
--- /dev/null
+++ b/examples/kendo-react-redux-undo/src/index.css
@@ -0,0 +1,9 @@
+body {
+ margin: 0;
+ padding: 0;
+ font-family: sans-serif;
+}
+
+.button-right {
+ float: left;
+}
diff --git a/examples/kendo-react-redux-undo/src/index.js b/examples/kendo-react-redux-undo/src/index.js
new file mode 100644
index 00000000..ff78f977
--- /dev/null
+++ b/examples/kendo-react-redux-undo/src/index.js
@@ -0,0 +1,15 @@
+import React from 'react';
+import './index.css';
+import App from './App';
+import { Provider } from 'react-redux';
+import { store } from './reducers/index';
+import { createRoot } from 'react-dom/client';
+
+const container = document.getElementById('root');
+const root = createRoot(container);
+
+root.render(
+
+
+
+);
\ No newline at end of file
diff --git a/examples/kendo-react-redux-undo/src/logo.svg b/examples/kendo-react-redux-undo/src/logo.svg
new file mode 100644
index 00000000..6b60c104
--- /dev/null
+++ b/examples/kendo-react-redux-undo/src/logo.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/examples/kendo-react-redux-undo/src/reducers/index.js b/examples/kendo-react-redux-undo/src/reducers/index.js
new file mode 100644
index 00000000..335296fd
--- /dev/null
+++ b/examples/kendo-react-redux-undo/src/reducers/index.js
@@ -0,0 +1,12 @@
+import { configureStore } from '@reduxjs/toolkit';
+import { combineReducers } from 'redux';
+import { undoableProducts, undoableDataState } from './reducers';
+
+const rootReducer = combineReducers({
+ products: undoableProducts,
+ dataState: undoableDataState,
+});
+
+export const store = configureStore({
+ reducer: rootReducer
+});
\ No newline at end of file
diff --git a/examples/kendo-react-redux-undo/src/reducers/reducers.js b/examples/kendo-react-redux-undo/src/reducers/reducers.js
new file mode 100644
index 00000000..0f6b3668
--- /dev/null
+++ b/examples/kendo-react-redux-undo/src/reducers/reducers.js
@@ -0,0 +1,74 @@
+import {
+ ADD_PRODUCT,
+ UPDATE_PRODUCT,
+ DELETE_PRODUCT,
+ CHANGE_EDIT,
+ DATASTATE_CHANGE
+ } from '../actions/actions'
+import { sampleProducts } from '../data/products'
+import undoable from 'redux-undo'
+
+export function productsReducer(state = {products: sampleProducts}, action) {
+ switch (action.type) {
+
+ case ADD_PRODUCT:
+ let makeNewID = Math.random() * new Date().getMilliseconds() * 100
+ let newProductID = Math.floor(makeNewID)
+ let productForAdd = { inEdit: true, ProductID: newProductID };
+ let productsAfterAdd = state.products.map(product => ({
+ ...product,
+ inEdit: false
+ }));
+ productsAfterAdd.unshift(productForAdd)
+ return {
+ ...state,
+ products: productsAfterAdd
+ };
+
+ case UPDATE_PRODUCT:
+ const newProducts = state.products.map(product => {
+ const newProduct = {
+ ...product
+ };
+ if(product.ProductID === action.payload.dataItem.ProductID) {
+ newProduct[action.payload.field] = action.payload.value;
+ }
+ return newProduct;
+ })
+ return Object.assign({ products: newProducts }, {});
+
+ case DELETE_PRODUCT:
+ let index = state.products.findIndex(product => product.ProductID === action.payload.ProductID);
+ let productsAfterDelete = state.products.slice()
+ productsAfterDelete.splice(index, 1)
+ return Object.assign({ products: productsAfterDelete }, {});
+ case CHANGE_EDIT:
+ const newProductsSelection = state.products.map(product => {
+ const newProduct = {
+ ...product
+ };
+ if(product.ProductID === action.payload.ProductID) {
+ newProduct.inEdit = true
+ } else {
+ newProduct.inEdit = false
+ }
+ return newProduct;
+ })
+ return Object.assign({ products: newProductsSelection }, {});
+ default:
+ return state
+ }
+}
+
+export function gridStateReducer(state = {sort: [], filter:[], skip: 0, take: 10}, action) {
+ switch (action.type) {
+ case DATASTATE_CHANGE:
+ const dataState = action.payload.data
+ return Object.assign({ sort: dataState.sort, filter: dataState.filter, skip: dataState.skip, take: dataState.take }, {});
+ default:
+ return state
+ }
+}
+
+export const undoableProducts = undoable(productsReducer)
+export const undoableDataState = undoable(gridStateReducer)
\ No newline at end of file
diff --git a/examples/kendo-react-redux-undo/src/registerServiceWorker.js b/examples/kendo-react-redux-undo/src/registerServiceWorker.js
new file mode 100644
index 00000000..a3e6c0cf
--- /dev/null
+++ b/examples/kendo-react-redux-undo/src/registerServiceWorker.js
@@ -0,0 +1,117 @@
+// In production, we register a service worker to serve assets from local cache.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on the "N+1" visit to a page, since previously
+// cached resources are updated in the background.
+
+// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
+// This link also includes instructions on opting out of this behavior.
+
+const isLocalhost = Boolean(
+ window.location.hostname === 'localhost' ||
+ // [::1] is the IPv6 localhost address.
+ window.location.hostname === '[::1]' ||
+ // 127.0.0.1/8 is considered localhost for IPv4.
+ window.location.hostname.match(
+ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
+ )
+);
+
+export default function register() {
+ if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+ // The URL constructor is available in all browsers that support SW.
+ const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
+ if (publicUrl.origin !== window.location.origin) {
+ // Our service worker won't work if PUBLIC_URL is on a different origin
+ // from what our page is served on. This might happen if a CDN is used to
+ // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
+ return;
+ }
+
+ window.addEventListener('load', () => {
+ const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+
+ if (isLocalhost) {
+ // This is running on localhost. Lets check if a service worker still exists or not.
+ checkValidServiceWorker(swUrl);
+
+ // Add some additional logging to localhost, pointing developers to the
+ // service worker/PWA documentation.
+ navigator.serviceWorker.ready.then(() => {
+ console.log(
+ 'This web app is being served cache-first by a service ' +
+ 'worker. To learn more, visit https://goo.gl/SC7cgQ'
+ );
+ });
+ } else {
+ // Is not local host. Just register service worker
+ registerValidSW(swUrl);
+ }
+ });
+ }
+}
+
+function registerValidSW(swUrl) {
+ navigator.serviceWorker
+ .register(swUrl)
+ .then(registration => {
+ registration.onupdatefound = () => {
+ const installingWorker = registration.installing;
+ installingWorker.onstatechange = () => {
+ if (installingWorker.state === 'installed') {
+ if (navigator.serviceWorker.controller) {
+ // At this point, the old content will have been purged and
+ // the fresh content will have been added to the cache.
+ // It's the perfect time to display a "New content is
+ // available; please refresh." message in your web app.
+ console.log('New content is available; please refresh.');
+ } else {
+ // At this point, everything has been precached.
+ // It's the perfect time to display a
+ // "Content is cached for offline use." message.
+ console.log('Content is cached for offline use.');
+ }
+ }
+ };
+ };
+ })
+ .catch(error => {
+ console.error('Error during service worker registration:', error);
+ });
+}
+
+function checkValidServiceWorker(swUrl) {
+ // Check if the service worker can be found. If it can't reload the page.
+ fetch(swUrl)
+ .then(response => {
+ // Ensure service worker exists, and that we really are getting a JS file.
+ if (
+ response.status === 404 ||
+ response.headers.get('content-type').indexOf('javascript') === -1
+ ) {
+ // No service worker found. Probably a different app. Reload the page.
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister().then(() => {
+ window.location.reload();
+ });
+ });
+ } else {
+ // Service worker found. Proceed as normal.
+ registerValidSW(swUrl);
+ }
+ })
+ .catch(() => {
+ console.log(
+ 'No internet connection found. App is running in offline mode.'
+ );
+ });
+}
+
+export function unregister() {
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister();
+ });
+ }
+}