Redux-Form sample webpart (#503)
* Redux-Form sample SPFx webpart added. New SPFx webpart sample added that uses redux-form library. Sample is a data entry form with dynamic grid built using typescript, react, redux and redux-form library. * Readme file updated. Readme file updated with correct webpart demo gif file path.
This commit is contained in:
parent
87e30dd02a
commit
13954cc7bc
|
@ -0,0 +1,25 @@
|
||||||
|
# EditorConfig helps developers define and maintain consistent
|
||||||
|
# coding styles between different editors and IDEs
|
||||||
|
# editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
|
||||||
|
[*]
|
||||||
|
|
||||||
|
# change these settings to your own preference
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# we recommend you to keep these unchanged
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[{package,bower}.json]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Build generated files
|
||||||
|
dist
|
||||||
|
lib
|
||||||
|
solution
|
||||||
|
temp
|
||||||
|
*.sppkg
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# OSX
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Visual Studio files
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
.vs
|
||||||
|
bin
|
||||||
|
obj
|
||||||
|
|
||||||
|
# Resx Generated Code
|
||||||
|
*.resx.ts
|
||||||
|
|
||||||
|
# Styles Generated Code
|
||||||
|
*.scss.ts
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"@microsoft/generator-sharepoint": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"libraryName": "sp-fx-dynamic-grid",
|
||||||
|
"libraryId": "23cfb83a-b316-4112-8f37-5c769a66c024",
|
||||||
|
"environment": "spo"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
# SPFx webpart using Redux-Form library and React
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
Sample webpart to demonstrate the use of [Redux-Form](https://github.com/erikras/redux-form) library with SPFx, React and Typescript. Demonstrates how to easily build a dynamic grid using redux-form.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
![SPFx redux-form webpart](https://github.com/vipulkelkar/sp-dev-fx-webparts/blob/ReduxFormSample/samples/react-reduxform/assets/ReduxFormWebpart.gif)
|
||||||
|
|
||||||
|
|
||||||
|
## Used SharePoint Framework Version
|
||||||
|
![drop](https://img.shields.io/badge/version-GA-green.svg)
|
||||||
|
|
||||||
|
## Applies to
|
||||||
|
|
||||||
|
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||||
|
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Basic knowledge of react-redux concepts - reducer,actions and dispatch.
|
||||||
|
- PnP PowerShell - to setup Fields and Lists to work with the webpart.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Solution|Author(s)
|
||||||
|
--------|---------
|
||||||
|
react-reduxform | Vipul Kelkar @vipulkelkar
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
Version|Date|Comments
|
||||||
|
-------|----|--------
|
||||||
|
1.0|May 02, 2018|Initial release
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Minimal Path to Awesome
|
||||||
|
|
||||||
|
- The webpart requires two custom lists in the SharePoint site. The folder "SetupScript" contains a PnP PowerShell script that will setup the custom fields, content type and the required lists.
|
||||||
|
|
||||||
|
- Change the site URL in the PowerShell script and execute with proper credentials to setup the lists.
|
||||||
|
|
||||||
|
- Clone this repository
|
||||||
|
- in the command line run:
|
||||||
|
- `npm install`
|
||||||
|
- `gulp serve`
|
||||||
|
|
||||||
|
- Navigate to - <Your SP site>/_layouts/workbench.aspx and add the "ReduxFormWebpart"
|
||||||
|
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- This is a data entry webpart to create a "Purchase Request". Each purchase request can have several items that can be ordered within it.
|
||||||
|
|
||||||
|
- The webpart interacts with two SharePoint lists - "PurchaseRequest" and "PurchaseRequestItems".
|
||||||
|
|
||||||
|
- The purchase request is created in the "PurchaseRequest" list. The purchase items are stored in separate "PurchaseRequestItems" list along with the ListItem ID of the corrosponding PurchaseRequest item.
|
||||||
|
|
||||||
|
|
||||||
|
Redux-Form :
|
||||||
|
|
||||||
|
- Webpart makes use of [Redux-Form](https://github.com/erikras/redux-form) library which makes it easy to maintain the state of form fields without explicitly requiring to maintain the state everytime data in a form field is added/updated/deleted.
|
||||||
|
Refer to the [Getting started](https://redux-form.com/6.4.3/docs/gettingstarted.md/) guide for the basics of redux-form
|
||||||
|
|
||||||
|
- The sample demonstrates how to integrate redux-form in SPFx webpart and develop a dynamic grid component using [FieldArray](https://redux-form.com/6.4.3/docs/api/fieldarray.md/) component of redux-form.
|
||||||
|
|
||||||
|
- The sample also uses custom renderers for form fields along with required field and number validations.
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-reduxform" />
|
|
@ -0,0 +1,59 @@
|
||||||
|
Connect-PnPOnline -Url <URL>
|
||||||
|
|
||||||
|
Write-Host "Starting the setup....."
|
||||||
|
|
||||||
|
$fieldAndCTGroup = "ReduxFormSample"
|
||||||
|
|
||||||
|
Write-Host "Creating fields....."
|
||||||
|
|
||||||
|
Add-PnPField -DisplayName ProductCode -InternalName ProductCode -Type Text -Group $fieldAndCTGroup -ErrorAction SilentlyContinue
|
||||||
|
Add-PnPField -DisplayName PurchasedFor -InternalName PurchasedFor -Type Choice -Choices IT,Admin,HR,Finance -Group $fieldAndCTGroup -ErrorAction SilentlyContinue
|
||||||
|
Add-PnPField -DisplayName TypeOfPR -InternalName TypeOfPR -Type Choice -Choices @("Maintenance","IT Asset Purchase","Stationary","Other Services") -Group $fieldAndCTGroup -ErrorAction SilentlyContinue
|
||||||
|
Add-PnPField -DisplayName PurchaseRequestID -InternalName PurchaseRequestID -Type Number -Group $fieldAndCTGroup -ErrorAction SilentlyContinue
|
||||||
|
Add-PnPField -DisplayName Quantity -InternalName Quantity -Type Number -Group $fieldAndCTGroup -ErrorAction SilentlyContinue
|
||||||
|
Add-PnPField -DisplayName RatePerUnit -InternalName RatePerUnit -Type Number -Group $fieldAndCTGroup -ErrorAction SilentlyContinue
|
||||||
|
Add-PnPField -DisplayName TotalCost -InternalName TotalCost -Type Number -Group $fieldAndCTGroup -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
Write-Host "Creating content types....."
|
||||||
|
|
||||||
|
Add-PnPContentType -Name 'PurchaseRequest' -Group $fieldAndCTGroup -ErrorAction SilentlyContinue
|
||||||
|
$purchaseRequestCT = Get-PnPContentType -Identity 'PurchaseRequest'
|
||||||
|
|
||||||
|
Add-PnPContentType -Name 'PurchaseRequestItems' -Group $fieldAndCTGroup -ErrorAction SilentlyContinue
|
||||||
|
$purchaseRequestItemsCT = Get-PnPContentType -Identity 'PurchaseRequestItems'
|
||||||
|
|
||||||
|
|
||||||
|
Write-Host "Adding fields to content type....."
|
||||||
|
|
||||||
|
# Add columns to purchase request CT
|
||||||
|
Add-PnPFieldToContentType -Field PurchasedFor -ContentType $purchaseRequestCT
|
||||||
|
Add-PnPFieldToContentType -Field TypeOfPR -ContentType $purchaseRequestCT
|
||||||
|
|
||||||
|
# Add columns to purchase request items CT
|
||||||
|
Add-PnPFieldToContentType -Field ProductCode -ContentType $purchaseRequestItemsCT
|
||||||
|
Add-PnPFieldToContentType -Field Quantity -ContentType $purchaseRequestItemsCT
|
||||||
|
Add-PnPFieldToContentType -Field RatePerUnit -ContentType $purchaseRequestItemsCT
|
||||||
|
Add-PnPFieldToContentType -Field TotalCost -ContentType $purchaseRequestItemsCT
|
||||||
|
Add-PnPFieldToContentType -Field PurchaseRequestID -ContentType $purchaseRequestItemsCT
|
||||||
|
|
||||||
|
Write-Host "Creating lists....."
|
||||||
|
|
||||||
|
New-PnPList -Title 'PurchaseRequest' -Template GenericList -Url Lists/PurchaseRequest -ErrorAction SilentlyContinue
|
||||||
|
$purchaseRequestList = Get-PnPList -Identity Lists/PurchaseRequest
|
||||||
|
Set-PnPList -Identity 'PurchaseRequest' -EnableContentTypes $true
|
||||||
|
Add-PnPContentTypeToList -List $purchaseRequestList -ContentType $purchaseRequestCT -DefaultContentType
|
||||||
|
|
||||||
|
|
||||||
|
New-PnPList -Title 'PurchaseRequestItems' -Template GenericList -Url Lists/PurchaseRequestItems -ErrorAction SilentlyContinue
|
||||||
|
$purchaseRequestItemsList = Get-PnPList -Identity Lists/PurchaseRequestItems
|
||||||
|
Set-PnPList -Identity 'PurchaseRequestItems' -EnableContentTypes $true
|
||||||
|
Add-PnPContentTypeToList -List $purchaseRequestItemsList -ContentType $purchaseRequestItemsCT -DefaultContentType
|
||||||
|
|
||||||
|
|
||||||
|
Write-Host "Setting up list views....."
|
||||||
|
|
||||||
|
Add-PnPView -List $purchaseRequestList -Title PurchaseRequest -SetAsDefault -Fields PurchasedFor,TypeOfPR,Author
|
||||||
|
Add-PnPView -List $purchaseRequestItemsList -Title PurchaseRequestItems -SetAsDefault -Fields ProductCode,Quantity,RatePerUnit,TotalCost,PurchaseRequestID
|
||||||
|
|
||||||
|
|
||||||
|
Write-Host "Setup completed....."
|
Binary file not shown.
After Width: | Height: | Size: 589 KiB |
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||||
|
"version": "2.0",
|
||||||
|
"bundles": {
|
||||||
|
"dynamic-grid-webpart-web-part": {
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"entrypoint": "./lib/webparts/PurchaseRequestWebpart/ReduxFormWebpart.js",
|
||||||
|
"manifest": "./src/webparts/PurchaseRequestWebpart/ReduxFormWebpartWebPart.manifest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externals": {},
|
||||||
|
"localizedResources": {
|
||||||
|
"DynamicGridWebpartWebPartStrings": "lib/webparts/PurchaseRequestWebpart/loc/{locale}.js"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||||
|
"deployCdnPath": "temp/deploy"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||||
|
"workingDir": "./temp/deploy/",
|
||||||
|
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||||
|
"container": "sp-fx-dynamic-grid",
|
||||||
|
"accessKey": "<!-- ACCESS KEY -->"
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||||
|
"solution": {
|
||||||
|
"name": "sp-fx-dynamic-grid-client-side-solution",
|
||||||
|
"id": "23cfb83a-b316-4112-8f37-5c769a66c024",
|
||||||
|
"version": "1.0.0.0",
|
||||||
|
"includeClientSideAssets": true,
|
||||||
|
"skipFeatureDeployment": true
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"zippedPackage": "solution/sp-fx-dynamic-grid.sppkg"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/core-build/serve.schema.json",
|
||||||
|
"port": 4321,
|
||||||
|
"https": true,
|
||||||
|
"initialPage": "https://localhost:5432/workbench",
|
||||||
|
"api": {
|
||||||
|
"port": 5432,
|
||||||
|
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/core-build/tslint.schema.json",
|
||||||
|
// Display errors as warnings
|
||||||
|
"displayAsWarning": true,
|
||||||
|
// The TSLint task may have been configured with several custom lint rules
|
||||||
|
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
|
||||||
|
// project). If true, this flag will deactivate any of these rules.
|
||||||
|
"removeExistingRules": true,
|
||||||
|
// When true, the TSLint task is configured with some default TSLint "rules.":
|
||||||
|
"useDefaultConfigAsBase": false,
|
||||||
|
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
|
||||||
|
// which are active, other than the list of rules below.
|
||||||
|
"lintConfig": {
|
||||||
|
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
|
||||||
|
"rules": {
|
||||||
|
"class-name": false,
|
||||||
|
"export-name": false,
|
||||||
|
"forin": false,
|
||||||
|
"label-position": false,
|
||||||
|
"member-access": true,
|
||||||
|
"no-arg": false,
|
||||||
|
"no-console": false,
|
||||||
|
"no-construct": false,
|
||||||
|
"no-duplicate-case": true,
|
||||||
|
"no-duplicate-variable": true,
|
||||||
|
"no-eval": false,
|
||||||
|
"no-function-expression": true,
|
||||||
|
"no-internal-module": true,
|
||||||
|
"no-shadowed-variable": true,
|
||||||
|
"no-switch-case-fall-through": true,
|
||||||
|
"no-unnecessary-semicolons": true,
|
||||||
|
"no-unused-expression": true,
|
||||||
|
"no-use-before-declare": true,
|
||||||
|
"no-with-statement": true,
|
||||||
|
"semicolon": true,
|
||||||
|
"trailing-comma": false,
|
||||||
|
"typedef": false,
|
||||||
|
"typedef-whitespace": false,
|
||||||
|
"use-named-parameter": true,
|
||||||
|
"valid-typeof": true,
|
||||||
|
"variable-name": false,
|
||||||
|
"whitespace": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||||
|
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const gulp = require('gulp');
|
||||||
|
const build = require('@microsoft/sp-build-web');
|
||||||
|
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||||
|
|
||||||
|
build.initialize(gulp);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"name": "sp-fx-dynamic-grid",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "gulp bundle",
|
||||||
|
"clean": "gulp clean",
|
||||||
|
"test": "gulp test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@microsoft/sp-core-library": "~1.4.1",
|
||||||
|
"@microsoft/sp-lodash-subset": "~1.4.1",
|
||||||
|
"@microsoft/sp-office-ui-fabric-core": "~1.4.1",
|
||||||
|
"@microsoft/sp-webpart-base": "~1.4.1",
|
||||||
|
"@types/react": "15.6.6",
|
||||||
|
"@types/react-dom": "15.5.6",
|
||||||
|
"@types/redux-form": "^7.2.4",
|
||||||
|
"@types/webpack-env": ">=1.12.1 <1.14.0",
|
||||||
|
"react": "15.6.2",
|
||||||
|
"react-dom": "15.6.2",
|
||||||
|
"react-redux": "^5.0.7",
|
||||||
|
"redux": "^4.0.0",
|
||||||
|
"redux-form": "^7.3.0",
|
||||||
|
"redux-thunk": "^2.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@microsoft/sp-build-web": "~1.4.1",
|
||||||
|
"@microsoft/sp-module-interfaces": "~1.4.1",
|
||||||
|
"@microsoft/sp-webpart-workbench": "~1.4.1",
|
||||||
|
"@types/chai": ">=3.4.34 <3.6.0",
|
||||||
|
"@types/mocha": ">=2.2.33 <2.6.0",
|
||||||
|
"ajv": "~5.2.2",
|
||||||
|
"gulp": "~3.9.1",
|
||||||
|
"sp-pnp-js": "^3.0.7"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactDom from 'react-dom';
|
||||||
|
import { Version } from '@microsoft/sp-core-library';
|
||||||
|
import {
|
||||||
|
BaseClientSideWebPart,
|
||||||
|
IPropertyPaneConfiguration,
|
||||||
|
PropertyPaneTextField
|
||||||
|
} from '@microsoft/sp-webpart-base';
|
||||||
|
|
||||||
|
import * as strings from 'DynamicGridWebpartWebPartStrings';
|
||||||
|
import PurchaseRequestWebpart from './components/PurchaseRequestWebpart/PurchaseRequestWebpart';
|
||||||
|
import { IPurchaseRequestWebpartProps } from './components/PurchaseRequestWebpart/IPurchaseRequestWebpartProps';
|
||||||
|
import { UrlQueryParameterCollection } from '@microsoft/sp-core-library';
|
||||||
|
|
||||||
|
export interface IReduxFormWebpartProps {
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ReduxFormWebpart extends BaseClientSideWebPart<IReduxFormWebpartProps> {
|
||||||
|
|
||||||
|
public render(): void {
|
||||||
|
|
||||||
|
var queryParameters = new UrlQueryParameterCollection(window.location.href);
|
||||||
|
let id: string = "";
|
||||||
|
if (queryParameters.getValue("itemid")) {
|
||||||
|
id = queryParameters.getValue("itemid");
|
||||||
|
}
|
||||||
|
|
||||||
|
const element: React.ReactElement<IReduxFormWebpartProps> = React.createElement(
|
||||||
|
PurchaseRequestWebpart,
|
||||||
|
{
|
||||||
|
description: this.properties.description,
|
||||||
|
siteUrl:this.context.pageContext.web.absoluteUrl,
|
||||||
|
spHttpClient:this.context.spHttpClient,
|
||||||
|
itemId:id
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ReactDom.render(element, this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get dataVersion(): Version {
|
||||||
|
return Version.parse('1.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
|
return {
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
header: {
|
||||||
|
description: strings.PropertyPaneDescription
|
||||||
|
},
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
groupName: strings.BasicGroupName,
|
||||||
|
groupFields: [
|
||||||
|
PropertyPaneTextField('description', {
|
||||||
|
label: strings.DescriptionFieldLabel
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||||
|
"id": "5eae71fd-ab9e-4c89-ac18-24d2e19e9e79",
|
||||||
|
"alias": "DynamicGridWebpartWebPart",
|
||||||
|
"componentType": "WebPart",
|
||||||
|
|
||||||
|
// The "*" signifies that the version should be taken from the package.json
|
||||||
|
"version": "*",
|
||||||
|
"manifestVersion": 2,
|
||||||
|
|
||||||
|
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||||
|
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||||
|
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||||
|
"requiresCustomScript": false,
|
||||||
|
|
||||||
|
"preconfiguredEntries": [{
|
||||||
|
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||||
|
"group": { "default": "Other" },
|
||||||
|
"title": { "default": "ReduxFormWebpart" },
|
||||||
|
"description": { "default": "Sample webpart using redux-form library in SPFx." },
|
||||||
|
"officeFabricIconFontName": "Page",
|
||||||
|
"properties": {
|
||||||
|
"description": "Sample webpart using redux-form library in SPFx."
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { INewFormState } from '../state/INewFormControlsState';
|
||||||
|
import NewPurchaseRequestService from '../services/NewPurchaseRequestService';
|
||||||
|
|
||||||
|
// The file contains actions for the NewPurchaseRequestReducer
|
||||||
|
|
||||||
|
// Gets the choices for dropdown fields in the new form. The values are fetched from the choice field options.
|
||||||
|
export function GetInitialControlValuesAction(){
|
||||||
|
|
||||||
|
return dispatch => {
|
||||||
|
|
||||||
|
let newPurchaseRequestServiceObj:NewPurchaseRequestService = new NewPurchaseRequestService();
|
||||||
|
let formControlsState = {purchasedForOptions:[],typeOfPurchaseRequestOptions:[]} as INewFormState;
|
||||||
|
|
||||||
|
newPurchaseRequestServiceObj.getNewFormControlsState().then((resp:INewFormState) => {
|
||||||
|
|
||||||
|
formControlsState.purchasedForOptions = resp.purchasedForOptions;
|
||||||
|
formControlsState.typeOfPurchaseRequestOptions = resp.typeOfPurchaseRequestOptions;
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type:"GET_DEFAULT_CONTROL_VALUES",
|
||||||
|
payload:formControlsState
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Creates a new purchase request.
|
||||||
|
export function CreateNewPurchaseRequest(purchaseRequestData:INewFormState, siteUrl){
|
||||||
|
return dispatch => {
|
||||||
|
|
||||||
|
let newPurchaseRequestServiceObj:NewPurchaseRequestService = new NewPurchaseRequestService();
|
||||||
|
|
||||||
|
newPurchaseRequestServiceObj.createNewPurchaseRequest(purchaseRequestData,siteUrl).then(response =>{
|
||||||
|
alert("Purchase request created...");
|
||||||
|
}).catch(()=>{
|
||||||
|
alert("Error in creating purchase request...")
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type:"CREATE_NEW_REQUEST",
|
||||||
|
payload:purchaseRequestData
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { INewFormState } from '../../state/INewFormControlsState';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { IPurchaseRequestWebpartProps } from '../PurchaseRequestWebpart/IPurchaseRequestWebpartProps';
|
||||||
|
import { GetInitialControlValuesAction, CreateNewPurchaseRequest } from '../../actions/NewFormControlsValuesAction';
|
||||||
|
import { Dropdown, IDropdown, DropdownMenuItemType, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
|
||||||
|
import { DefaultButton, IButtonProps } from 'office-ui-fabric-react/lib/Button';
|
||||||
|
import { Label } from 'office-ui-fabric-react/lib/Label';
|
||||||
|
import { Field, reduxForm, InjectedFormProps, FieldArray, WrappedFieldArrayProps, BaseFieldArrayProps } from 'redux-form';
|
||||||
|
import pnp from 'sp-pnp-js';
|
||||||
|
import { renderDropDown, renderInput } from '../Redux-Form-CustomComponents/FieldRenderers';
|
||||||
|
|
||||||
|
// Connected state
|
||||||
|
interface INewFormConnectedState{
|
||||||
|
|
||||||
|
// Represents a purchase request and the data from the form.
|
||||||
|
newFormControlValues : INewFormState;
|
||||||
|
|
||||||
|
// Represents the initial values. << Unused now. Useful for edit item feature >>
|
||||||
|
initialValues:any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Represents the connected dispatch
|
||||||
|
interface INewFormConnectedDispatch{
|
||||||
|
|
||||||
|
// Gets the options for dropdown fields
|
||||||
|
getDefaultControlsData:() => void;
|
||||||
|
|
||||||
|
createNewPurchaseRequest:(purchaseRequestData:INewFormState, siteUrl:string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validations for the redux form
|
||||||
|
const required = value => (value ? undefined : ' *');
|
||||||
|
const number = value =>
|
||||||
|
value && isNaN(Number(value)) ? ' Invalid value' : undefined;
|
||||||
|
|
||||||
|
|
||||||
|
// Represents the repeating purchase items component.
|
||||||
|
// Used in "NewRequestComponent" react component below along with "FieldsArray" from redux-form.
|
||||||
|
// Renders custom input component with validations
|
||||||
|
class PurchaseItemsComponent extends React.Component<WrappedFieldArrayProps<any>,{}>{
|
||||||
|
|
||||||
|
public render(){
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
<button type="button" onClick={() => this.props.fields.push({})}>Add purchase item</button>
|
||||||
|
{this.props.fields.map((purchaseItem, index) =>
|
||||||
|
<tr>
|
||||||
|
<div>
|
||||||
|
<h4>Item {index + 1}</h4>
|
||||||
|
<td>
|
||||||
|
<Field name={`${purchaseItem}.productCode`} type="text" component={renderInput} placeholder="Product code" validate={[required]}/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Field name={`${purchaseItem}.quantity`} type="text" component={renderInput} placeholder="Quantity" validate={[required,number]}/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Field name={`${purchaseItem}.ratePerUnit`} type="text" component={renderInput} placeholder="Price per unit" validate={[required,number]}/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Field name={`${purchaseItem}.totalCost`} type="text" component={renderInput} placeholder="Total Cost" validate={[required,number]}/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" title="Remove Item" onClick={() => this.props.fields.remove(index)}>Remove item</button>
|
||||||
|
</td>
|
||||||
|
</div>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class NewRequestComponent extends React.Component<INewFormConnectedState & INewFormConnectedDispatch & IPurchaseRequestWebpartProps & InjectedFormProps<{}, INewFormConnectedState>>{
|
||||||
|
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(){
|
||||||
|
|
||||||
|
return(
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{/* Sent the props as well to the SubmitForm handler to use the Connected Dispatch. Renders custom dropdown component with validation*/}
|
||||||
|
<form onSubmit={this.props.handleSubmit(((values)=>this.SubmitForm(values,this.props)))}>
|
||||||
|
<div>
|
||||||
|
<Field component={renderDropDown} label="Purchased for : " name="purchasedFor" validate={required}>
|
||||||
|
<option key='' value=''></option>
|
||||||
|
{this.props.newFormControlValues.purchasedForOptions.map(purchasedFor => {return <option key={purchasedFor} value={purchasedFor}>{purchasedFor}</option>})};
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div>
|
||||||
|
<Field component={renderDropDown} label="Type of purchase request : " name="typeOfPurchaseRequest" validate={required}>
|
||||||
|
<option key='' value=''></option>
|
||||||
|
{this.props.newFormControlValues.typeOfPurchaseRequestOptions.map(typeOfPr => {return <option key={typeOfPr} value={typeOfPr}>{typeOfPr}</option>})} ;
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<table>
|
||||||
|
<FieldArray name="purchaseItems" component={PurchaseItemsComponent}/>
|
||||||
|
</table>
|
||||||
|
<br/>
|
||||||
|
<button type="submit" disabled={this.props.submitting}>Create purchase request</button>
|
||||||
|
<br/>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the submit form.
|
||||||
|
SubmitForm(values, props){
|
||||||
|
|
||||||
|
let purchaseRequestData = {} as INewFormState;
|
||||||
|
purchaseRequestData = values;
|
||||||
|
purchaseRequestData.purchasedForOptions = props.newFormControlValues.purchasedForOptions;
|
||||||
|
purchaseRequestData.typeOfPurchaseRequestOptions = props.newFormControlValues.typeOfPurchaseRequestOptions;
|
||||||
|
|
||||||
|
// Call the connected dispatch to create new purchase request
|
||||||
|
props.createNewPurchaseRequest(purchaseRequestData,props.siteUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
componentDidMount(){
|
||||||
|
this.props.getDefaultControlsData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps the State to props
|
||||||
|
const mapStateToProps = (state) : INewFormConnectedState => {
|
||||||
|
|
||||||
|
// Includes the initialValues property to load the form with initial values
|
||||||
|
return{
|
||||||
|
newFormControlValues : state.NewFormControlValues,
|
||||||
|
initialValues : state.NewFormControlValues
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Maps dispatch to props
|
||||||
|
const mapDispatchToProps = (dispatch):INewFormConnectedDispatch => {
|
||||||
|
return{
|
||||||
|
getDefaultControlsData:() => {
|
||||||
|
return dispatch(GetInitialControlValuesAction());
|
||||||
|
},
|
||||||
|
createNewPurchaseRequest:(purchaseRequestData:INewFormState, siteUrl:string) => {
|
||||||
|
return dispatch(CreateNewPurchaseRequest(purchaseRequestData,siteUrl));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default connect(mapStateToProps,mapDispatchToProps)(
|
||||||
|
reduxForm<{},INewFormConnectedState>(
|
||||||
|
{
|
||||||
|
form:'NewPurchaseRequestForm',
|
||||||
|
destroyOnUnmount:false,
|
||||||
|
|
||||||
|
// Reinitializes when the state changes. << Unused at the moment. Useful in edit item feature >>
|
||||||
|
enableReinitialize:true
|
||||||
|
}
|
||||||
|
)(NewRequestComponent)
|
||||||
|
);
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { SPHttpClient } from '@microsoft/sp-http';
|
||||||
|
|
||||||
|
export interface IPurchaseRequestWebpartProps {
|
||||||
|
description: string;
|
||||||
|
siteUrl:string;
|
||||||
|
spHttpClient:SPHttpClient;
|
||||||
|
itemId:string;
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||||
|
|
||||||
|
.dynamicGridWebpart {
|
||||||
|
.container {
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 0px auto;
|
||||||
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
@include ms-Grid-row;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
background-color: $ms-color-themeDark;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
@include ms-Grid-col;
|
||||||
|
@include ms-lg10;
|
||||||
|
@include ms-xl8;
|
||||||
|
@include ms-xlPush2;
|
||||||
|
@include ms-lgPush1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@include ms-font-xl;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subTitle {
|
||||||
|
@include ms-font-l;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
@include ms-font-l;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
// Our button
|
||||||
|
text-decoration: none;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
// Primary Button
|
||||||
|
min-width: 80px;
|
||||||
|
background-color: $ms-color-themePrimary;
|
||||||
|
border-color: $ms-color-themePrimary;
|
||||||
|
color: $ms-color-white;
|
||||||
|
|
||||||
|
// Basic Button
|
||||||
|
outline: transparent;
|
||||||
|
position: relative;
|
||||||
|
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-size: $ms-font-size-m;
|
||||||
|
font-weight: $ms-font-weight-regular;
|
||||||
|
border-width: 0;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 16px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-weight: $ms-font-weight-semibold;
|
||||||
|
font-size: $ms-font-size-m;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
margin: 0 4px;
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './PurchaseRequestWebpart.module.scss';
|
||||||
|
import { IPurchaseRequestWebpartProps } from './IPurchaseRequestWebpartProps';
|
||||||
|
import { escape } from '@microsoft/sp-lodash-subset';
|
||||||
|
import ConfigureStore from "../../store/ConfigureStore";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import {INewFormState} from "../../state/INewFormControlsState";
|
||||||
|
import { Provider } from "react-redux";
|
||||||
|
import NewPurchaseRequestComponent from "../CreateNewRequest/CreateNewRequestComponent";
|
||||||
|
|
||||||
|
export default class PurchaseRequestWebpart extends React.Component<IPurchaseRequestWebpartProps, {}> {
|
||||||
|
|
||||||
|
public render(){
|
||||||
|
|
||||||
|
// Initialize the redux store
|
||||||
|
const purchaseRequertStore = ConfigureStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Provider store={purchaseRequertStore}>
|
||||||
|
<NewPurchaseRequestComponent {...this.props}/>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
// The file contains custom field render components used in the redux form in the purchase requestwebpart.
|
||||||
|
|
||||||
|
var requiredMessageStyle={
|
||||||
|
color:'Red'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Custom renderer for dropdown field with validation message
|
||||||
|
export class renderDropDown extends React.Component<any,{}>{
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<label>{this.props.label}</label>
|
||||||
|
<select {...this.props.input}>
|
||||||
|
{this.props.children}
|
||||||
|
</select>
|
||||||
|
<br/>
|
||||||
|
{this.props.meta.touched && this.props.meta.error && <span style={requiredMessageStyle}>{this.props.meta.error}</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom renderer for Input fields with validation message
|
||||||
|
export class renderInput extends React.Component<any,{}>{
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<label>{this.props.label}</label>
|
||||||
|
<input {...this.props.input} placeholder={this.props.placeholder}></input>
|
||||||
|
<br/>
|
||||||
|
{this.props.meta.touched && this.props.meta.error && <span style={requiredMessageStyle}>{this.props.meta.error}</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
define([], function() {
|
||||||
|
return {
|
||||||
|
"PropertyPaneDescription": "Description",
|
||||||
|
"BasicGroupName": "Group Name",
|
||||||
|
"DescriptionFieldLabel": "Description Field"
|
||||||
|
}
|
||||||
|
});
|
10
samples/react-reduxform/src/webparts/PurchaseRequestWebpart/loc/mystrings.d.ts
vendored
Normal file
10
samples/react-reduxform/src/webparts/PurchaseRequestWebpart/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
declare interface IDynamicGridWebpartWebPartStrings {
|
||||||
|
PropertyPaneDescription: string;
|
||||||
|
BasicGroupName: string;
|
||||||
|
DescriptionFieldLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'DynamicGridWebpartWebPartStrings' {
|
||||||
|
const strings: IDynamicGridWebpartWebPartStrings;
|
||||||
|
export = strings;
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { INewFormState } from '../state/INewFormControlsState';
|
||||||
|
import { GetInitialControlValuesAction } from '../actions/NewFormControlsValuesAction';
|
||||||
|
|
||||||
|
// Initial state of the purcahse request.
|
||||||
|
export const newFormControlsInitialState:INewFormState = {
|
||||||
|
purchasedFor:"",
|
||||||
|
typeOfPurchaseRequest:"",
|
||||||
|
purchasedForOptions:[],
|
||||||
|
typeOfPurchaseRequestOptions:[],
|
||||||
|
purchaseItems:[]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NewPurchaseRequestReducer = (state:INewFormState=newFormControlsInitialState,action) => {
|
||||||
|
switch(action.type){
|
||||||
|
|
||||||
|
// Gets the values for dropdown fields from SharePoint choice columns.
|
||||||
|
case "GET_DEFAULT_CONTROL_VALUES":
|
||||||
|
|
||||||
|
state={
|
||||||
|
...state,
|
||||||
|
purchasedForOptions : action.payload.purchasedForOptions,
|
||||||
|
typeOfPurchaseRequestOptions : action.payload.typeOfPurchaseRequestOptions,
|
||||||
|
};
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Creates a new purchase request.
|
||||||
|
case "CREATE_NEW_REQUEST":
|
||||||
|
|
||||||
|
state={
|
||||||
|
...state,
|
||||||
|
purchasedFor : action.payload.purchasedFor,
|
||||||
|
typeOfPurchaseRequest : action.payload.typeOfPurchaseRequest,
|
||||||
|
purchasedForOptions : action.payload.purchasedForOptions,
|
||||||
|
typeOfPurchaseRequestOptions : action.payload.typeOfPurchaseRequestOptions,
|
||||||
|
purchaseItems: action.payload.purchaseItems
|
||||||
|
};
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { INewFormState } from '../state/INewFormControlsState';
|
||||||
|
|
||||||
|
// Represents the service to interact with SharePoint to work with purchase request.
|
||||||
|
export default interface INewPurchaseRequestService{
|
||||||
|
getNewFormControlsState() : Promise<any>;
|
||||||
|
createNewPurchaseRequest(purchaseRequestData:INewFormState,siteUrl) : Promise<any>;
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
import INewPurchaseRequestService from "./INewPurchaseRequestService";
|
||||||
|
import pnp from "sp-pnp-js";
|
||||||
|
import { INewFormState } from "../state/INewFormControlsState";
|
||||||
|
import { ItemAddResult,Web } from "sp-pnp-js";
|
||||||
|
|
||||||
|
|
||||||
|
export default class NewPurchaseRequestService implements INewPurchaseRequestService{
|
||||||
|
|
||||||
|
private getPurchasedForControlValues():Promise<any>{
|
||||||
|
return pnp.sp.web.fields.getByTitle("PurchasedFor").select("Choices").get().then(response => {
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTypeOfPurchaseRequestValues():Promise<any>{
|
||||||
|
return pnp.sp.web.fields.getByTitle("TypeOfPR").select("Choices").get().then(response => {
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the choices to be displayed in the dropdown fields.
|
||||||
|
getNewFormControlsState():Promise<any>{
|
||||||
|
|
||||||
|
let newFormControlsState = {} as INewFormState;
|
||||||
|
|
||||||
|
return this.getPurchasedForControlValues().then(purchasedForValuesResponse => {
|
||||||
|
newFormControlsState.purchasedForOptions = purchasedForValuesResponse.Choices;
|
||||||
|
|
||||||
|
return this.getTypeOfPurchaseRequestValues().then(typeOfPurchaseRequestResponse => {
|
||||||
|
newFormControlsState.typeOfPurchaseRequestOptions = typeOfPurchaseRequestResponse.Choices;
|
||||||
|
|
||||||
|
return newFormControlsState;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new purchase request. The request is created in two list. One where the master data is stored and one
|
||||||
|
// where the purchase items are stored with a reference of the ID of master request.
|
||||||
|
async createNewPurchaseRequest(purchaseRequestData:INewFormState,siteUrl) : Promise<any>{
|
||||||
|
|
||||||
|
return pnp.sp.web.lists.getByTitle("PurchaseRequest").items.add({
|
||||||
|
PurchasedFor:purchaseRequestData.purchasedFor,
|
||||||
|
TypeOfPR:purchaseRequestData.typeOfPurchaseRequest})
|
||||||
|
|
||||||
|
.then((result:ItemAddResult)=>{
|
||||||
|
let purchaseRequestID = result.data.Id;
|
||||||
|
console.log("Purchase request created : " + purchaseRequestID);
|
||||||
|
if(purchaseRequestData.purchaseItems != null && purchaseRequestData.purchaseItems.length > 0){
|
||||||
|
|
||||||
|
// Creates the multiple purchase items in batch.
|
||||||
|
let web = new Web(siteUrl);
|
||||||
|
let batch = web.createBatch();
|
||||||
|
|
||||||
|
purchaseRequestData.purchaseItems.forEach(purchaseItem => {
|
||||||
|
web.lists.getByTitle("PurchaseRequestItems").items.inBatch(batch).add({
|
||||||
|
ProductCode:purchaseItem.productCode,
|
||||||
|
Quantity:purchaseItem.quantity,
|
||||||
|
RatePerUnit:purchaseItem.ratePerUnit,
|
||||||
|
TotalCost:purchaseItem.totalCost,
|
||||||
|
PurchaseRequestID:purchaseRequestID
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
batch.execute().then(()=>{
|
||||||
|
console.log("Purchase items added to the list....");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
alert('Select atleast one purchase item.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Represents a purchase request
|
||||||
|
export interface INewFormState{
|
||||||
|
|
||||||
|
// Represent the choices to be displayed in dropdown when the form loads.
|
||||||
|
purchasedForOptions:string[];
|
||||||
|
typeOfPurchaseRequestOptions:string[];
|
||||||
|
|
||||||
|
// Represent the values selected for the fields
|
||||||
|
purchasedFor:string;
|
||||||
|
typeOfPurchaseRequest:string;
|
||||||
|
purchaseItems:IPurchaseItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Represents one purchase item in the purchase request.
|
||||||
|
export interface IPurchaseItem{
|
||||||
|
productCode:string;
|
||||||
|
quantity:number;
|
||||||
|
ratePerUnit:number;
|
||||||
|
totalCost:number;
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { createStore,combineReducers, applyMiddleware } from "redux";
|
||||||
|
import { NewPurchaseRequestReducer } from "../reducers/NewPurchaseRequestReducer";
|
||||||
|
import thunk from "redux-thunk";
|
||||||
|
import { reducer as formReducer } from 'redux-form';
|
||||||
|
|
||||||
|
|
||||||
|
// Configures the redux store.
|
||||||
|
export default function ConfigureStore():any{
|
||||||
|
|
||||||
|
// Combine multiple reducers to create the store. FormReducer is for the redux-form.
|
||||||
|
const PurchaseRequestStore = createStore(
|
||||||
|
combineReducers
|
||||||
|
({
|
||||||
|
NewFormControlValues:NewPurchaseRequestReducer,
|
||||||
|
form:formReducer
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
applyMiddleware(thunk)
|
||||||
|
);
|
||||||
|
|
||||||
|
return PurchaseRequestStore;
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"jsx": "react",
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"typeRoots": [
|
||||||
|
"./node_modules/@types",
|
||||||
|
"./node_modules/@microsoft"
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
"es6-promise",
|
||||||
|
"webpack-env"
|
||||||
|
],
|
||||||
|
"lib": [
|
||||||
|
"es5",
|
||||||
|
"dom",
|
||||||
|
"es2015.collection"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue