This commit is contained in:
Pavlina Hadjieva 2017-10-19 17:38:41 +03:00
Родитель 8544ffa5d2
Коммит 775143feb9
21 изменённых файлов: 4242 добавлений и 1 удалений

Просмотреть файл

@ -1,2 +1,2 @@
# react-redux-grid
React Redux Grid application
Kendo UI Grid wrapper component for React using the Redux Pattern

21
redux-grid-app/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,21 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

2164
redux-grid-app/README.md Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,28 @@
{
"name": "redux-grid-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@progress/kendo-ui": "2017.3.1018",
"@progress/kendo-grid-react-wrapper": "latest",
"@progress/kendo-data-query": "^1.0.6",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react-scripts-ts": "2.8.0",
"@progress/kendo-theme-default": "latest",
"react-redux": "^5.0.6",
"redux": "^3.7.2"
},
"scripts": {
"start": "react-scripts-ts start",
"build": "react-scripts-ts build",
"test": "react-scripts-ts test --env=jsdom",
"eject": "react-scripts-ts eject"
},
"devDependencies": {
"@types/jest": "^21.1.4",
"@types/node": "^8.0.44",
"@types/react": "^16.0.13",
"@types/react-dom": "^16.0.1"
}
}

Двоичные данные
redux-grid-app/public/favicon.ico Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.8 KiB

Просмотреть файл

@ -0,0 +1,42 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Просмотреть файл

@ -0,0 +1,15 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "192x192",
"type": "image/png"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

Просмотреть файл

@ -0,0 +1,5 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';

Просмотреть файл

@ -0,0 +1,80 @@
import * as React from 'react';
import { Grid, GridColumn } from '@progress/kendo-grid-react-wrapper';
export default class ReduxGrid extends React.Component<any, any> {
private widgetInstance: kendo.ui.Grid = null;
private options: any = null;
constructor(props: any) {
super(props);
this.widgetRef = this.widgetRef.bind(this);
this.options = {
selectable: true,
filterable: true,
groupable: true,
sortable: {
mode: 'multiple',
allowUnsort: true,
showIndexes: true
},
pageable: true,
widgetRef: this.widgetRef,
sort: this.props.onSort,
group: this.props.onGroup,
page: this.props.onPage,
editable: 'popup',
toolbar: ['create'],
save: this.props.onSave,
remove: this.props.onRemove,
filter: this.props.onFilter
};
}
widgetRef(widget: kendo.ui.Grid) {
this.widgetInstance = widget;
}
render() {
const { products, sort, group, page, filter, pageSize } = this.props;
const serverSorting = true;
const serverGrouping = true;
const serverPaging = true;
const serverFiltering = true;
const dataSource = new kendo.data.DataSource({
data: products,
sort, group, page, filter,
serverSorting, serverGrouping, serverPaging, serverFiltering, pageSize,
schema: {
model: {
id: 'ProductID',
fields: {
ProductID: { editable: false, nullable: true },
ProductName: { validation: { required: true } },
UnitPrice: { type: 'number', validation: { required: true, min: 1 } },
UnitsInStock: { type: 'number', validation: { min: 0, required: true } },
Discontinued: { type: 'boolean' }
}
},
total: 'total'
}
});
return (
<div style={{ 'marginBottom': '30px' }}>
<Grid dataSource={dataSource} {...this.options}>
<GridColumn field="ProductID" title="ID" filterable={false} key={1} />
<GridColumn field="ProductName" title="Product Name" key={2} />
<GridColumn field="UnitPrice" title="Unit Price" format="{0:c}" width="130px" key={3} />
<GridColumn field="UnitsInStock" title="Units In Stock" width="130px" key={4} />
<GridColumn field="Discontinued" width="130px" key={5} />
<GridColumn command={[{"name": "edit", "text": "Edit"}, {"name": "destroy", "text": "Delete"}]} title="&nbsp;" width="250px" key={6}/>
</Grid>
</div>
);
}
}

Просмотреть файл

@ -0,0 +1,48 @@
import { connect } from 'react-redux';
import { sort, group, page, filter, remove, add, update } from './actions';
import ReduxGrid from './ReduxGrid';
const mapStateToProps = (state) => ({
products: state.products,
sort: state.sort,
group: state.group,
filter: state.filter,
page: state.page,
pageSize: state.pageSize
});
const mapDispatchToProps = (dispatch) => ({
onSort: (event: any) => {
event.preventDefault();
dispatch(sort(event.sort));
},
onGroup: (event: any) => {
event.preventDefault();
dispatch(group(event.groups));
},
onPage: (event: any) => {
event.preventDefault();
dispatch(page(event.page));
},
onRemove: (event: any) => {
event.preventDefault();
dispatch(remove(event.model.ProductID));
},
onSave: (event: any) => {
event.preventDefault();
dispatch(event.model.id ? update(event.model) : add(event.model));
},
onFilter: (event: any) => {
event.preventDefault();
dispatch(filter(event.filter));
}
});
const ReduxGridCont = connect(
mapStateToProps,
mapDispatchToProps
)(ReduxGrid);
export default ReduxGridCont;

Просмотреть файл

@ -0,0 +1,51 @@
import { SortDescriptor, CompositeFilterDescriptor, FilterDescriptor } from '@progress/kendo-data-query';
import * as n from './names';
export const sort = function (sortDesc: SortDescriptor) {
return {
type: n.SORT,
sort: sortDesc
};
};
export const group = function (groupDesc: [SortDescriptor]) {
return {
type: n.GROUP,
group: groupDesc
};
};
export const page = function (pageNumber: number) {
return {
type: n.PAGE,
page: pageNumber
};
};
export const remove = function (productID: number) {
return {
type: n.REMOVE,
ProductID: productID
};
};
export const add = function (model: any) {
return {
type: n.ADD,
model
};
};
export const update = function (model: any) {
return {
type: n.UPDATE,
model
};
};
export const filter = function (filterDesc: CompositeFilterDescriptor | FilterDescriptor) {
return {
type: n.FILTER,
filter: filterDesc
};
};

Просмотреть файл

@ -0,0 +1,7 @@
export const SORT = 'SORT';
export const GROUP = 'GROUP';
export const PAGE = 'PAGE';
export const REMOVE = 'REMOVE';
export const ADD = 'ADD';
export const UPDATE = 'UPDATE';
export const FILTER = 'FILTER';

Просмотреть файл

@ -0,0 +1,16 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import '@progress/kendo-ui';
import ReduxGrid from './ReduxGridCont';
import { Provider } from 'react-redux';
import { products } from './products';
import { createStoreHelper } from './reduxUtils/createStoreHelper';
import '@progress/kendo-theme-default/dist/all.css';
ReactDOM.render(
<Provider store={createStoreHelper(products)}>
<ReduxGrid />
</Provider>,
document.getElementById('root') as HTMLElement
);

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,98 @@
import { CompositeFilterDescriptor } from '@progress/kendo-data-query';
import * as actionNames from '../actions/names';
import createState from '../reduxUtils/createState';
function mergeSortDescriptors(sourceDescriptors: any, newDesciptor: any) {
let result = [];
let matchFound = false;
sourceDescriptors.forEach(element => {
if (element.field === newDesciptor.field) {
// If the field was already sorted
matchFound = true;
if (newDesciptor.dir !== undefined) {
// and the sorting is not removed, add the new descriptor.
result.push(newDesciptor);
}
} else {
// Preserve the not affected desciptors.
result.push(element);
}
});
if (!matchFound) {
// If the field was not already sorted, add the new descriptor.
result.push(newDesciptor);
}
return result;
}
export function mergeFilters(sourceFilters: any, newFilter: any): CompositeFilterDescriptor {
let result: CompositeFilterDescriptor = { logic: 'and', filters: [] };
// If a filter is presented
if (sourceFilters.length || (sourceFilters.filters && sourceFilters.filters.length && newFilter)) {
let isNewFilterInserted = false;
// If a filter with the same target exists
sourceFilters.filters.forEach((currentFilter) => {
// If the current filter doesnt have nested filters and has the same target
if (currentFilter.field === newFilter.filters[0].field) {
let tmp: CompositeFilterDescriptor = { logic: newFilter.logic, filters: [] };
newFilter.filters.forEach((fltr) => {
// Insert all new filters
tmp.filters.push(fltr);
});
result.filters.push(tmp);
} else {
// If the current filter DOES have nested filters and those filter has the same target
if (currentFilter.filters && currentFilter.filters[0].field === newFilter.filters[0].field) {
result.filters.push.apply(result.filters, newFilter);
} else {
// Copy all filters that does not have the same target into result
result.filters.push(currentFilter);
}
}
});
// If the new filter hasnt been inserted into result
if (!isNewFilterInserted) {
result.filters.push(newFilter);
}
} else {
// If the new filter is null, return empty array
if (newFilter) {
result.filters.push(newFilter);
}
}
return result;
}
export default (state, action) => {
switch (action.type) {
case actionNames.SORT:
return createState(
action.allProducts,
state.page,
mergeSortDescriptors(state.sort, action.sort),
state.group,
state.filter);
case actionNames.GROUP:
return createState(action.allProducts, state.page, state.sort, action.group, state.filter);
case actionNames.PAGE:
return createState(action.allProducts, action.page, state.sort, state.group, state.filter);
case actionNames.FILTER:
return createState(
action.allProducts,
state.page,
state.sort,
state.group,
mergeFilters(state.filter, action.filter));
case actionNames.REMOVE:
case actionNames.ADD:
case actionNames.UPDATE:
return createState(action.allProducts, state.page, state.sort, state.group, state.filter);
default:
return state;
}
};
export const pageSize = 10;

Просмотреть файл

@ -0,0 +1,16 @@
import queryItems from './queryItems';
export default function createState(products: any, page: number, sort: any, group: any, filter: any) {
return {
products: queryItems(products, page, 10, sort, group, filter),
sort,
group,
filter,
page,
pageSize: 10
};
}
export function createInitialState(products: any) {
return createState(products, 1, [], [], []);
}

Просмотреть файл

@ -0,0 +1,40 @@
import { createStore, applyMiddleware } from 'redux';
import reducer from './../reducers';
import { createInitialState } from './createState';
import * as actionNames from './../actions/names';
// The ProductsMiddleware:
// - handles CUD operations
// - assigns all available products to each action
function createProductsMiddleware(products: any) {
return store => next => action => {
switch (action.type) {
case actionNames.REMOVE:
products.splice(products.findIndex(pr => pr.ProductID === action.ProductID), 1);
break;
case actionNames.ADD:
let product = action.model.toJSON();
product.ProductID = Math.max(...products.map(pr => pr.ProductID));
product.ProductID++;
products.unshift(product);
break;
case actionNames.UPDATE:
products[products.findIndex(el => el.ProductID === action.model.ProductID)] = action.model.toJSON();
break;
default:
break;
}
action.allProducts = products;
return next(action);
};
}
export function createStoreHelper(products: any): any {
return createStore(
reducer,
createInitialState(products),
applyMiddleware(createProductsMiddleware(products)));
}

Просмотреть файл

@ -0,0 +1,66 @@
import { groupBy, orderBy, filterBy } from '@progress/kendo-data-query';
function getItemsPerPage(items: any, page: number, pageSize: number) {
return items.slice((page - 1) * pageSize, page * pageSize);
}
function parseDiscontinued(filter: any) {
if (filter.filters) {
filter.filters = filter.filters.map((fltr) => {
return parseDiscontinued(fltr);
});
} else {
if (filter.field === 'Discontinued') {
filter.value = filter.value === 'true' || filter.value === true;
}
}
return filter;
}
export default function (
items: any,
page: number,
pageSize: number,
sortDescriptors: any,
groupDescriptors: any,
filter: any) {
if (filter) {
filter = parseDiscontinued(filter);
}
let result: any = filterBy(items, filter);
result = orderBy(result, groupDescriptors.concat(sortDescriptors));
result = getItemsPerPage(result, page, pageSize);
result = groupBy(result, groupDescriptors);
result = toGroupable(result);
result.total = items.length;
return result;
}
export function toGroupable(data: any) {
if (Array.isArray(data)) {
data = data.map(tmp => {
if (tmp.items) {
tmp.items = tmp.items.map((item) => {
if (item.items) {
tmp.hasSubgroups = true;
}
return toGroupable(item);
});
}
return tmp;
});
} else {
if (data.items) {
data.items = data.items.map(item => {
if (item.items) {
data.hasSubgroups = true;
}
return toGroupable(item);
});
}
}
return data;
}

Просмотреть файл

@ -0,0 +1,23 @@
{
"compilerOptions": {
"sourceMap": true,
"noImplicitAny": false,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"lib": ["es2015", "es2017", "dom"],
"declaration": false,
"outDir": "./dist"
},
"awesomeTypescriptLoaderOptions": {
"useTranspileModule": true,
"transpileOnly": true,
"useCache": true
},
"include": [
"./src"
],
"exclude": [
"node_modules"
]
}

Просмотреть файл

@ -0,0 +1,6 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs"
}
}

Просмотреть файл

@ -0,0 +1,99 @@
{
"extends": ["tslint-react"],
"rules": {
"align": [
true,
"parameters",
"arguments",
"statements"
],
"ban": false,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"eofline": false,
"forin": true,
"indent": [ true, "spaces" ],
"interface-name": [true, "never-prefix"],
"jsdoc-format": true,
"jsx-no-lambda": false,
"jsx-no-multiline-js": false,
"label-position": true,
"max-line-length": [ true, 120 ],
"member-ordering": [
true,
"public-before-private",
"static-before-instance",
"variables-before-functions"
],
"no-any": true,
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"log",
"error",
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-consecutive-blank-lines": true,
"no-construct": true,
"no-debugger": true,
"no-duplicate-variable": true,
"no-empty": true,
"no-eval": true,
"no-shadowed-variable": true,
"no-string-literal": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": false,
"no-unused-expression": true,
"no-use-before-declare": true,
"one-line": [
true,
"check-catch",
"check-else",
"check-open-brace",
"check-whitespace"
],
"quotemark": [true, "single", "jsx-double"],
"radix": true,
"semicolon": [true, "always"],
"switch-default": true,
"trailing-comma": [false],
"triple-equals": [ true, "allow-null-check" ],
"typedef": [
true,
"parameter",
"property-declaration"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case"],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-module",
"check-operator",
"check-separator",
"check-type",
"check-typecast"
]
}
}