Merge branch 'dev'
|
@ -11,7 +11,7 @@ dist
|
||||||
lib
|
lib
|
||||||
solution
|
solution
|
||||||
temp
|
temp
|
||||||
*.sppkg
|
#*.sppkg
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
# Coverage directory used by tools like istanbul
|
||||||
coverage
|
coverage
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
"framework": "react",
|
"framework": "react",
|
||||||
"plusBeta": true,
|
"plusBeta": true,
|
||||||
"isCreatingSolution": true,
|
"isCreatingSolution": true,
|
||||||
"version": "1.8.0",
|
"version": "1.8.2",
|
||||||
"libraryName": "react-calendar",
|
"libraryName": "react-calendar",
|
||||||
"libraryId": "3a13208b-3874-4036-9262-4edd22e88187",
|
"libraryId": "3a13208b-3874-4036-9262-4edd22e88187",
|
||||||
"packageManager": "npm",
|
"packageManager": "npm",
|
||||||
|
|
|
@ -5,13 +5,10 @@ This Web Part allows you to manage events in a calendar.
|
||||||
Uses a list of existing calendars on any website.
|
Uses a list of existing calendars on any website.
|
||||||
The location and name of the list and the dates of the events to be displayed are defined in the properties of the web part.
|
The location and name of the list and the dates of the events to be displayed are defined in the properties of the web part.
|
||||||
|
|
||||||
The Events are created in Site TimeZone, defined in site Regional Settings.
|
|
||||||
|
|
||||||
Each category has its own color that is generated in the load.
|
Each category has its own color that is generated in the load.
|
||||||
|
|
||||||
The Web Part checks the user's permissions for the View, Add, Edit, and Delete events.
|
The Web Part checks the user's permissions for the View, Add, Edit, and Delete events.
|
||||||
|
|
||||||
The Web Part does not show recurring events, I will work on it soon.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,11 +16,28 @@ The Web Part does not show recurring events, I will work on it soon.
|
||||||
![callendar](/samples/react-calendar/assets/animatevideo.gif)
|
![callendar](/samples/react-calendar/assets/animatevideo.gif)
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
![callendar](/samples/react-calendar/assets/weekly_moderncalendar.gif)
|
||||||
|
|
||||||
|
##
|
||||||
|
![callendar](/samples/react-calendar/assets/modercalendar_monthly.gif)
|
||||||
|
|
||||||
|
##
|
||||||
|
![callendar](/samples/react-calendar/assets/moderncalendar_yearly.gif)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Web Part - Screenshots
|
## Web Part - Screenshots
|
||||||
![callendar](/samples/react-calendar//assets/screen1.png)
|
|
||||||
|
![callendar](/samples/react-calendar/assets/calendar_teams.jpg)
|
||||||
|
|
||||||
|
![callendar](/samples/react-calendar/assets/calendar_teams2.jpg)
|
||||||
|
|
||||||
|
![callendar](/samples/react-calendar/assets/screen1.png)
|
||||||
|
|
||||||
|
|
||||||
![callendar](/samples/react-calendar/assets/screen1.0.jpg)
|
![callendar](/samples/react-calendar/assets/screen1.0.png)
|
||||||
|
|
||||||
|
|
||||||
![callendar](/samples/react-calendar/assets/screen1.1.png)
|
![callendar](/samples/react-calendar/assets/screen1.1.png)
|
||||||
|
@ -35,10 +49,10 @@ The Web Part does not show recurring events, I will work on it soon.
|
||||||
![callendar](/samples/react-calendar/assets/screen1.3.png)
|
![callendar](/samples/react-calendar/assets/screen1.3.png)
|
||||||
|
|
||||||
|
|
||||||
![callendar](/samples/react-calendar//assets/screen1.4.png)
|
![callendar](/samples/react-calendar/assets/screen1.4.png)
|
||||||
|
|
||||||
|
|
||||||
![callendar](/samples/react-calendar//assets/screen2.png)
|
![callendar](/samples/react-calendar/assets/screen2.png)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,47 +60,45 @@ The Web Part does not show recurring events, I will work on it soon.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
![callendar](/samples/react-calendar//assets/screen4.png)
|
![callendar](/samples/react-calendar/assets/screen4.png)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
![callendar](/samples/react-calendar/assets/screen5.png)
|
![callendar](/samples/react-calendar/assets/screen5.png)
|
||||||
|
|
||||||
|
|
||||||
![callendar](/samples/react-calendar//assets/screen6.png)
|
![callendar](/samples/react-calendar/assets/screen6.png)
|
||||||
|
|
||||||
|
|
||||||
![callendar](/samples/react-calendar//assets/screen7.png)
|
![callendar](/samples/react-calendar/assets/screen7.png)
|
||||||
|
|
||||||
|
|
||||||
![callendar](/samples/react-calendar/assets/screen8.png)
|
![callendar](/samples/react-calendar/assets/screen8.png)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
![callendar](/samples/react-calendar//assets/screen9.png)
|
![callendar](/samples/react-calendar/assets/screen9.png)
|
||||||
##
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Used SharePoint Framework Version
|
## Used SharePoint Framework Version
|
||||||
![drop](https://img.shields.io/badge/version-GA-green.svg)
|
![drop](https://img.shields.io/badge/version-1.8.2-green.svg)
|
||||||
|
|
||||||
## Applies to
|
## Applies to
|
||||||
|
|
||||||
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||||
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||||
|
|
||||||
> Update accordingly as needed.
|
|
||||||
|
|
||||||
## WebPart Properties
|
## WebPart Properties
|
||||||
|
|
||||||
Property |Type|Required| comments
|
Property |Type|Required| comments
|
||||||
--------------------|----|--------|----------
|
--------------------|----|--------|----------
|
||||||
Site Url of Calendar List | Text| yes|
|
Site Url of Calendar List | Text| yes|
|
||||||
Calendar list| Text| yes| this is filled with all list of type "event list" created
|
Calendar list| Choice/Dropdown | yes| this is filled with all list of type "event list" created
|
||||||
Start Date | Date | yes | Event Date
|
Start Date | Date | yes | Event Date
|
||||||
End Date| Date| yes | Event Date
|
End Date| Date| yes | Event Date
|
||||||
|
|
||||||
|
@ -103,6 +115,7 @@ Calendar Web Part|João Mendes
|
||||||
Version|Date|Comments
|
Version|Date|Comments
|
||||||
-------|----|--------
|
-------|----|--------
|
||||||
1.0.0|April 25, 2019|Initial release
|
1.0.0|April 25, 2019|Initial release
|
||||||
|
1.0.1|June 10, 2019|update add recurrence events
|
||||||
|
|
||||||
## Disclaimer
|
## 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.**
|
**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.**
|
||||||
|
@ -122,4 +135,4 @@ Version|Date|Comments
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template" />
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-calendar" />
|
||||||
|
|
After Width: | Height: | Size: 145 KiB |
After Width: | Height: | Size: 152 KiB |
After Width: | Height: | Size: 18 MiB |
After Width: | Height: | Size: 13 MiB |
After Width: | Height: | Size: 76 KiB |
After Width: | Height: | Size: 16 MiB |
|
@ -8,6 +8,7 @@
|
||||||
"entrypoint": "./lib/webparts/calendar/CalendarWebPart.js",
|
"entrypoint": "./lib/webparts/calendar/CalendarWebPart.js",
|
||||||
"manifest": "./src/webparts/calendar/CalendarWebPart.manifest.json"
|
"manifest": "./src/webparts/calendar/CalendarWebPart.manifest.json"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "react-calendar",
|
"name": "react-calendar",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
@ -14,12 +14,11 @@
|
||||||
"test:watch": "./node_modules/.bin/jest --config ./config/jest.config.json --watchAll"
|
"test:watch": "./node_modules/.bin/jest --config ./config/jest.config.json --watchAll"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@microsoft/rush-stack-compiler-3.2": "^0.3.6",
|
"@microsoft/sp-core-library": "1.8.2",
|
||||||
"@microsoft/sp-core-library": "1.8.0-plusbeta",
|
"@microsoft/sp-lodash-subset": "1.8.2",
|
||||||
"@microsoft/sp-lodash-subset": "1.8.0-plusbeta",
|
"@microsoft/sp-office-ui-fabric-core": "1.8.2",
|
||||||
"@microsoft/sp-office-ui-fabric-core": "1.8.0-plusbeta",
|
"@microsoft/sp-property-pane": "1.8.2",
|
||||||
"@microsoft/sp-property-pane": "1.8.0-plusbeta",
|
"@microsoft/sp-webpart-base": "1.8.2",
|
||||||
"@microsoft/sp-webpart-base": "1.8.0-plusbeta",
|
|
||||||
"@pnp/pnpjs": "^1.3.0",
|
"@pnp/pnpjs": "^1.3.0",
|
||||||
"@pnp/spfx-controls-react": "1.12.0",
|
"@pnp/spfx-controls-react": "1.12.0",
|
||||||
"@pnp/spfx-property-controls": "1.14.1",
|
"@pnp/spfx-property-controls": "1.14.1",
|
||||||
|
@ -29,32 +28,37 @@
|
||||||
"@types/jquery": "^3.3.29",
|
"@types/jquery": "^3.3.29",
|
||||||
"@types/react": "16.7.22",
|
"@types/react": "16.7.22",
|
||||||
"@types/react-big-calendar": "^0.20.13",
|
"@types/react-big-calendar": "^0.20.13",
|
||||||
"@types/react-dom": "16.0.5",
|
"@types/react-dom": "16.8.0",
|
||||||
"@types/webpack-env": "1.13.1",
|
"@types/webpack-env": "1.13.1",
|
||||||
|
"@uifabric/fluent-theme": "^0.16.7",
|
||||||
"draft-js": "^0.10.5",
|
"draft-js": "^0.10.5",
|
||||||
"draftjs-to-html": "^0.8.4",
|
"draftjs-to-html": "^0.8.4",
|
||||||
"globalize": "^1.4.2",
|
"globalize": "^1.4.2",
|
||||||
"immutable": "^4.0.0-rc.12",
|
"immutable": "^4.0.0-rc.12",
|
||||||
"jquery": "^3.3.1",
|
"jquery": "^3.3.1",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"moment-timezone": "^0.5.25",
|
"office-ui-fabric-react": "6.143.0",
|
||||||
"react": "16.7.0",
|
"react": "16.7.0",
|
||||||
"react-big-calendar": "^0.20.4",
|
"react-big-calendar": "^0.20.4",
|
||||||
"react-dom": "16.7.0",
|
"react-dom": "16.7.0",
|
||||||
"react-draft-wysiwyg": "^1.13.2",
|
"react-draft-wysiwyg": "^1.13.2",
|
||||||
"typescript": "^3.2.4"
|
"spfx-uifabric-themes": "^0.6.0",
|
||||||
|
"typescript": "^3.2.4",
|
||||||
|
"xml2js": "^0.4.19"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/react": "16.4.2"
|
"@types/react": "16.7.22"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@microsoft/rush-stack-compiler-2.7": "0.4.0",
|
"@microsoft/rush-stack-compiler-2.9": "0.7.7",
|
||||||
"@microsoft/sp-build-web": "1.8.0-plusbeta",
|
"@microsoft/rush-stack-compiler-3.2": "0.3.17",
|
||||||
"@microsoft/sp-module-interfaces": "1.8.0-plusbeta",
|
"@microsoft/sp-build-web": "1.8.2",
|
||||||
"@microsoft/sp-tslint-rules": "1.8.0-plusbeta",
|
"@microsoft/sp-module-interfaces": "1.8.2",
|
||||||
"@microsoft/sp-webpart-workbench": "1.8.0-plusbeta",
|
"@microsoft/sp-tslint-rules": "1.8.2",
|
||||||
|
"@microsoft/sp-webpart-workbench": "1.8.2",
|
||||||
"@types/chai": "3.4.34",
|
"@types/chai": "3.4.34",
|
||||||
"@types/mocha": "2.2.38",
|
"@types/mocha": "2.2.38",
|
||||||
|
"@types/xml2js": "^0.4.4",
|
||||||
"@voitanos/jest-preset-spfx-react16": "^1.1.0",
|
"@voitanos/jest-preset-spfx-react16": "^1.1.0",
|
||||||
"ajv": "~5.2.2",
|
"ajv": "~5.2.2",
|
||||||
"gulp": "~3.9.1",
|
"gulp": "~3.9.1",
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
.description:hover {
|
.description:hover {
|
||||||
border-color: rgb( 51, 51, 51 );
|
border-color: rgb( 51, 51, 51 );
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar {
|
.calendar {
|
||||||
.container {
|
.container {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
@ -23,13 +22,11 @@
|
||||||
height: 600px;
|
height: 600px;
|
||||||
margin: 0px auto;
|
margin: 0px auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eventTitle {
|
.eventTitle {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
@include ms-Grid-row;
|
@include ms-Grid-row;
|
||||||
@include ms-fontColor-white;
|
@include ms-fontColor-white;
|
||||||
|
@ -37,7 +34,6 @@
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column {
|
.column {
|
||||||
@include ms-Grid-col;
|
@include ms-Grid-col;
|
||||||
@include ms-lg10;
|
@include ms-lg10;
|
||||||
|
@ -45,22 +41,18 @@
|
||||||
@include ms-xlPush2;
|
@include ms-xlPush2;
|
||||||
@include ms-lgPush1;
|
@include ms-lgPush1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
@include ms-font-xl;
|
@include ms-font-xl;
|
||||||
@include ms-fontColor-white;
|
@include ms-fontColor-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subTitle {
|
.subTitle {
|
||||||
@include ms-font-l;
|
@include ms-font-l;
|
||||||
@include ms-fontColor-white;
|
@include ms-fontColor-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
@include ms-font-l;
|
@include ms-font-l;
|
||||||
@include ms-fontColor-white;
|
@include ms-fontColor-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
// Our button
|
// Our button
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
@ -84,6 +76,7 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
font-weight: $ms-font-weight-semibold;
|
font-weight: $ms-font-weight-semibold;
|
||||||
|
@ -95,4 +88,3 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -25,4 +25,8 @@ export interface IEventState {
|
||||||
userPermissions?: IUserPermissions;
|
userPermissions?: IUserPermissions;
|
||||||
isloading:boolean;
|
isloading:boolean;
|
||||||
siteRegionalSettings: any;
|
siteRegionalSettings: any;
|
||||||
|
recurrenceSeriesEdited?:boolean;
|
||||||
|
showRecurrenceSeriesInfo:boolean;
|
||||||
|
newRecurrenceEvent:boolean;
|
||||||
|
recurrenceAction:string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,12 +37,14 @@ import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogType,
|
DialogType,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
Toggle
|
Toggle,
|
||||||
|
ActionButton,
|
||||||
|
IButtonProps
|
||||||
|
|
||||||
}
|
}
|
||||||
from 'office-ui-fabric-react';
|
from 'office-ui-fabric-react';
|
||||||
import { addMonths, addYears } from 'office-ui-fabric-react/lib/utilities/dateMath/DateMath';
|
import { addMonths, addYears } from 'office-ui-fabric-react/lib/utilities/dateMath/DateMath';
|
||||||
import { _ComponentBaseKillSwitches } from '@microsoft/sp-component-base';
|
|
||||||
import { IPanelModelEnum } from './IPanelModeEnum';
|
import { IPanelModelEnum } from './IPanelModeEnum';
|
||||||
import { EditorState, convertToRaw, ContentState } from 'draft-js';
|
import { EditorState, convertToRaw, ContentState } from 'draft-js';
|
||||||
import { Editor } from 'react-draft-wysiwyg';
|
import { Editor } from 'react-draft-wysiwyg';
|
||||||
|
@ -51,11 +53,13 @@ import htmlToDraft from 'html-to-draftjs';
|
||||||
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
|
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
|
||||||
import spservices from '../../services/spservices';
|
import spservices from '../../services/spservices';
|
||||||
import { Map, ICoordinates, MapType } from "@pnp/spfx-controls-react/lib/Map";
|
import { Map, ICoordinates, MapType } from "@pnp/spfx-controls-react/lib/Map";
|
||||||
|
import { EventRecurrenceInfo } from '../../controls/EventRecurrenceInfo/EventRecurrenceInfo';
|
||||||
|
import { string } from 'prop-types';
|
||||||
|
import { getGUID } from '@pnp/common';
|
||||||
|
|
||||||
const today: Date = new Date(Date.now());
|
const today: Date = new Date(Date.now());
|
||||||
const DayPickerStrings: IDatePickerStrings = {
|
const DayPickerStrings: IDatePickerStrings = {
|
||||||
months: [strings.January, strings.February, strings.March, strings.April, strings.May, strings.June, strings.July, strings.August, strings.September, strings.October, strings.November, strings.Dezember],
|
months: [strings.January, strings.February, strings.March, strings.April, strings.May, strings.June, strings.July, strings.August, strings.September, strings.October, strings.November, strings.December],
|
||||||
shortMonths: [strings.Jan, strings.Feb, strings.Mar, strings.Apr, strings.May, strings.Jun, strings.Jul, strings.Aug, strings.Sep, strings.Oct, strings.Nov, strings.Dez],
|
shortMonths: [strings.Jan, strings.Feb, strings.Mar, strings.Apr, strings.May, strings.Jun, strings.Jul, strings.Aug, strings.Sep, strings.Oct, strings.Nov, strings.Dez],
|
||||||
days: [strings.Sunday, strings.Monday, strings.Tuesday, strings.Wednesday, strings.Thursday, strings.Friday, strings.Saturday],
|
days: [strings.Sunday, strings.Monday, strings.Tuesday, strings.Wednesday, strings.Thursday, strings.Friday, strings.Saturday],
|
||||||
shortDays: [strings.ShortDay_S, strings.ShortDay_M, strings.ShortDay_T, strings.ShortDay_W, strings.ShortDay_Tursday, strings.ShortDay_Friday, strings.ShortDay_Saunday],
|
shortDays: [strings.ShortDay_S, strings.ShortDay_M, strings.ShortDay_T, strings.ShortDay_W, strings.ShortDay_Tursday, strings.ShortDay_Friday, strings.ShortDay_Saunday],
|
||||||
|
@ -74,6 +78,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
private attendees: IPersonaProps[] = [];
|
private attendees: IPersonaProps[] = [];
|
||||||
private latitude: number = 41.1931819;
|
private latitude: number = 41.1931819;
|
||||||
private longitude: number = -8.4897452;
|
private longitude: number = -8.4897452;
|
||||||
|
private returnedRecurrenceInfo: { recurrenceData: string, eventDate: Date, endDate: Date } = undefined;
|
||||||
|
|
||||||
private categoryDropdownOption: IDropdownOption[] = [];
|
private categoryDropdownOption: IDropdownOption[] = [];
|
||||||
|
|
||||||
|
@ -93,7 +98,6 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
}
|
}
|
||||||
// Initialize Map coordinates
|
// Initialize Map coordinates
|
||||||
|
|
||||||
console.log('ini', this.latitude, this.longitude);
|
|
||||||
this.state = {
|
this.state = {
|
||||||
showPanel: false,
|
showPanel: false,
|
||||||
eventData: this.props.event,
|
eventData: this.props.event,
|
||||||
|
@ -112,6 +116,10 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
displayDialog: false,
|
displayDialog: false,
|
||||||
isloading: false,
|
isloading: false,
|
||||||
siteRegionalSettings: undefined,
|
siteRegionalSettings: undefined,
|
||||||
|
recurrenceSeriesEdited: false,
|
||||||
|
showRecurrenceSeriesInfo:false,
|
||||||
|
newRecurrenceEvent:false,
|
||||||
|
recurrenceAction: 'display',
|
||||||
userPermissions: { hasPermissionAdd: false, hasPermissionDelete: false, hasPermissionEdit: false, hasPermissionView: false },
|
userPermissions: { hasPermissionAdd: false, hasPermissionDelete: false, hasPermissionEdit: false, hasPermissionView: false },
|
||||||
};
|
};
|
||||||
// local copia of props
|
// local copia of props
|
||||||
|
@ -131,8 +139,9 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
this.onDelete = this.onDelete.bind(this);
|
this.onDelete = this.onDelete.bind(this);
|
||||||
this.closeDialog = this.closeDialog.bind(this);
|
this.closeDialog = this.closeDialog.bind(this);
|
||||||
this.confirmDelete = this.confirmDelete.bind(this);
|
this.confirmDelete = this.confirmDelete.bind(this);
|
||||||
this.onAllDayEventChange = this.onAllDayEventChange.bind(this);
|
|
||||||
this.onCategoryChanged = this.onCategoryChanged.bind(this);
|
this.onCategoryChanged = this.onCategoryChanged.bind(this);
|
||||||
|
this.onEditRecurrence = this.onEditRecurrence.bind(this);
|
||||||
|
this.returnRecurrenceInfo = this.returnRecurrenceInfo.bind(this);
|
||||||
this.spService = new spservices(this.props.context);
|
this.spService = new spservices(this.props.context);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -152,27 +161,58 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
private async onSave() {
|
private async onSave() {
|
||||||
|
|
||||||
let eventData: IEventData = this.state.eventData;
|
let eventData: IEventData = this.state.eventData;
|
||||||
// All Day event ?
|
let panelMode = this.props.panelMode;
|
||||||
|
|
||||||
|
let startDate: string = null;
|
||||||
|
let endDate: string = null;
|
||||||
|
eventData.fRecurrence = false;
|
||||||
|
// if there are new Event recurrence or Edited recurrence series
|
||||||
|
if (this.state.recurrenceSeriesEdited || this.state.newRecurrenceEvent) {
|
||||||
|
eventData.RecurrenceData = this.returnedRecurrenceInfo.recurrenceData;
|
||||||
|
startDate = `${moment(this.returnedRecurrenceInfo.eventDate).format('YYYY/MM/DD')}`;
|
||||||
|
endDate = `${moment(this.returnedRecurrenceInfo.endDate).format('YYYY/MM/DD')}`;
|
||||||
|
|
||||||
|
if (eventData.EventType == "0" && this.state.newRecurrenceEvent) {
|
||||||
|
eventData.EventType = "1";
|
||||||
|
eventData.fRecurrence= true;
|
||||||
|
eventData.UID = getGUID();
|
||||||
|
}
|
||||||
|
if (eventData.EventType == "1" && this.state.recurrenceSeriesEdited) {
|
||||||
|
eventData.fRecurrence= true;
|
||||||
|
eventData.UID = getGUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (this.state.eventData.EventType == '1'){ // recurrence exception
|
||||||
|
eventData.RecurrenceID = eventData.EventDate.toString();
|
||||||
|
eventData.MasterSeriesItemID = eventData.ID.toString();
|
||||||
|
eventData.EventType = "4";
|
||||||
|
eventData.fRecurrence = true;
|
||||||
|
eventData.UID = getGUID();
|
||||||
|
panelMode = IPanelModelEnum.add;
|
||||||
|
}
|
||||||
|
startDate = `${moment(this.state.startDate).format('YYYY/MM/DD')}`;
|
||||||
|
endDate = `${moment(this.state.endDate).format('YYYY/MM/DD')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const startDate = `${moment(this.state.startDate).format('YYYY/MM/DD')}`;
|
|
||||||
const startTime = `${this.state.startSelectedHour.key}:${this.state.startSelectedMin.key}`;
|
const startTime = `${this.state.startSelectedHour.key}:${this.state.startSelectedMin.key}`;
|
||||||
const startDateTime = `${startDate} ${startTime}`;
|
const startDateTime = `${startDate} ${startTime}`;
|
||||||
const start = moment(startDateTime, 'YYYY/MM/DD HH:mm').toLocaleString();
|
const start = moment(startDateTime, 'YYYY/MM/DD HH:mm').toLocaleString();
|
||||||
eventData.start = new Date(start);
|
eventData.EventDate = new Date(start);
|
||||||
|
|
||||||
// End Date
|
// End Date
|
||||||
const endDate = `${moment(this.state.endDate).format('YYYY/MM/DD')}`;
|
|
||||||
const endTime = `${this.state.endSelectedHour.key}:${this.state.endSelectedMin.key}`;
|
const endTime = `${this.state.endSelectedHour.key}:${this.state.endSelectedMin.key}`;
|
||||||
const endDateTime = `${endDate} ${endTime}`;
|
const endDateTime = `${endDate} ${endTime}`;
|
||||||
const end = moment(endDateTime, 'YYYY/MM/DD HH:mm').toLocaleString();
|
const end = moment(endDateTime, 'YYYY/MM/DD HH:mm').toLocaleString();
|
||||||
eventData.end = new Date(end);
|
eventData.EndDate = new Date(end);
|
||||||
|
|
||||||
|
|
||||||
// get Geolocation
|
// get Geolocation
|
||||||
|
|
||||||
eventData.geolocation = { Latitude: this.latitude, Longitude: this.longitude };
|
eventData.geolocation = { Latitude: this.latitude, Longitude: this.longitude };
|
||||||
const locationInfo = await this.spService.getGeoLactionName(this.latitude, this.longitude);
|
const locationInfo = await this.spService.getGeoLactionName(this.latitude, this.longitude);
|
||||||
eventData.location = locationInfo ? locationInfo.display_name : 'N/A';
|
eventData.location = locationInfo ? locationInfo.display_name : 'N/A';
|
||||||
console.log('beforeupd',eventData.geolocation);
|
|
||||||
// get Attendees
|
// get Attendees
|
||||||
if (!eventData.attendes) { //vinitialize if no attendees
|
if (!eventData.attendes) { //vinitialize if no attendees
|
||||||
eventData.attendes = [];
|
eventData.attendes = [];
|
||||||
|
@ -185,12 +225,12 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
for (const user of this.attendees) {
|
for (const user of this.attendees) {
|
||||||
|
|
||||||
const userInfo: any = await this.spService.getUserByLoginName(user.id, this.props.siteUrl);
|
const userInfo: any = await this.spService.getUserByLoginName(user.id, this.props.siteUrl);
|
||||||
eventData.attendes.push(parseInt(userInfo.Id));
|
eventData.attendes.push(Number(userInfo.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ isSaving: true });
|
this.setState({ isSaving: true });
|
||||||
|
|
||||||
switch (this.props.panelMode) {
|
switch (panelMode) {
|
||||||
case IPanelModelEnum.edit:
|
case IPanelModelEnum.edit:
|
||||||
await this.spService.updateEvent(eventData, this.props.siteUrl, this.props.listId);
|
await this.spService.updateEvent(eventData, this.props.siteUrl, this.props.listId);
|
||||||
break;
|
break;
|
||||||
|
@ -215,15 +255,21 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
* @memberof Event
|
* @memberof Event
|
||||||
*/
|
*/
|
||||||
public componentDidCatch(error: any, errorInfo: any) {
|
public componentDidCatch(error: any, errorInfo: any) {
|
||||||
this.setState({ hasError: true, errorMessage: errorInfo.componentStack });
|
this.setState({ hasError: true, errorMessage: errorInfo.message });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
|
* @private
|
||||||
|
* @param {number} [eventId]
|
||||||
* @memberof Event
|
* @memberof Event
|
||||||
*/
|
*/
|
||||||
public async componentDidMount() {
|
private async renderEventData(eventId?: number) {
|
||||||
|
|
||||||
this.setState({ isloading: true });
|
this.setState({ isloading: true });
|
||||||
|
const event: IEventData = !eventId ? this.props.event : await this.spService.getEvent(this.props.siteUrl, this.props.listId, eventId);
|
||||||
|
|
||||||
let editorState: EditorState;
|
let editorState: EditorState;
|
||||||
// Load Regional Settings
|
// Load Regional Settings
|
||||||
const siteRegionalSettigns = await this.spService.getSiteRegionalSettingsTimeZone(this.props.siteUrl);
|
const siteRegionalSettigns = await this.spService.getSiteRegionalSettingsTimeZone(this.props.siteUrl);
|
||||||
|
@ -232,16 +278,16 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
// Load Categories
|
// Load Categories
|
||||||
this.categoryDropdownOption = await this.spService.getChoiceFieldOptions(this.props.siteUrl, this.props.listId, 'Category');
|
this.categoryDropdownOption = await this.spService.getChoiceFieldOptions(this.props.siteUrl, this.props.listId, 'Category');
|
||||||
// Edit Mode ?
|
// Edit Mode ?
|
||||||
if (this.props.panelMode == IPanelModelEnum.edit && this.props.event) {
|
if (this.props.panelMode == IPanelModelEnum.edit && event) {
|
||||||
|
|
||||||
// Get hours of event
|
// Get hours of event
|
||||||
const startHour = moment(this.props.event.start).format('HH').toString();
|
const startHour = moment(event.EventDate).format('HH').toString();
|
||||||
const startMin = moment(this.props.event.start).format('mm').toString();
|
const startMin = moment(event.EventDate).format('mm').toString();
|
||||||
const endHour = moment(this.props.event.end).format('HH').toString();
|
const endHour = moment(event.EndDate).format('HH').toString();
|
||||||
const endMin = moment(this.props.event.end).format('mm').toString();
|
const endMin = moment(event.EndDate).format('mm').toString();
|
||||||
|
|
||||||
// Get Descript and covert to RichText Control
|
// Get Descript and covert to RichText Control
|
||||||
const html = this.props.event.Description;
|
const html = event.Description;
|
||||||
const contentBlock = htmlToDraft(html);
|
const contentBlock = htmlToDraft(html);
|
||||||
|
|
||||||
if (contentBlock) {
|
if (contentBlock) {
|
||||||
|
@ -250,7 +296,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// testa attendees
|
// testa attendees
|
||||||
const attendees = this.props.event.attendes;
|
const attendees = event.attendes;
|
||||||
let selectedUsers: string[] = [];
|
let selectedUsers: string[] = [];
|
||||||
if (attendees && attendees.length > 0) {
|
if (attendees && attendees.length > 0) {
|
||||||
for (const userId of attendees) {
|
for (const userId of attendees) {
|
||||||
|
@ -261,14 +307,16 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Has geolocation ?
|
// Has geolocation ?
|
||||||
this.latitude = this.props.event.geolocation && this.props.event.geolocation.Latitude ? this.props.event.geolocation.Latitude : this.latitude;
|
this.latitude = event.geolocation && event.geolocation.Latitude ? event.geolocation.Latitude : this.latitude;
|
||||||
this.longitude = this.props.event.geolocation && this.props.event.geolocation.Longitude ? this.props.event.geolocation.Longitude : this.longitude;
|
this.longitude = event.geolocation && event.geolocation.Longitude ? event.geolocation.Longitude : this.longitude;
|
||||||
|
|
||||||
|
event.geolocation.Latitude = this.latitude;
|
||||||
|
event.geolocation.Longitude = this.longitude;
|
||||||
// Update Component Data
|
// Update Component Data
|
||||||
this.setState({
|
this.setState({
|
||||||
eventData: this.props.event,
|
eventData: event,
|
||||||
startDate: this.props.event.start,
|
startDate: event.EventDate,
|
||||||
endDate: this.props.event.end,
|
endDate: event.EndDate,
|
||||||
startSelectedHour: { key: startHour, text: startHour },
|
startSelectedHour: { key: startHour, text: startHour },
|
||||||
startSelectedMin: { key: startMin, text: startMin },
|
startSelectedMin: { key: startMin, text: startMin },
|
||||||
endSelectedHour: { key: endHour, text: endHour },
|
endSelectedHour: { key: endHour, text: endHour },
|
||||||
|
@ -290,24 +338,30 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
userPermissions: userListPermissions,
|
userPermissions: userListPermissions,
|
||||||
isloading: false,
|
isloading: false,
|
||||||
siteRegionalSettings: siteRegionalSettigns,
|
siteRegionalSettings: siteRegionalSettigns,
|
||||||
|
eventData: { ...event, EventType: "0" },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
*
|
||||||
*
|
*
|
||||||
* @memberof Event
|
* @memberof Event
|
||||||
*/
|
*/
|
||||||
public componentWillMount() {
|
public async componentDidMount() {
|
||||||
|
|
||||||
|
|
||||||
|
await this.renderEventData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @memberof Event
|
* @memberof Event
|
||||||
*/
|
*/
|
||||||
private onStartChangeHour = (ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
|
private onStartChangeHour = (ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
|
||||||
ev.preventDefault();
|
|
||||||
this.setState({ startSelectedHour: item });
|
this.setState({ startSelectedHour: item });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,7 +370,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
* @memberof Event
|
* @memberof Event
|
||||||
*/
|
*/
|
||||||
private onEndChangeHour = (ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
|
private onEndChangeHour = (ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
|
||||||
ev.preventDefault();
|
|
||||||
this.setState({ endSelectedHour: item });
|
this.setState({ endSelectedHour: item });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,7 +379,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
* @memberof Event
|
* @memberof Event
|
||||||
*/
|
*/
|
||||||
private onStartChangeMin = (ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
|
private onStartChangeMin = (ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
|
||||||
ev.preventDefault();
|
|
||||||
this.setState({ startSelectedMin: item });
|
this.setState({ startSelectedMin: item });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,12 +428,11 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
* @memberof Event
|
* @memberof Event
|
||||||
*/
|
*/
|
||||||
private onEndChangeMin(ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void {
|
private onEndChangeMin(ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void {
|
||||||
ev.preventDefault();
|
|
||||||
this.setState({ endSelectedMin: item });
|
this.setState({ endSelectedMin: item });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {React.FormEvent<HTMLDivElement>} ev
|
* @param {React.FormEvent<HTMLDivElement>} ev
|
||||||
|
@ -387,7 +440,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
* @memberof Event
|
* @memberof Event
|
||||||
*/
|
*/
|
||||||
private onCategoryChanged(ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void {
|
private onCategoryChanged(ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void {
|
||||||
ev.preventDefault();
|
|
||||||
this.setState({ eventData: { ...this.state.eventData, Category: item.text } });
|
this.setState({ eventData: { ...this.state.eventData, Category: item.text } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,6 +466,13 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
this.setState({ displayDialog: false });
|
this.setState({ displayDialog: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.MouseEvent<HTMLDivElement>} ev
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
private async confirmDelete(ev: React.MouseEvent<HTMLDivElement>) {
|
private async confirmDelete(ev: React.MouseEvent<HTMLDivElement>) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
try {
|
try {
|
||||||
|
@ -420,7 +480,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
|
|
||||||
switch (this.props.panelMode) {
|
switch (this.props.panelMode) {
|
||||||
case IPanelModelEnum.edit:
|
case IPanelModelEnum.edit:
|
||||||
await this.spService.deleteEvent(this.state.eventData, this.props.siteUrl, this.props.listId);
|
await this.spService.deleteEvent(this.state.eventData, this.props.siteUrl, this.props.listId, this.state.recurrenceSeriesEdited);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -428,7 +488,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
this.setState({ isDeleting: false });
|
this.setState({ isDeleting: false });
|
||||||
this.props.onDissmissPanel(true);
|
this.props.onDissmissPanel(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.setState({ hasError: true, errorMessage: error.message, isDeleting: false });
|
this.setState({ hasError: true, errorMessage: error.message, isDeleting: false, displayDialog:false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,14 +505,20 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
</DefaultButton>
|
</DefaultButton>
|
||||||
{
|
{
|
||||||
this.props.panelMode == IPanelModelEnum.edit && this.state.userPermissions.hasPermissionDelete && (
|
this.props.panelMode == IPanelModelEnum.edit && this.state.userPermissions.hasPermissionDelete && (
|
||||||
<DefaultButton onClick={this.onDelete} style={{ marginBottom: '15px', marginRight: '8px', float: 'right' }}>
|
<DefaultButton
|
||||||
|
|
||||||
|
onClick={this.onDelete}
|
||||||
|
style={{ marginBottom: '15px', marginRight: '8px', float: 'right' }}>
|
||||||
{strings.DeleteButtonLabel}
|
{strings.DeleteButtonLabel}
|
||||||
</DefaultButton>
|
</DefaultButton>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
(this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit) &&
|
(this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit) &&
|
||||||
<PrimaryButton disabled={this.state.disableButton} onClick={this.onSave} style={{ marginBottom: '15px', marginRight: '8px', float: 'right' }}>
|
<PrimaryButton
|
||||||
|
disabled={this.state.disableButton}
|
||||||
|
onClick={this.onSave}
|
||||||
|
style={{ marginBottom: '15px', marginRight: '8px', float: 'right' }}>
|
||||||
{strings.SaveButtonLabel}
|
{strings.SaveButtonLabel}
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
|
|
||||||
|
@ -485,10 +551,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private onAllDayEventChange(ev: React.MouseEvent<HTMLElement>, checked: boolean) {
|
|
||||||
ev.preventDefault();
|
|
||||||
this.setState({ eventData: { ...this.state.eventData, allDayEvent: checked } });
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
|
@ -498,13 +561,45 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
private async onUpdateCoordinates(coordinates: ICoordinates) {
|
private async onUpdateCoordinates(coordinates: ICoordinates) {
|
||||||
this.latitude = coordinates.latitude;
|
this.latitude = coordinates.latitude;
|
||||||
this.longitude = coordinates.longitude;
|
this.longitude = coordinates.longitude;
|
||||||
console.log('upcoor',this.latitude + ' ' + this.longitude);
|
|
||||||
const locationInfo = await this.spService.getGeoLactionName(this.latitude, this.longitude);
|
const locationInfo = await this.spService.getGeoLactionName(this.latitude, this.longitude);
|
||||||
this.setState({ eventData: { ...this.state.eventData, location: locationInfo.display_name } });
|
this.setState({ eventData: { ...this.state.eventData, location: locationInfo.display_name } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.MouseEvent<HTMLButtonElement>} ev
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
private async onEditRecurrence(ev: React.MouseEvent<HTMLButtonElement>) {
|
||||||
|
ev.preventDefault();
|
||||||
|
// EventType = 4 Recurrence Exception
|
||||||
|
await this.renderEventData(this.state.eventData.EventType == '4' ? Number(this.state.eventData.MasterSeriesItemID) : this.state.eventData.Id);
|
||||||
|
this.setState({ showRecurrenceSeriesInfo: true, recurrenceSeriesEdited: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {Date} startDate
|
||||||
|
* @param {string} recurrenceData
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
|
public async returnRecurrenceInfo(startDate: Date, recurrenceData: string) {
|
||||||
|
this.returnedRecurrenceInfo = { recurrenceData: recurrenceData, eventDate: startDate, endDate: moment().add(20, 'years').toDate() };
|
||||||
|
//this.setState({ editRecurrenceSeries:false})
|
||||||
|
//console.log(this.returnedRecurrenceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @returns {React.ReactElement<IEventProps>}
|
||||||
|
* @memberof Event
|
||||||
|
*/
|
||||||
public render(): React.ReactElement<IEventProps> {
|
public render(): React.ReactElement<IEventProps> {
|
||||||
console.log(this.state.locationLatitude + '-' + this.state.locationLongitude);
|
|
||||||
const { editorState } = this.state;
|
const { editorState } = this.state;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -531,7 +626,23 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
{
|
{
|
||||||
!this.state.isloading &&
|
!this.state.isloading &&
|
||||||
<div>
|
<div>
|
||||||
|
{
|
||||||
|
(this.state.eventData && (this.state.eventData.EventType !== "0" && this.state.showRecurrenceSeriesInfo !== true)) ?
|
||||||
<div>
|
<div>
|
||||||
|
<h2 style={{ display: 'inline-block', verticalAlign: 'top' }}>Recurrence Event</h2>
|
||||||
|
<DefaultButton
|
||||||
|
style={{ display: 'inline-block', marginLeft: '330px', verticalAlign: 'top', width: 'auto' }}
|
||||||
|
iconProps={{ iconName: 'RecurringEvent' }}
|
||||||
|
allowDisabledFocus={true}
|
||||||
|
onClick={this.onEditRecurrence}
|
||||||
|
>
|
||||||
|
Edit Recurrence Series
|
||||||
|
</DefaultButton>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
<div style={{ marginTop: 10 }} >
|
||||||
<TextField
|
<TextField
|
||||||
label={strings.EventTitleLabel}
|
label={strings.EventTitleLabel}
|
||||||
value={this.state.eventData ? this.state.eventData.title : ''}
|
value={this.state.eventData ? this.state.eventData.title : ''}
|
||||||
|
@ -562,6 +673,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
label={strings.StartDateLabel}
|
label={strings.StartDateLabel}
|
||||||
onSelectDate={this.onSelectDateStart}
|
onSelectDate={this.onSelectDateStart}
|
||||||
disabled={this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit ? false : true}
|
disabled={this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit ? false : true}
|
||||||
|
hidden={this.state.showRecurrenceSeriesInfo}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingRight: 10 }}>
|
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingRight: 10 }}>
|
||||||
|
@ -632,6 +744,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
label={strings.EndDateLabel}
|
label={strings.EndDateLabel}
|
||||||
onSelectDate={this.onSelectDateEnd}
|
onSelectDate={this.onSelectDateEnd}
|
||||||
disabled={this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit ? false : true}
|
disabled={this.state.userPermissions.hasPermissionAdd || this.state.userPermissions.hasPermissionEdit ? false : true}
|
||||||
|
hidden={this.state.showRecurrenceSeriesInfo}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingRight: 10 }}>
|
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingRight: 10 }}>
|
||||||
|
@ -693,6 +806,41 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
</div>
|
</div>
|
||||||
<Label>{this.state.siteRegionalSettings ? this.state.siteRegionalSettings.Description : ''}</Label>
|
<Label>{this.state.siteRegionalSettings ? this.state.siteRegionalSettings.Description : ''}</Label>
|
||||||
<br />
|
<br />
|
||||||
|
{
|
||||||
|
|
||||||
|
this.state.eventData && (this.state.eventData.EventType == "0") ?
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', width: '200px' }}>
|
||||||
|
<Toggle
|
||||||
|
defaultChecked={false}
|
||||||
|
inlineLabel={true}
|
||||||
|
label="Recurrence ?"
|
||||||
|
onText="On"
|
||||||
|
offText="Off"
|
||||||
|
onChange={(ev, checked: boolean) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.setState({ showRecurrenceSeriesInfo: checked, newRecurrenceEvent: checked });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
''
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
this.state.showRecurrenceSeriesInfo && (
|
||||||
|
<EventRecurrenceInfo
|
||||||
|
context={this.props.context}
|
||||||
|
display={true}
|
||||||
|
recurrenceData={this.state.eventData.RecurrenceData}
|
||||||
|
startDate={this.state.startDate}
|
||||||
|
siteUrl={this.props.siteUrl}
|
||||||
|
returnRecurrenceData={this.returnRecurrenceInfo}
|
||||||
|
>
|
||||||
|
|
||||||
|
</EventRecurrenceInfo>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
< Label > Event Description</Label>
|
< Label > Event Description</Label>
|
||||||
|
|
||||||
<div className={styles.description}>
|
<div className={styles.description}>
|
||||||
|
@ -760,7 +908,9 @@ export class Event extends React.Component<IEventProps, IEventState> {
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</Panel>
|
</Panel>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
.divWrraper {
|
||||||
|
border-width:1px;
|
||||||
|
border-color:#adadad;
|
||||||
|
padding: 20px;
|
||||||
|
border-style: solid;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './EventRecurrenceInfo.module.scss';
|
||||||
|
import * as strings from 'CalendarWebPartStrings';
|
||||||
|
import { IEventRecurrenceInfoProps } from './IEventRecurrenceInfoProps';
|
||||||
|
import { IEventRecurrenceInfoState } from './IEventRecurrenceInfoState';
|
||||||
|
import { escape } from '@microsoft/sp-lodash-subset';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { parseString, Builder } from "xml2js";
|
||||||
|
import {
|
||||||
|
ChoiceGroup,
|
||||||
|
IChoiceGroupOption,
|
||||||
|
|
||||||
|
} from 'office-ui-fabric-react';
|
||||||
|
|
||||||
|
|
||||||
|
import { EventRecurrenceInfoDaily } from './../EventRecurrenceInfoDaily/EventRecurrenceInfoDaily';
|
||||||
|
import { EventRecurrenceInfoWeekly } from './../EventRecurrenceInfoWeekly/EventRecurrenceInfoWeekly';
|
||||||
|
import { EventRecurrenceInfoMonthly } from './../EventRecurrenceInfoMonthly/EventRecurrenceInfoMonthly';
|
||||||
|
import { EventRecurrenceInfoYearly } from './../EventRecurrenceInfoYearly/EventRecurrenceInfoYearly';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class EventRecurrenceInfo extends React.Component<IEventRecurrenceInfoProps, IEventRecurrenceInfoState> {
|
||||||
|
|
||||||
|
public constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this._onRecurrenceFrequenceChange = this._onRecurrenceFrequenceChange.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
selectedKey: 'daily',
|
||||||
|
selectPatern: 'every',
|
||||||
|
startDate: moment().toDate(),
|
||||||
|
endDate: moment().endOf('month').toDate(),
|
||||||
|
numberOcurrences: '1',
|
||||||
|
numberOfDays: '1',
|
||||||
|
disableNumberOfDays: false,
|
||||||
|
disableNumberOcurrences: true,
|
||||||
|
selectdateRangeOption: 'noDate',
|
||||||
|
disableEndDate: true,
|
||||||
|
selectedRecurrenceRule: 'daily',
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private _onRecurrenceFrequenceChange(ev: React.SyntheticEvent<HTMLElement>, option: IChoiceGroupOption): void {
|
||||||
|
this.setState({
|
||||||
|
selectedRecurrenceRule: option.key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @memberof EventRecurrenceInfo
|
||||||
|
*/
|
||||||
|
public async componentDidMount() {
|
||||||
|
if (this.props.recurrenceData) {
|
||||||
|
if (this.props.recurrenceData.indexOf('<daily') != -1) {
|
||||||
|
this.setState({ selectedRecurrenceRule: 'daily' });
|
||||||
|
}
|
||||||
|
if (this.props.recurrenceData.indexOf('<weekly') != -1) {
|
||||||
|
this.setState({ selectedRecurrenceRule: 'weekly' });
|
||||||
|
}
|
||||||
|
if (this.props.recurrenceData.indexOf('<monthly') != -1) {
|
||||||
|
this.setState({ selectedRecurrenceRule: 'monthly' });
|
||||||
|
}
|
||||||
|
if (this.props.recurrenceData.indexOf('<monthlyByDay') != -1) {
|
||||||
|
this.setState({ selectedRecurrenceRule: 'monthly' });
|
||||||
|
}
|
||||||
|
if (this.props.recurrenceData.indexOf('<yearly') != -1) {
|
||||||
|
this.setState({ selectedRecurrenceRule: 'yearly' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @returns {React.ReactElement<IEventRecurrenceInfoProps>}
|
||||||
|
* @memberof EventRecurrenceInfo
|
||||||
|
*/
|
||||||
|
public render(): React.ReactElement<IEventRecurrenceInfoProps> {
|
||||||
|
return (
|
||||||
|
<div className={styles.divWrraper} >
|
||||||
|
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top' }}>
|
||||||
|
<ChoiceGroup
|
||||||
|
label="Recurrence Information"
|
||||||
|
selectedKey={this.state.selectedRecurrenceRule}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
key: 'daily',
|
||||||
|
iconProps: { iconName: 'CalendarDay' },
|
||||||
|
text: 'Daily'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'weekly',
|
||||||
|
iconProps: { iconName: 'CalendarWeek' },
|
||||||
|
text: 'Weekly'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'monthly',
|
||||||
|
iconProps: { iconName: 'Calendar' },
|
||||||
|
text: 'Monthly',
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'yearly',
|
||||||
|
iconProps: { iconName: 'Calendar' },
|
||||||
|
text: 'Yearly',
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
onChange={this._onRecurrenceFrequenceChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
this.state.selectedRecurrenceRule === 'daily' && (
|
||||||
|
<EventRecurrenceInfoDaily
|
||||||
|
display={true}
|
||||||
|
recurrenceData={this.props.recurrenceData}
|
||||||
|
startDate={this.props.startDate}
|
||||||
|
context={this.props.context}
|
||||||
|
siteUrl={this.props.siteUrl}
|
||||||
|
returnRecurrenceData={this.props.returnRecurrenceData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.selectedRecurrenceRule === 'weekly' && (
|
||||||
|
<EventRecurrenceInfoWeekly
|
||||||
|
display={true}
|
||||||
|
recurrenceData={this.props.recurrenceData}
|
||||||
|
startDate={this.props.startDate}
|
||||||
|
context={this.props.context}
|
||||||
|
siteUrl={this.props.siteUrl}
|
||||||
|
returnRecurrenceData={this.props.returnRecurrenceData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.selectedRecurrenceRule === 'monthly' && (
|
||||||
|
<EventRecurrenceInfoMonthly
|
||||||
|
display={true}
|
||||||
|
recurrenceData={this.props.recurrenceData}
|
||||||
|
startDate={this.props.startDate}
|
||||||
|
context={this.props.context}
|
||||||
|
siteUrl={this.props.siteUrl}
|
||||||
|
returnRecurrenceData={this.props.returnRecurrenceData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.selectedRecurrenceRule === 'yearly' && (
|
||||||
|
<EventRecurrenceInfoYearly
|
||||||
|
display={true}
|
||||||
|
recurrenceData={this.props.recurrenceData}
|
||||||
|
startDate={this.props.startDate}
|
||||||
|
context={this.props.context}
|
||||||
|
siteUrl={this.props.siteUrl}
|
||||||
|
returnRecurrenceData={this.props.returnRecurrenceData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||||
|
export interface IEventRecurrenceInfoProps {
|
||||||
|
display:boolean;
|
||||||
|
recurrenceData: string;
|
||||||
|
startDate:Date;
|
||||||
|
context:WebPartContext;
|
||||||
|
siteUrl:string;
|
||||||
|
returnRecurrenceData: (startDate:Date,recurrenceData:string) => void;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
export interface IEventRecurrenceInfoState {
|
||||||
|
selectedKey:string;
|
||||||
|
selectPatern:string;
|
||||||
|
startDate: Date;
|
||||||
|
endDate:Date;
|
||||||
|
numberOcurrences:string;
|
||||||
|
numberOfDays:string;
|
||||||
|
disableNumberOfDays: boolean;
|
||||||
|
disableNumberOcurrences: boolean;
|
||||||
|
selectdateRangeOption:string;
|
||||||
|
disableEndDate:boolean;
|
||||||
|
selectedRecurrenceRule:string;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
.divWrraper {
|
||||||
|
border-width:1px;
|
||||||
|
border-color:#adadad;
|
||||||
|
padding: 20px;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
|
@ -0,0 +1,408 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './EventRecurrenceInfoDaily.module.scss';
|
||||||
|
import * as strings from 'CalendarWebPartStrings';
|
||||||
|
import { IEventRecurrenceInfoDailyProps } from './IEventRecurrenceInfoDailyProps';
|
||||||
|
import { IEventRecurrenceInfoDailyState } from './IEventRecurrenceInfoDailyState';
|
||||||
|
import { escape } from '@microsoft/sp-lodash-subset';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { parseString, Builder } from "xml2js";
|
||||||
|
import {
|
||||||
|
ChoiceGroup,
|
||||||
|
IChoiceGroupOption,
|
||||||
|
Label,
|
||||||
|
MaskedTextField,
|
||||||
|
} from 'office-ui-fabric-react';
|
||||||
|
import { DatePicker, DayOfWeek, IDatePickerStrings } from 'office-ui-fabric-react/lib/DatePicker';
|
||||||
|
|
||||||
|
import spservices from '../../services/spservices';
|
||||||
|
|
||||||
|
const DayPickerStrings: IDatePickerStrings = {
|
||||||
|
months: [strings.January, strings.February, strings.March, strings.April, strings.May, strings.June, strings.July, strings.August, strings.September, strings.October, strings.November, strings.December],
|
||||||
|
shortMonths: [strings.Jan, strings.Feb, strings.Mar, strings.Apr, strings.May, strings.Jun, strings.Jul, strings.Aug, strings.Sep, strings.Oct, strings.Nov, strings.Dez],
|
||||||
|
days: [strings.Sunday, strings.Monday, strings.Tuesday, strings.Wednesday, strings.Thursday, strings.Friday, strings.Saturday],
|
||||||
|
shortDays: [strings.ShortDay_S, strings.ShortDay_M, strings.ShortDay_T, strings.ShortDay_W, strings.ShortDay_Tursday, strings.ShortDay_Friday, strings.ShortDay_Saunday],
|
||||||
|
goToToday: strings.GoToDay,
|
||||||
|
prevMonthAriaLabel: strings.PrevMonth,
|
||||||
|
nextMonthAriaLabel: strings.NextMonth,
|
||||||
|
prevYearAriaLabel: strings.PrevYear,
|
||||||
|
nextYearAriaLabel: strings.NextYear,
|
||||||
|
closeButtonAriaLabel: strings.CloseDate,
|
||||||
|
isRequiredErrorMessage: strings.IsRequired,
|
||||||
|
invalidInputErrorMessage: strings.InvalidDateFormat,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class EventRecurrenceInfoDaily
|
||||||
|
* @extends {React.Component<IEventRecurrenceInfoDailyProps, IEventRecurrenceInfoDailyState>}
|
||||||
|
*/
|
||||||
|
export class EventRecurrenceInfoDaily extends React.Component<IEventRecurrenceInfoDailyProps, IEventRecurrenceInfoDailyState> {
|
||||||
|
private spService: spservices = null;
|
||||||
|
public constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
|
||||||
|
this.onPaternChange = this.onPaternChange.bind(this);
|
||||||
|
this.state = {
|
||||||
|
selectedKey: 'daily',
|
||||||
|
selectPatern: 'every',
|
||||||
|
startDate: this.props.startDate ? this.props.startDate : moment().toDate(),
|
||||||
|
endDate: moment().endOf('month').toDate(),
|
||||||
|
numberOcurrences: '1',
|
||||||
|
numberOfDays: '1',
|
||||||
|
disableNumberOfDays: false,
|
||||||
|
disableNumberOcurrences: true,
|
||||||
|
selectdateRangeOption: 'noDate',
|
||||||
|
disableEndDate: true,
|
||||||
|
selectedRecurrenceRule: 'daily',
|
||||||
|
isLoading: false,
|
||||||
|
errorMessageNumberOcurrences: '',
|
||||||
|
errorMessageNumberOfDays: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
this.onNumberOfDaysChange = this.onNumberOfDaysChange.bind(this);
|
||||||
|
this.onNumberOfOcurrencesChange = this.onNumberOfOcurrencesChange.bind(this);
|
||||||
|
this.onDataRangeOptionChange = this.onDataRangeOptionChange.bind(this);
|
||||||
|
this.onEndDateChange = this.onEndDateChange.bind(this);
|
||||||
|
this.onStartDateChange = this.onStartDateChange.bind(this);
|
||||||
|
this.onApplyRecurrence = this.onApplyRecurrence.bind(this);
|
||||||
|
|
||||||
|
this.spService = new spservices(this.props.context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Date} date
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onStartDateChange(date: Date) {
|
||||||
|
this.setState({ startDate: date });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Date} date
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onEndDateChange(date: Date) {
|
||||||
|
this.setState({ endDate: date });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.SyntheticEvent<HTMLElement>} ev
|
||||||
|
* @param {string} value
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onNumberOfDaysChange(ev: React.SyntheticEvent<HTMLElement>, value: string) {
|
||||||
|
ev.preventDefault();
|
||||||
|
let errorMessage = '';
|
||||||
|
setTimeout(() => {
|
||||||
|
|
||||||
|
if (Number(value.trim()) == 0 || Number(value.trim()) > 255) {
|
||||||
|
value = '1 ';
|
||||||
|
errorMessage = 'Allowed values 1 to 255';
|
||||||
|
}
|
||||||
|
this.setState({ numberOfDays: value, errorMessageNumberOfDays: errorMessage });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}, 2500);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.SyntheticEvent<HTMLElement>} ev
|
||||||
|
* @param {string} value
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onNumberOfOcurrencesChange(ev: React.SyntheticEvent<HTMLElement>, value: string) {
|
||||||
|
ev.preventDefault();
|
||||||
|
let errorMessage = '';
|
||||||
|
setTimeout(() => {
|
||||||
|
|
||||||
|
if (Number(value.trim()) == 0 || Number(value.trim()) > 999) {
|
||||||
|
value = '1 ';
|
||||||
|
errorMessage = 'Allowed values 1 to 999';
|
||||||
|
}
|
||||||
|
this.setState({ numberOcurrences: value , errorMessageNumberOcurrences: errorMessage });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}, 2500);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.SyntheticEvent<HTMLElement>} ev
|
||||||
|
* @param {IChoiceGroupOption} option
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onDataRangeOptionChange(ev: React.SyntheticEvent<HTMLElement>, option: IChoiceGroupOption): void {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selectdateRangeOption: option.key,
|
||||||
|
disableNumberOcurrences: option.key == 'endAfter' ? false : true,
|
||||||
|
disableEndDate: option.key == 'endDate' ? false : true,
|
||||||
|
});
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
private onPaternChange(ev: React.SyntheticEvent<HTMLElement>, option: IChoiceGroupOption): void {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.setState({
|
||||||
|
selectPatern: option.key,
|
||||||
|
disableNumberOfDays: option.key == 'every' ? false : true,
|
||||||
|
});
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async componentWillMount() {
|
||||||
|
// await this.load();
|
||||||
|
await this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async componentDidUpdate(prevProps: IEventRecurrenceInfoDailyProps, prevState: IEventRecurrenceInfoDailyState) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private async load() {
|
||||||
|
let patern: any = {};
|
||||||
|
let dateRange: { repeatForever?: string, repeatInstances?: string, windowEnd?: Date } = {};
|
||||||
|
let dailyPatern: { dayFrequency?: string, weekDay?: string } = {};
|
||||||
|
let recurrenceRule: string;
|
||||||
|
|
||||||
|
if (this.props.recurrenceData) {
|
||||||
|
|
||||||
|
parseString(this.props.recurrenceData, { explicitArray: false }, (error, result) => {
|
||||||
|
|
||||||
|
if (result.recurrence.rule.repeat) {
|
||||||
|
patern = result.recurrence.rule.repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
if (result.recurrence.rule.repeatForever) {
|
||||||
|
dateRange = { repeatForever: result.recurrence.rule.repeatForever };
|
||||||
|
}
|
||||||
|
if (result.recurrence.rule.repeatInstances) {
|
||||||
|
dateRange = { repeatInstances: result.recurrence.rule.repeatInstances };
|
||||||
|
}
|
||||||
|
if (result.recurrence.rule.windowEnd) {
|
||||||
|
dateRange = { windowEnd: result.recurrence.rule.windowEnd };
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
// daily Patern
|
||||||
|
if (patern.daily) {
|
||||||
|
recurrenceRule = 'daily';
|
||||||
|
if (patern.daily.$.dayFrequency) {
|
||||||
|
dailyPatern = { dayFrequency: patern.daily.$.dayFrequency };
|
||||||
|
}
|
||||||
|
if (patern.daily.$.weekday) {
|
||||||
|
dailyPatern = { weekDay: 'weekDay' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectDateRangeOption: string = 'noDate';
|
||||||
|
if (dateRange.repeatForever) {
|
||||||
|
selectDateRangeOption = 'noDate';
|
||||||
|
} else if (dateRange.repeatInstances) {
|
||||||
|
selectDateRangeOption = 'endAfter';
|
||||||
|
} else if (dateRange.windowEnd) {
|
||||||
|
selectDateRangeOption = 'endDate';
|
||||||
|
}
|
||||||
|
|
||||||
|
// weekday patern
|
||||||
|
this.setState({
|
||||||
|
selectedRecurrenceRule: recurrenceRule,
|
||||||
|
selectPatern: dailyPatern.dayFrequency ? 'every' : 'everweekday',
|
||||||
|
numberOfDays: dailyPatern.dayFrequency ? dailyPatern.dayFrequency : '1',
|
||||||
|
disableNumberOfDays: dailyPatern.dayFrequency ? false : true,
|
||||||
|
selectdateRangeOption: selectDateRangeOption,
|
||||||
|
numberOcurrences: dateRange.repeatInstances ? dateRange.repeatInstances : '10',
|
||||||
|
disableNumberOcurrences: dateRange.repeatInstances ? false : true,
|
||||||
|
endDate: dateRange.windowEnd ? new Date(moment(dateRange.windowEnd).format('YYYY/MM/DD')) : this.state.endDate,
|
||||||
|
disableEndDate: dateRange.windowEnd ? false : true,
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async onApplyRecurrence(ev: React.MouseEvent<HTMLButtonElement>) {
|
||||||
|
await this.applyRecurrence();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.MouseEvent<HTMLButtonElement>} ev
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private async applyRecurrence() {
|
||||||
|
|
||||||
|
const siteTimeZoneHours: number = await this.spService.getSiteTimeZoneHours(this.props.siteUrl);
|
||||||
|
const eventDate = new Date(moment(this.state.startDate).add(siteTimeZoneHours, 'hours').toISOString());
|
||||||
|
const endDate = moment(this.state.endDate).add(siteTimeZoneHours, 'hours').toISOString();
|
||||||
|
let selectDateRangeOption;
|
||||||
|
switch (this.state.selectdateRangeOption) {
|
||||||
|
case 'noDate':
|
||||||
|
selectDateRangeOption = `<repeatForever>FALSE</repeatForever>`;
|
||||||
|
break;
|
||||||
|
case 'endAfter':
|
||||||
|
selectDateRangeOption = `<repeatInstances>${this.state.numberOcurrences}</repeatInstances>`;
|
||||||
|
break;
|
||||||
|
case 'endDate':
|
||||||
|
selectDateRangeOption = `<windowEnd>${endDate}</windowEnd>`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const recurrenceXML = `<recurrence><rule><firstDayOfWeek>su</firstDayOfWeek><repeat>` +
|
||||||
|
`<daily ${ this.state.selectPatern === 'every' ? `dayFrequency="${this.state.numberOfDays.trim()}"/>` : 'weekday'}</repeat>${selectDateRangeOption}</rule></recurrence>`;
|
||||||
|
// console.log(recurrenceXML);
|
||||||
|
this.props.returnRecurrenceData(this.state.startDate, recurrenceXML);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @returns {React.ReactElement<IEventRecurrenceInfoDailyProps>}
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
public render(): React.ReactElement<IEventRecurrenceInfoDailyProps> {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
<div style={{ display: 'inline-block', float: 'right', paddingTop: '10px', height: '40px' }}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '100%', paddingTop: '10px' }}>
|
||||||
|
<Label>Patern</Label>
|
||||||
|
<ChoiceGroup
|
||||||
|
selectedKey={this.state.selectPatern}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
key: 'every',
|
||||||
|
text: strings.every,
|
||||||
|
ariaLabel: 'every',
|
||||||
|
|
||||||
|
onRenderField: (props, render) => {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
{render!(props)}
|
||||||
|
<MaskedTextField
|
||||||
|
styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '100px', paddingLeft: '10px' } }}
|
||||||
|
mask="999"
|
||||||
|
maskChar=' '
|
||||||
|
disabled={this.state.disableNumberOfDays}
|
||||||
|
value={this.state.numberOfDays}
|
||||||
|
errorMessage={this.state.errorMessageNumberOfDays}
|
||||||
|
onChange={this.onNumberOfDaysChange} />
|
||||||
|
<Label styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '60px', paddingLeft: '10px' } }}>{strings.days}</Label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'everweekday',
|
||||||
|
text: strings.everyweekdays,
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
onChange={this.onPaternChange}
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ paddingTop: '22px' }}>
|
||||||
|
<Label>Date Range</Label>
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingRight: '35px', paddingTop: '10px' }}>
|
||||||
|
|
||||||
|
<DatePicker
|
||||||
|
firstDayOfWeek={DayOfWeek.Sunday}
|
||||||
|
strings={DayPickerStrings}
|
||||||
|
placeholder={strings.StartDatePlaceHolder}
|
||||||
|
ariaLabel={strings.StartDatePlaceHolder}
|
||||||
|
label={strings.StartDateLabel}
|
||||||
|
value={this.state.startDate}
|
||||||
|
onSelectDate={this.onStartDateChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingTop: '10px' }}>
|
||||||
|
<ChoiceGroup
|
||||||
|
selectedKey={this.state.selectdateRangeOption}
|
||||||
|
onChange={this.onDataRangeOptionChange}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
key: 'noDate',
|
||||||
|
text: strings.noEndDate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'endDate',
|
||||||
|
text: strings.EndByLabel,
|
||||||
|
onRenderField: (props, render) => {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
{render!(props)}
|
||||||
|
<DatePicker
|
||||||
|
firstDayOfWeek={DayOfWeek.Sunday}
|
||||||
|
strings={DayPickerStrings}
|
||||||
|
placeholder={strings.StartDatePlaceHolder}
|
||||||
|
ariaLabel="Select a date"
|
||||||
|
style={{ display: 'inline-block', verticalAlign: 'top', paddingLeft: '22px', }}
|
||||||
|
onSelectDate={this.onEndDateChange}
|
||||||
|
value={this.state.endDate}
|
||||||
|
disabled={this.state.disableEndDate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'endAfter',
|
||||||
|
text: strings.EndAfterLabel,
|
||||||
|
onRenderField: (props, render) => {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
{render!(props)}
|
||||||
|
<MaskedTextField
|
||||||
|
styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '100px', paddingLeft: '10px' } }}
|
||||||
|
mask="999"
|
||||||
|
maskChar=' '
|
||||||
|
value={this.state.numberOcurrences}
|
||||||
|
disabled={this.state.disableNumberOcurrences}
|
||||||
|
errorMessage={this.state.errorMessageNumberOcurrences}
|
||||||
|
onChange={this.onNumberOfOcurrencesChange} />
|
||||||
|
<Label styles={{ root: { display: 'inline-block', verticalAlign: 'top', paddingLeft: '10px' } }}>Ocurrences</Label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||||
|
export interface IEventRecurrenceInfoDailyProps {
|
||||||
|
display:boolean;
|
||||||
|
recurrenceData: string;
|
||||||
|
startDate:Date;
|
||||||
|
context: WebPartContext;
|
||||||
|
siteUrl:string;
|
||||||
|
returnRecurrenceData: (startDate:Date,recurrenceData:string) => void;
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
export interface IEventRecurrenceInfoDailyState {
|
||||||
|
selectedKey:string;
|
||||||
|
selectPatern:string;
|
||||||
|
startDate: Date;
|
||||||
|
endDate:Date;
|
||||||
|
numberOcurrences:string;
|
||||||
|
numberOfDays:string;
|
||||||
|
disableNumberOfDays: boolean;
|
||||||
|
disableNumberOcurrences: boolean;
|
||||||
|
selectdateRangeOption:string;
|
||||||
|
disableEndDate:boolean;
|
||||||
|
selectedRecurrenceRule:string;
|
||||||
|
isLoading:boolean;
|
||||||
|
errorMessageNumberOfDays: string;
|
||||||
|
errorMessageNumberOcurrences: string;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
.divWrraper {
|
||||||
|
border-width:1px;
|
||||||
|
border-color:#adadad;
|
||||||
|
padding: 20px;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
|
@ -0,0 +1,645 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './EventRecurrenceInfoMonthly.module.scss';
|
||||||
|
import * as strings from 'CalendarWebPartStrings';
|
||||||
|
import { IEventRecurrenceInfoMonthlyProps } from './IEventRecurrenceInfoMonthlyProps';
|
||||||
|
import { IEventRecurrenceInfoMonthlyState } from './IEventRecurrenceInfoMonthlyState';
|
||||||
|
import { escape } from '@microsoft/sp-lodash-subset';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { parseString, Builder } from "xml2js";
|
||||||
|
import {
|
||||||
|
ChoiceGroup,
|
||||||
|
IChoiceGroupOption,
|
||||||
|
Dropdown,
|
||||||
|
IDropdownOption,
|
||||||
|
Label,
|
||||||
|
MaskedTextField,
|
||||||
|
} from 'office-ui-fabric-react';
|
||||||
|
|
||||||
|
import { DatePicker, DayOfWeek, IDatePickerStrings } from 'office-ui-fabric-react/lib/DatePicker';
|
||||||
|
|
||||||
|
import spservices from '../../services/spservices';
|
||||||
|
import { string } from 'prop-types';
|
||||||
|
|
||||||
|
const DayPickerStrings: IDatePickerStrings = {
|
||||||
|
months: [strings.January, strings.February, strings.March, strings.April, strings.May, strings.June, strings.July, strings.August, strings.September, strings.October, strings.November, strings.December],
|
||||||
|
shortMonths: [strings.Jan, strings.Feb, strings.Mar, strings.Apr, strings.May, strings.Jun, strings.Jul, strings.Aug, strings.Sep, strings.Oct, strings.Nov, strings.Dez],
|
||||||
|
days: [strings.Sunday, strings.Monday, strings.Tuesday, strings.Wednesday, strings.Thursday, strings.Friday, strings.Saturday],
|
||||||
|
shortDays: [strings.ShortDay_S, strings.ShortDay_M, strings.ShortDay_T, strings.ShortDay_W, strings.ShortDay_Tursday, strings.ShortDay_Friday, strings.ShortDay_Saunday],
|
||||||
|
goToToday: strings.GoToDay,
|
||||||
|
prevMonthAriaLabel: strings.PrevMonth,
|
||||||
|
nextMonthAriaLabel: strings.NextMonth,
|
||||||
|
prevYearAriaLabel: strings.PrevYear,
|
||||||
|
nextYearAriaLabel: strings.NextYear,
|
||||||
|
closeButtonAriaLabel: strings.CloseDate,
|
||||||
|
isRequiredErrorMessage: strings.IsRequired,
|
||||||
|
invalidInputErrorMessage: strings.InvalidDateFormat,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class EventRecurrenceInfoDaily
|
||||||
|
* @extends {React.Component<IEventRecurrenceInfoMonthlyProps, IEventRecurrenceInfoMonthlyState>}
|
||||||
|
*/
|
||||||
|
export class EventRecurrenceInfoMonthly extends React.Component<IEventRecurrenceInfoMonthlyProps, IEventRecurrenceInfoMonthlyState> {
|
||||||
|
private spService: spservices = null;
|
||||||
|
public constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
|
||||||
|
this.onPaternChange = this.onPaternChange.bind(this);
|
||||||
|
this.state = {
|
||||||
|
selectedKey: 'daily',
|
||||||
|
selectPatern: 'monthly',
|
||||||
|
startDate: this.props.startDate ? this.props.startDate : moment().toDate(),
|
||||||
|
endDate: moment(this.props.startDate).add('month', 1).toDate(),
|
||||||
|
numberOcurrences: '10',
|
||||||
|
disableDayOfMonth: false,
|
||||||
|
disableNumberOcurrences: true,
|
||||||
|
selectdateRangeOption: 'noDate',
|
||||||
|
disableEndDate: true,
|
||||||
|
selectedRecurrenceRule: 'monthly',
|
||||||
|
dayOfMonth: this.props.startDate ? moment(this.props.startDate).format('D') : moment().format('D'),
|
||||||
|
everyNumberOfMonths: '1',
|
||||||
|
isLoading: false,
|
||||||
|
errorMessageNumberOfMonth: '',
|
||||||
|
errorMessageDayOfMonth: '',
|
||||||
|
selectedWeekOrderMonth: 'first',
|
||||||
|
selectedWeekDay: 'day',
|
||||||
|
errorMessageNumberOfMonthWeekDay: '',
|
||||||
|
everyNumberOfMonthsWeekDay: '1',
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
this.onDayOfMonthChange = this.onDayOfMonthChange.bind(this);
|
||||||
|
this.onNumberOfOcurrencesChange = this.onNumberOfOcurrencesChange.bind(this);
|
||||||
|
this.onDataRangeOptionChange = this.onDataRangeOptionChange.bind(this);
|
||||||
|
this.onEndDateChange = this.onEndDateChange.bind(this);
|
||||||
|
this.onStartDateChange = this.onStartDateChange.bind(this);
|
||||||
|
this.onApplyRecurrence = this.onApplyRecurrence.bind(this);
|
||||||
|
this.onDayOfMonthGetErrorMessage = this.onDayOfMonthGetErrorMessage.bind(this);
|
||||||
|
this.onEveryNumberOfMonthsChange = this.onEveryNumberOfMonthsChange.bind(this);
|
||||||
|
this.onEveryNumberOfMonthsWeekDayChange = this.onEveryNumberOfMonthsWeekDayChange.bind(this);
|
||||||
|
this.onSelectedWeekDayChange = this.onSelectedWeekDayChange.bind(this);
|
||||||
|
this.onWeekOrderMonthChange = this.onWeekOrderMonthChange.bind(this);
|
||||||
|
|
||||||
|
this.spService = new spservices(this.props.context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Date} date
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onStartDateChange(date: Date) {
|
||||||
|
this.setState({ startDate: date });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Date} date
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onEndDateChange(date: Date) {
|
||||||
|
this.setState({ endDate: date });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.SyntheticEvent<HTMLElement>} ev
|
||||||
|
* @param {string} value
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onDayOfMonthChange(ev: React.SyntheticEvent<HTMLElement>, value: string) {
|
||||||
|
ev.preventDefault();
|
||||||
|
setTimeout(() => {
|
||||||
|
let errorMessage = '';
|
||||||
|
if (Number(value.trim()) == 0 || Number(value.trim()) > 31) {
|
||||||
|
value = '1 ';
|
||||||
|
errorMessage = 'Allowed values 1 to 31';
|
||||||
|
}
|
||||||
|
this.setState({ dayOfMonth: value, errorMessageDayOfMonth: errorMessage });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}, 2500);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} value
|
||||||
|
* @returns
|
||||||
|
* @memberof EventRecurrenceInfoMonthly
|
||||||
|
*/
|
||||||
|
private onDayOfMonthGetErrorMessage(value: string) {
|
||||||
|
return (Number(value.trim()) != 0 && Number(value.trim()) <= 31) ? '' : "Day must be beteween 1 and 31";
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.SyntheticEvent<HTMLElement>} ev
|
||||||
|
* @param {string} value
|
||||||
|
* @memberof EventRecurrenceInfoMonthly
|
||||||
|
*/
|
||||||
|
private onEveryNumberOfMonthsChange(ev: React.SyntheticEvent<HTMLElement>, value: string) {
|
||||||
|
ev.preventDefault();
|
||||||
|
setTimeout(() => {
|
||||||
|
let errorMessage = '';
|
||||||
|
if (Number(value.trim()) == 0 || Number(value.trim()) > 12) {
|
||||||
|
value = '1 ';
|
||||||
|
errorMessage = 'Allowed values 1 to 12';
|
||||||
|
}
|
||||||
|
this.setState({ everyNumberOfMonths: value, errorMessageNumberOfMonth: errorMessage });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}, 2500);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.SyntheticEvent<HTMLElement>} ev
|
||||||
|
* @param {string} value
|
||||||
|
* @memberof EventRecurrenceInfoMonthly
|
||||||
|
*/
|
||||||
|
private onEveryNumberOfMonthsWeekDayChange(ev: React.SyntheticEvent<HTMLElement>, value: string) {
|
||||||
|
ev.preventDefault();
|
||||||
|
setTimeout(() => {
|
||||||
|
let errorMessage = '';
|
||||||
|
if (Number(value.trim()) == 0 || Number(value.trim()) > 12) {
|
||||||
|
value = '1 ';
|
||||||
|
errorMessage = strings.AllowedValues1to12Label;
|
||||||
|
}
|
||||||
|
this.setState({ everyNumberOfMonthsWeekDay: value, errorMessageNumberOfMonthWeekDay: errorMessage });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}, 2500);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.SyntheticEvent<HTMLElement>} ev
|
||||||
|
* @param {string} value
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onNumberOfOcurrencesChange(ev: React.SyntheticEvent<HTMLElement>, value: string) {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.setState({ numberOcurrences: value });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.SyntheticEvent<HTMLElement>} ev
|
||||||
|
* @param {IChoiceGroupOption} option
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onDataRangeOptionChange(ev: React.SyntheticEvent<HTMLElement>, option: IChoiceGroupOption): void {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selectdateRangeOption: option.key,
|
||||||
|
disableNumberOcurrences: option.key == 'endAfter' ? false : true,
|
||||||
|
disableEndDate: option.key == 'endDate' ? false : true,
|
||||||
|
});
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.SyntheticEvent<HTMLElement>} ev
|
||||||
|
* @param {IChoiceGroupOption} option
|
||||||
|
* @memberof EventRecurrenceInfoMonthly
|
||||||
|
*/
|
||||||
|
private onPaternChange(ev: React.SyntheticEvent<HTMLElement>, option: IChoiceGroupOption): void {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.setState({
|
||||||
|
selectPatern: option.key,
|
||||||
|
disableDayOfMonth: option.key == 'monthly' ? false : true,
|
||||||
|
});
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async componentWillMount() {
|
||||||
|
|
||||||
|
await this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.FormEvent<HTMLDivElement>} ev
|
||||||
|
* @param {IDropdownOption} item
|
||||||
|
* @memberof EventRecurrenceInfoMonthly
|
||||||
|
*/
|
||||||
|
private onWeekOrderMonthChange(ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption):void {
|
||||||
|
this.setState({selectedWeekOrderMonth: item.text});
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.FormEvent<HTMLDivElement>} ev
|
||||||
|
* @param {IDropdownOption} item
|
||||||
|
* @memberof EventRecurrenceInfoMonthly
|
||||||
|
*/
|
||||||
|
private onSelectedWeekDayChange(ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption):void {
|
||||||
|
this.setState({selectedWeekDay: item.key});
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async componentDidUpdate(prevProps: IEventRecurrenceInfoMonthlyProps, prevState: IEventRecurrenceInfoMonthlyState) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @memberof EventRecurrenceInfoMonthly
|
||||||
|
*/
|
||||||
|
private async load() {
|
||||||
|
let patern: any = {};
|
||||||
|
let dateRange: { repeatForever?: string, repeatInstances?: string, windowEnd?: Date } = {};
|
||||||
|
let monthlyPatern: { monthFrequency?: string, day?: string } = {};
|
||||||
|
let monthlyByDayPatern: { monthFrequency?: string, weekdayOfMonth?: string, weekDay?: string } = {};
|
||||||
|
let recurrenceRule: string;
|
||||||
|
|
||||||
|
if (this.props.recurrenceData) {
|
||||||
|
|
||||||
|
parseString(this.props.recurrenceData, { explicitArray: false }, (error, result) => {
|
||||||
|
|
||||||
|
if (result.recurrence.rule.repeat) {
|
||||||
|
patern = result.recurrence.rule.repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
if (result.recurrence.rule.repeatForever) {
|
||||||
|
dateRange = { repeatForever: result.recurrence.rule.repeatForever };
|
||||||
|
}
|
||||||
|
if (result.recurrence.rule.repeatInstances) {
|
||||||
|
dateRange = { repeatInstances: result.recurrence.rule.repeatInstances };
|
||||||
|
}
|
||||||
|
if (result.recurrence.rule.windowEnd) {
|
||||||
|
dateRange = { windowEnd: result.recurrence.rule.windowEnd };
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
// monthly Patern
|
||||||
|
if (patern.monthly) {
|
||||||
|
recurrenceRule = 'monthly';
|
||||||
|
if (patern.monthly.$.monthFrequency && patern.monthly.$.day) {
|
||||||
|
monthlyPatern = { monthFrequency: patern.monthly.$.monthFrequency, day: patern.monthly.$.day };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// monthlyByDay Patern
|
||||||
|
if (patern.monthlyByDay) {
|
||||||
|
recurrenceRule = 'monthly';
|
||||||
|
let weekDay = 'day';
|
||||||
|
|
||||||
|
if (patern.monthlyByDay.$.su) weekDay = 'sunday';
|
||||||
|
if (patern.monthlyByDay.$.mo) weekDay = 'monday';
|
||||||
|
if (patern.monthlyByDay.$.tu) weekDay = 'tuesday';
|
||||||
|
if (patern.monthlyByDay.$.we) weekDay = 'wednesday';
|
||||||
|
if (patern.monthlyByDay.$.th) weekDay = 'thursday';
|
||||||
|
if (patern.monthlyByDay.$.fr) weekDay = 'friday';
|
||||||
|
if (patern.monthlyByDay.$.sa) weekDay = 'saturday';
|
||||||
|
if (patern.monthlyByDay.$.day) weekDay = 'day';
|
||||||
|
if (patern.monthlyByDay.$.weekday) weekDay = 'weekday';
|
||||||
|
if (patern.monthlyByDay.$.weekend_day) weekDay = 'weekdendday';
|
||||||
|
|
||||||
|
monthlyByDayPatern = {
|
||||||
|
monthFrequency: patern.monthlyByDay.$.monthFrequency,
|
||||||
|
weekdayOfMonth: patern.monthlyByDay.$.weekdayOfMonth,
|
||||||
|
weekDay: weekDay,
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectDateRangeOption: string = 'noDate';
|
||||||
|
if (dateRange.repeatForever) {
|
||||||
|
selectDateRangeOption = 'noDate';
|
||||||
|
} else if (dateRange.repeatInstances) {
|
||||||
|
selectDateRangeOption = 'endAfter';
|
||||||
|
} else if (dateRange.windowEnd) {
|
||||||
|
selectDateRangeOption = 'endDate';
|
||||||
|
}
|
||||||
|
|
||||||
|
// weekday patern
|
||||||
|
this.setState({
|
||||||
|
selectedRecurrenceRule: recurrenceRule,
|
||||||
|
selectPatern: patern.monthly ? 'monthly' : 'monthlyByDay',
|
||||||
|
dayOfMonth: monthlyPatern.day ? monthlyPatern.day : '1',
|
||||||
|
everyNumberOfMonths: monthlyPatern.monthFrequency ? monthlyPatern.monthFrequency : monthlyByDayPatern.monthFrequency ,
|
||||||
|
everyNumberOfMonthsWeekDay: monthlyByDayPatern.monthFrequency ? monthlyByDayPatern.monthFrequency : '1',
|
||||||
|
selectedWeekOrderMonth: monthlyByDayPatern.weekdayOfMonth ? monthlyByDayPatern.weekdayOfMonth : 'first',
|
||||||
|
selectedWeekDay: monthlyByDayPatern.weekDay ? monthlyByDayPatern.weekDay : 'day',
|
||||||
|
disableDayOfMonth: patern.monthly ? false : true,
|
||||||
|
selectdateRangeOption: selectDateRangeOption,
|
||||||
|
numberOcurrences: dateRange.repeatInstances ? dateRange.repeatInstances : '10',
|
||||||
|
disableNumberOcurrences: dateRange.repeatInstances ? false : true,
|
||||||
|
endDate: dateRange.windowEnd ? new Date(moment(dateRange.windowEnd).format('YYYY/MM/DD')) : this.state.endDate,
|
||||||
|
disableEndDate: dateRange.windowEnd ? false : true,
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.MouseEvent<HTMLButtonElement>} ev
|
||||||
|
* @memberof EventRecurrenceInfoMonthly
|
||||||
|
*/
|
||||||
|
private async onApplyRecurrence(ev: React.MouseEvent<HTMLButtonElement>) {
|
||||||
|
await this.applyRecurrence();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.MouseEvent<HTMLButtonElement>} ev
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private async applyRecurrence() {
|
||||||
|
|
||||||
|
const siteTimeZoneHours: number = await this.spService.getSiteTimeZoneHours(this.props.siteUrl);
|
||||||
|
const eventDate = new Date(moment(this.state.startDate).add(siteTimeZoneHours, 'hours').toISOString());
|
||||||
|
const endDate = moment(this.state.endDate).add(siteTimeZoneHours, 'hours').toISOString();
|
||||||
|
let selectDateRangeOption;
|
||||||
|
switch (this.state.selectdateRangeOption) {
|
||||||
|
case 'noDate':
|
||||||
|
selectDateRangeOption = `<repeatForever>FALSE</repeatForever>`;
|
||||||
|
break;
|
||||||
|
case 'endAfter':
|
||||||
|
selectDateRangeOption = `<repeatInstances>${this.state.numberOcurrences}</repeatInstances>`;
|
||||||
|
break;
|
||||||
|
case 'endDate':
|
||||||
|
selectDateRangeOption = `<windowEnd>${endDate}</windowEnd>`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let recurrencePatern: string = '';
|
||||||
|
if (this.state.selectPatern == 'monthly') {
|
||||||
|
recurrencePatern = `<monthly monthFrequency="${this.state.everyNumberOfMonths}" day="${this.state.dayOfMonth}" /></repeat>${selectDateRangeOption}</rule></recurrence>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.selectPatern == 'monthlyByDay') {
|
||||||
|
|
||||||
|
recurrencePatern = `<monthlyByDay weekdayOfMonth="${this.state.selectedWeekOrderMonth}" `;
|
||||||
|
switch (this.state.selectedWeekDay) {
|
||||||
|
case 'day':
|
||||||
|
recurrencePatern = recurrencePatern + `day="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'weekday':
|
||||||
|
recurrencePatern = recurrencePatern + `weekday="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'weekendday':
|
||||||
|
recurrencePatern = recurrencePatern + `weekend_day="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'sunday':
|
||||||
|
recurrencePatern = recurrencePatern + `su="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'monday':
|
||||||
|
recurrencePatern = recurrencePatern + `mo="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'tuesday':
|
||||||
|
recurrencePatern = recurrencePatern + `tu="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'wednesday':
|
||||||
|
recurrencePatern = recurrencePatern + `we="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'thursday':
|
||||||
|
recurrencePatern = recurrencePatern + `th="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'friday':
|
||||||
|
recurrencePatern = recurrencePatern + `fr="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'saturday':
|
||||||
|
recurrencePatern = recurrencePatern + `sa="TRUE"`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
recurrencePatern = recurrencePatern + ` monthFrequency="${this.state.everyNumberOfMonthsWeekDay}" /></repeat>${selectDateRangeOption}</rule></recurrence>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recurrenceXML = `<recurrence><rule><firstDayOfWeek>su</firstDayOfWeek><repeat>` + recurrencePatern;
|
||||||
|
|
||||||
|
// console.log(recurrenceXML);
|
||||||
|
this.props.returnRecurrenceData(this.state.startDate, recurrenceXML);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @returns {React.ReactElement<IEventRecurrenceInfoDailyProps>}
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
public render(): React.ReactElement<IEventRecurrenceInfoMonthlyProps> {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
<div style={{ display: 'inline-block', float: 'right', paddingTop: '10px', height: '40px' }}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '100%', paddingTop: '10px' }}>
|
||||||
|
<Label>Patern</Label>
|
||||||
|
<ChoiceGroup
|
||||||
|
selectedKey={this.state.selectPatern}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
key: 'monthly',
|
||||||
|
text: strings.dayLable,
|
||||||
|
ariaLabel: strings.dayLable,
|
||||||
|
|
||||||
|
onRenderField: (props, render) => {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
{render!(props)}
|
||||||
|
<MaskedTextField
|
||||||
|
styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '100px', paddingLeft: '10px' } }}
|
||||||
|
mask="99"
|
||||||
|
maskChar=' '
|
||||||
|
disabled={this.state.disableDayOfMonth}
|
||||||
|
value={this.state.dayOfMonth}
|
||||||
|
errorMessage={this.state.errorMessageDayOfMonth}
|
||||||
|
onChange={this.onDayOfMonthChange} />
|
||||||
|
<Label styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '65px', paddingLeft: '10px' } }}>{strings.ofEveryLabel}</Label>
|
||||||
|
<MaskedTextField
|
||||||
|
styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '100px', paddingLeft: '10px' } }}
|
||||||
|
mask="99"
|
||||||
|
maskChar=' '
|
||||||
|
disabled={this.state.disableDayOfMonth}
|
||||||
|
value={this.state.everyNumberOfMonths}
|
||||||
|
errorMessage={this.state.errorMessageNumberOfMonth}
|
||||||
|
onChange={this.onEveryNumberOfMonthsChange} />
|
||||||
|
<Label styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '100px', paddingLeft: '10px' } }}>{strings.MonthsLabel}</Label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'monthlyByDay',
|
||||||
|
text: strings.theLabel,
|
||||||
|
onRenderField: (props, render) => {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
{render!(props)}
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', width: '90px', paddingLeft: '10px' }}>
|
||||||
|
<Dropdown
|
||||||
|
selectedKey={this.state.selectedWeekOrderMonth}
|
||||||
|
onChange={this.onWeekOrderMonthChange}
|
||||||
|
disabled={!this.state.disableDayOfMonth}
|
||||||
|
options={[
|
||||||
|
{ key: 'first', text: strings.firstLabel },
|
||||||
|
{ key: 'second', text:strings.secondLabel},
|
||||||
|
{ key: 'third', text: strings.thirdLabel },
|
||||||
|
{ key: 'fourth', text:strings.fourthLabel },
|
||||||
|
{ key: 'last', text: strings.lastLabel },
|
||||||
|
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', width: '100px', paddingLeft: '5px' }}>
|
||||||
|
<Dropdown
|
||||||
|
selectedKey={this.state.selectedWeekDay}
|
||||||
|
disabled={!this.state.disableDayOfMonth}
|
||||||
|
onChange={this.onSelectedWeekDayChange}
|
||||||
|
options={[
|
||||||
|
{ key: 'day', text: strings.dayLable },
|
||||||
|
{ key: 'weekday', text: strings.weekDayLabel },
|
||||||
|
{ key: 'weekendday', text:strings.weekEndDay },
|
||||||
|
{ key: 'sunday', text: strings.Sunday},
|
||||||
|
{ key: 'monday', text: strings.Monday },
|
||||||
|
{ key: 'tuesday', text: strings.Tuesday },
|
||||||
|
{ key: 'wednesday', text: strings.Wednesday },
|
||||||
|
{ key: 'thursday', text: strings.Thursday},
|
||||||
|
{ key: 'friday', text: strings.Friday },
|
||||||
|
{ key: 'saturday', text: strings.Saturday },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Label styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '65px', paddingLeft: '10px' } }}>{strings.ofEveryLabel}</Label>
|
||||||
|
<MaskedTextField
|
||||||
|
styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '100px', paddingLeft: '10px' } }}
|
||||||
|
mask="99"
|
||||||
|
maskChar=' '
|
||||||
|
disabled={!this.state.disableDayOfMonth}
|
||||||
|
value={this.state.everyNumberOfMonthsWeekDay}
|
||||||
|
errorMessage={this.state.errorMessageNumberOfMonthWeekDay}
|
||||||
|
onChange={this.onEveryNumberOfMonthsWeekDayChange} />
|
||||||
|
<Label styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '80px', paddingLeft: '10px' } }}>{strings.MonthsLabel}</Label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
onChange={this.onPaternChange}
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ paddingTop: '22px' }}>
|
||||||
|
<Label>{strings.dateRangeLabel}</Label>
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingRight: '35px', paddingTop: '10px' }}>
|
||||||
|
|
||||||
|
<DatePicker
|
||||||
|
firstDayOfWeek={DayOfWeek.Sunday}
|
||||||
|
strings={DayPickerStrings}
|
||||||
|
placeholder={strings.StartDatePlaceHolder}
|
||||||
|
ariaLabel={strings.StartDatePlaceHolder}
|
||||||
|
label={strings.StartDateLabel}
|
||||||
|
value={this.state.startDate}
|
||||||
|
onSelectDate={this.onStartDateChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingTop: '10px' }}>
|
||||||
|
<ChoiceGroup
|
||||||
|
selectedKey={this.state.selectdateRangeOption}
|
||||||
|
onChange={this.onDataRangeOptionChange}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
key: 'noDate',
|
||||||
|
text: strings.noEndDate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'endDate',
|
||||||
|
text: strings.EndByLabel,
|
||||||
|
onRenderField: (props, render) => {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
{render!(props)}
|
||||||
|
<DatePicker
|
||||||
|
firstDayOfWeek={DayOfWeek.Sunday}
|
||||||
|
strings={DayPickerStrings}
|
||||||
|
placeholder={strings.StartDatePlaceHolder}
|
||||||
|
ariaLabel={strings.StartDatePlaceHolder}
|
||||||
|
style={{ display: 'inline-block', verticalAlign: 'top', paddingLeft: '22px', }}
|
||||||
|
onSelectDate={this.onEndDateChange}
|
||||||
|
value={this.state.endDate}
|
||||||
|
disabled={this.state.disableEndDate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'endAfter',
|
||||||
|
text: strings.EndAfterLabel,
|
||||||
|
onRenderField: (props, render) => {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
{render!(props)}
|
||||||
|
<MaskedTextField
|
||||||
|
styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '100px', paddingLeft: '10px' } }}
|
||||||
|
mask="999"
|
||||||
|
maskChar=' '
|
||||||
|
value={this.state.numberOcurrences}
|
||||||
|
disabled={this.state.disableNumberOcurrences}
|
||||||
|
onChange={this.onNumberOfOcurrencesChange} />
|
||||||
|
<Label styles={{ root: { display: 'inline-block', verticalAlign: 'top', paddingLeft: '10px' } }}>{strings.OcurrencesLabel}</Label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||||
|
export interface IEventRecurrenceInfoMonthlyProps {
|
||||||
|
display:boolean;
|
||||||
|
recurrenceData: string;
|
||||||
|
startDate:Date;
|
||||||
|
context: WebPartContext;
|
||||||
|
siteUrl:string;
|
||||||
|
returnRecurrenceData: (startDate:Date,recurrenceData:string) => void;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
export interface IEventRecurrenceInfoMonthlyState {
|
||||||
|
selectedKey:string;
|
||||||
|
selectPatern:string;
|
||||||
|
startDate: Date;
|
||||||
|
endDate:Date;
|
||||||
|
numberOcurrences:string;
|
||||||
|
dayOfMonth:string;
|
||||||
|
everyNumberOfMonths: string;
|
||||||
|
disableDayOfMonth: boolean;
|
||||||
|
disableNumberOcurrences: boolean;
|
||||||
|
selectdateRangeOption:string;
|
||||||
|
disableEndDate:boolean;
|
||||||
|
selectedRecurrenceRule:string;
|
||||||
|
isLoading:boolean;
|
||||||
|
errorMessageDayOfMonth:string;
|
||||||
|
errorMessageNumberOfMonth:string;
|
||||||
|
selectedWeekOrderMonth:string;
|
||||||
|
selectedWeekDay:string | number;
|
||||||
|
everyNumberOfMonthsWeekDay:string;
|
||||||
|
errorMessageNumberOfMonthWeekDay:string;
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
.divWrraper {
|
||||||
|
border-width:1px;
|
||||||
|
border-color:#adadad;
|
||||||
|
padding: 20px;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
.ckeckBoxInline {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
width: 80px;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
.dateRange{
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
padding-right: 35px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
|
@ -0,0 +1,481 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './EventRecurrenceInfoWeekly.module.scss';
|
||||||
|
import * as strings from 'CalendarWebPartStrings';
|
||||||
|
import { IEventRecurrenceInfoWeeklyProps } from './IEventRecurrenceInfoWeeklyProps';
|
||||||
|
import { IEventRecurrenceInfoWeeklyState } from './IEventRecurrenceInfoWeeklyState';
|
||||||
|
import { escape } from '@microsoft/sp-lodash-subset';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { parseString, Builder } from "xml2js";
|
||||||
|
import {
|
||||||
|
ChoiceGroup,
|
||||||
|
IChoiceGroupOption,
|
||||||
|
Dropdown,
|
||||||
|
IDropdownOption,
|
||||||
|
TextField,
|
||||||
|
SpinButton,
|
||||||
|
Label,
|
||||||
|
PrimaryButton,
|
||||||
|
MaskedTextField,
|
||||||
|
CommandBarButton, IButtonProps,
|
||||||
|
DefaultButton,
|
||||||
|
Checkbox,
|
||||||
|
} from 'office-ui-fabric-react';
|
||||||
|
import { Position } from 'office-ui-fabric-react/lib/utilities/positioning';
|
||||||
|
import { Root } from '@pnp/graph';
|
||||||
|
import { DatePicker, DayOfWeek, IDatePickerStrings } from 'office-ui-fabric-react/lib/DatePicker';
|
||||||
|
|
||||||
|
import spservices from '../../services/spservices';
|
||||||
|
|
||||||
|
const DayPickerStrings: IDatePickerStrings = {
|
||||||
|
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||||||
|
|
||||||
|
shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||||
|
|
||||||
|
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
||||||
|
|
||||||
|
shortDays: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
|
||||||
|
|
||||||
|
goToToday: 'Go to today',
|
||||||
|
prevMonthAriaLabel: 'Go to previous month',
|
||||||
|
nextMonthAriaLabel: 'Go to next month',
|
||||||
|
prevYearAriaLabel: 'Go to previous year',
|
||||||
|
nextYearAriaLabel: 'Go to next year',
|
||||||
|
closeButtonAriaLabel: 'Close date picker'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class EventRecurrenceInfoDaily
|
||||||
|
* @extends {React.Component<IEventRecurrenceInfoWeeklyProps, IEventRecurrenceInfoWeeklyState>}
|
||||||
|
*/
|
||||||
|
export class EventRecurrenceInfoWeekly extends React.Component<IEventRecurrenceInfoWeeklyProps, IEventRecurrenceInfoWeeklyState> {
|
||||||
|
private spService: spservices = null;
|
||||||
|
public constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.onPaternChange = this.onPaternChange.bind(this);
|
||||||
|
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
selectedKey: 'daily',
|
||||||
|
selectPatern: 'every',
|
||||||
|
startDate: this.props.startDate ? this.props.startDate : moment().toDate(),
|
||||||
|
endDate: moment().endOf('month').toDate(),
|
||||||
|
numberOcurrences: '10',
|
||||||
|
numberOfWeeks: '1',
|
||||||
|
disableNumberOfWeeks: false,
|
||||||
|
disableNumberOcurrences: true,
|
||||||
|
selectdateRangeOption: 'noDate',
|
||||||
|
disableEndDate: true,
|
||||||
|
weeklySunday: moment().weekday() === 0 ? true: false,
|
||||||
|
weeklyMonday: moment().weekday() === 1 ? true: false,
|
||||||
|
weekklyTuesday: moment().weekday() === 2 ? true: false,
|
||||||
|
weekklyWednesday: moment().weekday() === 3 ? true: false,
|
||||||
|
weekklyThursday: moment().weekday() === 4 ? true: false,
|
||||||
|
weeklyFriday: moment().weekday() === 5 ? true: false,
|
||||||
|
weeklySaturday: moment().weekday() === 6 ? true: false,
|
||||||
|
isLoading: false,
|
||||||
|
errorMessageNumberOfWeeks: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
this.onNumberOfWeeksChange = this.onNumberOfWeeksChange.bind(this);
|
||||||
|
this.onNumberOfOcurrencesChange = this.onNumberOfOcurrencesChange.bind(this);
|
||||||
|
this.onDataRangeOptionChange = this.onDataRangeOptionChange.bind(this);
|
||||||
|
this.onEndDateChange = this.onEndDateChange.bind(this);
|
||||||
|
this.onStartDateChange = this.onStartDateChange.bind(this);
|
||||||
|
this.onApplyRecurrence = this.onApplyRecurrence.bind(this);
|
||||||
|
this.onCheckboxSundayChange = this.onCheckboxSundayChange.bind(this);
|
||||||
|
this.onCheckboxMondayChange = this.onCheckboxMondayChange.bind(this);
|
||||||
|
this.onCheckboxTuesdayChange = this.onCheckboxTuesdayChange.bind(this);
|
||||||
|
this.onCheckboxWednesdayChange = this.onCheckboxWednesdayChange.bind(this);
|
||||||
|
this.onCheckboxThursdayChange = this.onCheckboxThursdayChange.bind(this);
|
||||||
|
this.onCheckboxFridayChange = this.onCheckboxFridayChange.bind(this);
|
||||||
|
this.onCheckboxSaturdayChange = this.onCheckboxSaturdayChange.bind(this);
|
||||||
|
|
||||||
|
this.spService = new spservices(this.props.context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Date} date
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onStartDateChange(date: Date) {
|
||||||
|
this.setState({ startDate: date });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Date} date
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onEndDateChange(date: Date) {
|
||||||
|
this.setState({ endDate: date });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.SyntheticEvent<HTMLElement>} ev
|
||||||
|
* @param {string} value
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onNumberOfWeeksChange(ev: React.SyntheticEvent<HTMLElement>, value: string) {
|
||||||
|
ev.preventDefault();
|
||||||
|
setTimeout(() => {
|
||||||
|
let errorMessage:string ='';
|
||||||
|
if (Number(value.trim()) == 0 || Number(value.trim()) > 255) {
|
||||||
|
value = '1 ';
|
||||||
|
errorMessage = 'Allowed values 1 to 255';
|
||||||
|
}
|
||||||
|
this.setState({ numberOfWeeks: value , errorMessageNumberOfWeeks: errorMessage });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.SyntheticEvent<HTMLElement>} ev
|
||||||
|
* @param {string} value
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onNumberOfOcurrencesChange(ev: React.SyntheticEvent<HTMLElement>, value: string) {
|
||||||
|
ev.preventDefault();
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setState({ numberOcurrences: value.trim().length > 0 ? value : "10 " });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.SyntheticEvent<HTMLElement>} ev
|
||||||
|
* @param {IChoiceGroupOption} option
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onDataRangeOptionChange(ev: React.SyntheticEvent<HTMLElement>, option: IChoiceGroupOption): void {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selectdateRangeOption: option.key,
|
||||||
|
disableNumberOcurrences: option.key == 'endAfter' ? false : true,
|
||||||
|
disableEndDate: option.key == 'endDate' ? false : true,
|
||||||
|
});
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPaternChange(ev: React.SyntheticEvent<HTMLElement>, option: IChoiceGroupOption): void {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.setState({
|
||||||
|
selectPatern: option.key,
|
||||||
|
disableNumberOfWeeks: option.key == 'every' ? false : true,
|
||||||
|
});
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async componentWillMount() {
|
||||||
|
// await this.load();
|
||||||
|
await this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async componentDidUpdate(prevProps: IEventRecurrenceInfoWeeklyProps, prevState: IEventRecurrenceInfoWeeklyState) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private async load() {
|
||||||
|
let patern: any = {};
|
||||||
|
let dateRange: { repeatForever?: string, repeatInstances?: string, windowEnd?: Date } = {};
|
||||||
|
let weeklyPatern: { weekFrequency?: string, su?: boolean, mo?: boolean, tu?: boolean, we?: boolean, th?: boolean, fr?: boolean, sa?: boolean } = {};
|
||||||
|
|
||||||
|
|
||||||
|
if (this.props.recurrenceData) {
|
||||||
|
|
||||||
|
parseString(this.props.recurrenceData, { explicitArray: false }, (error, result) => {
|
||||||
|
|
||||||
|
if (result.recurrence.rule.repeat) {
|
||||||
|
patern = result.recurrence.rule.repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
if (result.recurrence.rule.repeatForever) {
|
||||||
|
dateRange = { repeatForever: result.recurrence.rule.repeatForever };
|
||||||
|
}
|
||||||
|
if (result.recurrence.rule.repeatInstances) {
|
||||||
|
dateRange = { repeatInstances: result.recurrence.rule.repeatInstances };
|
||||||
|
}
|
||||||
|
if (result.recurrence.rule.windowEnd) {
|
||||||
|
dateRange = { windowEnd: result.recurrence.rule.windowEnd };
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
// daily Patern
|
||||||
|
if (patern.weekly) {
|
||||||
|
|
||||||
|
weeklyPatern = patern.weekly.$.weekFrequency ? { weekFrequency: patern.weekly.$.weekFrequency } : { weekFrequency: 1 };
|
||||||
|
const weeklysu = patern.weekly.$.su ? true : false;
|
||||||
|
const weeklymo = patern.weekly.$.mo ? true : false;
|
||||||
|
const weeklytu = patern.weekly.$.tu ? true : false;
|
||||||
|
const weeklywe = patern.weekly.$.we ? true : false;
|
||||||
|
const weeklyth = patern.weekly.$.th ? true : false;
|
||||||
|
const weeklyfr = patern.weekly.$.fr ? true : false;
|
||||||
|
const weeklysa = patern.weekly.$.sa ? true : false;
|
||||||
|
weeklyPatern = { su: weeklysu, mo: weeklymo, tu: weeklytu, we: weeklywe, th: weeklyth, fr: weeklyfr, sa: weeklysa };
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectDateRangeOption: string = 'noDate';
|
||||||
|
if (dateRange.repeatForever) {
|
||||||
|
selectDateRangeOption = 'noDate';
|
||||||
|
} else if (dateRange.repeatInstances) {
|
||||||
|
selectDateRangeOption = 'endAfter';
|
||||||
|
} else if (dateRange.windowEnd) {
|
||||||
|
selectDateRangeOption = 'endDate';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
console.log(selectDateRangeOption, new Date(moment(dateRange.windowEnd).format('YYYY/MM/DD')));
|
||||||
|
// weekday patern
|
||||||
|
this.setState({
|
||||||
|
weeklySunday: weeklyPatern.su,
|
||||||
|
weeklyMonday: weeklyPatern.mo,
|
||||||
|
weekklyTuesday: weeklyPatern.tu,
|
||||||
|
weekklyWednesday: weeklyPatern.we,
|
||||||
|
weekklyThursday: weeklyPatern.th,
|
||||||
|
weeklyFriday: weeklyPatern.fr,
|
||||||
|
weeklySaturday: weeklyPatern.sa,
|
||||||
|
selectPatern: weeklyPatern.weekFrequency,
|
||||||
|
numberOfWeeks: weeklyPatern.weekFrequency ? weeklyPatern.weekFrequency : '1',
|
||||||
|
selectdateRangeOption: selectDateRangeOption,
|
||||||
|
numberOcurrences: dateRange.repeatInstances ? dateRange.repeatInstances : '1',
|
||||||
|
disableNumberOcurrences: dateRange.repeatInstances ? false : true,
|
||||||
|
endDate: dateRange.windowEnd ? new Date(moment(dateRange.windowEnd).format('YYYY/MM/DD')) : this.state.endDate,
|
||||||
|
disableEndDate: dateRange.windowEnd ? false : true,
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
await this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async onApplyRecurrence(ev: React.MouseEvent<HTMLButtonElement>) {
|
||||||
|
await this.applyRecurrence();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.MouseEvent<HTMLButtonElement>} ev
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private async applyRecurrence() {
|
||||||
|
|
||||||
|
const siteTimeZoneHours: number = await this.spService.getSiteTimeZoneHours(this.props.siteUrl);
|
||||||
|
const eventDate = new Date(moment(this.state.startDate).add(siteTimeZoneHours, 'hours').toISOString());
|
||||||
|
const endDate = moment(this.state.endDate).add(siteTimeZoneHours, 'hours').toISOString();
|
||||||
|
let selectDateRangeOption;
|
||||||
|
switch (this.state.selectdateRangeOption) {
|
||||||
|
case 'noDate':
|
||||||
|
selectDateRangeOption = `<repeatForever>FALSE</repeatForever>`;
|
||||||
|
break;
|
||||||
|
case 'endAfter':
|
||||||
|
selectDateRangeOption = `<repeatInstances>${this.state.numberOcurrences.trim()}</repeatInstances>`;
|
||||||
|
break;
|
||||||
|
case 'endDate':
|
||||||
|
selectDateRangeOption = `<windowEnd>${endDate}</windowEnd>`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// test weekDays
|
||||||
|
let weekdays: string = '';
|
||||||
|
if (this.state.weeklySunday) {
|
||||||
|
weekdays = 'su="TRUE" ';
|
||||||
|
}
|
||||||
|
if (this.state.weeklyMonday) {
|
||||||
|
weekdays = `${weekdays} mo="TRUE"`;
|
||||||
|
}
|
||||||
|
if (this.state.weekklyTuesday) {
|
||||||
|
weekdays = `${weekdays} tu="TRUE"`;
|
||||||
|
}
|
||||||
|
if (this.state.weekklyWednesday) {
|
||||||
|
weekdays = `${weekdays} we="TRUE"`;
|
||||||
|
}
|
||||||
|
if (this.state.weekklyThursday) {
|
||||||
|
weekdays = `${weekdays} th="TRUE"`;
|
||||||
|
}
|
||||||
|
if (this.state.weeklyFriday) {
|
||||||
|
weekdays = `${weekdays} fr="TRUE"`;
|
||||||
|
}
|
||||||
|
if (this.state.weeklySaturday) {
|
||||||
|
weekdays = `${weekdays} sa="TRUE"`;
|
||||||
|
}
|
||||||
|
const recurrenceXML = `<recurrence><rule><firstDayOfWeek>su</firstDayOfWeek><repeat>` +
|
||||||
|
`<weekly ${weekdays} weekFrequency="${this.state.numberOfWeeks.trim()}" /></repeat>${selectDateRangeOption}</rule></recurrence>`;
|
||||||
|
console.log(recurrenceXML);
|
||||||
|
this.props.returnRecurrenceData(this.state.startDate, recurrenceXML);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onCheckboxSundayChange(ev: React.FormEvent<HTMLElement>, isChecked: boolean) {
|
||||||
|
this.setState({ weeklySunday: isChecked });
|
||||||
|
await this.applyRecurrence();
|
||||||
|
}
|
||||||
|
private async onCheckboxMondayChange(ev: React.FormEvent<HTMLElement>, isChecked: boolean) {
|
||||||
|
this.setState({ weeklyMonday: isChecked });
|
||||||
|
await this.applyRecurrence();
|
||||||
|
}
|
||||||
|
private async onCheckboxTuesdayChange(ev: React.FormEvent<HTMLElement>, isChecked: boolean) {
|
||||||
|
this.setState({ weekklyTuesday: isChecked });
|
||||||
|
await this.applyRecurrence();
|
||||||
|
}
|
||||||
|
private async onCheckboxWednesdayChange(ev: React.FormEvent<HTMLElement>, isChecked: boolean) {
|
||||||
|
this.setState({ weekklyWednesday: isChecked });
|
||||||
|
await this.applyRecurrence();
|
||||||
|
}
|
||||||
|
private async onCheckboxThursdayChange(ev: React.FormEvent<HTMLElement>, isChecked: boolean) {
|
||||||
|
this.setState({ weekklyThursday: isChecked });
|
||||||
|
await this.applyRecurrence();
|
||||||
|
}
|
||||||
|
private async onCheckboxFridayChange(ev: React.FormEvent<HTMLElement>, isChecked: boolean) {
|
||||||
|
this.setState({ weeklyFriday: isChecked });
|
||||||
|
await this.applyRecurrence();
|
||||||
|
}
|
||||||
|
private async onCheckboxSaturdayChange(ev: React.FormEvent<HTMLElement>, isChecked: boolean) {
|
||||||
|
this.setState({ weeklySaturday: isChecked });
|
||||||
|
await this.applyRecurrence();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @returns {React.ReactElement<IEventRecurrenceInfoWeeklyProps>}
|
||||||
|
* @memberof EventRecurrenceInfoWeekly
|
||||||
|
*/
|
||||||
|
public render(): React.ReactElement<IEventRecurrenceInfoWeeklyProps> {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
<div style={{ display: 'inline-block', float: 'right', paddingTop: '10px', height: '40px' }}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '100%', paddingTop: '10px' }}>
|
||||||
|
<Label>{strings.PaternLabel}</Label>
|
||||||
|
<div>
|
||||||
|
<Label styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '40px' } }}>{strings.every}</Label>
|
||||||
|
<MaskedTextField
|
||||||
|
styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '100px', paddingLeft: '5px' } }}
|
||||||
|
mask="999"
|
||||||
|
maskChar=' '
|
||||||
|
errorMessage={this.state.errorMessageNumberOfWeeks}
|
||||||
|
value={this.state.numberOfWeeks}
|
||||||
|
onChange={this.onNumberOfWeeksChange} />
|
||||||
|
<Label styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '80px', paddingLeft: '10px' } }}>{strings.WeeksOnLabel}</Label>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: '10px' }}>
|
||||||
|
<Checkbox label="Sunday" className={styles.ckeckBoxInline} checked={this.state.weeklySunday} onChange={this.onCheckboxSundayChange} />
|
||||||
|
<Checkbox label="Monday" className={styles.ckeckBoxInline} checked={this.state.weeklyMonday} onChange={this.onCheckboxMondayChange} />
|
||||||
|
<Checkbox label="Tuesday" className={styles.ckeckBoxInline} checked={this.state.weekklyTuesday} onChange={this.onCheckboxTuesdayChange} />
|
||||||
|
<Checkbox label="Wednesday" className={styles.ckeckBoxInline} checked={this.state.weekklyWednesday} onChange={this.onCheckboxWednesdayChange} />
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: '10px' }}>
|
||||||
|
<Checkbox label="Thursday" className={styles.ckeckBoxInline} checked={this.state.weekklyThursday} onChange={this.onCheckboxThursdayChange} />
|
||||||
|
<Checkbox label="Friday" className={styles.ckeckBoxInline} checked={this.state.weeklyFriday} onChange={this.onCheckboxFridayChange} />
|
||||||
|
<Checkbox label="Saturday" className={styles.ckeckBoxInline} checked={this.state.weeklySaturday} onChange={this.onCheckboxSaturdayChange} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ paddingTop: '22px' }}>
|
||||||
|
<Label>{strings.dateRangeLabel}</Label>
|
||||||
|
<div className={styles.dateRange}>
|
||||||
|
|
||||||
|
<DatePicker
|
||||||
|
firstDayOfWeek={DayOfWeek.Sunday}
|
||||||
|
strings={DayPickerStrings}
|
||||||
|
placeholder={strings.StartDatePlaceHolder}
|
||||||
|
ariaLabel={strings.StartDatePlaceHolder}
|
||||||
|
label={strings.StartDateLabel}
|
||||||
|
value={this.state.startDate}
|
||||||
|
onSelectDate={this.onStartDateChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingTop: '10px' }}>
|
||||||
|
<ChoiceGroup
|
||||||
|
selectedKey={this.state.selectdateRangeOption}
|
||||||
|
onChange={this.onDataRangeOptionChange}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
key: 'noDate',
|
||||||
|
text: strings.noEndDate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'endDate',
|
||||||
|
text: strings.EndByLabel,
|
||||||
|
onRenderField: (props, render) => {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
{render!(props)}
|
||||||
|
<DatePicker
|
||||||
|
firstDayOfWeek={DayOfWeek.Sunday}
|
||||||
|
strings={DayPickerStrings}
|
||||||
|
placeholder={strings.StartDatePlaceHolder}
|
||||||
|
ariaLabel={strings.StartDatePlaceHolder}
|
||||||
|
style={{ display: 'inline-block', verticalAlign: 'top', paddingLeft: '22px', }}
|
||||||
|
onSelectDate={this.onEndDateChange}
|
||||||
|
value={this.state.endDate}
|
||||||
|
disabled={this.state.disableEndDate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'endAfter',
|
||||||
|
text: strings.EndAfterLabel,
|
||||||
|
onRenderField: (props, render) => {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
{render!(props)}
|
||||||
|
<MaskedTextField
|
||||||
|
styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '100px', paddingLeft: '10px' } }}
|
||||||
|
mask="999"
|
||||||
|
maskChar=' '
|
||||||
|
value={this.state.numberOcurrences}
|
||||||
|
disabled={this.state.disableNumberOcurrences}
|
||||||
|
onChange={this.onNumberOfOcurrencesChange} />
|
||||||
|
<Label styles={{ root: { display: 'inline-block', verticalAlign: 'top', paddingLeft: '10px' } }}>{strings.OcurrencesLabel}</Label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||||
|
export interface IEventRecurrenceInfoWeeklyProps {
|
||||||
|
display:boolean;
|
||||||
|
recurrenceData: string;
|
||||||
|
startDate:Date;
|
||||||
|
context: WebPartContext;
|
||||||
|
siteUrl:string;
|
||||||
|
returnRecurrenceData: (startDate:Date,recurrenceData:string) => void;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
export interface IEventRecurrenceInfoWeeklyState {
|
||||||
|
selectedKey:string;
|
||||||
|
selectPatern:string;
|
||||||
|
startDate: Date;
|
||||||
|
endDate:Date;
|
||||||
|
numberOcurrences:string;
|
||||||
|
numberOfWeeks:string;
|
||||||
|
disableNumberOfWeeks: boolean;
|
||||||
|
disableNumberOcurrences: boolean;
|
||||||
|
selectdateRangeOption:string;
|
||||||
|
disableEndDate:boolean;
|
||||||
|
weeklySunday:boolean;
|
||||||
|
weeklyMonday:boolean;
|
||||||
|
weekklyTuesday:boolean;
|
||||||
|
weekklyWednesday:boolean;
|
||||||
|
weekklyThursday:boolean;
|
||||||
|
weeklyFriday:boolean;
|
||||||
|
weeklySaturday:boolean;
|
||||||
|
isLoading:boolean;
|
||||||
|
errorMessageNumberOfWeeks:string;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
.divWrraper {
|
||||||
|
border-width:1px;
|
||||||
|
border-color:#adadad;
|
||||||
|
padding: 20px;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
|
@ -0,0 +1,644 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './EventRecurrenceInfoYearly.module.scss';
|
||||||
|
import * as strings from 'CalendarWebPartStrings';
|
||||||
|
import { IEventRecurrenceInfoYearlyProps } from './IEventRecurrenceInfoYearlyProps';
|
||||||
|
import { IEventRecurrenceInfoYearlyState } from './IEventRecurrenceInfoYearlyState';
|
||||||
|
import { escape } from '@microsoft/sp-lodash-subset';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { parseString, Builder } from "xml2js";
|
||||||
|
import {
|
||||||
|
ChoiceGroup,
|
||||||
|
IChoiceGroupOption,
|
||||||
|
Dropdown,
|
||||||
|
IDropdownOption,
|
||||||
|
TextField,
|
||||||
|
SpinButton,
|
||||||
|
Label,
|
||||||
|
PrimaryButton,
|
||||||
|
MaskedTextField,
|
||||||
|
CommandBarButton, IButtonProps,
|
||||||
|
DefaultButton
|
||||||
|
} from 'office-ui-fabric-react';
|
||||||
|
import { Position } from 'office-ui-fabric-react/lib/utilities/positioning';
|
||||||
|
import { Root } from '@pnp/graph';
|
||||||
|
import { DatePicker, DayOfWeek, IDatePickerStrings } from 'office-ui-fabric-react/lib/DatePicker';
|
||||||
|
|
||||||
|
import spservices from '../../services/spservices';
|
||||||
|
|
||||||
|
const DayPickerStrings: IDatePickerStrings = {
|
||||||
|
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||||||
|
|
||||||
|
shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||||
|
|
||||||
|
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
||||||
|
|
||||||
|
shortDays: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
|
||||||
|
|
||||||
|
goToToday: 'Go to today',
|
||||||
|
prevMonthAriaLabel: 'Go to previous month',
|
||||||
|
nextMonthAriaLabel: 'Go to next month',
|
||||||
|
prevYearAriaLabel: 'Go to previous year',
|
||||||
|
nextYearAriaLabel: 'Go to next year',
|
||||||
|
closeButtonAriaLabel: 'Close date picker'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class EventRecurrenceInfoDaily
|
||||||
|
* @extends {React.Component<IEventRecurrenceInfoYearlyProps, IEventRecurrenceInfoYearlyState>}
|
||||||
|
*/
|
||||||
|
export class EventRecurrenceInfoYearly extends React.Component<IEventRecurrenceInfoYearlyProps, IEventRecurrenceInfoYearlyState> {
|
||||||
|
private spService: spservices = null;
|
||||||
|
public constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
|
||||||
|
this.onPaternChange = this.onPaternChange.bind(this);
|
||||||
|
this.state = {
|
||||||
|
selectedKey: 'daily',
|
||||||
|
selectPatern: 'yearly',
|
||||||
|
startDate: this.props.startDate ? this.props.startDate : moment().toDate(),
|
||||||
|
endDate: moment().endOf('month').toDate(),
|
||||||
|
numberOcurrences: '1',
|
||||||
|
disableDayOfMonth: false,
|
||||||
|
disableNumberOcurrences: true,
|
||||||
|
selectdateRangeOption: 'noDate',
|
||||||
|
disableEndDate: true,
|
||||||
|
selectedRecurrenceRule: 'yearly',
|
||||||
|
dayOfMonth: this.props.startDate ? moment(this.props.startDate).format('D') : moment().format('D'),
|
||||||
|
isLoading: false,
|
||||||
|
errorMessageDayOfMonth: '',
|
||||||
|
selectedWeekOrderMonth: 'first',
|
||||||
|
selectedWeekDay: 'day',
|
||||||
|
selectedMonth: moment().format('M'),
|
||||||
|
selectedYearlyByDayMonth: moment().format('M'),
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
this.onDayOfMonthChange = this.onDayOfMonthChange.bind(this);
|
||||||
|
this.onNumberOfOcurrencesChange = this.onNumberOfOcurrencesChange.bind(this);
|
||||||
|
this.onDataRangeOptionChange = this.onDataRangeOptionChange.bind(this);
|
||||||
|
this.onEndDateChange = this.onEndDateChange.bind(this);
|
||||||
|
this.onStartDateChange = this.onStartDateChange.bind(this);
|
||||||
|
this.onApplyRecurrence = this.onApplyRecurrence.bind(this);
|
||||||
|
this.onYearlyByDayMonthChange = this.onYearlyByDayMonthChange.bind(this);
|
||||||
|
this.onSelectedWeekDayChange = this.onSelectedWeekDayChange.bind(this);
|
||||||
|
this.onWeekOrderMonthChange = this.onWeekOrderMonthChange.bind(this);
|
||||||
|
this.onMonthChange = this.onMonthChange.bind(this);
|
||||||
|
|
||||||
|
this.spService = new spservices(this.props.context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Date} date
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onStartDateChange(date: Date) {
|
||||||
|
this.setState({ startDate: date });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Date} date
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onEndDateChange(date: Date) {
|
||||||
|
this.setState({ endDate: date });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.SyntheticEvent<HTMLElement>} ev
|
||||||
|
* @param {string} value
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onDayOfMonthChange(ev: React.SyntheticEvent<HTMLElement>, value: string) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
let errorMessage = '';
|
||||||
|
if (Number(value.trim()) < 1 || Number(value.trim()) > 31) {
|
||||||
|
value = '1 ';
|
||||||
|
errorMessage = 'Allowed values 1 to 31';
|
||||||
|
|
||||||
|
}
|
||||||
|
this.setState({ dayOfMonth: value, errorMessageDayOfMonth: errorMessage });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private onMonthChange(ev: React.SyntheticEvent<HTMLElement>, item: IDropdownOption) {
|
||||||
|
this.setState({ selectedMonth: item.key });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.SyntheticEvent<HTMLElement>} ev
|
||||||
|
* @param {string} value
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onNumberOfOcurrencesChange(ev: React.SyntheticEvent<HTMLElement>, value: string) {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.setState({ numberOcurrences: value });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.SyntheticEvent<HTMLElement>} ev
|
||||||
|
* @param {IChoiceGroupOption} option
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private onDataRangeOptionChange(ev: React.SyntheticEvent<HTMLElement>, option: IChoiceGroupOption): void {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selectdateRangeOption: option.key,
|
||||||
|
disableNumberOcurrences: option.key == 'endAfter' ? false : true,
|
||||||
|
disableEndDate: option.key == 'endDate' ? false : true,
|
||||||
|
});
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.SyntheticEvent<HTMLElement>} ev
|
||||||
|
* @param {IChoiceGroupOption} option
|
||||||
|
* @memberof EventRecurrenceInfoYearly
|
||||||
|
*/
|
||||||
|
private onPaternChange(ev: React.SyntheticEvent<HTMLElement>, option: IChoiceGroupOption): void {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.setState({
|
||||||
|
selectPatern: option.key,
|
||||||
|
disableDayOfMonth: option.key == 'yearly' ? false : true,
|
||||||
|
});
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async componentDidMount() {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
public async componentWillMount() {
|
||||||
|
await this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.FormEvent<HTMLDivElement>} ev
|
||||||
|
* @param {IDropdownOption} item
|
||||||
|
* @memberof EventRecurrenceInfoYearly
|
||||||
|
*/
|
||||||
|
private onWeekOrderMonthChange(ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void {
|
||||||
|
this.setState({ selectedWeekOrderMonth: item.text });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.FormEvent<HTMLDivElement>} ev
|
||||||
|
* @param {IDropdownOption} item
|
||||||
|
* @memberof EventRecurrenceInfoYearly
|
||||||
|
*/
|
||||||
|
private onYearlyByDayMonthChange(ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void {
|
||||||
|
this.setState({ selectedYearlyByDayMonth: item.key });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.FormEvent<HTMLDivElement>} ev
|
||||||
|
* @param {IDropdownOption} item
|
||||||
|
* @memberof EventRecurrenceInfoYearly
|
||||||
|
*/
|
||||||
|
private onSelectedWeekDayChange(ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void {
|
||||||
|
this.setState({ selectedWeekDay: item.text });
|
||||||
|
this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async componentDidUpdate(prevProps: IEventRecurrenceInfoYearlyProps, prevState: IEventRecurrenceInfoYearlyState) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @memberof EventRecurrenceInfoYearly
|
||||||
|
*/
|
||||||
|
private async load() {
|
||||||
|
let patern: any = {};
|
||||||
|
let dateRange: { repeatForever?: string, repeatInstances?: string, windowEnd?: Date } = {};
|
||||||
|
let yearlyPatern: { yearFrequency?: string, day?: string, month?: string } = {};
|
||||||
|
let yearlyByDayPatern: { yearFrequency?: string, weekdayOfMonth?: string, weekDay?: string, month?: string } = {};
|
||||||
|
let recurrenceRule: string;
|
||||||
|
|
||||||
|
if (this.props.recurrenceData) {
|
||||||
|
|
||||||
|
parseString(this.props.recurrenceData, { explicitArray: false }, (error, result) => {
|
||||||
|
|
||||||
|
if (result.recurrence.rule.repeat) {
|
||||||
|
patern = result.recurrence.rule.repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
if (result.recurrence.rule.repeatForever) {
|
||||||
|
dateRange = { repeatForever: result.recurrence.rule.repeatForever };
|
||||||
|
}
|
||||||
|
if (result.recurrence.rule.repeatInstances) {
|
||||||
|
dateRange = { repeatInstances: result.recurrence.rule.repeatInstances };
|
||||||
|
}
|
||||||
|
if (result.recurrence.rule.windowEnd) {
|
||||||
|
dateRange = { windowEnd: result.recurrence.rule.windowEnd };
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
// yearly Patern
|
||||||
|
if (patern.yearly) {
|
||||||
|
recurrenceRule = 'yearly';
|
||||||
|
if (patern.yearly.$.yearFrequency && patern.yearly.$.day) {
|
||||||
|
yearlyPatern = { yearFrequency: patern.yearly.$.yearFrequency, day: patern.yearly.$.day, month: patern.yearly.$.month };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// yearlyByDay Patern
|
||||||
|
if (patern.yearlyByDay) {
|
||||||
|
recurrenceRule = 'yearly';
|
||||||
|
let weekDay = 'day';
|
||||||
|
|
||||||
|
if (patern.yearlyByDay.$.su) weekDay = 'sunday';
|
||||||
|
if (patern.yearlyByDay.$.mo) weekDay = 'monday';
|
||||||
|
if (patern.yearlyByDay.$.tu) weekDay = 'tuesday';
|
||||||
|
if (patern.yearlyByDay.$.we) weekDay = 'wednesday';
|
||||||
|
if (patern.yearlyByDay.$.th) weekDay = 'thursday';
|
||||||
|
if (patern.yearlyByDay.$.fr) weekDay = 'friday';
|
||||||
|
if (patern.yearlyByDay.$.sa) weekDay = 'saturday';
|
||||||
|
if (patern.yearlyByDay.$.day) weekDay = 'day';
|
||||||
|
if (patern.yearlyByDay.$.weekday) weekDay = 'weekday';
|
||||||
|
if (patern.yearlyByDay.$.weekend_day) weekDay = 'weekdendday';
|
||||||
|
|
||||||
|
yearlyByDayPatern = {
|
||||||
|
yearFrequency: patern.yearlyByDay.$.yearFrequency,
|
||||||
|
weekdayOfMonth: patern.yearlyByDay.$.weekdayOfMonth,
|
||||||
|
weekDay: weekDay,
|
||||||
|
month: patern.yearlyByDay.$.month,
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectDateRangeOption: string = 'noDate';
|
||||||
|
if (dateRange.repeatForever) {
|
||||||
|
selectDateRangeOption = 'noDate';
|
||||||
|
} else if (dateRange.repeatInstances) {
|
||||||
|
selectDateRangeOption = 'endAfter';
|
||||||
|
} else if (dateRange.windowEnd) {
|
||||||
|
selectDateRangeOption = 'endDate';
|
||||||
|
}
|
||||||
|
|
||||||
|
// weekday patern
|
||||||
|
this.setState({
|
||||||
|
selectedRecurrenceRule: recurrenceRule,
|
||||||
|
selectPatern: patern.yearly ? 'yearly' : 'yearlyByDay',
|
||||||
|
dayOfMonth: yearlyPatern.day ? yearlyPatern.day : '1',
|
||||||
|
selectedMonth: yearlyPatern.month ? yearlyPatern.month : moment().month(),
|
||||||
|
selectedYearlyByDayMonth: yearlyByDayPatern.month ? yearlyByDayPatern.month : moment().format('M'),
|
||||||
|
selectedWeekOrderMonth: yearlyByDayPatern.weekdayOfMonth ? yearlyByDayPatern.weekdayOfMonth : 'first',
|
||||||
|
selectedWeekDay: yearlyByDayPatern.weekDay ? yearlyByDayPatern.weekDay : 'day',
|
||||||
|
disableDayOfMonth: patern.yearly ? false : true,
|
||||||
|
selectdateRangeOption: selectDateRangeOption,
|
||||||
|
numberOcurrences: dateRange.repeatInstances ? dateRange.repeatInstances : '10',
|
||||||
|
disableNumberOcurrences: dateRange.repeatInstances ? false : true,
|
||||||
|
endDate: dateRange.windowEnd ? new Date(moment(dateRange.windowEnd).format('YYYY/MM/DD')) : this.state.endDate,
|
||||||
|
disableEndDate: dateRange.windowEnd ? false : true,
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await this.applyRecurrence();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.MouseEvent<HTMLButtonElement>} ev
|
||||||
|
* @memberof EventRecurrenceInfoYearly
|
||||||
|
*/
|
||||||
|
private async onApplyRecurrence(ev: React.MouseEvent<HTMLButtonElement>) {
|
||||||
|
await this.applyRecurrence();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {React.MouseEvent<HTMLButtonElement>} ev
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
private async applyRecurrence() {
|
||||||
|
|
||||||
|
const siteTimeZoneHours: number = await this.spService.getSiteTimeZoneHours(this.props.siteUrl);
|
||||||
|
const eventDate = new Date(moment(this.state.startDate).add(siteTimeZoneHours, 'hours').toISOString());
|
||||||
|
const endDate = moment(this.state.endDate).add(siteTimeZoneHours, 'hours').toISOString();
|
||||||
|
let selectDateRangeOption;
|
||||||
|
switch (this.state.selectdateRangeOption) {
|
||||||
|
case 'noDate':
|
||||||
|
selectDateRangeOption = `<repeatForever>FALSE</repeatForever>`;
|
||||||
|
break;
|
||||||
|
case 'endAfter':
|
||||||
|
selectDateRangeOption = `<repeatInstances>${this.state.numberOcurrences}</repeatInstances>`;
|
||||||
|
break;
|
||||||
|
case 'endDate':
|
||||||
|
selectDateRangeOption = `<windowEnd>${endDate}</windowEnd>`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let recurrencePatern: string = '';
|
||||||
|
if (this.state.selectPatern == 'yearly') {
|
||||||
|
recurrencePatern = `<yearly yearFrequency="1" day="${this.state.dayOfMonth}" month="${this.state.selectedMonth}" /></repeat>${selectDateRangeOption}</rule></recurrence>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.selectPatern == 'yearlyByDay') {
|
||||||
|
|
||||||
|
recurrencePatern = `<yearlyByDay weekdayOfMonth="${this.state.selectedWeekOrderMonth}" month="${this.state.selectedYearlyByDayMonth}"`;
|
||||||
|
|
||||||
|
switch (this.state.selectedWeekDay) {
|
||||||
|
case 'day':
|
||||||
|
recurrencePatern = recurrencePatern + `day="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'weekday':
|
||||||
|
recurrencePatern = recurrencePatern + `weekday="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'weekendday':
|
||||||
|
recurrencePatern = recurrencePatern + `weekend_day="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'sunday':
|
||||||
|
recurrencePatern = recurrencePatern + `su="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'monday':
|
||||||
|
recurrencePatern = recurrencePatern + `mo="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'tuesday':
|
||||||
|
recurrencePatern = recurrencePatern + `tu="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'wednesday':
|
||||||
|
recurrencePatern = recurrencePatern + `we="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'thursday':
|
||||||
|
recurrencePatern = recurrencePatern + `th="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'friday':
|
||||||
|
recurrencePatern = recurrencePatern + `fr="TRUE"`;
|
||||||
|
break;
|
||||||
|
case 'saturday':
|
||||||
|
recurrencePatern = recurrencePatern + `sa="TRUE"`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
recurrencePatern = recurrencePatern + ` yearFrequency="1" /></repeat>${selectDateRangeOption}</rule></recurrence>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recurrenceXML = `<recurrence><rule><firstDayOfWeek>su</firstDayOfWeek><repeat>` + recurrencePatern;
|
||||||
|
|
||||||
|
|
||||||
|
this.props.returnRecurrenceData(this.state.startDate, recurrenceXML);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @returns {React.ReactElement<IEventRecurrenceInfoDailyProps>}
|
||||||
|
* @memberof EventRecurrenceInfoDaily
|
||||||
|
*/
|
||||||
|
public render(): React.ReactElement<IEventRecurrenceInfoYearlyProps> {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
<div style={{ display: 'inline-block', float: 'right', paddingTop: '10px', height: '40px' }}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '100%', paddingTop: '10px' }}>
|
||||||
|
<Label>{strings.PaternLabel}</Label>
|
||||||
|
<ChoiceGroup
|
||||||
|
selectedKey={this.state.selectPatern}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
key: 'yearly',
|
||||||
|
text: strings.every,
|
||||||
|
ariaLabel: strings.every,
|
||||||
|
onRenderField: (props, render) => {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
{render!(props)}
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', width: '100px', paddingLeft: '10px' }}>
|
||||||
|
<Dropdown
|
||||||
|
selectedKey={this.state.selectedMonth}
|
||||||
|
onChange={this.onMonthChange}
|
||||||
|
disabled={this.state.disableDayOfMonth}
|
||||||
|
options={[
|
||||||
|
{ key: '1', text: strings.January },
|
||||||
|
{ key: '2', text: strings.February },
|
||||||
|
{ key: '3', text: strings.March },
|
||||||
|
{ key: '4', text: strings.April },
|
||||||
|
{ key: '5', text: strings.May },
|
||||||
|
{ key: '6', text: strings.June },
|
||||||
|
{ key: '7', text: strings.July },
|
||||||
|
{ key: '8', text: strings.August },
|
||||||
|
{ key: '9', text: strings.September },
|
||||||
|
{ key: '10', text: strings.October },
|
||||||
|
{ key: '11', text: strings.November },
|
||||||
|
{ key: '12', text: strings.December },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MaskedTextField
|
||||||
|
styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '100px', paddingLeft: '10px' } }}
|
||||||
|
mask="99"
|
||||||
|
maskChar=' '
|
||||||
|
disabled={this.state.disableDayOfMonth}
|
||||||
|
value={this.state.dayOfMonth}
|
||||||
|
errorMessage={this.state.errorMessageDayOfMonth}
|
||||||
|
onChange={this.onDayOfMonthChange} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'yearlyByDay',
|
||||||
|
text: strings.theLabel,
|
||||||
|
onRenderField: (props, render) => {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
{render!(props)}
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', width: '80px', paddingLeft: '10px' }}>
|
||||||
|
<Dropdown
|
||||||
|
selectedKey={this.state.selectedWeekOrderMonth}
|
||||||
|
onChange={this.onWeekOrderMonthChange}
|
||||||
|
disabled={!this.state.disableDayOfMonth}
|
||||||
|
options={[
|
||||||
|
{ key: 'first', text: strings.firstLabel },
|
||||||
|
{ key: 'second', text: strings.secondLabel },
|
||||||
|
{ key: 'third', text: strings.thirdLabel },
|
||||||
|
{ key: 'fourth', text: strings.fourthLabel },
|
||||||
|
{ key: 'last', text: strings.lastLabel },
|
||||||
|
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', width: '100px', paddingLeft: '5px' }}>
|
||||||
|
<Dropdown
|
||||||
|
selectedKey={this.state.selectedWeekDay}
|
||||||
|
disabled={!this.state.disableDayOfMonth}
|
||||||
|
onChange={this.onSelectedWeekDayChange}
|
||||||
|
options={[
|
||||||
|
{ key: 'day', text: strings.dayLable },
|
||||||
|
{ key: 'weekday', text: strings.weekDayLabel },
|
||||||
|
{ key: 'weekendday', text: strings.weekEndDay },
|
||||||
|
{ key: 'sunday', text: strings.Sunday },
|
||||||
|
{ key: 'monday', text: strings.Monday },
|
||||||
|
{ key: 'tuesday', text: strings.Tuesday },
|
||||||
|
{ key: 'wednesday', text: strings.Wednesday },
|
||||||
|
{ key: 'thursday', text: strings.Thursday },
|
||||||
|
{ key: 'friday', text: strings.Friday },
|
||||||
|
{ key: 'saturday', text: strings.Saturday },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Label styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '30px', paddingLeft: '10px' } }}>of</Label>
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', width: '100px', paddingLeft: '5px' }}>
|
||||||
|
<Dropdown
|
||||||
|
selectedKey={this.state.selectedYearlyByDayMonth}
|
||||||
|
onChange={this.onYearlyByDayMonthChange}
|
||||||
|
disabled={!this.state.disableDayOfMonth}
|
||||||
|
options={[
|
||||||
|
{ key: '1', text: strings.January },
|
||||||
|
{ key: '2', text: strings.February },
|
||||||
|
{ key: '3', text: strings.March },
|
||||||
|
{ key: '4', text: strings.April },
|
||||||
|
{ key: '5', text: strings.May },
|
||||||
|
{ key: '6', text: strings.June },
|
||||||
|
{ key: '7', text: strings.July },
|
||||||
|
{ key: '8', text: strings.August },
|
||||||
|
{ key: '9', text: strings.September },
|
||||||
|
{ key: '10', text: strings.October },
|
||||||
|
{ key: '11', text: strings.November },
|
||||||
|
{ key: '12', text: strings.December },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
onChange={this.onPaternChange}
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ paddingTop: '22px' }}>
|
||||||
|
<Label>Date Range</Label>
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingRight: '35px', paddingTop: '10px' }}>
|
||||||
|
|
||||||
|
<DatePicker
|
||||||
|
firstDayOfWeek={DayOfWeek.Sunday}
|
||||||
|
strings={DayPickerStrings}
|
||||||
|
placeholder={strings.StartDatePlaceHolder}
|
||||||
|
ariaLabel={strings.StartDatePlaceHolder}
|
||||||
|
label={strings.StartDateLabel}
|
||||||
|
value={this.state.startDate}
|
||||||
|
onSelectDate={this.onStartDateChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingTop: '10px' }}>
|
||||||
|
<ChoiceGroup
|
||||||
|
selectedKey={this.state.selectdateRangeOption}
|
||||||
|
onChange={this.onDataRangeOptionChange}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
key: 'noDate',
|
||||||
|
text: strings.noEndDate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'endDate',
|
||||||
|
text: strings.EndByLabel,
|
||||||
|
onRenderField: (props, render) => {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
{render!(props)}
|
||||||
|
<DatePicker
|
||||||
|
firstDayOfWeek={DayOfWeek.Sunday}
|
||||||
|
strings={DayPickerStrings}
|
||||||
|
placeholder={strings.StartDatePlaceHolder}
|
||||||
|
ariaLabel={strings.StartDatePlaceHolder}
|
||||||
|
style={{ display: 'inline-block', verticalAlign: 'top', paddingLeft: '22px', }}
|
||||||
|
onSelectDate={this.onEndDateChange}
|
||||||
|
value={this.state.endDate}
|
||||||
|
disabled={this.state.disableEndDate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'endAfter',
|
||||||
|
text: strings.EndAfterLabel,
|
||||||
|
onRenderField: (props, render) => {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
{render!(props)}
|
||||||
|
<MaskedTextField
|
||||||
|
styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '100px', paddingLeft: '10px' } }}
|
||||||
|
mask="999"
|
||||||
|
maskChar=' '
|
||||||
|
value={this.state.numberOcurrences}
|
||||||
|
disabled={this.state.disableNumberOcurrences}
|
||||||
|
onChange={this.onNumberOfOcurrencesChange} />
|
||||||
|
<Label styles={{ root: { display: 'inline-block', verticalAlign: 'top', paddingLeft: '10px' } }}>{strings.OcurrencesLabel}</Label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||||
|
export interface IEventRecurrenceInfoYearlyProps {
|
||||||
|
display:boolean;
|
||||||
|
recurrenceData: string;
|
||||||
|
startDate:Date;
|
||||||
|
context: WebPartContext;
|
||||||
|
siteUrl:string;
|
||||||
|
returnRecurrenceData: (startDate:Date,recurrenceData:string) => void;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
export interface IEventRecurrenceInfoYearlyState {
|
||||||
|
selectedKey:string;
|
||||||
|
selectPatern:string;
|
||||||
|
startDate: Date;
|
||||||
|
endDate:Date;
|
||||||
|
numberOcurrences:string;
|
||||||
|
dayOfMonth:string;
|
||||||
|
|
||||||
|
disableDayOfMonth: boolean;
|
||||||
|
disableNumberOcurrences: boolean;
|
||||||
|
selectdateRangeOption:string;
|
||||||
|
disableEndDate:boolean;
|
||||||
|
selectedRecurrenceRule:string;
|
||||||
|
isLoading:boolean;
|
||||||
|
errorMessageDayOfMonth:string;
|
||||||
|
selectedWeekOrderMonth:string;
|
||||||
|
selectedWeekDay:string;
|
||||||
|
selectedMonth:string | number;
|
||||||
|
selectedYearlyByDayMonth: string | number;
|
||||||
|
}
|
|
@ -1,17 +1,25 @@
|
||||||
export interface IEventData {
|
export interface IEventData {
|
||||||
id?:number;
|
Id?:number;
|
||||||
|
ID?:number;
|
||||||
title: string;
|
title: string;
|
||||||
Description?: any;
|
Description?: any;
|
||||||
location?:string;
|
location?:string;
|
||||||
start: Date;
|
EventDate: Date;
|
||||||
end: Date;
|
EndDate: Date;
|
||||||
color?:string;
|
color?:string;
|
||||||
ownerInitial?: string;
|
ownerInitial?: string;
|
||||||
ownerPhoto?:string;
|
ownerPhoto?:string;
|
||||||
ownerEmail?:string;
|
ownerEmail?:string;
|
||||||
ownerName?:string;
|
ownerName?:string;
|
||||||
allDayEvent?: boolean;
|
fAllDayEvent?: boolean;
|
||||||
attendes?: number[];
|
attendes?: number[];
|
||||||
geolocation?: {Longitude:number, Latitude: number};
|
geolocation?: {Longitude:number, Latitude: number};
|
||||||
Category?: string;
|
Category?: string;
|
||||||
|
Duration?: number;
|
||||||
|
RecurrenceData?:string;
|
||||||
|
fRecurrence?:string | boolean;
|
||||||
|
EventType?:string;
|
||||||
|
UID?:string;
|
||||||
|
RecurrenceID?: string;
|
||||||
|
MasterSeriesItemID?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
export default class parseRecurrentEvent {
|
||||||
|
constructor();
|
||||||
|
parseEvents(events: any, start: any, end: any): any[];
|
||||||
|
formatString(str: any): any;
|
||||||
|
parseDate(date: any, allDay: any): any;
|
||||||
|
parseEvent(e: any, start: any, end: any): any[];
|
||||||
|
cloneObj(obj: any): any;
|
||||||
|
}
|
|
@ -0,0 +1,401 @@
|
||||||
|
|
||||||
|
import * as moment from 'moment';
|
||||||
|
export default class parseRecurrentEvent {
|
||||||
|
|
||||||
|
private wEvents: any[] = [];
|
||||||
|
private full: any[] = [] ;
|
||||||
|
public parseEvents(events: any[], start: any, end: any) {
|
||||||
|
|
||||||
|
this.wEvents = events;
|
||||||
|
for (var i = 0; i < events.length; i++) {
|
||||||
|
end = null;
|
||||||
|
if (events[i].RecurrenceData.indexOf('<windowEnd>') != -1) {
|
||||||
|
let wDtEnd = events[i].RecurrenceData.substring(events[i].RecurrenceData.indexOf("<windowEnd>") + 11);
|
||||||
|
|
||||||
|
wDtEnd = wDtEnd.substring(0, wDtEnd.indexOf('<'));
|
||||||
|
end = moment(wDtEnd).toDate();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.full = this.full.concat(this.parseEvent(events[i], start, end));
|
||||||
|
}
|
||||||
|
// remove deleted recurrences EventType = 3
|
||||||
|
|
||||||
|
this.full = this.full.filter( (el,j)=>{
|
||||||
|
if (el.EventType != '3'){
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this.full;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RecurrenceExceptionExists(masterSeriesItemId, date) {
|
||||||
|
const found = this.wEvents.filter((el,i) => {
|
||||||
|
|
||||||
|
if (moment(el.RecurrenceID).isSame(moment(date)) && el.MasterSeriesItemID == masterSeriesItemId ) {
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return found.length > 0 ? true : false;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
|
||||||
|
public formatString(str: string) {
|
||||||
|
var arr = str.split("'");
|
||||||
|
str = arr.join('');
|
||||||
|
arr = str.split('"');
|
||||||
|
str = arr.join('');
|
||||||
|
arr = str.split('=');
|
||||||
|
str = arr.join(' ');
|
||||||
|
str.trim();
|
||||||
|
return str.split(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public parseDate(date: any, allDay: any) {
|
||||||
|
if (typeof date == 'string') {
|
||||||
|
if (allDay) {
|
||||||
|
if (date.lastIndexOf('Z') == date.length - 1) {
|
||||||
|
var dt = date.substring(0, date.length - 1);
|
||||||
|
return new Date(dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new Date(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
public parseEvent(e: any, start: any, end: any) {
|
||||||
|
if (e.fRecurrence == '0' || e.fRecurrence == '4') {
|
||||||
|
e.EventDate = new Date(this.parseDate(e.EventDate, e.fAllDayEvent));
|
||||||
|
e.EndDate = new Date(this.parseDate(e.EndDate, e.fAllDayEvent));
|
||||||
|
return [e];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
start = start || this.parseDate(e.EventDate, e.fAllDayEvent);
|
||||||
|
end = end || this.parseDate(e.EndDate, e.fAllDayEvent);
|
||||||
|
var er = [];
|
||||||
|
var wd = ['su', 'mo', 'tu', 'we', 'th', 'fr', 'sa'];
|
||||||
|
var wom = ['first', 'second', 'third', 'fourth'];
|
||||||
|
var rTotal: any = 0;
|
||||||
|
var total: any = 0;
|
||||||
|
if (e.RecurrenceData.indexOf('<repeatInstances>') != -1) {
|
||||||
|
rTotal = e.RecurrenceData.substring(e.RecurrenceData.indexOf("<repeatInstances>") + 17);
|
||||||
|
rTotal = parseInt(rTotal.substring(0, rTotal.indexOf('<')));
|
||||||
|
}
|
||||||
|
if (e.RecurrenceData.indexOf("<daily ") != -1) {
|
||||||
|
var str = e.RecurrenceData.substring(e.RecurrenceData.indexOf("<daily "));
|
||||||
|
str = str.substring(7, str.indexOf('/>') - 1);
|
||||||
|
var arr = this.formatString(str);
|
||||||
|
if (arr.indexOf("dayFrequency") != -1) {
|
||||||
|
var frequency = parseInt(arr[arr.indexOf("dayFrequency") + 1]);
|
||||||
|
var loop = true;
|
||||||
|
var init = this.parseDate(e.EventDate, e.fAllDayEvent);
|
||||||
|
while (loop) {
|
||||||
|
total++;
|
||||||
|
if ((new Date(init)).getTime() >= start.getTime()) {
|
||||||
|
var ed = new Date(init);
|
||||||
|
ed.setSeconds(ed.getSeconds() + e.Duration);
|
||||||
|
var ni = this.cloneObj(e);
|
||||||
|
ni.EventDate = new Date(init);
|
||||||
|
if (!this.RecurrenceExceptionExists(e.Id, ni.EventDate)) {
|
||||||
|
ni.EndDate = ed;
|
||||||
|
ni.fRecurrence = false;
|
||||||
|
ni.Id = e.Id;
|
||||||
|
ni.ID = e.Id;
|
||||||
|
er.push(ni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
init.setDate(init.getDate() + frequency);
|
||||||
|
if ((new Date(init) > end) || (rTotal > 0 && rTotal <= total)) loop = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (arr.indexOf("weekday") != -1) {
|
||||||
|
e.RecurrenceData = e.RecurrenceData + "<weekly mo='TRUE' tu='TRUE' we='TRUE' th='TRUE' fr='TRUE' weekFrequency='1' />";//change from daily on every weekday to weekly on every weekday
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (e.RecurrenceData.indexOf("<weekly ") != -1) {
|
||||||
|
var str = e.RecurrenceData.substring(e.RecurrenceData.indexOf("<weekly "));
|
||||||
|
str = str.substring(8, str.indexOf('/>') - 1);
|
||||||
|
var arr = this.formatString(str);
|
||||||
|
var frequency = parseInt(arr[arr.indexOf("weekFrequency") + 1]);
|
||||||
|
var loop = true;
|
||||||
|
var init = this.parseDate(e.EventDate, e.fAllDayEvent);
|
||||||
|
var initDay = init.getDay();
|
||||||
|
while (loop) {
|
||||||
|
for (var i = initDay; i < 7; i++) {
|
||||||
|
if (arr.indexOf(wd[i]) != -1 && (rTotal > total || rTotal == 0)) {
|
||||||
|
total++;
|
||||||
|
if ((new Date(init)).getTime() >= start.getTime()) {
|
||||||
|
var nd: any = new Date(init);
|
||||||
|
nd.setDate(nd.getDate() + (i - initDay));
|
||||||
|
var ed = new Date(nd);
|
||||||
|
ed.setSeconds(ed.getSeconds() + e.Duration);
|
||||||
|
var ni = this.cloneObj(e);
|
||||||
|
ni.EventDate = new Date(nd);
|
||||||
|
if (!this.RecurrenceExceptionExists(e.Id, ni.EventDate)) {
|
||||||
|
ni.EndDate = ed;
|
||||||
|
ni.fRecurrence = false;
|
||||||
|
ni.Id = e.Id;
|
||||||
|
ni.ID = e.Id;
|
||||||
|
er.push(ni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
init.setDate(init.getDate() + ((7 * frequency) - initDay));
|
||||||
|
initDay = 0;
|
||||||
|
if ((new Date(init) > end) || (rTotal > 0 && rTotal <= total)) loop = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (e.RecurrenceData.indexOf("<monthly ") != -1) {
|
||||||
|
var str = e.RecurrenceData.substring(e.RecurrenceData.indexOf("<monthly "));
|
||||||
|
str = str.substring(9, str.indexOf('/>') - 1);
|
||||||
|
var arr = this.formatString(str);
|
||||||
|
var frequency = parseInt(arr[arr.indexOf("monthFrequency") + 1]);
|
||||||
|
var loop = true;
|
||||||
|
var init = this.parseDate(e.EventDate, e.fAllDayEvent);
|
||||||
|
var day = parseInt(arr[arr.indexOf("day") + 1]);
|
||||||
|
while (loop) {
|
||||||
|
total++;
|
||||||
|
if ((new Date(init)).getTime() >= start.getTime()) {
|
||||||
|
var nd: any = new Date(init);
|
||||||
|
nd.setDate(day);
|
||||||
|
if (nd.getMonth() == init.getMonth()) {
|
||||||
|
var ed = new Date(nd);
|
||||||
|
ed.setSeconds(ed.getSeconds() + e.Duration);
|
||||||
|
var ni = this.cloneObj(e);
|
||||||
|
ni.EventDate = new Date(nd);
|
||||||
|
if (!this.RecurrenceExceptionExists(e.Id, ni.EventDate)) {
|
||||||
|
ni.EndDate = ed;
|
||||||
|
ni.fRecurrence = false;
|
||||||
|
ni.Id = e.Id;
|
||||||
|
ni.ID = e.Id;
|
||||||
|
er.push(ni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
init.setMonth(init.getMonth() + frequency);
|
||||||
|
if ((new Date(init) > end) || (rTotal > 0 && rTotal <= total)) loop = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (e.RecurrenceData.indexOf("<monthlyByDay ") != -1) {
|
||||||
|
var str = e.RecurrenceData.substring(e.RecurrenceData.indexOf("<monthlyByDay "));
|
||||||
|
str = str.substring(14, str.indexOf('/>') - 1);
|
||||||
|
var arr = this.formatString(str);
|
||||||
|
var frequency = parseInt(arr[arr.indexOf("monthFrequency") + 1]);
|
||||||
|
var loop = true;
|
||||||
|
var init = this.parseDate(e.EventDate, e.fAllDayEvent);
|
||||||
|
var weekdayOfMonth = arr[arr.indexOf("weekdayOfMonth") + 1];
|
||||||
|
var temp: any = new Date();
|
||||||
|
while (loop) {
|
||||||
|
total++;
|
||||||
|
if ((new Date(init)).getTime() >= start.getTime()) {
|
||||||
|
var nd: any = new Date(init);
|
||||||
|
nd.setDate(1); //set to first day of month
|
||||||
|
if (arr.indexOf("weekday") != -1) { //find first weekday - if not saturday or sunday, then current date is a weekday
|
||||||
|
if (nd.getDay() == 0) nd.setDate(nd.getDate() + 1);// add one day to sunday
|
||||||
|
else if (nd.getDay() == 6) nd.setDate(nd.getDate() + 2); //add two days to saturday
|
||||||
|
if (weekdayOfMonth == 'last') {
|
||||||
|
while (nd.getMonth() == init.getMonth()) {
|
||||||
|
temp = new Date(nd);
|
||||||
|
if (nd.getDay() == 5) nd.setDate(nd.getDate() + 3); //if the current date is friday, add three days to get to monday
|
||||||
|
else nd.setDate(nd.getDate() + 1); //otherwise, just add one day
|
||||||
|
}
|
||||||
|
nd = new Date(temp);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (var i: any = 0; i < wom.indexOf(weekdayOfMonth); i++) {
|
||||||
|
if (nd.getDay() == 5) nd.setDate(nd.getDate() + 3); //if the current date is friday, add three days to get to monday
|
||||||
|
else nd.setDate(nd.getDate() + 1); //otherwise, just add one day
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (arr.indexOf("weekend_day") != -1) { //find first weekend day
|
||||||
|
if (nd.getDay() != 0 && nd.getDay() != 6) nd.setDate(nd.getDate() + (6 - nd.getDay())); //if not saturday or sunday, then add days to get to saturday
|
||||||
|
if (weekdayOfMonth == 'last') {
|
||||||
|
while (nd.getMonth() == init.getMonth()) {
|
||||||
|
temp = new Date(nd);
|
||||||
|
if (nd.getDay() == 0) nd.setDate(nd.getDate() + 6); //if the current date is sunday, add six days to get to saturday
|
||||||
|
else nd.setDate(nd.getDate() + 1); //otherwise, just add one day
|
||||||
|
}
|
||||||
|
nd = new Date(temp);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (var i: any = 0; i < wom.indexOf(weekdayOfMonth); i++) {
|
||||||
|
if (nd.getDay() == 0) nd.setDate(nd.getDate() + 6); //if the current date is sunday, add six days to get to saturday
|
||||||
|
else nd.setDate(nd.getDate() + 1); //otherwise, just add one day
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (arr.indexOf("day") != -1) {//just looking for the Nth day in the month...
|
||||||
|
if (weekdayOfMonth == 'last') {
|
||||||
|
nd.setMonth(nd.getMonth() + 1);
|
||||||
|
nd.setDate(0);
|
||||||
|
}
|
||||||
|
else nd.setDate(nd.getDate() + (wom.indexOf(weekdayOfMonth))); //now add days to get to the Nth instance of this day
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (var i: any = 0; i < wd.length; i++) { //get first instance of the specified day
|
||||||
|
if (arr.indexOf(wd[i]) != -1) {
|
||||||
|
if (nd.getDay() > i) nd.setDate(nd.getDate() + (7 - (nd.getDay() - i)));
|
||||||
|
else nd.setDate(nd.getDate() + (i - nd.getDay() ));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (weekdayOfMonth == 'last') {
|
||||||
|
while (nd.getMonth() == init.getMonth()) {
|
||||||
|
temp = new Date(nd);
|
||||||
|
nd.setDate(nd.getDate() + 7); //add a week to each instance to get the Nth instance
|
||||||
|
}
|
||||||
|
nd = new Date(temp);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
for (var i: any = 0; i < wom.indexOf(weekdayOfMonth); i++) {
|
||||||
|
nd.setDate(nd.getDate() + 7); //add a week to each instance to get the Nth instance
|
||||||
|
console.log(nd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nd.getMonth() == init.getMonth()) { //make sure the new date calculated actually falls within the current month (sometimes there may be no 4th instance of a day)
|
||||||
|
var ed = new Date(nd);
|
||||||
|
ed.setSeconds(ed.getSeconds() + e.Duration);
|
||||||
|
var ni = this.cloneObj(e);
|
||||||
|
ni.EventDate = new Date(nd);
|
||||||
|
if (!this.RecurrenceExceptionExists(e.Id, ni.EventDate)) {
|
||||||
|
ni.EndDate = ed;
|
||||||
|
ni.fRecurrence = false;
|
||||||
|
ni.Id = e.Id;
|
||||||
|
ni.ID = e.Id;
|
||||||
|
er.push(ni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
init.setMonth(init.getMonth() + frequency);
|
||||||
|
if ((new Date(init) > end) || (rTotal > 0 && rTotal <= total)) loop = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (e.RecurrenceData.indexOf("<yearly ") != -1) {
|
||||||
|
var str = e.RecurrenceData.substring(e.RecurrenceData.indexOf("<yearly "));
|
||||||
|
str = str.substring(8, str.indexOf('/>') - 1);
|
||||||
|
var arr = this.formatString(str);
|
||||||
|
var frequency = parseInt(arr[arr.indexOf("yearFrequency") + 1]);
|
||||||
|
var loop = true;
|
||||||
|
var init = this.parseDate(e.EventDate, e.fAllDayEvent);
|
||||||
|
var month = (parseInt(arr[arr.indexOf("month") + 1]) - 1);
|
||||||
|
var day = parseInt(arr[arr.indexOf("day") + 1]);
|
||||||
|
while (loop) {
|
||||||
|
var nd: any = new Date(init);
|
||||||
|
nd.setMonth(month);
|
||||||
|
nd.setDate(day);
|
||||||
|
if ((new Date(init)).getTime() <= nd.getTime()) {
|
||||||
|
total++;
|
||||||
|
if ((new Date(init)).getTime() >= start.getTime()) {
|
||||||
|
var ed = new Date(nd);
|
||||||
|
ed.setSeconds(ed.getSeconds() + e.Duration);
|
||||||
|
var ni = this.cloneObj(e);
|
||||||
|
ni.EventDate = new Date(nd);
|
||||||
|
if (!this.RecurrenceExceptionExists(e.Id, ni.EventDate)) {
|
||||||
|
ni.EndDate = ed;
|
||||||
|
ni.fRecurrence = false;
|
||||||
|
ni.Id = e.Id;
|
||||||
|
ni.ID = e.Id;
|
||||||
|
er.push(ni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
init.setFullYear(init.getFullYear() + frequency);
|
||||||
|
if ((new Date(init) > end) || (rTotal > 0 && rTotal <= total)) loop = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (e.RecurrenceData.indexOf("<yearlyByDay ") != -1) {
|
||||||
|
var str = e.RecurrenceData.substring(e.RecurrenceData.indexOf("<yearlyByDay "));
|
||||||
|
str = str.substring(13, str.indexOf('/>') - 1);
|
||||||
|
var arr = this.formatString(str);
|
||||||
|
var frequency = parseInt(arr[arr.indexOf("yearFrequency") + 1]);
|
||||||
|
var loop = true;
|
||||||
|
var init = this.parseDate(e.EventDate, e.fAllDayEvent);
|
||||||
|
var month = (parseInt(arr[arr.indexOf("month") + 1]) - 1);
|
||||||
|
var weekdayOfMonth = arr[arr.indexOf("weekdayOfMonth") + 1];
|
||||||
|
var day = 0;
|
||||||
|
for (var i: any = 0; i < wd.length; i++) {
|
||||||
|
if (arr.indexOf(wd[i]) != -1) {
|
||||||
|
if (arr[arr.indexOf(wd[i]) + 1].toLowerCase() == 'true') day = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (loop) {
|
||||||
|
var nd: any = new Date(init);
|
||||||
|
nd.setMonth(month);
|
||||||
|
if ((new Date(init)).getTime() <= nd.getTime()) {
|
||||||
|
total++;
|
||||||
|
if ((new Date(init)).getTime() >= start.getTime()) {
|
||||||
|
nd.setDate(1);
|
||||||
|
var dayOfMonth = nd.getDay();
|
||||||
|
if (day < dayOfMonth) nd.setDate(nd.getDate() + ((7 - dayOfMonth) + day)); //first instance of this day in the selected month
|
||||||
|
else nd.setDate(nd.getDate() + (day - dayOfMonth));
|
||||||
|
if (weekdayOfMonth == 'last') {
|
||||||
|
var temp: any = new Date(nd);
|
||||||
|
while (temp.getMonth() == month) {
|
||||||
|
nd = new Date(temp);
|
||||||
|
temp.setDate(temp.getDate() + 7); //loop from first instance of month to last instance of month
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nd.setDate(nd.getDate() + (7 * (wom.indexOf(weekdayOfMonth))));
|
||||||
|
}
|
||||||
|
if (nd.getMonth() == month) {
|
||||||
|
var ed = new Date(nd);
|
||||||
|
ed.setSeconds(ed.getSeconds() + e.Duration);
|
||||||
|
var ni = this.cloneObj(e);
|
||||||
|
ni.EventDate = new Date(nd);
|
||||||
|
if (!this.RecurrenceExceptionExists(e.Id, ni.EventDate)) {
|
||||||
|
ni.EndDate = ed;
|
||||||
|
ni.fRecurrence = false;
|
||||||
|
ni.Id = e.Id;
|
||||||
|
ni.ID = e.Id;
|
||||||
|
er.push(ni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
init.setFullYear(init.getFullYear() + frequency);
|
||||||
|
init.setMonth(month);
|
||||||
|
init.setDate(1);
|
||||||
|
if ((new Date(init) > end) || (rTotal > 0 && rTotal <= total)) loop = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return er;
|
||||||
|
} //end recurrence check
|
||||||
|
}
|
||||||
|
|
||||||
|
public cloneObj(obj: any): any {
|
||||||
|
var copy: any;
|
||||||
|
if (null == obj || "object" != typeof obj) return obj;
|
||||||
|
if (obj instanceof Date) {
|
||||||
|
copy = new Date();
|
||||||
|
copy.setTime(obj.getTime());
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
if (obj instanceof Array) {
|
||||||
|
copy = [];
|
||||||
|
for (var i = 0, len = obj.length; i < len; i++) {
|
||||||
|
copy[i] = this.cloneObj(obj[i]);
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
if (obj instanceof Object) {
|
||||||
|
copy = {};
|
||||||
|
for (var attr in obj) {
|
||||||
|
if (obj.hasOwnProperty(attr)) copy[attr] = this.cloneObj(obj[attr]);
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
throw new Error("Unable to copy obj! Its type isn't supported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,14 +8,13 @@ import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions, HttpClient, M
|
||||||
import * as $ from 'jquery';
|
import * as $ from 'jquery';
|
||||||
import { IEventData } from './IEventData';
|
import { IEventData } from './IEventData';
|
||||||
import { registerDefaultFontFaces } from "@uifabric/styling";
|
import { registerDefaultFontFaces } from "@uifabric/styling";
|
||||||
import { EventArgs } from "@microsoft/sp-core-library";
|
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { SiteUser } from "@pnp/sp/src/siteusers";
|
import { SiteUser } from "@pnp/sp/src/siteusers";
|
||||||
import { IUserPermissions } from './IUserPermissions';
|
import { IUserPermissions } from './IUserPermissions';
|
||||||
import { dateAdd } from "@pnp/common";
|
import { dateAdd } from "@pnp/common";
|
||||||
import { escape } from '@microsoft/sp-lodash-subset';
|
import { escape, update } from '@microsoft/sp-lodash-subset';
|
||||||
|
import parseRecurrentEvent from './parseRecurrentEvent';
|
||||||
|
|
||||||
const ADMIN_ROLETEMPLATE_ID = "62e90394-69f5-4237-9190-012177145e10"; // Global Admin TemplateRoleId
|
|
||||||
// Class Services
|
// Class Services
|
||||||
export default class spservices {
|
export default class spservices {
|
||||||
|
|
||||||
|
@ -35,8 +34,6 @@ export default class spservices {
|
||||||
}
|
}
|
||||||
// OnInit Function
|
// OnInit Function
|
||||||
private async onInit() {
|
private async onInit() {
|
||||||
//this.appCatalogUrl = await this.getAppCatalogUrl();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,9 +43,9 @@ export default class spservices {
|
||||||
* @returns {Promise<number>}
|
* @returns {Promise<number>}
|
||||||
* @memberof spservices
|
* @memberof spservices
|
||||||
*/
|
*/
|
||||||
private async getSiteTimeZoneHoursToUtc(siteUrl: string): Promise<number> {
|
public async getSiteTimeZoneHours(siteUrl: string): Promise<number> {
|
||||||
let numberHours: number = 0;
|
let numberHours: number = 0;
|
||||||
let siteTimeZoneHoursToUTC: any;
|
let siteTimeZoneHours: any;
|
||||||
let siteTimeZoneBias: number;
|
let siteTimeZoneBias: number;
|
||||||
let siteTimeZoneDaylightBias: number;
|
let siteTimeZoneDaylightBias: number;
|
||||||
let currentDateTimeOffSet: number = new Date().getTimezoneOffset() / 60;
|
let currentDateTimeOffSet: number = new Date().getTimezoneOffset() / 60;
|
||||||
|
@ -86,26 +83,85 @@ export default class spservices {
|
||||||
try {
|
try {
|
||||||
const web = new Web(siteUrl);
|
const web = new Web(siteUrl);
|
||||||
|
|
||||||
const siteTimeZoneHoursToUTC: number = await this.getSiteTimeZoneHoursToUtc(siteUrl);
|
const siteTimeZoneHours: number = await this.getSiteTimeZoneHours(siteUrl);
|
||||||
//"Title","fRecurrence", "fAllDayEvent","EventDate", "EndDate", "Description","ID", "Location","Geolocation","ParticipantsPickerId"
|
|
||||||
|
|
||||||
results = await web.lists.getById(listId).items.add({
|
results = await web.lists.getById(listId).items.add({
|
||||||
Title: newEvent.title,
|
Title: newEvent.title,
|
||||||
Description: newEvent.Description,
|
Description: newEvent.Description,
|
||||||
Geolocation: newEvent.geolocation,
|
Geolocation: newEvent.geolocation,
|
||||||
ParticipantsPickerId: { results: newEvent.attendes },
|
ParticipantsPickerId: { results: newEvent.attendes },
|
||||||
EventDate: new Date(moment(newEvent.start).add(siteTimeZoneHoursToUTC, 'hours').toISOString()),
|
EventDate: new Date(moment(newEvent.EventDate).add(siteTimeZoneHours, 'hours').toISOString()),
|
||||||
EndDate: new Date(moment(newEvent.end).add(siteTimeZoneHoursToUTC, 'hours').toISOString()),
|
EndDate: new Date(moment(newEvent.EndDate).add(siteTimeZoneHours, 'hours').toISOString()),
|
||||||
Location: newEvent.location,
|
Location: newEvent.location,
|
||||||
fAllDayEvent: false,
|
fAllDayEvent: false,
|
||||||
fRecurrence: false,
|
fRecurrence: newEvent.fRecurrence,
|
||||||
Category: newEvent.Category,
|
Category: newEvent.Category,
|
||||||
|
EventType: newEvent.EventType,
|
||||||
|
UID: newEvent.UID,
|
||||||
|
RecurrenceData: newEvent.RecurrenceData ? await this.deCodeHtmlEntities(newEvent.RecurrenceData) : "",
|
||||||
|
MasterSeriesItemID: newEvent.MasterSeriesItemID,
|
||||||
|
RecurrenceID: newEvent.RecurrenceID ? moment(newEvent.RecurrenceID).add(siteTimeZoneHours, 'hours').toISOString() : undefined,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {string} siteUrl
|
||||||
|
* @param {string} listId
|
||||||
|
* @param {number} eventId
|
||||||
|
* @returns {Promise<IEventData>}
|
||||||
|
* @memberof spservices
|
||||||
|
*/
|
||||||
|
public async getEvent(siteUrl: string, listId: string, eventId: number): Promise<IEventData> {
|
||||||
|
let returnEvent: IEventData = undefined;
|
||||||
|
try {
|
||||||
|
const siteTimeZoneHours: number = await this.getSiteTimeZoneHours(siteUrl);
|
||||||
|
const web = new Web(siteUrl);
|
||||||
|
//"Title","fRecurrence", "fAllDayEvent","EventDate", "EndDate", "Description","ID", "Location","Geolocation","ParticipantsPickerId"
|
||||||
|
const event = await web.lists.getById(listId).items.usingCaching().getById(eventId)
|
||||||
|
.select("RecurrenceID", "MasterSeriesItemID", "Id", "ID", "ParticipantsPickerId", "EventType", "Title", "Description", "EventDate", "EndDate", "Location", "Author/SipAddress", "Author/Title", "Geolocation", "fAllDayEvent", "fRecurrence", "RecurrenceData", "RecurrenceData", "Duration", "Category", "UID")
|
||||||
|
.expand("Author")
|
||||||
|
.get();
|
||||||
|
|
||||||
|
|
||||||
|
returnEvent = {
|
||||||
|
Id: event.ID,
|
||||||
|
ID: event.ID,
|
||||||
|
EventType: event.EventType,
|
||||||
|
title: await this.deCodeHtmlEntities(event.Title),
|
||||||
|
Description: event.Description ? event.Description : '',
|
||||||
|
EventDate: new Date(moment(event.EventDate).subtract((siteTimeZoneHours), 'hour').toISOString()),
|
||||||
|
EndDate: new Date(moment(event.EndDate).subtract(siteTimeZoneHours, 'hour').toISOString()),
|
||||||
|
location: event.Location,
|
||||||
|
ownerEmail: event.Author.SipAddress,
|
||||||
|
ownerPhoto: "",
|
||||||
|
ownerInitial: '',
|
||||||
|
color: '',
|
||||||
|
ownerName: event.Author.Title,
|
||||||
|
attendes: event.ParticipantsPickerId,
|
||||||
|
fAllDayEvent: false,
|
||||||
|
geolocation: { Longitude: event.Geolocation ? event.Geolocation.Longitude : 0, Latitude: event.Geolocation ? event.Geolocation.Latitude : 0 },
|
||||||
|
Category: event.Category,
|
||||||
|
Duration: event.Duration,
|
||||||
|
UID: event.UID,
|
||||||
|
RecurrenceData: event.RecurrenceData ? await this.deCodeHtmlEntities(event.RecurrenceData) : "",
|
||||||
|
fRecurrence: event.fRecurrence,
|
||||||
|
RecurrenceID: event.RecurrenceID,
|
||||||
|
MasterSeriesItemID: event.MasterSeriesItemID,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
return returnEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {IEventData} newEvent
|
* @param {IEventData} newEvent
|
||||||
|
@ -118,21 +174,28 @@ export default class spservices {
|
||||||
let results = null;
|
let results = null;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const siteTimeZoneHoursToUTC: number = await this.getSiteTimeZoneHoursToUtc(siteUrl);
|
// delete all recursive extentions before update recurrence event
|
||||||
|
if (updateEvent.EventType.toString() == "1") await this.deleteRecurrenceExceptions(updateEvent, siteUrl, listId);
|
||||||
|
|
||||||
|
const siteTimeZoneHours: number = await this.getSiteTimeZoneHours(siteUrl);
|
||||||
|
|
||||||
const web = new Web(siteUrl);
|
const web = new Web(siteUrl);
|
||||||
//"Title","fRecurrence", "fAllDayEvent","EventDate", "EndDate", "Description","ID", "Location","Geolocation","ParticipantsPickerId"
|
//"Title","fRecurrence", "fAllDayEvent","EventDate", "EndDate", "Description","ID", "Location","Geolocation","ParticipantsPickerId"
|
||||||
results = await web.lists.getById(listId).items.getById(updateEvent.id).update({
|
results = await web.lists.getById(listId).items.getById(updateEvent.Id).update({
|
||||||
Title: updateEvent.title,
|
Title: updateEvent.title,
|
||||||
Description: updateEvent.Description,
|
Description: updateEvent.Description,
|
||||||
Geolocation: updateEvent.geolocation,
|
Geolocation: updateEvent.geolocation,
|
||||||
ParticipantsPickerId: { results: updateEvent.attendes },
|
ParticipantsPickerId: { results: updateEvent.attendes },
|
||||||
EventDate: new Date(moment(updateEvent.start).add(siteTimeZoneHoursToUTC, 'hours').toISOString()),
|
EventDate: new Date(moment(updateEvent.EventDate).add(siteTimeZoneHours, 'hours').toISOString()),
|
||||||
EndDate: new Date(moment(updateEvent.end).add(siteTimeZoneHoursToUTC, 'hours').toISOString()),
|
EndDate: new Date(moment(updateEvent.EndDate).add(siteTimeZoneHours, 'hours').toISOString()),
|
||||||
Location: updateEvent.location,
|
Location: updateEvent.location,
|
||||||
fAllDayEvent: false,
|
fAllDayEvent: false,
|
||||||
fRecurrence: false,
|
fRecurrence: updateEvent.fRecurrence,
|
||||||
Category: updateEvent.Category,
|
Category: updateEvent.Category,
|
||||||
|
UID: updateEvent.UID,
|
||||||
|
RecurrenceData: updateEvent.RecurrenceData ? await this.deCodeHtmlEntities(updateEvent.RecurrenceData) : "",
|
||||||
|
EventType: updateEvent.EventType,
|
||||||
|
MasterSeriesItemID: updateEvent.MasterSeriesItemID,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
@ -140,6 +203,25 @@ export default class spservices {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async deleteRecurrenceExceptions(event: IEventData, siteUrl: string, listId: string) {
|
||||||
|
let results = null;
|
||||||
|
try {
|
||||||
|
const web = new Web(siteUrl);
|
||||||
|
results = await web.lists.getById(listId).items
|
||||||
|
.select('Id')
|
||||||
|
.filter(`EventType eq '3' or EventType eq '4' and MasterSeriesItemID eq '${event.Id}' `)
|
||||||
|
.get();
|
||||||
|
if (results && results.length > 0) {
|
||||||
|
for (const recurrenceException of results) {
|
||||||
|
await web.lists.getById(listId).items.getById(recurrenceException.Id).delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {IEventData} event
|
* @param {IEventData} event
|
||||||
|
@ -148,17 +230,44 @@ export default class spservices {
|
||||||
* @returns
|
* @returns
|
||||||
* @memberof spservices
|
* @memberof spservices
|
||||||
*/
|
*/
|
||||||
public async deleteEvent(event: IEventData, siteUrl: string, listId: string) {
|
public async deleteEvent(event: IEventData, siteUrl: string, listId: string, recurrenceSeriesEdited: boolean) {
|
||||||
let results = null;
|
let results = null;
|
||||||
try {
|
try {
|
||||||
const web = new Web(siteUrl);
|
const web = new Web(siteUrl);
|
||||||
|
// Exception Recurrence eventtype = 4 ? update to deleted Recurrence eventtype=3
|
||||||
|
switch (event.EventType.toString()) {
|
||||||
|
case '4': // Exception Recurrence Event
|
||||||
|
results = await web.lists.getById(listId).items.getById(event.Id).update({
|
||||||
|
Title: `Delete: ${event.title}`,
|
||||||
|
EventType: '3',
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case '1': // recurrence Event
|
||||||
|
// if delete is a main recrrence delete all recurrences and main recurrence
|
||||||
|
if (recurrenceSeriesEdited) {
|
||||||
|
// delete execptions if exists before delete recurrence event
|
||||||
|
await this.deleteRecurrenceExceptions(event, siteUrl, listId);
|
||||||
|
await web.lists.getById(listId).items.getById(event.Id).delete();
|
||||||
|
} else {
|
||||||
|
// delete a single recurrence Exception. add new entry with eventtype 3
|
||||||
|
|
||||||
|
event.RecurrenceID = event.EventDate.toString();
|
||||||
|
event.MasterSeriesItemID = event.ID.toString();
|
||||||
|
event.fRecurrence = true;
|
||||||
|
event.EventType = '3';
|
||||||
|
await this.addEvent(event, siteUrl, listId);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '0': // normal Event
|
||||||
|
await web.lists.getById(listId).items.getById(event.Id).delete();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
//"Title","fRecurrence", "fAllDayEvent","EventDate", "EndDate", "Description","ID", "Location","Geolocation","ParticipantsPickerId"
|
|
||||||
results = await web.lists.getById(listId).items.getById(event.id).delete();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
return results;
|
return;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -218,7 +327,7 @@ export default class spservices {
|
||||||
public async getUserProfilePictureUrl(loginName: string) {
|
public async getUserProfilePictureUrl(loginName: string) {
|
||||||
let results: any = null;
|
let results: any = null;
|
||||||
try {
|
try {
|
||||||
results = await sp.profiles.getPropertiesFor(loginName);
|
results = await sp.profiles.usingCaching().getPropertiesFor(loginName);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
results = null;
|
results = null;
|
||||||
}
|
}
|
||||||
|
@ -241,12 +350,13 @@ export default class spservices {
|
||||||
try {
|
try {
|
||||||
const web = new Web(siteUrl);
|
const web = new Web(siteUrl);
|
||||||
const userEffectivePermissions = await web.lists.getById(listId).effectiveBasePermissions.get();
|
const userEffectivePermissions = await web.lists.getById(listId).effectiveBasePermissions.get();
|
||||||
// chaeck user permissions
|
// ...
|
||||||
hasPermissionAdd = sp.web.lists.getById(listId).hasPermissions(userEffectivePermissions, PermissionKind.AddListItems);
|
hasPermissionAdd = sp.web.lists.getById(listId).hasPermissions(userEffectivePermissions, PermissionKind.AddListItems);
|
||||||
hasPermissionEdit =sp.web.lists.getById(listId).hasPermissions(userEffectivePermissions, PermissionKind.EditListItems);
|
|
||||||
hasPermissionDelete = sp.web.lists.getById(listId).hasPermissions(userEffectivePermissions, PermissionKind.DeleteListItems);
|
hasPermissionDelete = sp.web.lists.getById(listId).hasPermissions(userEffectivePermissions, PermissionKind.DeleteListItems);
|
||||||
|
hasPermissionEdit = sp.web.lists.getById(listId).hasPermissions(userEffectivePermissions, PermissionKind.EditListItems);
|
||||||
hasPermissionView = sp.web.lists.getById(listId).hasPermissions(userEffectivePermissions, PermissionKind.ViewListItems);
|
hasPermissionView = sp.web.lists.getById(listId).hasPermissions(userEffectivePermissions, PermissionKind.ViewListItems);
|
||||||
userPermissions = { hasPermissionAdd: hasPermissionAdd, hasPermissionEdit: hasPermissionEdit, hasPermissionDelete: hasPermissionDelete, hasPermissionView: hasPermissionView };
|
userPermissions = { hasPermissionAdd: hasPermissionAdd, hasPermissionEdit: hasPermissionEdit, hasPermissionDelete: hasPermissionDelete, hasPermissionView: hasPermissionView };
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
@ -289,6 +399,7 @@ export default class spservices {
|
||||||
|
|
||||||
for (var i = 0; i < 6; i++) {
|
for (var i = 0; i < 6; i++) {
|
||||||
var x = Math.round(Math.random() * 14);
|
var x = Math.round(Math.random() * 14);
|
||||||
|
|
||||||
var y = hexValues[x];
|
var y = hexValues[x];
|
||||||
newColor += y;
|
newColor += y;
|
||||||
}
|
}
|
||||||
|
@ -344,7 +455,7 @@ export default class spservices {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// Get Regional Settings TimeZone Hours to UTC
|
// Get Regional Settings TimeZone Hours to UTC
|
||||||
const siteTimeZoneHoursToUTC: number = await this.getSiteTimeZoneHoursToUtc(siteUrl);
|
const siteTimeZoneHours: number = await this.getSiteTimeZoneHours(siteUrl);
|
||||||
// Get Category Field Choices
|
// Get Category Field Choices
|
||||||
const categoryDropdownOption = await this.getChoiceFieldOptions(siteUrl, listId, 'Category');
|
const categoryDropdownOption = await this.getChoiceFieldOptions(siteUrl, listId, 'Category');
|
||||||
let categoryColor: { category: string, color: string }[] = [];
|
let categoryColor: { category: string, color: string }[] = [];
|
||||||
|
@ -353,13 +464,12 @@ export default class spservices {
|
||||||
}
|
}
|
||||||
|
|
||||||
const web = new Web(siteUrl);
|
const web = new Web(siteUrl);
|
||||||
const results = await web.lists.getById(listId).renderListDataAsStream(
|
const results = await web.lists.getById(listId).usingCaching().renderListDataAsStream(
|
||||||
{
|
{
|
||||||
DatesInUtc: true,
|
DatesInUtc: true,
|
||||||
ViewXml: `<View><ViewFields><FieldRef Name='Author'/><FieldRef Name='Category'/><FieldRef Name='Description'/><FieldRef Name='ParticipantsPicker'/><FieldRef Name='Geolocation'/><FieldRef Name='ID'/><FieldRef Name='EndDate'/><FieldRef Name='EventDate'/><FieldRef Name='ID'/><FieldRef Name='Location'/><FieldRef Name='Title'/><FieldRef Name='fAllDayEvent'/></ViewFields>
|
ViewXml: `<View><ViewFields><FieldRef Name='RecurrenceData'/><FieldRef Name='Duration'/><FieldRef Name='Author'/><FieldRef Name='Category'/><FieldRef Name='Description'/><FieldRef Name='ParticipantsPicker'/><FieldRef Name='Geolocation'/><FieldRef Name='ID'/><FieldRef Name='EndDate'/><FieldRef Name='EventDate'/><FieldRef Name='ID'/><FieldRef Name='Location'/><FieldRef Name='Title'/><FieldRef Name='fAllDayEvent'/><FieldRef Name='EventType'/><FieldRef Name='UID' /><FieldRef Name='fRecurrence' /></ViewFields>
|
||||||
<Query>
|
<Query>
|
||||||
<Where>
|
<Where>
|
||||||
<And>
|
|
||||||
<And>
|
<And>
|
||||||
<Geq>
|
<Geq>
|
||||||
<FieldRef Name='EventDate' />
|
<FieldRef Name='EventDate' />
|
||||||
|
@ -370,11 +480,6 @@ export default class spservices {
|
||||||
<Value IncludeTimeValue='false' Type='DateTime'>${moment(eventEndDate).format('YYYY-MM-DD')}</Value>
|
<Value IncludeTimeValue='false' Type='DateTime'>${moment(eventEndDate).format('YYYY-MM-DD')}</Value>
|
||||||
</Leq>
|
</Leq>
|
||||||
</And>
|
</And>
|
||||||
<Eq>
|
|
||||||
<FieldRef Name='fRecurrence' />
|
|
||||||
<Value Type='Recurrence'>0</Value>
|
|
||||||
</Eq>
|
|
||||||
</And>
|
|
||||||
</Where>
|
</Where>
|
||||||
</Query>
|
</Query>
|
||||||
<RowLimit Paged=\"FALSE\">2000</RowLimit>
|
<RowLimit Paged=\"FALSE\">2000</RowLimit>
|
||||||
|
@ -383,7 +488,8 @@ export default class spservices {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (results && results.Row.length > 0) {
|
if (results && results.Row.length > 0) {
|
||||||
for (const event of results.Row) {
|
let event: any = '';
|
||||||
|
for (event of results.Row) {
|
||||||
const initialsArray: string[] = event.Author[0].title.split(' ');
|
const initialsArray: string[] = event.Author[0].title.split(' ');
|
||||||
const initials: string = initialsArray[0].charAt(0) + initialsArray[initialsArray.length - 1].charAt(0);
|
const initials: string = initialsArray[0].charAt(0) + initialsArray[initialsArray.length - 1].charAt(0);
|
||||||
const userPictureUrl = await this.getUserProfilePictureUrl(`i:0#.f|membership|${event.Author[0].email}`);
|
const userPictureUrl = await this.getUserProfilePictureUrl(`i:0#.f|membership|${event.Author[0].email}`);
|
||||||
|
@ -399,28 +505,40 @@ export default class spservices {
|
||||||
attendees.push(parseInt(attendee.id));
|
attendees.push(parseInt(attendee.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
events.push({
|
events.push({
|
||||||
id: event.ID,
|
Id: event.ID,
|
||||||
|
ID: event.ID,
|
||||||
|
EventType: event.EventType,
|
||||||
title: await this.deCodeHtmlEntities(event.Title),
|
title: await this.deCodeHtmlEntities(event.Title),
|
||||||
Description: event.Description,
|
Description: event.Description,
|
||||||
// start: moment(event.EventDate).utc().toDate().setUTCMinutes(this.siteTimeZoneOffSet),
|
|
||||||
start: new Date(moment(event.EventDate).subtract((siteTimeZoneHoursToUTC), 'hour').toISOString()),
|
EventDate: new Date(moment(event.EventDate).subtract((siteTimeZoneHours), 'hour').toISOString()),
|
||||||
// end: new Date(moment(event.EndDate).toLocaleString()),
|
|
||||||
end: new Date(moment(event.EndDate).subtract(siteTimeZoneHoursToUTC, 'hour').toISOString()),
|
EndDate: new Date(moment(event.EndDate).subtract(siteTimeZoneHours, 'hour').toISOString()),
|
||||||
location: event.Location,
|
location: event.Location,
|
||||||
ownerEmail: event.Author[0].email,
|
ownerEmail: event.Author[0].email,
|
||||||
ownerPhoto: userPictureUrl ?
|
ownerPhoto: userPictureUrl ?
|
||||||
`https://outlook.office365.com/owa/service.svc/s/GetPersonaPhoto?email=${event.Author[0].email}&UA=0&size=HR96x96` : '',
|
`https://outlook.office365.com/owa/service.svc/s/GetPersonaPhoto?email=${event.Author[0].email}&UA=0&size=HR96x96` : '',
|
||||||
ownerInitial: initials,
|
ownerInitial: initials,
|
||||||
// color: await this.colorGenerate(),
|
color: CategoryColorValue.length > 0 ? CategoryColorValue[0].color : '#1a75ff', // blue default
|
||||||
color: CategoryColorValue.length > 0 ? CategoryColorValue[0].color : await this.colorGenerate,
|
|
||||||
ownerName: event.Author[0].title,
|
ownerName: event.Author[0].title,
|
||||||
attendes: attendees,
|
attendes: attendees,
|
||||||
allDayEvent: false,
|
fAllDayEvent: false,
|
||||||
geolocation: { Longitude: parseFloat(geolocation[0]), Latitude: parseFloat(geolocation[1]) },
|
geolocation: { Longitude: parseFloat(geolocation[0]), Latitude: parseFloat(geolocation[1]) },
|
||||||
Category: event.Category
|
Category: event.Category,
|
||||||
|
Duration: event.Duration,
|
||||||
|
RecurrenceData: event.RecurrenceData ? await this.deCodeHtmlEntities(event.RecurrenceData) : "",
|
||||||
|
fRecurrence: event.fRecurrence,
|
||||||
|
RecurrenceID: event.RecurrenceID ? moment(event.RecurrenceID).subtract(siteTimeZoneHours, 'hour').toISOString() : undefined,
|
||||||
|
MasterSeriesItemID: event.MasterSeriesItemID,
|
||||||
|
UID: event.UID.replace("{", "").replace("}", ""),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let parseEvt: parseRecurrentEvent = new parseRecurrentEvent();
|
||||||
|
events = parseEvt.parseEvents(events, null, null);
|
||||||
}
|
}
|
||||||
// Return Data
|
// Return Data
|
||||||
return events;
|
return events;
|
||||||
|
@ -441,7 +559,7 @@ export default class spservices {
|
||||||
let regionalSettings: RegionalSettings;
|
let regionalSettings: RegionalSettings;
|
||||||
try {
|
try {
|
||||||
const web = new Web(siteUrl);
|
const web = new Web(siteUrl);
|
||||||
regionalSettings = await web.regionalSettings.timeZone.get();
|
regionalSettings = await web.regionalSettings.timeZone.usingCaching().get();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
|
|
@ -12,7 +12,8 @@
|
||||||
"requiresCustomScript": false,
|
"requiresCustomScript": false,
|
||||||
"supportedHosts": [
|
"supportedHosts": [
|
||||||
"SharePointWebPart",
|
"SharePointWebPart",
|
||||||
"TeamsTab"
|
"TeamsTab",
|
||||||
|
"SharePointFullPage"
|
||||||
],
|
],
|
||||||
"preconfiguredEntries": [
|
"preconfiguredEntries": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as ReactDom from 'react-dom';
|
import * as ReactDom from 'react-dom';
|
||||||
import { Version } from '@microsoft/sp-core-library';
|
import { Version } from '@microsoft/sp-core-library';
|
||||||
|
|
||||||
import { BaseClientSideWebPart, PropertyPaneHorizontalRule } from '@microsoft/sp-webpart-base';
|
import { BaseClientSideWebPart, PropertyPaneHorizontalRule } from '@microsoft/sp-webpart-base';
|
||||||
import {
|
import {
|
||||||
IPropertyPaneConfiguration,
|
IPropertyPaneConfiguration,
|
||||||
|
@ -66,7 +65,7 @@ export default class CalendarWebPart extends BaseClientSideWebPart<ICalendarWebP
|
||||||
public async onInit(): Promise<void> {
|
public async onInit(): Promise<void> {
|
||||||
|
|
||||||
this.spService = new spservices(this.context);
|
this.spService = new spservices(this.context);
|
||||||
this.properties.siteUrl = this.context.pageContext.site.absoluteUrl;
|
this.properties.siteUrl = this.properties.siteUrl ? this.properties.siteUrl : this.context.pageContext.site.absoluteUrl;
|
||||||
if (!this.properties.eventStartDate){
|
if (!this.properties.eventStartDate){
|
||||||
this.properties.eventStartDate = { value: moment().subtract(2,'years').startOf('month').toDate(), displayValue: moment().format('ddd MMM MM YYYY')};
|
this.properties.eventStartDate = { value: moment().subtract(2,'years').startOf('month').toDate(), displayValue: moment().format('ddd MMM MM YYYY')};
|
||||||
}
|
}
|
||||||
|
@ -75,9 +74,12 @@ export default class CalendarWebPart extends BaseClientSideWebPart<ICalendarWebP
|
||||||
}
|
}
|
||||||
if (this.properties.siteUrl && !this.properties.list) {
|
if (this.properties.siteUrl && !this.properties.list) {
|
||||||
const _lists = await this.loadLists();
|
const _lists = await this.loadLists();
|
||||||
|
if ( _lists.length > 0 ){
|
||||||
this.lists = _lists;
|
this.lists = _lists;
|
||||||
this.properties.list = this.lists.length > 0 ? this.lists[0].key.toString() : '';
|
this.properties.list = this.lists[0].key.toString();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,14 +132,16 @@ export default class CalendarWebPart extends BaseClientSideWebPart<ICalendarWebP
|
||||||
for (const list of results) {
|
for (const list of results) {
|
||||||
_lists.push({ key: list.Id, text: list.Title });
|
_lists.push({ key: list.Id, text: list.Title });
|
||||||
}
|
}
|
||||||
|
// push new item value
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.errorMessage = `${error.message} - ${strings.PropPanelSiteUrlErrorMessage}` ;
|
this.errorMessage = `${error.message} - please check if site url if valid.` ;
|
||||||
this.context.propertyPane.refresh();
|
this.context.propertyPane.refresh();
|
||||||
}
|
}
|
||||||
return _lists;
|
return _lists;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
*
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {string} date
|
* @param {string} date
|
||||||
|
@ -226,7 +230,7 @@ export default class CalendarWebPart extends BaseClientSideWebPart<ICalendarWebP
|
||||||
super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
|
super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.errorMessage = `${error.message} - ${strings.PropPanelSiteUrlErrorMessage}` ;
|
this.errorMessage = `${error.message} - please check if site url if valid.` ;
|
||||||
this.context.propertyPane.refresh();
|
this.context.propertyPane.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,6 +241,8 @@ export default class CalendarWebPart extends BaseClientSideWebPart<ICalendarWebP
|
||||||
* @memberof CalendarWebPart
|
* @memberof CalendarWebPart
|
||||||
*/
|
*/
|
||||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
|
// EndDate and Start Date defualt values
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pages: [
|
pages: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,21 @@
|
||||||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||||
|
@import './node_modules/spfx-uifabric-themes/office.theme.vars';
|
||||||
|
|
||||||
|
:export {
|
||||||
|
themeDark: $ms-color-themePrimary;
|
||||||
|
}
|
||||||
|
.eventStyleSetter {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 0px;
|
||||||
|
color: $ms-color-themePrimary;
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
border-width: '1.1px';
|
||||||
|
border-style: 'solid';
|
||||||
|
border-color: $ms-color-themePrimary;
|
||||||
|
border-left-width: '6px';
|
||||||
|
display: 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.Documentcard {
|
.Documentcard {
|
||||||
|
|
|
@ -8,8 +8,10 @@ import * as moment from 'moment';
|
||||||
import * as strings from 'CalendarWebPartStrings';
|
import * as strings from 'CalendarWebPartStrings';
|
||||||
import 'react-big-calendar/lib/css/react-big-calendar.css';
|
import 'react-big-calendar/lib/css/react-big-calendar.css';
|
||||||
require('./calendar.css');
|
require('./calendar.css');
|
||||||
import {
|
import { CommunicationColors , FluentCustomizations, FluentTheme } from '@uifabric/fluent-theme';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Customizer,
|
||||||
IPersonaSharedProps,
|
IPersonaSharedProps,
|
||||||
Persona,
|
Persona,
|
||||||
PersonaSize,
|
PersonaSize,
|
||||||
|
@ -132,7 +134,6 @@ export default class Calendar extends React.Component<ICalendarProps, ICalendarS
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
*
|
*
|
||||||
* @param {*} error
|
* @param {*} error
|
||||||
* @param {*} errorInfo
|
* @param {*} errorInfo
|
||||||
|
@ -170,7 +171,7 @@ export default class Calendar extends React.Component<ICalendarProps, ICalendarS
|
||||||
previewImages: [
|
previewImages: [
|
||||||
{
|
{
|
||||||
// previewImageSrc: event.ownerPhoto,
|
// previewImageSrc: event.ownerPhoto,
|
||||||
previewIconProps: { iconName: 'Calendar', styles: { root: { color: event.color } }, className: styles.previewEventIcon },
|
previewIconProps: { iconName: event.fRecurrence === '0' ? 'Calendar': 'RecurringEvent', styles: { root: { color: event.color } }, className: styles.previewEventIcon },
|
||||||
height: 43,
|
height: 43,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -195,14 +196,13 @@ export default class Calendar extends React.Component<ICalendarProps, ICalendarS
|
||||||
<div className={styles.DocumentCardDetails}>
|
<div className={styles.DocumentCardDetails}>
|
||||||
<DocumentCardTitle title={event.title} shouldTruncate={true} className={styles.DocumentCardTitle} styles={{ root: { color: event.color} }} />
|
<DocumentCardTitle title={event.title} shouldTruncate={true} className={styles.DocumentCardTitle} styles={{ root: { color: event.color} }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
moment(event.start).format('YYYY/MM/DD') !== moment(event.end).format('YYYY/MM/DD') ?
|
moment(event.EventDate).format('YYYY/MM/DD') !== moment(event.EndDate).format('YYYY/MM/DD') ?
|
||||||
<span className={styles.DocumentCardTitleTime}>{moment(event.start).format('dddd')} - {moment(event.end).format('dddd')} </span>
|
<span className={styles.DocumentCardTitleTime}>{moment(event.EventDate).format('dddd')} - {moment(event.EndDate).format('dddd')} </span>
|
||||||
:
|
:
|
||||||
<span className={styles.DocumentCardTitleTime}>{moment(event.start).format('dddd')} </span>
|
<span className={styles.DocumentCardTitleTime}>{moment(event.EventDate).format('dddd')} </span>
|
||||||
}
|
}
|
||||||
<span className={styles.DocumentCardTitleTime}>{moment(event.start).format('HH:mm')}H - {moment(event.end).format('HH:mm')}H</span>
|
<span className={styles.DocumentCardTitleTime}>{moment(event.EventDate).format('HH:mm')}H - {moment(event.EndDate).format('HH:mm')}H</span>
|
||||||
<Icon iconName='MapPin' className={styles.locationIcon} style={{ color: event.color }} />
|
<Icon iconName='MapPin' className={styles.locationIcon} style={{ color: event.color }} />
|
||||||
<DocumentCardTitle
|
<DocumentCardTitle
|
||||||
title={`${event.location}`}
|
title={`${event.location}`}
|
||||||
|
@ -223,7 +223,6 @@ export default class Calendar extends React.Component<ICalendarProps, ICalendarS
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<div style={{ height: 22 }}>
|
<div style={{ height: 22 }}>
|
||||||
<HoverCard
|
<HoverCard
|
||||||
cardDismissDelay={1000}
|
cardDismissDelay={1000}
|
||||||
|
@ -273,15 +272,16 @@ export default class Calendar extends React.Component<ICalendarProps, ICalendarS
|
||||||
* @memberof Calendar
|
* @memberof Calendar
|
||||||
*/
|
*/
|
||||||
public eventStyleGetter(event, start, end, isSelected): any {
|
public eventStyleGetter(event, start, end, isSelected): any {
|
||||||
|
|
||||||
let style: any = {
|
let style: any = {
|
||||||
backgroundColor: 'white',
|
backgroundColor: 'white',
|
||||||
borderRadius: '0px',
|
borderRadius: '0px',
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
color: 'black',
|
color: event.color,
|
||||||
borderWidth: '1.1px',
|
borderWidth: '1.1px',
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
borderColor: event.color,
|
borderColor: event.color,
|
||||||
borderLeftWidth: '5px',
|
borderLeftWidth: '6px',
|
||||||
display: 'block'
|
display: 'block'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -298,7 +298,10 @@ export default class Calendar extends React.Component<ICalendarProps, ICalendarS
|
||||||
public render(): React.ReactElement<ICalendarProps> {
|
public render(): React.ReactElement<ICalendarProps> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.calendar}>
|
<Customizer {...FluentCustomizations}>
|
||||||
|
|
||||||
|
|
||||||
|
<div className={styles.calendar} style={{backgroundColor: 'white', padding: '20px'}}>
|
||||||
<WebPartTitle displayMode={this.props.displayMode}
|
<WebPartTitle displayMode={this.props.displayMode}
|
||||||
title={this.props.title}
|
title={this.props.title}
|
||||||
updateProperty={this.props.updateProperty} />
|
updateProperty={this.props.updateProperty} />
|
||||||
|
@ -326,8 +329,8 @@ export default class Calendar extends React.Component<ICalendarProps, ICalendarS
|
||||||
localizer={localizer}
|
localizer={localizer}
|
||||||
selectable
|
selectable
|
||||||
events={this.state.eventData}
|
events={this.state.eventData}
|
||||||
startAccessor="start"
|
startAccessor="EventDate"
|
||||||
endAccessor="end"
|
endAccessor="EndDate"
|
||||||
eventPropGetter={this.eventStyleGetter}
|
eventPropGetter={this.eventStyleGetter}
|
||||||
onSelectSlot={this.onSelectSlot}
|
onSelectSlot={this.onSelectSlot}
|
||||||
components={{
|
components={{
|
||||||
|
@ -367,6 +370,7 @@ export default class Calendar extends React.Component<ICalendarProps, ICalendarS
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</Customizer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,34 @@
|
||||||
define([], function () {
|
define([], function () {
|
||||||
return {
|
return {
|
||||||
PropPanelSiteUrlErrorMessage:'Please verify if site url is valid',
|
WeeksOnLabel: "week(s) on",
|
||||||
|
PaternLabel: "Patern",
|
||||||
|
OcurrencesLabel: "Ocurrences",
|
||||||
|
dateRangeLabel: "Date Range",
|
||||||
|
weekEndDay: "Weekend Day",
|
||||||
|
weekDayLabel: "Weekday",
|
||||||
|
lastLabel: "last",
|
||||||
|
fourthLabel: "fourth",
|
||||||
|
thirdLabel: "third",
|
||||||
|
secondLabel: "second",
|
||||||
|
firstLabel: "first",
|
||||||
|
theLabel: "the",
|
||||||
|
MonthsLabel: "month(s)",
|
||||||
|
ofEveryLabel: "of every ",
|
||||||
|
AllowedValues1to12Label: "Allowed values 1 to 12",
|
||||||
|
noEndDate: "no end date",
|
||||||
|
everyweekdays: "every weekdays",
|
||||||
|
days: "days",
|
||||||
|
every: "every",
|
||||||
|
EndByLabel: "end by",
|
||||||
|
EndAfterLabel: "end after",
|
||||||
HttpErrorMessage: "Error reading calendar events:",
|
HttpErrorMessage: "Error reading calendar events:",
|
||||||
CategoryPlaceHolder: "Please select category",
|
CategoryPlaceHolder: "Please select category",
|
||||||
CategoryLabel: "Category",
|
CategoryLabel: "Category",
|
||||||
EnDateValidationMessage: "start date is greater than end date",
|
EnDateValidationMessage: "start date is greater than end date",
|
||||||
SartDateValidationMessage: "start date is greater than end date",
|
SartDateValidationMessage: "start date is greater than end date",
|
||||||
eventSelectDatesLabel: "Show only the events within the following dates",
|
eventSelectDatesLabel: "Show only the events within the following dates",
|
||||||
ConfirmeDeleteMessage: "Confirm delete event ?",
|
ConfirmeDeleteMessage: "Confirm delete event ? If the event is a recurrence event all entries will be deleted ",
|
||||||
DialogConfirmDeleteTitle: " 'Delete Event'",
|
DialogConfirmDeleteTitle: "Delete Event",
|
||||||
SpinnerDeletingLabel: "Deleting...",
|
SpinnerDeletingLabel: "Deleting...",
|
||||||
DialogCloseButtonLabel: "Cancel",
|
DialogCloseButtonLabel: "Cancel",
|
||||||
DialogConfirmDeleteLabel: "Delete",
|
DialogConfirmDeleteLabel: "Delete",
|
||||||
|
@ -54,7 +74,7 @@ define([], function () {
|
||||||
Oct:'Oct',
|
Oct:'Oct',
|
||||||
Nov:'Nov',
|
Nov:'Nov',
|
||||||
Dez:'Dez',
|
Dez:'Dez',
|
||||||
Dezember: "December",
|
December: "December",
|
||||||
November: " 'November'",
|
November: " 'November'",
|
||||||
October: "October",
|
October: "October",
|
||||||
September: "September",
|
September: "September",
|
||||||
|
|
|
@ -1,4 +1,25 @@
|
||||||
declare interface ICalendarWebPartStrings {
|
declare interface ICalendarWebPartStrings {
|
||||||
|
WeeksOnLabel: string;
|
||||||
|
PaternLabel: string;
|
||||||
|
OcurrencesLabel: string;
|
||||||
|
dateRangeLabel: string;
|
||||||
|
weekEndDay: string;
|
||||||
|
weekDayLabel: string;
|
||||||
|
lastLabel: string;
|
||||||
|
fourthLabel: string;
|
||||||
|
thirdLabel: string;
|
||||||
|
secondLabel: string;
|
||||||
|
firstLabel: string;
|
||||||
|
theLabel: string;
|
||||||
|
MonthsLabel: string;
|
||||||
|
ofEveryLabel: string;
|
||||||
|
AllowedValues1to12Label: string;
|
||||||
|
noEndDate: string;
|
||||||
|
everyweekdays: string;
|
||||||
|
days: string;
|
||||||
|
every: string;
|
||||||
|
EndByLabel: string;
|
||||||
|
EndAfterLabel: string;
|
||||||
HttpErrorMessage: string;
|
HttpErrorMessage: string;
|
||||||
CategoryPlaceHolder: string;
|
CategoryPlaceHolder: string;
|
||||||
CategoryLabel: string;
|
CategoryLabel: string;
|
||||||
|
@ -52,7 +73,7 @@ declare interface ICalendarWebPartStrings {
|
||||||
Oct:string;
|
Oct:string;
|
||||||
Nov:string;
|
Nov:string;
|
||||||
Dez:string;
|
Dez:string;
|
||||||
Dezember: string;
|
December: string;
|
||||||
November: string;
|
November: string;
|
||||||
October: string;
|
October: string;
|
||||||
September: string;
|
September: string;
|
||||||
|
@ -91,7 +112,6 @@ declare interface ICalendarWebPartStrings {
|
||||||
previousLabel: string;
|
previousLabel: string;
|
||||||
nextLabel: string;
|
nextLabel: string;
|
||||||
showMore: string;
|
showMore: string;
|
||||||
PropPanelSiteUrlErrorMessage: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'CalendarWebPartStrings' {
|
declare module 'CalendarWebPartStrings' {
|
||||||
|
|
|
@ -1,6 +1,26 @@
|
||||||
define([], function() {
|
define([], function() {
|
||||||
return {
|
return {
|
||||||
PropPanelSiteUrlErrorMessage:'Por favor verifique se site url é valido.',
|
WeeksOnLabel: "week(s) on",
|
||||||
|
PaternLabel: "Patern",
|
||||||
|
OcurrencesLabel: "Ocurrences",
|
||||||
|
dateRangeLabel: "Date Range",
|
||||||
|
weekEndDay: " 'weekend day'",
|
||||||
|
weekDayLabel: "weekday",
|
||||||
|
lastLabel: "last",
|
||||||
|
fourthLabel: " 'fourth'",
|
||||||
|
thirdLabel: "third",
|
||||||
|
secondLabel: " 'Second' ",
|
||||||
|
firstLabel: "first",
|
||||||
|
theLabel: "the",
|
||||||
|
MonthsLabel: "month(s)",
|
||||||
|
ofEveryLabel: "of every ",
|
||||||
|
AllowedValues1to12Label: "Allowed values 1 to 12",
|
||||||
|
noEndDate: "no end date",
|
||||||
|
everyweekdays: "every weekdays",
|
||||||
|
days: "days",
|
||||||
|
every: "every",
|
||||||
|
EndByLabel: "end by",
|
||||||
|
EndAfterLabel: "end after",
|
||||||
HttpErrorMessage: "Error reading calendar events:",
|
HttpErrorMessage: "Error reading calendar events:",
|
||||||
CategoryPlaceHolder: "Please select category",
|
CategoryPlaceHolder: "Please select category",
|
||||||
CategoryLabel: "Category",
|
CategoryLabel: "Category",
|
||||||
|
|
|
@ -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,12 @@
|
||||||
|
{
|
||||||
|
"@microsoft/generator-sharepoint": {
|
||||||
|
"version": "1.8.2",
|
||||||
|
"libraryName": "hello-world",
|
||||||
|
"libraryId": "06aabb02-0b8e-434a-9b14-eae5b3c24411",
|
||||||
|
"environment": "spo",
|
||||||
|
"packageManager": "npm",
|
||||||
|
"isCreatingSolution": true,
|
||||||
|
"isDomainIsolated": false,
|
||||||
|
"componentType": "webpart"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
# React Functional Component web part
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This web part is intended to be easier to understand for new developers building their first SPFx web part. It is a refactoring of the HelloWorld web part that is created by the **@microsoft/generator-sharepoint** Yeoman generator, and introduces React Functional Components.
|
||||||
|
|
||||||
|
![Screenshot](Screenshot.png "Screenshot - nothing to see here, move along")
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
This sample was built with version 1.82 of the SharePoint Framework. It has been modified to use version 16.8 of the React framework (by default the version used is React 16.7). React 16.8 supports React Hooks although this is not needed in the sample code because HelloWorld.tsx is a pure (or stateless) functional component.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Solution|Author(s)
|
||||||
|
--------|---------
|
||||||
|
react-functional-component | Bill Ayers
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
Version|Date|Comments
|
||||||
|
-------|----|--------
|
||||||
|
1.0|June 5, 2019|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
|
||||||
|
|
||||||
|
* Clone this repository
|
||||||
|
* Move to /samples/react-functional-component folder
|
||||||
|
* At the command line run:
|
||||||
|
* `npm install`
|
||||||
|
* `gulp serve`
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
The purpose of this web part is to make it easier to understand for new developers building their first SPFx web part, when teaching the SharePoint Framework. The web part is a refactoring of the HelloWorld web part that is created by the **@microsoft/generator-sharepoint** Yeoman generator. The resulting rendered web part should look exactly the same, but the complexity of the code has been significantly reduced, and should be much easier to understand for a newcomer to the framework.
|
||||||
|
|
||||||
|
It also introduces React Functional Components which offers a simpler way of building React Components using functions instead of classes.
|
||||||
|
|
||||||
|
* Simplification
|
||||||
|
* Functional Component
|
||||||
|
* Adding State
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-functional-component" />
|
||||||
|
|
||||||
|
## HelloWorldWebPart.ts Simplification
|
||||||
|
|
||||||
|
A number of simplifications have been made to the HelloWorldWebPart.ts file to make it easier to follow.
|
||||||
|
|
||||||
|
The use of an external string collection has been removed. This is only needed in multilingual situations and can be added as and when needed. For a first web part there is really no need to have the student wondering where these strings are defined. For this sample they are simply hard coded into the file to make it clear how the property pane configuration works.
|
||||||
|
|
||||||
|
The external interface to define the properties is moved from a separate file and inline into HelloWorldWebPart.ts. This interface is used by the web part and the component on the assumption that all the properties will be passed to the component as props. Adding more properties is simply a matter of adding them to the IHelloWorldProps interface, adding a section to the getPropertyPaneConfiguration return value and adding a default to the manifest file if needed. The property will then be available to the component through its **props** collection.
|
||||||
|
|
||||||
|
## Functional Component
|
||||||
|
|
||||||
|
The HelloWorld.tsx React Component has been refactored as a pure functional component. This simplifies the code structure and will also gain you additional kudos when talking to computer scientists and functional code enthusiasts. The structure is a simple JavaScript function with the name of the component, a single argument containing the React props, and a simple return of the component rendering. Because it is just a function, there is no need to worry about **this** or **that**, constructors etc.
|
||||||
|
|
||||||
|
In addition the React elements returned have been simplified. In particular the "Learn more" button, which was constructed from HTML primitives in the Yeoman-generated sample, has been replaced by an Office-UI-Fabric PrimaryButton component. This also means that it has been possible to greatly simplify the SASS file HelloWorld.module.scss.
|
||||||
|
|
||||||
|
## Adding State
|
||||||
|
|
||||||
|
You may be wondering how maintaining state, side effects or other complexities can be accomodated with functional components like the one used. This can be achieved using a fairly new feature called [React Hooks](https://reactjs.org/docs/hooks-intro.html) and will be demonstrated using [another sample](https://github.com/SharePoint/sp-dev-fx-webparts/tree/master/samples/react-functional-stateful-component).
|
||||||
|
|
||||||
|
## Building and testing
|
||||||
|
|
||||||
|
In the react-functional-component directory run **npm install** to resolve all the dependencies. Once this has completed you can run **gulp serve** to test the web part in the local workbench.
|
||||||
|
|
After Width: | Height: | Size: 78 KiB |
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||||
|
"version": "2.0",
|
||||||
|
"bundles": {
|
||||||
|
"hello-world-web-part": {
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"entrypoint": "./lib/webparts/helloWorld/HelloWorldWebPart.js",
|
||||||
|
"manifest": "./src/webparts/helloWorld/HelloWorldWebPart.manifest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externals": {},
|
||||||
|
"localizedResources": {
|
||||||
|
"HelloWorldWebPartStrings": "lib/webparts/helloWorld/loc/{locale}.js"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||||
|
"deployCdnPath": "temp/deploy"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||||
|
"workingDir": "./temp/deploy/",
|
||||||
|
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||||
|
"container": "hello-world",
|
||||||
|
"accessKey": "<!-- ACCESS KEY -->"
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||||
|
"solution": {
|
||||||
|
"name": "hello-world-client-side-solution",
|
||||||
|
"id": "06aabb02-0b8e-434a-9b14-eae5b3c24411",
|
||||||
|
"version": "1.0.0.0",
|
||||||
|
"includeClientSideAssets": true,
|
||||||
|
"isDomainIsolated": false,
|
||||||
|
"skipFeatureDeployment": true,
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"zippedPackage": "solution/hello-world.sppkg"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.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,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||||
|
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
'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.configureWebpack.mergeConfig({
|
||||||
|
additionalConfiguration: (config) => {
|
||||||
|
config.externals = config.externals.filter(name => !(["react", "react-dom"].includes(name)))
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
build.initialize(gulp);
|
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"name": "hello-world",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "gulp serve",
|
||||||
|
"build": "gulp bundle",
|
||||||
|
"clean": "gulp clean",
|
||||||
|
"test": "gulp test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@microsoft/sp-core-library": "1.8.2",
|
||||||
|
"@microsoft/sp-lodash-subset": "1.8.2",
|
||||||
|
"@microsoft/sp-office-ui-fabric-core": "1.8.2",
|
||||||
|
"@microsoft/sp-property-pane": "1.8.2",
|
||||||
|
"@microsoft/sp-webpart-base": "1.8.2",
|
||||||
|
"@types/es6-promise": "0.0.33",
|
||||||
|
"@types/webpack-env": "1.13.1",
|
||||||
|
"office-ui-fabric-react": "6.143.0",
|
||||||
|
"react": "^16.8.6",
|
||||||
|
"react-dom": "^16.8.6"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"@types/react": "16.7.22"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@microsoft/rush-stack-compiler-2.9": "0.7.7",
|
||||||
|
"@microsoft/rush-stack-compiler-3.3": "^0.2.15",
|
||||||
|
"@microsoft/sp-build-web": "1.8.2",
|
||||||
|
"@microsoft/sp-module-interfaces": "1.8.2",
|
||||||
|
"@microsoft/sp-tslint-rules": "1.8.2",
|
||||||
|
"@microsoft/sp-webpart-workbench": "1.8.2",
|
||||||
|
"@types/chai": "3.4.34",
|
||||||
|
"@types/mocha": "2.2.38",
|
||||||
|
"@types/react": "^16.8.19",
|
||||||
|
"@types/react-dom": "^16.8.4",
|
||||||
|
"ajv": "~5.2.2",
|
||||||
|
"gulp": "~3.9.1",
|
||||||
|
"typescript": "^3.5.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||||
|
"id": "6dd862f6-29a3-41a8-91f0-cf3ffb2daad7",
|
||||||
|
"alias": "HelloWorldWebPart",
|
||||||
|
"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,
|
||||||
|
"supportedHosts": ["SharePointWebPart", "TeamsTab"],
|
||||||
|
|
||||||
|
"preconfiguredEntries": [{
|
||||||
|
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||||
|
"group": { "default": "Other" },
|
||||||
|
"title": { "default": "HelloWorld" },
|
||||||
|
"description": { "default": "HelloWorld description" },
|
||||||
|
"officeFabricIconFontName": "Page",
|
||||||
|
"properties": {
|
||||||
|
"description": "HelloWorld"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactDom from 'react-dom';
|
||||||
|
import { Version } from '@microsoft/sp-core-library';
|
||||||
|
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||||
|
import { IPropertyPaneConfiguration, PropertyPaneTextField } from '@microsoft/sp-property-pane';
|
||||||
|
import HelloWorld from './components/HelloWorld';
|
||||||
|
|
||||||
|
export interface IHelloWorldProps {
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class HelloWorldWebPart extends BaseClientSideWebPart<IHelloWorldProps> {
|
||||||
|
|
||||||
|
public render(): void {
|
||||||
|
ReactDom.render(React.createElement(HelloWorld, this.properties), this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onDispose(): void {
|
||||||
|
ReactDom.unmountComponentAtNode(this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get dataVersion(): Version {
|
||||||
|
return Version.parse('1.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
|
return {
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
header: {
|
||||||
|
description: "Properties"
|
||||||
|
},
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
groupName: "General",
|
||||||
|
groupFields: [
|
||||||
|
PropertyPaneTextField('description', { label: "Description Text" })
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './HelloWorld.module.scss';
|
||||||
|
import { IHelloWorldProps } from '../HelloWorldWebPart';
|
||||||
|
import { escape } from '@microsoft/sp-lodash-subset';
|
||||||
|
import * as Fabric from 'office-ui-fabric-react';
|
||||||
|
|
||||||
|
export default function HelloWorld(props: IHelloWorldProps) {
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.row}>
|
||||||
|
<div className={styles.column}>
|
||||||
|
<span className={styles.title}>Welcome to SharePoint!</span>
|
||||||
|
<p>Customize SharePoint experiences using Web Parts.</p>
|
||||||
|
<p>{escape(props.description)}</p>
|
||||||
|
<Fabric.PrimaryButton href="https://aka.ms/spfx">Learn more</Fabric.PrimaryButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
// dummy locale file to keep the gulp task happy
|
||||||
|
define([], function() { return {}});
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"jsx": "react",
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"outDir": "lib",
|
||||||
|
"inlineSources": false,
|
||||||
|
"strictNullChecks": false,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"typeRoots": [
|
||||||
|
"./node_modules/@types",
|
||||||
|
"./node_modules/@microsoft"
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
"es6-promise",
|
||||||
|
"webpack-env"
|
||||||
|
],
|
||||||
|
"lib": [
|
||||||
|
"es5",
|
||||||
|
"dom",
|
||||||
|
"es2015.collection"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"lib"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||||
|
"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-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,
|
||||||
|
"variable-name": false,
|
||||||
|
"whitespace": false
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,12 @@
|
||||||
|
{
|
||||||
|
"@microsoft/generator-sharepoint": {
|
||||||
|
"version": "1.8.2",
|
||||||
|
"libraryName": "roman-numerals",
|
||||||
|
"libraryId": "f48eaad8-e027-4f27-bfca-0abd5239e65b",
|
||||||
|
"environment": "spo",
|
||||||
|
"packageManager": "npm",
|
||||||
|
"isCreatingSolution": true,
|
||||||
|
"isDomainIsolated": false,
|
||||||
|
"componentType": "webpart"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
# React Functional Stateful Component web part
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This web part demonstrates building a React functional component that includes state, using the recently introduced React Hooks feature. The example web part renders a number to Roman numerals conversion tool.
|
||||||
|
|
||||||
|
![Screenshot](Screenshot.png "Screenshot - Roman Numerals web part")
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
This sample was built with version 1.82 of the SharePoint Framework. It has been modified to use version 16.8 of the React framework (by default the version used is React 16.7). React 16.8 supports React Hooks which is required to support state management in a React functional component.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Solution|Author(s)
|
||||||
|
--------|---------
|
||||||
|
react-functional-stateful-component | Bill Ayers
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
Version|Date|Comments
|
||||||
|
-------|----|--------
|
||||||
|
1.0|June 5, 2019|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
|
||||||
|
|
||||||
|
* Clone this repository
|
||||||
|
* Move to /samples/react-functional-stateful-component folder
|
||||||
|
* At the command line run:
|
||||||
|
* `npm install`
|
||||||
|
* `gulp serve`
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
The purpose of this web part is to demonstrate building a React functional component that includes state. This is achieved using the recent React Hooks feature. The resulting code is cleaner and easier to follow than using a JavaScript/TypeScript class derived from React.Component. The example web part renders a number to Roman numerals conversion tool, although the functionality is just for the purposes of the demonstration.
|
||||||
|
|
||||||
|
This is an extension of the approach used in the [React-Functional-Component](https://github.com/SharePoint/sp-dev-fx-webparts/tree/master/samples/react-functional-component) sample.
|
||||||
|
|
||||||
|
* Simplification
|
||||||
|
* Functional Component
|
||||||
|
* Adding State
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-functional-stateful-component" />
|
||||||
|
|
||||||
|
## RomanNumeralsWebPart.ts Simplification
|
||||||
|
|
||||||
|
A number of simplifications have been made to the RomanNumeralsWebPart.ts file to make it easier to follow compared to the Yeoman generator starter project. The use of an external string collection has been removed - they are simply hard coded into the file to make it clear how the property pane configuration works.
|
||||||
|
|
||||||
|
The external interface to define the properties is moved from a separate file and inline into RomanNumeralsWebPart.ts. This interface is used by the web part and the component on the assumption that all the properties will be passed to the component as props. The property will then be available to the component through its **props** collection.
|
||||||
|
|
||||||
|
## Functional Component
|
||||||
|
|
||||||
|
The RomanNumerals.tsx React Component is a React functional component. This simplifies the code structure to a simple JavaScript function with the name of the component, a single argument containing the React props, and a simple return of the component rendering. Because it is just a function, there is no need to worry about **this** or **that**, constructors etc. In this example we are introducing state, so this is not a "pure" or "stateless" functional component. For a more complicated example it might be advantageous to break the component down into several nested components, some of which may well be stateless. Stateless components are very simple and trivially easy to test. But typically we need to manage the state for at least some of our components as described below.
|
||||||
|
|
||||||
|
## Adding State
|
||||||
|
|
||||||
|
State is managed by means of a fairly new feature called [React Hooks](https://reactjs.org/docs/hooks-intro.html). On line 11 of RomanNumerals.tsx the React.useState function is used to provide state:
|
||||||
|
|
||||||
|
```
|
||||||
|
const [value, setValue] = React.useState(parseInt(props.initialValue));
|
||||||
|
```
|
||||||
|
React.useState takes an initial value for the state variable and returns an array of two objects. The first is a variable containing the state value, and the second is a setter function for the value. We could refer to these as state[0] and state[1] or something, but the convention is to use the array destructuring operator to unpack them into local constants. The name of these is not important but a good practice is to use the form *[foo, setFoo]*, etc. Whenever we need to use the current value of the state variable we just refer to it (e.g. *{value}*), and wherever we need to change the value we call *useState(newValue)*. There is no need to use **this** because we are not inside a class, nor do we need to worry about the context of the **this** value, nor create a constructor to initialize state.
|
||||||
|
|
||||||
|
In the code we use the number input control to change the value of the state using a local function *onChange*, which simply sets a new value as the user types in characters. We also have a couple of additional buttons that can be used to increment and decrement the value which also demonstrates updating state with inline functions. Everything just works because the React framework will re-render the component whenever we update the state variable using the *setValue* function. If you need more complex state you could pass a more complex object to *useState* but a better approach is often to simply call *useState* once for each variable that makes up the state.
|
||||||
|
|
||||||
|
The output rendering uses the value of the state variable and does a conversion using the *romanToString* function:
|
||||||
|
|
||||||
|
```
|
||||||
|
<h3>{props.resultCaption} {romanToString(value)}</h3>
|
||||||
|
```
|
||||||
|
The *resultCaption* property is also rendered if defined.
|
||||||
|
|
||||||
|
## Building and testing
|
||||||
|
|
||||||
|
In the react-functional-component directory run **npm install** to resolve all the dependencies. Once this has completed you can run **gulp serve** to test the web part in the local workbench.
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 91 KiB |
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||||
|
"version": "2.0",
|
||||||
|
"bundles": {
|
||||||
|
"roman-numerals-web-part": {
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"entrypoint": "./lib/webparts/romanNumerals/RomanNumeralsWebPart.js",
|
||||||
|
"manifest": "./src/webparts/romanNumerals/RomanNumeralsWebPart.manifest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externals": {},
|
||||||
|
"localizedResources": {
|
||||||
|
"RomanNumeralsWebPartStrings": "lib/webparts/romanNumerals/loc/{locale}.js"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||||
|
"deployCdnPath": "temp/deploy"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||||
|
"workingDir": "./temp/deploy/",
|
||||||
|
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||||
|
"container": "roman-numerals",
|
||||||
|
"accessKey": "<!-- ACCESS KEY -->"
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||||
|
"solution": {
|
||||||
|
"name": "roman-numerals-client-side-solution",
|
||||||
|
"id": "f48eaad8-e027-4f27-bfca-0abd5239e65b",
|
||||||
|
"version": "1.0.0.0",
|
||||||
|
"includeClientSideAssets": true,
|
||||||
|
"isDomainIsolated": false
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"zippedPackage": "solution/roman-numerals.sppkg"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.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,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||||
|
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
'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.configureWebpack.mergeConfig({
|
||||||
|
additionalConfiguration: (config) => {
|
||||||
|
config.externals = config.externals.filter(name => !(["react", "react-dom"].includes(name)))
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
build.initialize(gulp);
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"name": "roman-numerals",
|
||||||
|
"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.8.2",
|
||||||
|
"@microsoft/sp-lodash-subset": "1.8.2",
|
||||||
|
"@microsoft/sp-office-ui-fabric-core": "1.8.2",
|
||||||
|
"@microsoft/sp-property-pane": "1.8.2",
|
||||||
|
"@microsoft/sp-webpart-base": "1.8.2",
|
||||||
|
"@types/es6-promise": "0.0.33",
|
||||||
|
"@types/webpack-env": "1.13.1",
|
||||||
|
"office-ui-fabric-react": "6.143.0",
|
||||||
|
"react": "^16.8.6",
|
||||||
|
"react-dom": "^16.8.6"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"@types/react": "16.7.22"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@microsoft/rush-stack-compiler-2.9": "0.7.7",
|
||||||
|
"@microsoft/rush-stack-compiler-3.3": "^0.2.15",
|
||||||
|
"@microsoft/sp-build-web": "1.8.2",
|
||||||
|
"@microsoft/sp-module-interfaces": "1.8.2",
|
||||||
|
"@microsoft/sp-tslint-rules": "1.8.2",
|
||||||
|
"@microsoft/sp-webpart-workbench": "1.8.2",
|
||||||
|
"@types/chai": "3.4.34",
|
||||||
|
"@types/mocha": "2.2.38",
|
||||||
|
"@types/react": "^16.8.19",
|
||||||
|
"@types/react-dom": "^16.8.4",
|
||||||
|
"ajv": "~5.2.2",
|
||||||
|
"gulp": "~3.9.1",
|
||||||
|
"typescript": "^3.5.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||||
|
"id": "0f227452-315b-4061-b627-930468a02acd",
|
||||||
|
"alias": "RomanNumeralsWebPart",
|
||||||
|
"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,
|
||||||
|
"supportedHosts": ["SharePointWebPart"],
|
||||||
|
|
||||||
|
"preconfiguredEntries": [{
|
||||||
|
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||||
|
"group": { "default": "Other" },
|
||||||
|
"title": { "default": "RomanNumerals" },
|
||||||
|
"description": { "default": "Roman Numeral Converter Web Part" },
|
||||||
|
"officeFabricIconFontName": "Page",
|
||||||
|
"properties": {
|
||||||
|
"title": "Convert Number to Roman Numerals",
|
||||||
|
"description": "Roman numeral converter - enter a number to see the equivalent in Roman numerals",
|
||||||
|
"initialValue": "2019",
|
||||||
|
"showUpdownButtons": false
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactDom from 'react-dom';
|
||||||
|
import { Version } from '@microsoft/sp-core-library';
|
||||||
|
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||||
|
import { IPropertyPaneConfiguration, PropertyPaneTextField, PropertyPaneToggle } from '@microsoft/sp-property-pane';
|
||||||
|
import RomanNumerals from './components/RomanNumerals';
|
||||||
|
|
||||||
|
export interface IRomanNumeralsProps {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
initialValue: string;
|
||||||
|
inputCaption: string;
|
||||||
|
resultCaption: string;
|
||||||
|
showUpdownButtons: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class HelloWorldWebPart extends BaseClientSideWebPart<IRomanNumeralsProps> {
|
||||||
|
|
||||||
|
public render(): void {
|
||||||
|
ReactDom.render(React.createElement(RomanNumerals, this.properties), this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onDispose(): void {
|
||||||
|
ReactDom.unmountComponentAtNode(this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get dataVersion(): Version {
|
||||||
|
return Version.parse('1.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
|
return {
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
header: {
|
||||||
|
description: "Properties"
|
||||||
|
},
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
groupName: "General",
|
||||||
|
groupFields: [
|
||||||
|
PropertyPaneTextField('title', { label: "Web part title" }),
|
||||||
|
PropertyPaneTextField('description', { label: "Description Text" }),
|
||||||
|
PropertyPaneToggle('showUpdownButtons', { label: "Show increment/decrement buttons" })
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
groupName: "Initialization",
|
||||||
|
groupFields: [
|
||||||
|
PropertyPaneTextField('initialValue', { label: "Initial Value (numeric)" })
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
groupName: "Captions",
|
||||||
|
groupFields: [
|
||||||
|
PropertyPaneTextField('inputCaption', { label: "Caption for input control" }),
|
||||||
|
PropertyPaneTextField('resultCaption', { label: "Caption for result" })
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
export function romanToString(val: number): string {
|
||||||
|
|
||||||
|
const roman = [
|
||||||
|
{ symbol: 'm', number: 1000000 },
|
||||||
|
{ symbol: 'd', number: 500000 },
|
||||||
|
{ symbol: 'c', number: 100000 },
|
||||||
|
{ symbol: 'l', number: 50000 },
|
||||||
|
{ symbol: 'x', number: 10000 },
|
||||||
|
{ symbol: 'v', number: 5000 },
|
||||||
|
{ symbol: 'M', number: 1000 },
|
||||||
|
{ symbol: 'D', number: 500 },
|
||||||
|
{ symbol: 'C', number: 100 },
|
||||||
|
{ symbol: 'L', number: 50 },
|
||||||
|
{ symbol: 'X', number: 10 },
|
||||||
|
{ symbol: 'V', number: 5 },
|
||||||
|
{ symbol: 'I', number: 1 }
|
||||||
|
];
|
||||||
|
|
||||||
|
var negative: boolean = (val < 0.0);
|
||||||
|
if (negative) val = -val;
|
||||||
|
var integer: number = Math.floor(val);
|
||||||
|
if (integer == 0) return "nihil";
|
||||||
|
var sText: string = "";
|
||||||
|
if (negative) sText += "minus ";
|
||||||
|
if (integer > 10000000 || integer > 80 * roman[0].number)
|
||||||
|
return "Sorry, number is too big for Roman Numerals";
|
||||||
|
|
||||||
|
for (var iDigit = 0; iDigit < roman.length; iDigit++) {
|
||||||
|
var n = Math.floor(integer / roman[iDigit].number);
|
||||||
|
integer = integer % roman[iDigit].number;
|
||||||
|
for (var i = 1; i <= n; i++) sText += roman[iDigit].symbol;
|
||||||
|
if (iDigit % 2 == 0 && iDigit + 2 < roman.length && Math.floor(integer / (roman[iDigit + 2].number * 9)) > 0) {
|
||||||
|
sText += roman[iDigit + 2].symbol;
|
||||||
|
sText += roman[iDigit].symbol;
|
||||||
|
integer -= roman[iDigit + 2].number * 9;
|
||||||
|
}
|
||||||
|
if (iDigit + 1 < roman.length && Math.floor(integer / (roman[iDigit + 1].number * 4)) > 0) {
|
||||||
|
sText += roman[iDigit + 1].symbol;
|
||||||
|
sText += roman[iDigit].symbol;
|
||||||
|
integer -= roman[iDigit + 1].number * 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sText;
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './RomanNumerals.module.scss';
|
||||||
|
import { IRomanNumeralsProps } from '../RomanNumeralsWebPart';
|
||||||
|
import { escape } from '@microsoft/sp-lodash-subset';
|
||||||
|
import * as Fabric from 'office-ui-fabric-react';
|
||||||
|
import { romanToString } from '../RomanToString';
|
||||||
|
|
||||||
|
export default function RomanNumerals(props: IRomanNumeralsProps) {
|
||||||
|
|
||||||
|
// Use React Hooks to manage state - useState returns value and setter as array...
|
||||||
|
const [value, setValue] = React.useState(parseInt(props.initialValue));
|
||||||
|
|
||||||
|
function onChange(event) {
|
||||||
|
setValue(parseInt(event.target.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
var updownButtons = null;
|
||||||
|
if (props.showUpdownButtons) updownButtons = (
|
||||||
|
<div className={styles.column}>
|
||||||
|
<br />
|
||||||
|
<Fabric.PrimaryButton onClick={() => setValue(value + 1)}>+</Fabric.PrimaryButton>
|
||||||
|
|
||||||
|
<Fabric.PrimaryButton onClick={() => setValue(value - 1)}>-</Fabric.PrimaryButton>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.row}>
|
||||||
|
<div className={styles.column}>
|
||||||
|
<span className={styles.title}>{props.title}</span>
|
||||||
|
<p>{escape(props.description)}</p>
|
||||||
|
</div>
|
||||||
|
<div className={styles.column}>
|
||||||
|
{props.inputCaption}<br />
|
||||||
|
<input type="number" min="0" max="9999999" value={value} onChange={onChange} />
|
||||||
|
</div>
|
||||||
|
{updownButtons}
|
||||||
|
<div className={styles.column}>
|
||||||
|
<br />
|
||||||
|
<h3>{props.resultCaption} {romanToString(value)}</h3>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
2
samples/react-functional-stateful-component/src/webparts/romanNumerals/loc/en-us.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// dummy locale file to keep gulp happy
|
||||||
|
define([], function() { return {}});
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"jsx": "react",
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"outDir": "lib",
|
||||||
|
"inlineSources": false,
|
||||||
|
"strictNullChecks": false,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"typeRoots": [
|
||||||
|
"./node_modules/@types",
|
||||||
|
"./node_modules/@microsoft"
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
"es6-promise",
|
||||||
|
"webpack-env"
|
||||||
|
],
|
||||||
|
"lib": [
|
||||||
|
"es5",
|
||||||
|
"dom",
|
||||||
|
"es2015.collection"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"lib"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||||
|
"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-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,
|
||||||
|
"variable-name": false,
|
||||||
|
"whitespace": false
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,12 @@
|
||||||
|
{
|
||||||
|
"@microsoft/generator-sharepoint": {
|
||||||
|
"isCreatingSolution": true,
|
||||||
|
"environment": "spo",
|
||||||
|
"version": "1.8.2",
|
||||||
|
"libraryName": "react-mobx-multiple-stores",
|
||||||
|
"libraryId": "94924d67-e7b2-415f-9bd3-3f69b18b37c8",
|
||||||
|
"packageManager": "npm",
|
||||||
|
"isDomainIsolated": false,
|
||||||
|
"componentType": "webpart"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Webpart with React and Mobx using multiple stores
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
A sample webpart that uses the [Mobx](https://mobx.js.org/) library with multiple stores to keep track of the applications state.
|
||||||
|
|
||||||
|
<img src="assets/demo.gif"/>
|
||||||
|
|
||||||
|
## Used SharePoint Framework Version
|
||||||
|
![drop](https://img.shields.io/badge/version-1.8.2-green.svg)
|
||||||
|
|
||||||
|
## Applies to
|
||||||
|
|
||||||
|
* [SharePoint Framework](https://dev.office.com/sharepoint)
|
||||||
|
* [SharePoint Framework Webpart Samples](https://github.com/SharePoint/sp-dev-fx-webparts)
|
||||||
|
* [Office 365 developer tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-developer-tenant)
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Solution|Author(s)
|
||||||
|
--------|---------
|
||||||
|
react-mobx-multiple-stores | Kemal Sinanagic / [@kemicza](http://twitter.com/kemicza) / kemicza@gmail.com
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
Version|Date|Comments
|
||||||
|
-------|----|--------
|
||||||
|
1.0|May 24, 2019|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
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ git clone https://github.com/SharePoint/sp-dev-fx-webparts
|
||||||
|
$ cd sp-dev-fx-webparts/samples/react-mobx-multiple-stores
|
||||||
|
$ npm install
|
||||||
|
$ gulp serve
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Enforces that the state always needs be updated in **actions**, using the <em>always</em> flag for <em>enforceActions</em>.
|
||||||
|
* Demonstrates the **toJS** method to convert an observable array to a javascript structure. This is used to render the items in a DetailsList.
|
||||||
|
* Out-of-the-box MobX **decorators** to keep the code clean.
|
||||||
|
* **Asynchronous** actions
|
||||||
|
* MobX **computed** values
|
||||||
|
* **Typescript** version 3.3.4 using <em>@microsoft/rush-stack-compiler-3.3</em> for compatibility with the latest MobX version and typings
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-mobx-multiple-stores" />
|
After Width: | Height: | Size: 449 KiB |
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||||
|
"version": "2.0",
|
||||||
|
"bundles": {
|
||||||
|
"mobx-tutorial-web-part": {
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"entrypoint": "./lib/webparts/mobxTutorial/MobxTutorialWebPart.js",
|
||||||
|
"manifest": "./src/webparts/mobxTutorial/MobxTutorialWebPart.manifest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externals": {},
|
||||||
|
"localizedResources": {
|
||||||
|
"MobxTutorialWebPartStrings": "lib/webparts/mobxTutorial/loc/{locale}.js"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||||
|
"deployCdnPath": "temp/deploy"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||||
|
"workingDir": "./temp/deploy/",
|
||||||
|
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||||
|
"container": "react-mobx-multiple-stores",
|
||||||
|
"accessKey": "<!-- ACCESS KEY -->"
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||||
|
"solution": {
|
||||||
|
"name": "react-mobx-multiple-stores-client-side-solution",
|
||||||
|
"id": "94924d67-e7b2-415f-9bd3-3f69b18b37c8",
|
||||||
|
"version": "1.0.0.0",
|
||||||
|
"includeClientSideAssets": true,
|
||||||
|
"isDomainIsolated": false
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"zippedPackage": "solution/react-mobx-multiple-stores.sppkg"
|
||||||
|
}
|
||||||
|
}
|