React project online (#349)
* Add new web part * Add project * Update README.md * Add assets * Update README.md * Update README.md
This commit is contained in:
parent
89cd64eadb
commit
99ea9eefde
|
@ -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.3.2",
|
||||
"libraryName": "react-project-online",
|
||||
"libraryId": "d6729fd9-103c-42c8-8ee5-17760e4ab92f",
|
||||
"environment": "spo"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
# React Project Online
|
||||
|
||||
## Summary
|
||||
This sample shows how to use SPFx to consume data from the Project Online REST API. The code uses Placeholder and ListView [reusable controls](https://github.com/SharePoint/sp-dev-fx-controls-react) to create a better experience to the end user.
|
||||
The web part is intended to be used on a project site within PWA site collection, as the web url used for the rest calls is being taken from the web part context object. To use this web part outside of the PWA site collection, just add a new property to the web part to allow the PWA site collection url to be configured (or when provisioning through a provisioning mechanist).
|
||||
The web part is currently returning project tasks as a simple proof of concept.
|
||||
|
||||
![Demo](./assets/Preview.gif)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/version-GA-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||
* [Project Online](https://dev.office.com/docs/add-ins/overview/office-add-ins?product=project)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Office 365 subscription with SharePoint Online and Project Online licence
|
||||
- SharePoint Framework [development environment](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment) already set up.
|
||||
-Project site with some tasks available.
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-project-online|Joel Rodrigues
|
||||
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|October 29, 2017|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 illustrates the following concepts on top of the SharePoint Framework:
|
||||
|
||||
-Using the SharePoint rest API for querying web properties.
|
||||
-Using the Project Online rest API for retrieving project tasks.
|
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"react-project-online-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/reactProjectOnline/ReactProjectOnlineWebPart.js",
|
||||
"manifest": "./src/webparts/reactProjectOnline/ReactProjectOnlineWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"ReactProjectOnlineWebPartStrings": "lib/webparts/reactProjectOnline/loc/{locale}.js",
|
||||
"ControlStrings": "./node_modules/@pnp/spfx-controls-react/lib/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-project-online",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-project-online-client-side-solution",
|
||||
"id": "d6729fd9-103c-42c8-8ee5-17760e4ab92f",
|
||||
"version": "1.0.0.0",
|
||||
"skipFeatureDeployment": true
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-project-online.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/core-build/tslint.schema.json",
|
||||
// Display errors as warnings
|
||||
"displayAsWarning": true,
|
||||
// The TSLint task may have been configured with several custom lint rules
|
||||
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
|
||||
// project). If true, this flag will deactivate any of these rules.
|
||||
"removeExistingRules": true,
|
||||
// When true, the TSLint task is configured with some default TSLint "rules.":
|
||||
"useDefaultConfigAsBase": false,
|
||||
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
|
||||
// which are active, other than the list of rules below.
|
||||
"lintConfig": {
|
||||
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
"forin": false,
|
||||
"label-position": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-case": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": false,
|
||||
"no-function-expression": true,
|
||||
"no-internal-module": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"valid-typeof": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
|
||||
build.initialize(gulp);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "react-project-online",
|
||||
"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.3.0",
|
||||
"@microsoft/sp-lodash-subset": "~1.3.0",
|
||||
"@microsoft/sp-webpart-base": "~1.3.0",
|
||||
"@pnp/spfx-controls-react": "1.0.0-beta.6",
|
||||
"@types/react": "15.0.38",
|
||||
"@types/react-addons-shallow-compare": "0.14.17",
|
||||
"@types/react-addons-test-utils": "0.14.15",
|
||||
"@types/react-addons-update": "0.14.14",
|
||||
"@types/react-dom": "0.14.18",
|
||||
"@types/webpack-env": ">=1.12.1 <1.14.0",
|
||||
"react": "15.4.2",
|
||||
"react-dom": "15.4.2",
|
||||
"sp-pnp-js": "3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "~1.3.0",
|
||||
"@microsoft/sp-module-interfaces": "~1.3.0",
|
||||
"@microsoft/sp-webpart-workbench": "~1.3.0",
|
||||
"gulp": "~3.9.1",
|
||||
"@types/chai": ">=3.4.34 <3.6.0",
|
||||
"@types/mocha": ">=2.2.33 <2.6.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export interface IPOTask {
|
||||
Id?: string;
|
||||
Name?: string;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './IPOTask';
|
|
@ -0,0 +1,5 @@
|
|||
import { IPOTask } from "./../interfaces";
|
||||
|
||||
export interface IPODataService {
|
||||
GetProjectTasks(webUrl: string, projectId: string, selectFields: string[], filter: string, orderBy: string, top: number): Promise<IPOTask[]>;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export interface ISPDataService {
|
||||
|
||||
GetWebProperties(webUrl: string, selectFields: string[]): Promise<any>;
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
import { IWebPartContext } from '@microsoft/sp-webpart-base';
|
||||
import {
|
||||
SPHttpClient,
|
||||
SPHttpClientBatch,
|
||||
SPHttpClientResponse
|
||||
} from '@microsoft/sp-http';
|
||||
import {
|
||||
Logger,
|
||||
ConsoleListener,
|
||||
LogLevel
|
||||
} from 'sp-pnp-js';
|
||||
|
||||
import { IPODataService } from ".";
|
||||
import { IPOTask } from "./../interfaces";
|
||||
|
||||
export class PODataService implements IPODataService {
|
||||
|
||||
private _webPartContext: IWebPartContext;
|
||||
|
||||
constructor(logLevel: LogLevel, webPartContext: IWebPartContext) {
|
||||
// setup logger
|
||||
Logger.subscribe(new ConsoleListener());
|
||||
Logger.activeLogLevel = logLevel;
|
||||
// set web part context
|
||||
this._webPartContext = webPartContext;
|
||||
}
|
||||
|
||||
public set webPartContext(value: IWebPartContext) {
|
||||
this._webPartContext = value;
|
||||
}
|
||||
|
||||
public get webPartContext(): IWebPartContext {
|
||||
return this._webPartContext;
|
||||
}
|
||||
|
||||
public async GetProjectTasks(webUrl: string, projectId: string, selectFields: string[], filter: string, orderBy: string, top: number): Promise<IPOTask[]> {
|
||||
return this._getProjectTasks(webUrl, projectId, selectFields, filter, orderBy, top);
|
||||
}
|
||||
|
||||
private async _getProjectTasks(webUrl: string, projectId: string, selectFields: string[], filter: string, orderBy: string, top: number): Promise<IPOTask[]> {
|
||||
|
||||
let data: IPOTask[] = [];
|
||||
const select = selectFields.join(',');
|
||||
|
||||
try {
|
||||
const response: SPHttpClientResponse = await this._webPartContext.spHttpClient.get(webUrl + `/_api/ProjectServer/Projects('${projectId}')/Tasks()?$select=${select}`, SPHttpClient.configurations.v1);
|
||||
if (response) {
|
||||
const jsonData: any = await response.json();
|
||||
if (jsonData.value) {
|
||||
data = jsonData.value;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
Logger.write('Error loading project tasks: ' + error, LogLevel.Error);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import {
|
||||
Logger,
|
||||
ConsoleListener,
|
||||
LogLevel
|
||||
} from 'sp-pnp-js';
|
||||
|
||||
import { IPODataService } from ".";
|
||||
import { IPOTask } from "./../interfaces";
|
||||
|
||||
export class POMockDataService implements IPODataService {
|
||||
|
||||
constructor(logLevel: LogLevel) {
|
||||
|
||||
// setup logger
|
||||
Logger.subscribe(new ConsoleListener());
|
||||
Logger.activeLogLevel = logLevel;
|
||||
}
|
||||
|
||||
public async GetProjectTasks(webUrl: string, projectId: string, selectFields: string[], filter: string, orderBy: string, top: number): Promise<IPOTask[]> {
|
||||
return this._getListItems(webUrl, projectId, selectFields, filter, orderBy, top);
|
||||
}
|
||||
|
||||
private async _getListItems(webUrl: string, projectId: string, selectFields: string[], filter: string, orderBy: string, top: number): Promise<IPOTask[]> {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import {
|
||||
Logger,
|
||||
ConsoleListener,
|
||||
LogLevel,
|
||||
sp,
|
||||
SearchQuery,
|
||||
SearchResults,
|
||||
Web,
|
||||
ItemAddResult
|
||||
} from 'sp-pnp-js';
|
||||
|
||||
import { ISPDataService } from ".";
|
||||
|
||||
export class SPDataService implements ISPDataService {
|
||||
|
||||
constructor(logLevel: LogLevel) {
|
||||
|
||||
// setup logger
|
||||
Logger.subscribe(new ConsoleListener());
|
||||
Logger.activeLogLevel = logLevel;
|
||||
}
|
||||
|
||||
public async GetWebProperties(webUrl: string, selectFields: string[]): Promise<any> {
|
||||
return this._getWebProperties(webUrl, selectFields);
|
||||
}
|
||||
|
||||
private async _getWebProperties(webUrl: string, selectFields: string[]): Promise<any> {
|
||||
let web = new Web(webUrl);
|
||||
let data: any = null;
|
||||
// prefix all properties with the expanded field
|
||||
selectFields.forEach((value, index, array) => {
|
||||
array[index] = "AllProperties/" + value;
|
||||
});
|
||||
const select = selectFields.join(',');
|
||||
|
||||
try {
|
||||
data = await web
|
||||
.select(select)
|
||||
.expand('AllProperties')
|
||||
.usingCaching()
|
||||
.get();
|
||||
|
||||
} catch (error) {
|
||||
Logger.write('Error loading web properties: ' + error, LogLevel.Error);
|
||||
}
|
||||
return data.AllProperties;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import {
|
||||
Logger,
|
||||
ConsoleListener,
|
||||
LogLevel
|
||||
} from 'sp-pnp-js';
|
||||
|
||||
import { ISPDataService } from ".";
|
||||
|
||||
export class SPMockDataService implements ISPDataService {
|
||||
|
||||
constructor(logLevel: LogLevel) {
|
||||
|
||||
// setup logger
|
||||
Logger.subscribe(new ConsoleListener());
|
||||
Logger.activeLogLevel = logLevel;
|
||||
}
|
||||
|
||||
public async GetWebProperties(webUrl: string, selectFields: string[]): Promise<any> {
|
||||
return this._getWebProperties(webUrl, selectFields);
|
||||
}
|
||||
|
||||
private async _getWebProperties(webUrl: string, selectFields: string[]): Promise<any> {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export * from './ISPDataService';
|
||||
export * from './SPDataService';
|
||||
export * from './SPMockDataService';
|
||||
export * from './IPODataService';
|
||||
export * from './PODataService';
|
||||
export * from './POMockDataService';
|
|
@ -0,0 +1,6 @@
|
|||
import { LogLevel } from 'sp-pnp-js';
|
||||
|
||||
export interface IReactProjectOnlineWebPartProps {
|
||||
dataSourceId: string;
|
||||
logLevel: LogLevel;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "9dd62689-0ed4-4b35-82e0-848742751bdc",
|
||||
"alias": "ReactProjectOnlineWebPart",
|
||||
"componentType": "WebPart",
|
||||
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"requiresCustomScript": false,
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "React Project Online Sample" },
|
||||
"description": { "default": "Sample web part to demonstrate how to retrieve Project Online data using REST" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"logLevel": 99
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version, Environment, EnvironmentType } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField,
|
||||
PropertyPaneDropdown
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
import { LogLevel } from 'sp-pnp-js';
|
||||
|
||||
import * as strings from 'ReactProjectOnlineWebPartStrings';
|
||||
import { IReactProjectOnlineWebPartProps } from './IReactProjectOnlineWebPartProps';
|
||||
|
||||
import ReactProjectOnline from './components/ReactProjectOnline';
|
||||
import { IReactProjectOnlineProps } from './components/IReactProjectOnlineProps';
|
||||
import {
|
||||
ISPDataService, SPDataService, SPMockDataService,
|
||||
IPODataService, PODataService, POMockDataService
|
||||
} from "./../../shared/services";
|
||||
|
||||
export default class ReactProjectOnlineWebPart extends BaseClientSideWebPart<IReactProjectOnlineWebPartProps> {
|
||||
|
||||
private _spDataService: ISPDataService;
|
||||
private _poDataService: IPODataService;
|
||||
|
||||
public onInit(): Promise<void> {
|
||||
|
||||
this.context.statusRenderer.displayLoadingIndicator(this.domElement, strings.TitleFieldLabel);
|
||||
|
||||
/*
|
||||
Create the appropriate data provider depending on where the web part is running.
|
||||
The DEBUG flag will ensure the mock data provider is not bundled with the web part when you package the solution for distribution,
|
||||
that is, using the --ship flag with the package-solution gulp command.
|
||||
*/
|
||||
if (DEBUG && Environment.type === EnvironmentType.Local) {
|
||||
// Local environment
|
||||
this._spDataService = new SPMockDataService(this.properties.logLevel);
|
||||
this._poDataService = new POMockDataService(this.properties.logLevel);
|
||||
} else {
|
||||
this._spDataService = new SPDataService(this.properties.logLevel);
|
||||
this._poDataService = new PODataService(this.properties.logLevel, this.context);
|
||||
}
|
||||
|
||||
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
|
||||
return super.onInit();
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IReactProjectOnlineProps> = React.createElement(
|
||||
ReactProjectOnline,
|
||||
{
|
||||
baseProperties: this.properties,
|
||||
spDataService: this._spDataService,
|
||||
poDataService: this._poDataService,
|
||||
webPartContext: this.context
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
protected async onPropertyPaneConfigurationStart(): Promise<any> {
|
||||
|
||||
// load data only when user opens the property pane to configure webpart
|
||||
if (this.properties.dataSourceId.length === 0) {
|
||||
await this._getProjectID();
|
||||
}
|
||||
|
||||
super.onPropertyPaneConfigurationStart();
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField('dataSourceId', {
|
||||
label: strings.DataSourceIdFieldLabel,
|
||||
disabled: true
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
groupName: strings.ConfigurationGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneDropdown('logLevel', {
|
||||
label: strings.LogLevelLabel,
|
||||
selectedKey: this.properties.logLevel,
|
||||
options: [
|
||||
{ key: LogLevel.Off, text: 'Off' },
|
||||
{ key: LogLevel.Error, text: 'Error' },
|
||||
{ key: LogLevel.Warning, text: 'Warning' },
|
||||
{ key: LogLevel.Info, text: 'Info' },
|
||||
{ key: LogLevel.Verbose, text: 'Verbose' }
|
||||
]
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
private async _getProjectID(): Promise<string> {
|
||||
let id: string = null;
|
||||
const webUrl = this.context.pageContext.web.absoluteUrl;
|
||||
const selectWebProperties = ['MSPWAPROJUID'];
|
||||
|
||||
try {
|
||||
// get project ID
|
||||
const projectUID: any = await this._spDataService.GetWebProperties(webUrl, selectWebProperties);
|
||||
if (projectUID) {
|
||||
this.properties.dataSourceId = projectUID.MSPWAPROJUID;
|
||||
this.context.propertyPane.refresh();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log('Error getting project ID from data service: ', error);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import { IReactProjectOnlineWebPartProps } from "./../IReactProjectOnlineWebPartProps";
|
||||
import { ISPDataService, IPODataService } from "./../../../shared/services";
|
||||
import { IWebPartContext } from '@microsoft/sp-webpart-base';
|
||||
|
||||
export interface IReactProjectOnlineProps {
|
||||
baseProperties: IReactProjectOnlineWebPartProps;
|
||||
spDataService: ISPDataService;
|
||||
poDataService: IPODataService;
|
||||
webPartContext: IWebPartContext;
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
.reactProjectOnline {
|
||||
.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 {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.listItem {
|
||||
max-width: 715px;
|
||||
margin: 5px auto 5px auto;
|
||||
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.button {
|
||||
// Our button
|
||||
text-decoration: none;
|
||||
height: 32px;
|
||||
|
||||
// Primary Button
|
||||
min-width: 80px;
|
||||
background-color: "[theme:themePrimary, default:#0078d7]";
|
||||
border-color: "[theme:themePrimary, default:#0078d7]";
|
||||
color: #ffffff;
|
||||
|
||||
// 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: 14px;
|
||||
font-weight: 400;
|
||||
border-width: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 0 16px;
|
||||
|
||||
.label {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 4px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
import * as React from 'react';
|
||||
import { Util } from 'sp-pnp-js';
|
||||
|
||||
import * as strings from 'ReactProjectOnlineWebPartStrings';
|
||||
import styles from './ReactProjectOnline.module.scss';
|
||||
|
||||
import { ListView, IViewField, SelectionMode } from "@pnp/spfx-controls-react/lib/ListView";
|
||||
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
|
||||
|
||||
import { IReactProjectOnlineProps, ReactProjectOnlineState } from '.';
|
||||
import { IPOTask } from './../../../shared/interfaces';
|
||||
|
||||
|
||||
export default class ReactProjectOnline extends React.Component<IReactProjectOnlineProps, ReactProjectOnlineState> {
|
||||
|
||||
private _maxResults = 1000;
|
||||
private _taskItems: IPOTask[] = [];
|
||||
private _selectedTaskItems: IPOTask[] = [];
|
||||
|
||||
constructor(props: IReactProjectOnlineProps) {
|
||||
super(props);
|
||||
|
||||
// evaluate if configuration is required, and if so, display a placeholder
|
||||
const showPlaceHolder = Util.stringIsNullOrEmpty(this.props.baseProperties.dataSourceId);
|
||||
|
||||
// initialise state
|
||||
this.state = {
|
||||
dataLoaded: false,
|
||||
showPlaceHolder: showPlaceHolder
|
||||
};
|
||||
|
||||
// bind functions to set the correct context
|
||||
this._onConfigure = this._onConfigure.bind(this);
|
||||
this._getTaskSelection = this._getTaskSelection.bind(this);
|
||||
}
|
||||
|
||||
// componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here.
|
||||
// If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
|
||||
// Setting state in this method will trigger a re-rendering.
|
||||
public async componentDidMount() {
|
||||
this.props.webPartContext.statusRenderer.displayLoadingIndicator(document.getElementsByClassName(styles.reactProjectOnline)[0], strings.TitleFieldLabel);
|
||||
|
||||
// load data and update state
|
||||
if (!Util.stringIsNullOrEmpty(this.props.baseProperties.dataSourceId)) {
|
||||
this._taskItems = await this._getTaskItems();
|
||||
this.setState({
|
||||
dataLoaded: true
|
||||
});
|
||||
}
|
||||
this.props.webPartContext.statusRenderer.clearLoadingIndicator(document.getElementsByClassName(styles.reactProjectOnline)[0]);
|
||||
}
|
||||
|
||||
// componentWillReceiveProps() is invoked before a mounted component receives new props.
|
||||
// If you need to update the state in response to prop changes (for example, to reset it), you may compare this.props and nextProps and perform
|
||||
// state transitions using this.setState() in this method.
|
||||
public async componentWillReceiveProps(props: IReactProjectOnlineProps) {
|
||||
// load data and update state
|
||||
if (!Util.stringIsNullOrEmpty(this.props.baseProperties.dataSourceId)) {
|
||||
this._taskItems = await this._getTaskItems();
|
||||
this.setState({
|
||||
showPlaceHolder: false,
|
||||
dataLoaded: true
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
showPlaceHolder: true,
|
||||
dataLoaded: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IReactProjectOnlineProps> {
|
||||
|
||||
// Fields that need to be viewed in the task listview
|
||||
const taskViewFields: IViewField[] = [
|
||||
{
|
||||
name: 'Name',
|
||||
displayName: 'Name',
|
||||
sorting: true
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<section>
|
||||
{this.state.showPlaceHolder &&
|
||||
<Placeholder
|
||||
iconName='Edit'
|
||||
iconText='Configure your web part'
|
||||
description='Please configure the web part.'
|
||||
buttonLabel='Configure'
|
||||
onConfigure={this._onConfigure} />
|
||||
}
|
||||
<div className={styles.reactProjectOnline}>
|
||||
{this.state.dataLoaded &&
|
||||
<div className={styles.container}>
|
||||
<ListView
|
||||
items={this._taskItems}
|
||||
viewFields={taskViewFields}
|
||||
compact={true}
|
||||
selectionMode={SelectionMode.single}
|
||||
selection={this._getTaskSelection} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
private _onConfigure() {
|
||||
// Context of the web part
|
||||
this.props.webPartContext.propertyPane.open();
|
||||
}
|
||||
|
||||
private _getTaskSelection(items: IPOTask[]) {
|
||||
this._selectedTaskItems = items;
|
||||
console.log(this._selectedTaskItems);
|
||||
|
||||
}
|
||||
|
||||
private async _getTaskItems(): Promise<IPOTask[]> {
|
||||
let items: IPOTask[] = [];
|
||||
const webUrl = this.props.webPartContext.pageContext.web.absoluteUrl;
|
||||
const selectFields = ['ID', 'Name'];
|
||||
const filter = '';
|
||||
const orderBy = '';
|
||||
|
||||
try {
|
||||
// get tasks
|
||||
items = await this.props.poDataService.GetProjectTasks(
|
||||
webUrl,
|
||||
this.props.baseProperties.dataSourceId,
|
||||
selectFields,
|
||||
filter,
|
||||
orderBy,
|
||||
this._maxResults
|
||||
) as IPOTask[];
|
||||
|
||||
} catch (error) {
|
||||
console.log('Error loading tasks data from data service: ', error);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export interface ReactProjectOnlineState {
|
||||
showPlaceHolder?: boolean;
|
||||
dataLoaded?: boolean;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export * from './IReactProjectOnlineProps';
|
||||
export * from './ReactProjectOnlineState';
|
||||
export * from './ReactProjectOnline';
|
|
@ -0,0 +1,10 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "React Project Online sample web part properties",
|
||||
"BasicGroupName": "Basic Configuration",
|
||||
"TitleFieldLabel": "React Project Online Sample",
|
||||
"DataSourceIdFieldLabel": "Project Id (MSPWAPROJUID)",
|
||||
"ConfigurationGroupName": "Advanced Configuration",
|
||||
"LogLevelLabel": "Log Level"
|
||||
}
|
||||
});
|
13
samples/react-project-online/src/webparts/reactProjectOnline/loc/mystrings.d.ts
vendored
Normal file
13
samples/react-project-online/src/webparts/reactProjectOnline/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
declare interface IReactProjectOnlineWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
TitleFieldLabel: string;
|
||||
DataSourceIdFieldLabel: string;
|
||||
ConfigurationGroupName: string;
|
||||
LogLevelLabel: string;
|
||||
}
|
||||
|
||||
declare module 'ReactProjectOnlineWebPartStrings' {
|
||||
const strings: IReactProjectOnlineWebPartStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/// <reference types="mocha" />
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('ReactProjectOnlineWebPart', () => {
|
||||
it('should do something', () => {
|
||||
assert.ok(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "commonjs",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"types": [
|
||||
"es6-promise",
|
||||
"es6-collections",
|
||||
"webpack-env"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Type definitions for Microsoft ODSP projects
|
||||
// Project: ODSP
|
||||
|
||||
/* Global definition for UNIT_TEST builds
|
||||
Code that is wrapped inside an if(UNIT_TEST) {...}
|
||||
block will not be included in the final bundle when the
|
||||
--ship flag is specified */
|
||||
declare const UNIT_TEST: boolean;
|
||||
|
||||
/* Global defintion for SPO builds */
|
||||
declare const DATACENTER: boolean;
|
|
@ -0,0 +1 @@
|
|||
/// <reference path="@ms/odsp.d.ts" />
|
Loading…
Reference in New Issue