Added the Aggregated Calendar Webpart using React framework (#566)
* Initial Release * Updated Readme * Update Read Me * Updated readme with technolgies
This commit is contained in:
parent
293bbba17a
commit
ea3b8efe1e
|
@ -0,0 +1,25 @@
|
||||||
|
# EditorConfig helps developers define and maintain consistent
|
||||||
|
# coding styles between different editors and IDEs
|
||||||
|
# editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
|
||||||
|
[*]
|
||||||
|
|
||||||
|
# change these settings to your own preference
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# we recommend you to keep these unchanged
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[{package,bower}.json]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Build generated files
|
||||||
|
dist
|
||||||
|
lib
|
||||||
|
solution
|
||||||
|
temp
|
||||||
|
*.sppkg
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# OSX
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Visual Studio files
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
.vs
|
||||||
|
bin
|
||||||
|
obj
|
||||||
|
|
||||||
|
# Resx Generated Code
|
||||||
|
*.resx.ts
|
||||||
|
|
||||||
|
# Styles Generated Code
|
||||||
|
*.scss.ts
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"@microsoft/generator-sharepoint": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"libraryName": "react-aggregated-calendar",
|
||||||
|
"libraryId": "e904a5f2-6d72-4ac2-96d8-5679cb4c7384",
|
||||||
|
"environment": "spo"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
# React Aggregated Calendar Webpart
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
This is a sample webpart developed using React Framework to gather the aggregated events from the multiple calendars from multiple sites using Full Calendar from fullcalendar.io
|
||||||
|
|
||||||
|
|
||||||
|
![The web part in action](./assets/react-aggregated-calendar.gif)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
The webpart was designed to create an aggregated view of calendar to fetch events from multiple calendars across the sites and site collection.
|
||||||
|
The webpart will show the event information using the callout functionality of Office UI Fabric
|
||||||
|
|
||||||
|
Webpart is developed using below technologies
|
||||||
|
* React Framework
|
||||||
|
* Full Calendar(fullcalendar.io)
|
||||||
|
* jQuery
|
||||||
|
* Office UI Fabric
|
||||||
|
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
Before you can use this webpart exmaple, you will need atleast one Out of the Box Calendar created.
|
||||||
|
|
||||||
|
It is required that the users have view access on the calendar.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Solution|Author(s)
|
||||||
|
--------|---------
|
||||||
|
react-aggregated-calendar | [Dhaval Shah](https://www.linkedin.com/in/dhavalshah27) ([@beingdhavalshah](https://twitter.com/BeingDhavalShah))
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
Version|Date|Comments
|
||||||
|
-------|----|--------
|
||||||
|
|
||||||
|
1.0 |July 16, 2018 | Initial Release
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Minimal Path to Awesome
|
||||||
|
|
||||||
|
- Clone this repository
|
||||||
|
- in the command line run:
|
||||||
|
- `npm install`
|
||||||
|
- `gulp serve`
|
||||||
|
|
||||||
|
|
||||||
|
## Features
|
||||||
|
This Web Part displays the events from multiple calendars located in various sites/site collection of sharepoint:
|
||||||
|
|
||||||
|
- Aggregated events for Calendar
|
||||||
|
- Supports Sub-Sites and Site Collection level
|
||||||
|
- Display of Legend for each Calendar
|
||||||
|
- Formatted Date time
|
||||||
|
- Display of Event Details over event click
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-aggregated-calendar" />
|
||||||
|
|
||||||
|
## React Aggregated Calendar
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Building the code
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone the repo
|
||||||
|
npm i
|
||||||
|
npm i -g gulp
|
||||||
|
gulp
|
||||||
|
```
|
||||||
|
|
||||||
|
This package produces the following:
|
||||||
|
|
||||||
|
* lib/* - intermediate-stage commonjs build artifacts
|
||||||
|
* dist/* - the bundled script, along with other resources
|
||||||
|
* deploy/* - all resources which should be uploaded to a CDN.
|
||||||
|
|
||||||
|
### Build options
|
||||||
|
|
||||||
|
gulp clean
|
||||||
|
gulp test
|
||||||
|
gulp serve
|
||||||
|
gulp bundle
|
||||||
|
gulp package-solution
|
Binary file not shown.
After Width: | Height: | Size: 11 MiB |
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||||
|
"version": "2.0",
|
||||||
|
"bundles": {
|
||||||
|
"react-aggregated-calendar-web-part": {
|
||||||
|
"components": [{
|
||||||
|
"entrypoint": "./lib/webparts/reactAggregatedCalendar/ReactAggregatedCalendarWebPart.js",
|
||||||
|
"manifest": "./src/webparts/reactAggregatedCalendar/ReactAggregatedCalendarWebPart.manifest.json"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externals": {
|
||||||
|
"sp-client-custom-fields": "node_modules/sp-client-custom-fields/dist/sp-client-custom-fields.bundle.js",
|
||||||
|
"jquery": {
|
||||||
|
"path": "https://code.jquery.com/jquery-1.11.1.min.js",
|
||||||
|
"globalName": "jQuery"
|
||||||
|
},
|
||||||
|
"moment": "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js",
|
||||||
|
"fullcalendar": {
|
||||||
|
"path": "https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.js",
|
||||||
|
"globalName": "jQuery",
|
||||||
|
"globalDependencies": [
|
||||||
|
"jquery"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"localizedResources": {
|
||||||
|
"sp-client-custom-fields/strings": "node_modules/sp-client-custom-fields/lib/loc/{locale}.js",
|
||||||
|
"ReactAggregatedCalendarWebPartStrings": "lib/webparts/reactAggregatedCalendar/loc/{locale}.js"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||||
|
"deployCdnPath": "temp/deploy"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||||
|
"workingDir": "./temp/deploy/",
|
||||||
|
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||||
|
"container": "react-aggregated-calendar",
|
||||||
|
"accessKey": "<!-- ACCESS KEY -->"
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||||
|
"solution": {
|
||||||
|
"name": "react-aggregated-calendar-app",
|
||||||
|
"id": "e904a5f2-6d72-4ac2-96d8-5679cb4c7384",
|
||||||
|
"version": "1.0.0.0",
|
||||||
|
"includeClientSideAssets": true,
|
||||||
|
"skipFeatureDeployment": true
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"zippedPackage": "solution/react-aggregated-calendar.sppkg"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/core-build/serve.schema.json",
|
||||||
|
"port": 4321,
|
||||||
|
"https": true,
|
||||||
|
"initialPage": "https://localhost:5432/workbench",
|
||||||
|
"api": {
|
||||||
|
"port": 5432,
|
||||||
|
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/core-build/tslint.schema.json",
|
||||||
|
|
||||||
|
"displayAsWarning": true,
|
||||||
|
"removeExistingRules": true,
|
||||||
|
"useDefaultConfigAsBase": false,
|
||||||
|
"lintConfig": {
|
||||||
|
"rules": {
|
||||||
|
"class-name": false,
|
||||||
|
"export-name": false,
|
||||||
|
"forin": false,
|
||||||
|
"label-position": false,
|
||||||
|
"member-access": true,
|
||||||
|
"no-arg": false,
|
||||||
|
"no-console": false,
|
||||||
|
"no-construct": false,
|
||||||
|
"no-duplicate-case": true,
|
||||||
|
"no-duplicate-variable": true,
|
||||||
|
"no-eval": false,
|
||||||
|
"no-function-expression": true,
|
||||||
|
"no-internal-module": true,
|
||||||
|
"no-shadowed-variable": true,
|
||||||
|
"no-switch-case-fall-through": true,
|
||||||
|
"no-unnecessary-semicolons": true,
|
||||||
|
"no-unused-expression": true,
|
||||||
|
"no-use-before-declare": true,
|
||||||
|
"no-with-statement": true,
|
||||||
|
"semicolon": true,
|
||||||
|
"trailing-comma": false,
|
||||||
|
"typedef": false,
|
||||||
|
"typedef-whitespace": false,
|
||||||
|
"use-named-parameter": true,
|
||||||
|
"valid-typeof": true,
|
||||||
|
"variable-name": false,
|
||||||
|
"whitespace": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||||
|
"cdnBasePath": "https://sharepointarena.sharepoint.com/sites/SPFX/CDN/react-aggregated-calendar"
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const gulp = require('gulp');
|
||||||
|
const build = require('@microsoft/sp-build-web');
|
||||||
|
const path = require('path');
|
||||||
|
const bundleAnalyzer = require('webpack-bundle-analyzer');
|
||||||
|
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||||
|
build.configureWebpack.mergeConfig({
|
||||||
|
additionalConfiguration: (generatedConfiguration) => {
|
||||||
|
const lastDirName = path.basename(__dirname);
|
||||||
|
const dropPath = path.join(__dirname, 'temp', 'stats');
|
||||||
|
generatedConfiguration.plugins.push(new bundleAnalyzer.BundleAnalyzerPlugin({
|
||||||
|
openAnalyzer: false,
|
||||||
|
analyzerMode: 'static',
|
||||||
|
reportFilename: path.join(dropPath, `${lastDirName}.stats.html`),
|
||||||
|
generateStatsFile: true,
|
||||||
|
statsFilename: path.join(dropPath, `${lastDirName}.stats.json`),
|
||||||
|
logLevel: 'error'
|
||||||
|
}));
|
||||||
|
|
||||||
|
return generatedConfiguration;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
build.initialize(gulp);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"name": "react-aggregated-calendar",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "gulp bundle",
|
||||||
|
"clean": "gulp clean",
|
||||||
|
"test": "gulp test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@microsoft/sp-core-library": "~1.4.1",
|
||||||
|
"@microsoft/sp-lodash-subset": "~1.4.1",
|
||||||
|
"@microsoft/sp-office-ui-fabric-core": "~1.4.1",
|
||||||
|
"@microsoft/sp-webpart-base": "~1.4.1",
|
||||||
|
"@types/fullcalendar": "^3.8.0",
|
||||||
|
"@types/jquery": "^3.3.2",
|
||||||
|
"@types/react": "15.6.6",
|
||||||
|
"@types/react-dom": "15.5.6",
|
||||||
|
"@types/webpack-env": ">=1.12.1 <1.14.0",
|
||||||
|
"moment": "^2.22.2",
|
||||||
|
"react": "15.6.2",
|
||||||
|
"react-dom": "15.6.2",
|
||||||
|
"sp-client-custom-fields": "^1.3.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@microsoft/sp-build-web": "~1.4.1",
|
||||||
|
"@microsoft/sp-module-interfaces": "~1.4.1",
|
||||||
|
"@microsoft/sp-webpart-workbench": "~1.4.1",
|
||||||
|
"@types/chai": ">=3.4.34 <3.6.0",
|
||||||
|
"@types/mocha": ">=2.2.33 <2.6.0",
|
||||||
|
"ajv": "~5.2.2",
|
||||||
|
"gulp": "~3.9.1",
|
||||||
|
"webpack-bundle-analyzer": "^2.13.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||||
|
"id": "be293fec-29f2-4db2-869a-604e560980fa",
|
||||||
|
"alias": "ReactAggregatedCalendarWebPart",
|
||||||
|
"componentType": "WebPart",
|
||||||
|
"version": "*",
|
||||||
|
"manifestVersion": 2,
|
||||||
|
"requiresCustomScript": false,
|
||||||
|
"preconfiguredEntries": [{
|
||||||
|
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
|
||||||
|
"group": {
|
||||||
|
"default": "Ayka"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"default": "React Aggreagated Calendar App"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"default": "React Aggreagated Calendar App developed by Dhaval Shah"
|
||||||
|
},
|
||||||
|
"officeFabricIconFontName": "SearchCalendar",
|
||||||
|
"properties": {
|
||||||
|
"header": "My Calendar",
|
||||||
|
"showWeekends": "Off",
|
||||||
|
"showLegend": true,
|
||||||
|
"dateFormat": "MMMM Do YYYY, h:mm a",
|
||||||
|
"defaultView": "month",
|
||||||
|
"availableViews": [
|
||||||
|
"month",
|
||||||
|
"agendaWeek",
|
||||||
|
"agendaDay",
|
||||||
|
"listMonth"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactDom from 'react-dom';
|
||||||
|
import { Log, Version } from '@microsoft/sp-core-library';
|
||||||
|
import { BaseClientSideWebPart, IPropertyPaneConfiguration, PropertyPaneTextField, PropertyPaneDropdown, PropertyPaneToggle } from '@microsoft/sp-webpart-base';
|
||||||
|
import { SPComponentLoader } from '@microsoft/sp-loader';
|
||||||
|
import * as strings from 'ReactAggregatedCalendarWebPartStrings';
|
||||||
|
import ReactAggregatedCalendar from './components/ReactAggregatedCalendar';
|
||||||
|
import { IReactAggregatedCalendarProps } from './components/IReactAggregatedCalendarProps';
|
||||||
|
import MessageComponent, { IMessageComponentProps } from '../shared/components/MessageComponent';
|
||||||
|
import { MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
|
||||||
|
import { PropertyFieldCustomList, CustomListFieldType } from 'sp-client-custom-fields/lib/PropertyFieldCustomList';
|
||||||
|
import { SelectedCalendar } from './model/SelectedCalendar';
|
||||||
|
import { PropertyFieldDropDownSelect } from 'sp-client-custom-fields/lib/PropertyFieldDropDownSelect';
|
||||||
|
import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for the Aggregated Calendar Webpart Class Properties
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface IReactAggregatedCalendarWebPartProps
|
||||||
|
*/
|
||||||
|
export interface IReactAggregatedCalendarWebPartProps {
|
||||||
|
header: string;
|
||||||
|
calendarList: SelectedCalendar[];
|
||||||
|
dateFormat: string;
|
||||||
|
showLegend: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggregated Calendar Webpart Class
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class ReactAggregatedCalendarWebPart
|
||||||
|
* @extends {BaseClientSideWebPart<IReactAggregatedCalendarWebPartProps>}
|
||||||
|
*/
|
||||||
|
export default class ReactAggregatedCalendarWebPart extends BaseClientSideWebPart<IReactAggregatedCalendarWebPartProps> {
|
||||||
|
private availableViews: IDropdownOption[] = require("../shared/availableViews.json");
|
||||||
|
private timeFormat: IDropdownOption[] = require("../shared/timeFormat.json");
|
||||||
|
protected onInit(): Promise<void> {
|
||||||
|
SPComponentLoader.loadCss('https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.css');
|
||||||
|
return super.onInit();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Renders the React Agggregated Calendar Webpart
|
||||||
|
*
|
||||||
|
* @memberof ReactAggregatedCalendarWebPart
|
||||||
|
*/
|
||||||
|
public render(): void {
|
||||||
|
Log.verbose("render()", "Inside Render", this.context.serviceScope);
|
||||||
|
if (this.needsConfiguration()) {
|
||||||
|
Log.warn("render()", "Webpart not configured", this.context.serviceScope);
|
||||||
|
this.renderMessage(strings.WebPartNotConfigured, MessageBarType.error, true);
|
||||||
|
} else {
|
||||||
|
Log.info("render()", "Webpart configuration not needed", this.context.serviceScope);
|
||||||
|
const element: React.ReactElement<IReactAggregatedCalendarProps> = React.createElement(
|
||||||
|
ReactAggregatedCalendar,
|
||||||
|
{
|
||||||
|
header: this.properties.header,
|
||||||
|
selectedCalendarLists: this.properties.calendarList,
|
||||||
|
context: this.context,
|
||||||
|
domElement: this.domElement,
|
||||||
|
dateFormat: this.properties.dateFormat,
|
||||||
|
showLegend: this.properties.showLegend
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ReactDom.render(element, this.domElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the data Version of the Webpart
|
||||||
|
*
|
||||||
|
* @readonly
|
||||||
|
* @protected
|
||||||
|
* @type {Version}
|
||||||
|
* @memberof ReactAggregatedCalendarWebPart
|
||||||
|
*/
|
||||||
|
protected get dataVersion(): Version {
|
||||||
|
return Version.parse('1.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the SPFx Property Pane of the Aggregated Calendar Webpart
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
* @returns {IPropertyPaneConfiguration}
|
||||||
|
* @memberof ReactAggregatedCalendarWebPart
|
||||||
|
*/
|
||||||
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
|
return {
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
header: {
|
||||||
|
description: strings.PropertyPaneDescription
|
||||||
|
},
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
groupName: strings.BasicGroupName,
|
||||||
|
groupFields: [
|
||||||
|
PropertyPaneTextField('header', {
|
||||||
|
label: strings.HeaderFieldLabel
|
||||||
|
}),
|
||||||
|
PropertyFieldCustomList('calendarList', {
|
||||||
|
label: strings.SelectCalendarLabel,
|
||||||
|
value: this.properties.calendarList,
|
||||||
|
headerText: 'Manage Calendar',
|
||||||
|
fields: [
|
||||||
|
{ id: 'CalendarTitle', title: 'Calendar Title', required: true, type: CustomListFieldType.string },
|
||||||
|
{ id: 'SiteUrl', title: 'Site Url', required: true, type: CustomListFieldType.string },
|
||||||
|
{
|
||||||
|
id: 'CalendarListTitle', title: 'Calendar List Title', required: true,
|
||||||
|
type: CustomListFieldType.string
|
||||||
|
},
|
||||||
|
{ id: 'Color', title: 'Color', required: false, type: CustomListFieldType.color }
|
||||||
|
],
|
||||||
|
disabled: false,
|
||||||
|
onPropertyChange: this.onPropertyPaneFieldChanged.bind(this),
|
||||||
|
render: this.render.bind(this),
|
||||||
|
disableReactivePropertyChanges: this.disableReactivePropertyChanges,
|
||||||
|
properties: this.properties,
|
||||||
|
context: this.context,
|
||||||
|
key: 'calendarList'
|
||||||
|
}),
|
||||||
|
PropertyPaneDropdown('dateFormat', {
|
||||||
|
label: strings.SelectDateFormatFieldLabel,
|
||||||
|
selectedKey: "MMMM Do YYYY, h: mm a",
|
||||||
|
options: this.timeFormat
|
||||||
|
}),
|
||||||
|
PropertyPaneToggle('showLegend', {
|
||||||
|
label: strings.ShowLegendFieldLabel,
|
||||||
|
onText: strings.OnTextFieldLabel,
|
||||||
|
offText: strings.OffTextFieldLabel,
|
||||||
|
checked: false
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether Aggregated Calendar needs configuration
|
||||||
|
* or not
|
||||||
|
* @private
|
||||||
|
* @returns {boolean}
|
||||||
|
* @memberof ReactAggregatedCalendarWebPart
|
||||||
|
*/
|
||||||
|
private needsConfiguration(): boolean {
|
||||||
|
Log.verbose("needsConfiguration()", "calendarList : " + this.properties.calendarList, this.context.serviceScope);
|
||||||
|
return this.properties.calendarList === null ||
|
||||||
|
this.properties.calendarList === undefined ||
|
||||||
|
this.properties.calendarList.length === 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Render Message method to render the message component
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} statusMessage
|
||||||
|
* @param {MessageBarType} statusMessageType
|
||||||
|
* @param {boolean} display
|
||||||
|
* @memberof ReactAggregatedCalendarWebPart
|
||||||
|
*/
|
||||||
|
private renderMessage(statusMessage: string, statusMessageType: MessageBarType,
|
||||||
|
display: boolean): void {
|
||||||
|
Log.verbose("renderMessage()", "Rendering Message " + statusMessage + " of type " + statusMessageType, this.context.serviceScope);
|
||||||
|
const messageElement: React.ReactElement<IMessageComponentProps> = React.createElement(
|
||||||
|
MessageComponent,
|
||||||
|
{
|
||||||
|
Message: statusMessage,
|
||||||
|
Type: statusMessageType,
|
||||||
|
Display: display
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ReactDom.render(messageElement, this.domElement);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { SelectedCalendar } from "../model/SelectedCalendar";
|
||||||
|
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface IReactAggregatedCalendarProps
|
||||||
|
*/
|
||||||
|
export interface IReactAggregatedCalendarProps {
|
||||||
|
header: string;
|
||||||
|
selectedCalendarLists: SelectedCalendar[];
|
||||||
|
context: WebPartContext;
|
||||||
|
domElement: HTMLElement;
|
||||||
|
dateFormat: string;
|
||||||
|
showLegend: boolean;
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||||
|
@import '~office-ui-fabric-react/dist/sass/_References.scss';
|
||||||
|
@import '~office-ui-fabric-react/dist/sass/Fabric.scss';
|
||||||
|
.closeIconFocus:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calloutInnerEventContent {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msCalloutclose {
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
right: 12px;
|
||||||
|
padding: 8px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
font-size: $ms-font-size-m;
|
||||||
|
color: $ms-color-neutralSecondary;
|
||||||
|
//z-index: ($ms-zIndex-Callout + $ms-zIndex-front);
|
||||||
|
}
|
||||||
|
|
||||||
|
.msCalloutheader {
|
||||||
|
//z-index: ($ms-zIndex-Callout + $ms-zIndex-middle);
|
||||||
|
padding: 18px 24px 12px;
|
||||||
|
background-color: "[theme: inputBackgroundCheckedHovered, default: #0078d7]" !important ;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msCallouttitle {
|
||||||
|
margin: 0; //font-family: $ms-font-family-semilight;
|
||||||
|
//font-size: $ms-font-size-xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msCalloutinner {
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 24px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msCalloutsubText {
|
||||||
|
margin: 0; //font-family: $ms-font-family-semilight;
|
||||||
|
color: $ms-color-neutralPrimary;
|
||||||
|
font-size: $ms-font-size-s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reactAggregatedCalendar {
|
||||||
|
.legend {
|
||||||
|
border: solid 1px #eee;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: right;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
.outerLegendDiv {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px 20px 5px 5px;
|
||||||
|
line-height: 15px;
|
||||||
|
max-width: 300px;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.innerLegendDiv {
|
||||||
|
border: 1px solid gray;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
font-size: 1px;
|
||||||
|
position: relative;
|
||||||
|
float: left;
|
||||||
|
margin-right: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
|
||||||
|
// background-color: $ms-color-themeDark;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
@include ms-Grid-col;
|
||||||
|
@include ms-lg12;
|
||||||
|
@include ms-xl12;
|
||||||
|
@include ms-xlPush2;
|
||||||
|
@include ms-lgPush1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@include ms-font-xl;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subTitle {
|
||||||
|
@include ms-font-l;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
@include ms-font-l;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
// Our button
|
||||||
|
text-decoration: none;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
// Primary Button
|
||||||
|
min-width: 80px;
|
||||||
|
background-color: $ms-color-themePrimary;
|
||||||
|
border-color: $ms-color-themePrimary;
|
||||||
|
color: $ms-color-white;
|
||||||
|
|
||||||
|
// Basic Button
|
||||||
|
outline: transparent;
|
||||||
|
position: relative;
|
||||||
|
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-size: $ms-font-size-m;
|
||||||
|
font-weight: $ms-font-weight-regular;
|
||||||
|
border-width: 0;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 16px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-weight: $ms-font-weight-semibold;
|
||||||
|
font-size: $ms-font-size-m;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
margin: 0 4px;
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,277 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as $ from 'jquery';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import 'fullcalendar';
|
||||||
|
import styles from './ReactAggregatedCalendar.module.scss';
|
||||||
|
import { IReactAggregatedCalendarProps } from './IReactAggregatedCalendarProps';
|
||||||
|
import { EnvironmentType, Environment } from '@microsoft/sp-core-library';
|
||||||
|
import { AggregatedCalendarService } from '../service/AggregatedCalendarService';
|
||||||
|
import { AggregatedCalendarMockService } from '../service/AggregatedCalendarMockService';
|
||||||
|
import { css } from 'office-ui-fabric-react/lib/Utilities';
|
||||||
|
import * as strings from 'ReactAggregatedCalendarWebPartStrings';
|
||||||
|
import { FullCalendarEvent } from '../model/FullCalendarEvent';
|
||||||
|
import { DirectionalHint, Callout } from 'office-ui-fabric-react/lib/Callout';
|
||||||
|
import { Label } from 'office-ui-fabric-react/lib/Label';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for maintaining ReactAggregatedCalendar webpart state
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface IReactAggregatedCalendarState
|
||||||
|
*/
|
||||||
|
export interface IReactAggregatedCalendarState {
|
||||||
|
isCalloutVisible?: boolean;
|
||||||
|
selectedEvent: FullCalendarEvent;
|
||||||
|
directionalHint?: DirectionalHint;
|
||||||
|
isBeakVisible?: boolean;
|
||||||
|
gapSpace?: number;
|
||||||
|
beakWidth?: number;
|
||||||
|
EventElement: HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React Component for ReactAggregatedCalendar Webpart
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class ReactAggregatedCalendar
|
||||||
|
* @extends {React.Component<IReactAggregatedCalendarProps, IReactAggregatedCalendarState>}
|
||||||
|
*/
|
||||||
|
export default class ReactAggregatedCalendar
|
||||||
|
extends React.Component<IReactAggregatedCalendarProps, IReactAggregatedCalendarState> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*Creates an instance of ReactAggregatedCalendar.
|
||||||
|
* @param {IReactAggregatedCalendarProps} props
|
||||||
|
* @memberof ReactAggregatedCalendar
|
||||||
|
*/
|
||||||
|
public constructor(props: IReactAggregatedCalendarProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.onCalloutDismiss = this.onCalloutDismiss.bind(this);
|
||||||
|
this.eventClickHandler = this.eventClickHandler.bind(this);
|
||||||
|
|
||||||
|
// Initialize the State for ReactAggregatedCalendar
|
||||||
|
this.state = {
|
||||||
|
isCalloutVisible: false,
|
||||||
|
selectedEvent: {
|
||||||
|
id: 0,
|
||||||
|
title: '',
|
||||||
|
color: '',
|
||||||
|
start: moment(),
|
||||||
|
end: moment(),
|
||||||
|
description: '',
|
||||||
|
location: '',
|
||||||
|
allDay: false,
|
||||||
|
category: ''
|
||||||
|
},
|
||||||
|
directionalHint: DirectionalHint.bottomCenter,
|
||||||
|
isBeakVisible: true,
|
||||||
|
gapSpace: 10,
|
||||||
|
beakWidth: 20,
|
||||||
|
EventElement: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* componentDidMount
|
||||||
|
*
|
||||||
|
* @memberof ReactAggregatedCalendar
|
||||||
|
*/
|
||||||
|
public componentDidMount() {
|
||||||
|
this.renderContents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* componentDidUpdate
|
||||||
|
*
|
||||||
|
* @memberof ReactAggregatedCalendar
|
||||||
|
*/
|
||||||
|
public componentDidUpdate() {
|
||||||
|
this.renderContents();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Render method for the ReactAggregatedCalendar React Component
|
||||||
|
*
|
||||||
|
* @returns {React.ReactElement<IReactAggregatedCalendarProps>}
|
||||||
|
* @memberof ReactAggregatedCalendar
|
||||||
|
*/
|
||||||
|
public render(): React.ReactElement<IReactAggregatedCalendarProps> {
|
||||||
|
const { isCalloutVisible } = this.state;
|
||||||
|
let calendarLegend: JSX.Element[] = ([]);
|
||||||
|
|
||||||
|
// Render the Legend for the Calendar Events
|
||||||
|
calendarLegend = this.props.selectedCalendarLists.map((calendar) => {
|
||||||
|
let calendarLegendColor = {
|
||||||
|
'background-color': `${calendar.Color}`
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className={styles.outerLegendDiv} title={calendar.CalendarTitle}>
|
||||||
|
<div className={styles.innerLegendDiv} style={calendarLegendColor}>
|
||||||
|
</div>
|
||||||
|
{calendar.CalendarTitle}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render the FullCalendar container
|
||||||
|
return (
|
||||||
|
<div className={styles.reactAggregatedCalendar}>
|
||||||
|
<h1>{this.props.header}</h1>
|
||||||
|
<div >
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div id="aggregatedCalendarComp">
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
this.props.showLegend &&
|
||||||
|
<div className={styles.legend}>
|
||||||
|
{calendarLegend}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isCalloutVisible && (
|
||||||
|
<Callout
|
||||||
|
className="ms-CalloutExample"
|
||||||
|
ariaLabelledBy={'callout-label-1'}
|
||||||
|
ariaDescribedBy={'callout-description-1'}
|
||||||
|
role={'alertdialog'}
|
||||||
|
target={this.state.EventElement}
|
||||||
|
onDismiss={this.onCalloutDismiss}
|
||||||
|
gapSpace={this.state.gapSpace}
|
||||||
|
isBeakVisible={this.state.isBeakVisible}
|
||||||
|
beakWidth={this.state.beakWidth}
|
||||||
|
directionalHint={this.state.directionalHint}
|
||||||
|
setInitialFocus={true}>
|
||||||
|
<button onClick={this.onCalloutDismiss}
|
||||||
|
className={css(styles.msCalloutclose, styles.closeIconFocus, 'ms-fontColor-white')} >
|
||||||
|
<i className="ms-Icon ms-Icon--Clear"></i>
|
||||||
|
</button>
|
||||||
|
<div className={css(styles.msCalloutheader, 'ms-fontColor-white')}>
|
||||||
|
<p className={styles.msCallouttitle}>{this.state.selectedEvent.title}</p>
|
||||||
|
</div>
|
||||||
|
<div className={css(styles.msCalloutinner, styles.calloutInnerEventContent)}>
|
||||||
|
<div className="ms-Callout-content">
|
||||||
|
<p className={styles.msCalloutsubText} dangerouslySetInnerHTML={this.createMarkup(this.state.selectedEvent.description)} />
|
||||||
|
<p className={styles.msCalloutsubText}>
|
||||||
|
<Label>{strings.StartTimeLabel}{this.state.selectedEvent.start.format(this.props.dateFormat)} </Label>
|
||||||
|
{
|
||||||
|
this.state.selectedEvent.end !== null &&
|
||||||
|
<Label>{strings.EndTimeLabel} {this.state.selectedEvent.end.format(this.props.dateFormat)}</Label>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.selectedEvent.location !== '' &&
|
||||||
|
<Label>{strings.LocationLabel}{this.state.selectedEvent.location}</Label>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.selectedEvent.category !== '' &&
|
||||||
|
<Label>{strings.CategoryLabel}{this.state.selectedEvent.category}</Label>
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Callout>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the Full Calendar Plugin
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @memberof ReactAggregatedCalendar
|
||||||
|
*/
|
||||||
|
private renderContents() {
|
||||||
|
let containerEl: JQuery = $('#aggregatedCalendarComp');
|
||||||
|
let eventSourcesArray: any[] = [];
|
||||||
|
const dataService = (Environment.type === EnvironmentType.Test
|
||||||
|
|| Environment.type === EnvironmentType.Local) ? new AggregatedCalendarMockService() :
|
||||||
|
this.props.context.serviceScope.consume(AggregatedCalendarService.serviceKey);
|
||||||
|
console.log(this.props.selectedCalendarLists);
|
||||||
|
this.props.selectedCalendarLists.forEach((calendarData) => {
|
||||||
|
const calendarRestApi: string = calendarData.SiteUrl.trim()
|
||||||
|
+ '/_api/Web/Lists/GetByTitle(\'' + calendarData.CalendarListTitle.trim() + '\')/items';
|
||||||
|
|
||||||
|
eventSourcesArray.push({
|
||||||
|
events: ((start: moment.Moment, end: moment.Moment, timezone, callback) => {
|
||||||
|
const startDate = start.format('YYYY-MM-DD');
|
||||||
|
const endDate = end.format('YYYY-MM-DD');
|
||||||
|
dataService.getEventsForCalendar(calendarRestApi, calendarData.Color, startDate, endDate)
|
||||||
|
.then((response: FullCalendarEvent[]) => {
|
||||||
|
callback(response);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
containerEl.fullCalendar({
|
||||||
|
timezone: 'local',
|
||||||
|
header: {
|
||||||
|
left: 'prev,next today',
|
||||||
|
center: 'title'
|
||||||
|
},
|
||||||
|
defaultDate: new Date(),
|
||||||
|
navLinks: true,
|
||||||
|
editable: true,
|
||||||
|
eventLimit: true,
|
||||||
|
eventSources: eventSourcesArray,
|
||||||
|
eventClick: this.eventClickHandler
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Click Event handler when the event is clicked on the Calendar
|
||||||
|
* Display the Callout function to display event details
|
||||||
|
* @private
|
||||||
|
* @param {*} eventObj
|
||||||
|
* @param {*} jsEvent
|
||||||
|
* @param {*} view
|
||||||
|
* @memberof ReactAggregatedCalendar
|
||||||
|
*/
|
||||||
|
private eventClickHandler(eventObj: any, jsEvent: any, view: any) {
|
||||||
|
|
||||||
|
this.setState(() => {
|
||||||
|
return {
|
||||||
|
isCalloutVisible: !this.state.isCalloutVisible,
|
||||||
|
selectedEvent: {
|
||||||
|
id: eventObj.id,
|
||||||
|
title: eventObj.title,
|
||||||
|
color: eventObj.color,
|
||||||
|
start: moment(eventObj.start),
|
||||||
|
end: moment(eventObj.end),
|
||||||
|
description: eventObj.description,
|
||||||
|
location: eventObj.location,
|
||||||
|
allDay: eventObj.allDay,
|
||||||
|
category: eventObj.category
|
||||||
|
},
|
||||||
|
EventElement: jsEvent.toElement
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the call out component on close
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @memberof ReactAggregatedCalendar
|
||||||
|
*/
|
||||||
|
private onCalloutDismiss() {
|
||||||
|
this.setState({
|
||||||
|
isCalloutVisible: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create markup for rendering HTML on react component
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns
|
||||||
|
* @memberof ReactAggregatedCalendar
|
||||||
|
*/
|
||||||
|
private createMarkup(description: string) {
|
||||||
|
return { __html: description };
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
22
samples/react-aggregated-calendar/src/webparts/reactAggregatedCalendar/loc/en-us.js
vendored
Normal file
22
samples/react-aggregated-calendar/src/webparts/reactAggregatedCalendar/loc/en-us.js
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
define([], function () {
|
||||||
|
return {
|
||||||
|
"PropertyPaneDescription": "React Aggregated Calendar View App",
|
||||||
|
"BasicGroupName": "Dynamic Properties",
|
||||||
|
"DescriptionFieldLabel": "Description Field",
|
||||||
|
"SelectCalendarLabel": "Add Calendars",
|
||||||
|
"HeaderFieldLabel": "App Header Value",
|
||||||
|
"WebPartNotConfigured": "Webpart is not configured. Please configure webpart by Editing the page.",
|
||||||
|
"StartTimeLabel": "Start Time : ",
|
||||||
|
"EndTimeLabel": "End Time : ",
|
||||||
|
"LocationLabel": "Location : ",
|
||||||
|
"CategoryLabel": "Category : ",
|
||||||
|
"SelectDateFormatFieldLabel": "Select Date & Time Format",
|
||||||
|
"ShowWeekendsFieldLabel": "Show Weekends",
|
||||||
|
"OnTextFieldLabel": "On",
|
||||||
|
"OffTextFieldLabel": "Off",
|
||||||
|
"DefautlViewFieldLabel": "Default Calendar View",
|
||||||
|
"ShowLegendFieldLabel": "Show Legend",
|
||||||
|
"AvailableViewFieldLabel": "Select Available Views",
|
||||||
|
"HeightFieldLabel":"Enter Calendar Height"
|
||||||
|
}
|
||||||
|
});
|
25
samples/react-aggregated-calendar/src/webparts/reactAggregatedCalendar/loc/mystrings.d.ts
vendored
Normal file
25
samples/react-aggregated-calendar/src/webparts/reactAggregatedCalendar/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
declare interface IReactAggregatedCalendarWebPartStrings {
|
||||||
|
PropertyPaneDescription: string;
|
||||||
|
BasicGroupName: string;
|
||||||
|
DescriptionFieldLabel: string;
|
||||||
|
SelectDateFormatFieldLabel: string;
|
||||||
|
ShowWeekendsFieldLabel: string;
|
||||||
|
WebPartNotConfigured: string;
|
||||||
|
SelectCalendarLabel: string;
|
||||||
|
HeaderFieldLabel: string;
|
||||||
|
StartTimeLabel: string;
|
||||||
|
EndTimeLabel: string;
|
||||||
|
LocationLabel: string;
|
||||||
|
CategoryLabel: string;
|
||||||
|
OnTextFieldLabel: string;
|
||||||
|
OffTextFieldLabel: string;
|
||||||
|
DefautlViewFieldLabel: string;
|
||||||
|
ShowLegendFieldLabel: string;
|
||||||
|
AvailableViewFieldLabel: string;
|
||||||
|
HeightFieldLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'ReactAggregatedCalendarWebPartStrings' {
|
||||||
|
const strings: IReactAggregatedCalendarWebPartStrings;
|
||||||
|
export = strings;
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for FullCalendarEvent
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface FullCalendarEvent
|
||||||
|
*/
|
||||||
|
export interface FullCalendarEvent {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
start: moment.Moment;
|
||||||
|
end: moment.Moment;
|
||||||
|
color: string;
|
||||||
|
allDay: boolean;
|
||||||
|
description: string;
|
||||||
|
location: string;
|
||||||
|
category: string;
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
* Interface for SPCalendarItemsValue
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface SPCalendarItemsValue
|
||||||
|
*/
|
||||||
|
export interface SPCalendarItemsValue {
|
||||||
|
Id: number;
|
||||||
|
Title: string;
|
||||||
|
Location?: any;
|
||||||
|
EventDate: Date;
|
||||||
|
EndDate: Date;
|
||||||
|
Description?: any;
|
||||||
|
fAllDayEvent?: boolean;
|
||||||
|
Category?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for SPCalendarItems
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface SPCalendarItems
|
||||||
|
*/
|
||||||
|
export interface SPCalendarItems {
|
||||||
|
value: SPCalendarItemsValue[];
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
/**
|
||||||
|
* Interface for SelectedCalendar
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface SelectedCalendar
|
||||||
|
*/
|
||||||
|
export interface SelectedCalendar {
|
||||||
|
CalendarTitle: string;
|
||||||
|
SiteUrl: string;
|
||||||
|
CalendarListTitle: string;
|
||||||
|
Color: string;
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { IAggregatedCalendarService } from './IAggregatedCalendarService';
|
||||||
|
import { SelectedCalendar } from '../model/SelectedCalendar';
|
||||||
|
import { FullCalendarEvent } from '../model/FullCalendarEvent';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
/**
|
||||||
|
* Mock Service for AggregatedCalendarService
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class AggregatedCalendarMockService
|
||||||
|
* @implements {IAggregatedCalendarService}
|
||||||
|
*/
|
||||||
|
export class AggregatedCalendarMockService implements IAggregatedCalendarService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mock data for the calendar events
|
||||||
|
*
|
||||||
|
* @param {string} calendarRestApi
|
||||||
|
* @param {string} calendarColor
|
||||||
|
* @param {string} startDate
|
||||||
|
* @param {string} endDate
|
||||||
|
* @returns {Promise<FullCalendarEvent[]>}
|
||||||
|
* @memberof AggregatedCalendarMockService
|
||||||
|
*/
|
||||||
|
public getEventsForCalendar(calendarRestApi: string, calendarColor: string, startDate: string,
|
||||||
|
endDate: string):
|
||||||
|
Promise<FullCalendarEvent[]> {
|
||||||
|
return new Promise<FullCalendarEvent[]>((resolve, reject) => {
|
||||||
|
let calendarLists: FullCalendarEvent[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: "Lunch",
|
||||||
|
start: moment().add(1, 'days'),
|
||||||
|
end: moment().add(1, 'days').add(1, "h"),
|
||||||
|
color: "blue",
|
||||||
|
allDay: false,
|
||||||
|
description: "",
|
||||||
|
location: "18223 Kilmacolm Drive, Richmond, TX 77407",
|
||||||
|
category: "Get-together"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: "Lunch & Learn",
|
||||||
|
start: moment(),
|
||||||
|
end: moment().add(1, "h"),
|
||||||
|
color: "blue",
|
||||||
|
allDay: false,
|
||||||
|
description: "<p>Lunch & Learn Session</p>\r\n",
|
||||||
|
location: "Microsoft Store, 5015 Westheimer Rd Ste A2421, Houston, TX, United States",
|
||||||
|
category: "Meeting"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: "Town Hall",
|
||||||
|
start: moment("2018-07-08T21:30:00.000Z"),
|
||||||
|
end: moment("2018-07-08T22:30:00.000Z"),
|
||||||
|
color: "red",
|
||||||
|
allDay: false,
|
||||||
|
description: "",
|
||||||
|
location: "Deer Park, Texas, United States",
|
||||||
|
category: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: "Team Outing",
|
||||||
|
start: moment("2018-07-12T00:00:00.000Z"),
|
||||||
|
end: moment("2018-07-12T23:59:00.000Z"),
|
||||||
|
color: "red",
|
||||||
|
allDay: false,
|
||||||
|
description: "",
|
||||||
|
location: "Seaworld San Antonio, San Antonio, Texas, United States",
|
||||||
|
category: ""
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
resolve(calendarLists);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { IAggregatedCalendarService } from './IAggregatedCalendarService';
|
||||||
|
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
|
||||||
|
import { ServiceKey, ServiceScope, Log } from '@microsoft/sp-core-library';
|
||||||
|
import { PageContext } from '@microsoft/sp-page-context';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { SPCalendarItems } from '../model/SPCalendarItems';
|
||||||
|
import { FullCalendarEvent } from '../model/FullCalendarEvent';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggregated Calendar Service for teh Aggregated Calendar Webpart to get the Calendar Events
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class AggregatedCalendarService
|
||||||
|
* @implements {IAggregatedCalendarService}
|
||||||
|
*/
|
||||||
|
export class AggregatedCalendarService implements IAggregatedCalendarService {
|
||||||
|
public static readonly serviceKey: ServiceKey<IAggregatedCalendarService>
|
||||||
|
= ServiceKey.create<IAggregatedCalendarService>('ayka:IAggregatedCalendarService', AggregatedCalendarService);
|
||||||
|
private _spHttpClient: SPHttpClient;
|
||||||
|
private _serviceScope: ServiceScope;
|
||||||
|
/**
|
||||||
|
*Creates an instance of AggregatedCalendarService.
|
||||||
|
* @param {ServiceScope} serviceScope
|
||||||
|
* @memberof AggregatedCalendarService
|
||||||
|
*/
|
||||||
|
constructor(serviceScope: ServiceScope) {
|
||||||
|
serviceScope.whenFinished(() => {
|
||||||
|
this._spHttpClient = serviceScope.consume(SPHttpClient.serviceKey);
|
||||||
|
this._serviceScope = serviceScope;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Events from the SharePoint Calendar between startDate and endDate
|
||||||
|
*
|
||||||
|
* @param {string} calendarRestApi
|
||||||
|
* @param {string} calendarColor
|
||||||
|
* @param {string} startDate
|
||||||
|
* @param {string} endDate
|
||||||
|
* @returns {Promise<any[]>}
|
||||||
|
* @memberof AggregatedCalendarService
|
||||||
|
*/
|
||||||
|
public getEventsForCalendar(calendarRestApi: string, calendarColor: string, startDate: string, endDate: string): Promise<any[]> {
|
||||||
|
return new Promise<FullCalendarEvent[]>((resolve, reject) => {
|
||||||
|
let _webRestApi: string = calendarRestApi +
|
||||||
|
'?$Select=Title,EventDate,EndDate,Location,Description,Category,fAllDayEvent&$filter=((EventDate ge \''
|
||||||
|
+ startDate + '\' and EventDate le \'' + endDate + '\'))';
|
||||||
|
Log.info("getEventsForCalendar()", "REST API : " + calendarRestApi, this._serviceScope);
|
||||||
|
this._spHttpClient.get(_webRestApi, SPHttpClient.configurations.v1)
|
||||||
|
.then((response: SPHttpClientResponse) => {
|
||||||
|
response.json().then((spEvents: SPCalendarItems) => {
|
||||||
|
Log.verbose("getEventsForCalendar()", JSON.stringify(spEvents), this._serviceScope);
|
||||||
|
let fullCalendarEvents: FullCalendarEvent[] = [];
|
||||||
|
|
||||||
|
// Convert the SharePoint Events into compatible Full Calendar Events
|
||||||
|
spEvents.value.forEach((spEvent) => {
|
||||||
|
|
||||||
|
fullCalendarEvents.push({
|
||||||
|
id: spEvent.Id,
|
||||||
|
title: spEvent.Title,
|
||||||
|
start: moment(spEvent.EventDate),
|
||||||
|
end: moment(spEvent.EndDate),
|
||||||
|
color: calendarColor,
|
||||||
|
allDay: spEvent.fAllDayEvent,
|
||||||
|
description: spEvent.Description || '',
|
||||||
|
location: spEvent.Location || '',
|
||||||
|
category: spEvent.Category || ''
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
Log.info("getEventsForCalendar()", "Returning Full Calendar Events ", this._serviceScope);
|
||||||
|
Log.verbose("getEventsForCalendar()", JSON.stringify(fullCalendarEvents), this._serviceScope);
|
||||||
|
resolve(fullCalendarEvents);
|
||||||
|
}).catch((error) => {
|
||||||
|
Log.error("getEventsForCalendar()", new Error("Error Fetching events from Calendar"), this._serviceScope);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { FullCalendarEvent } from "../model/FullCalendarEvent";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface Service for the AggregatedCalendarService
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface IAggregatedCalendarService
|
||||||
|
*/
|
||||||
|
export interface IAggregatedCalendarService {
|
||||||
|
getEventsForCalendar(calendarRestApi: string, calendarColor: string,
|
||||||
|
startDate: string, endDate: string): Promise<FullCalendarEvent[]>;
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
[{
|
||||||
|
"key": "month",
|
||||||
|
"text": "Month"
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "agendaWeek",
|
||||||
|
"text": "Weekly"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "agendaDay",
|
||||||
|
"text": "Daily"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "listMonth",
|
||||||
|
"text": "Monthly List"
|
||||||
|
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,60 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to implement the MessageComponent Webpart
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface IMessageComponentProps
|
||||||
|
*/
|
||||||
|
export interface IMessageComponentProps {
|
||||||
|
Message: string;
|
||||||
|
Type: MessageBarType;
|
||||||
|
Display: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React MessageComponent for displaying the messages
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class MessageComponent
|
||||||
|
* @extends {React.Component<IMessageComponentProps, any>}
|
||||||
|
*/
|
||||||
|
export default class MessageComponent extends React.Component<IMessageComponentProps, any> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*Creates an instance of MessageComponent.
|
||||||
|
* @param {IMessageComponentProps} props
|
||||||
|
* @memberof MessageComponent
|
||||||
|
*/
|
||||||
|
public constructor(props: IMessageComponentProps) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render method of the Message Component
|
||||||
|
*
|
||||||
|
* @returns {React.ReactElement<IMessageComponentProps>}
|
||||||
|
* @memberof MessageComponent
|
||||||
|
*/
|
||||||
|
public render(): React.ReactElement<IMessageComponentProps> {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`ms-Grid-row`}>
|
||||||
|
<div className={`ms-Grid-col ms-sm12`}>
|
||||||
|
{
|
||||||
|
this.props.Display &&
|
||||||
|
<div>
|
||||||
|
<MessageBar
|
||||||
|
messageBarType={MessageBarType.error}
|
||||||
|
isMultiline={false}
|
||||||
|
dismissButtonAriaLabel="Close">
|
||||||
|
{this.props.Message}
|
||||||
|
</MessageBar>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
[{
|
||||||
|
"key": "MMMM Do YYYY, h:mm a",
|
||||||
|
"text": "MMMM Do YYYY, h:mm a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ddd MMM Do HH:mm YYYY",
|
||||||
|
"text": "ddd MMM Do HH:mm YYYY"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "dddd, MMMM Do YYYY, h:mm:ss a",
|
||||||
|
"text": "dddd, MMMM Do YYYY, h:mm:ss a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "dddd, DD MMMM YYYY [at] hh:mm:ss A",
|
||||||
|
"text": "dddd, DD MMMM YYYY [at] hh:mm:ss A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MM/d/YYYY HH:mm:ss",
|
||||||
|
"text": "MM/d/YYYYY HH:mm:ss"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "d/M/YYYY HH:mm:ss",
|
||||||
|
"text": "d/M/YYYYY HH:mm:ss"
|
||||||
|
}, {
|
||||||
|
"key": "dddd, MMMM Do, YYYY, h:mm:ss",
|
||||||
|
"text": "dddd, MMMM Do, YYYY, h:mm:ss"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"jsx": "react",
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"typeRoots": [
|
||||||
|
"./node_modules/@types",
|
||||||
|
"./node_modules/@microsoft"
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
"es6-promise",
|
||||||
|
"webpack-env"
|
||||||
|
],
|
||||||
|
"lib": [
|
||||||
|
"es5",
|
||||||
|
"dom",
|
||||||
|
"es2015.collection"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue