Added sample showing how to call custom APIs secured with Azure Active Directory without ADAL JS (#193)
This commit is contained in:
parent
ccb54125d6
commit
c4e72f1584
|
@ -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 @@
|
||||||
|
* text=auto
|
|
@ -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,14 @@
|
||||||
|
# Folders
|
||||||
|
.vscode
|
||||||
|
coverage
|
||||||
|
node_modules
|
||||||
|
sharepoint
|
||||||
|
src
|
||||||
|
temp
|
||||||
|
|
||||||
|
# Files
|
||||||
|
*.csproj
|
||||||
|
.git*
|
||||||
|
.yo-rc.json
|
||||||
|
gulpfile.js
|
||||||
|
tsconfig.json
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"@microsoft/generator-sharepoint": {
|
||||||
|
"libraryName": "aad-api-spo-cookie",
|
||||||
|
"framework": "none",
|
||||||
|
"version": "1.0.2",
|
||||||
|
"libraryId": "ae8e010a-71b4-479b-904b-15405d951d65"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,205 @@
|
||||||
|
# Call custom APIs secured with Azure Active Directory without ADAL JS
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Sample SharePoint Framework client-side web part showing how to access a custom API secured with Azure Active Directory (AAD) without using ADAL JS.
|
||||||
|
|
||||||
|
### Recent orders
|
||||||
|
|
||||||
|
Sample web part showing the list of latest orders retrieved from a custom API secured with AAD.
|
||||||
|
|
||||||
|
![Web part showing the list of latest orders retrieved from a custom API secured with AAD](./assets/preview-orders.png)
|
||||||
|
|
||||||
|
## Used SharePoint Framework Version
|
||||||
|
![drop](https://img.shields.io/badge/drop-GA-green.svg)
|
||||||
|
|
||||||
|
## Applies to
|
||||||
|
|
||||||
|
* [SharePoint Framework](http://dev.office.com/sharepoint/docs/spfx/sharepoint-framework-overview)
|
||||||
|
* [Office 365 developer tenant](http://dev.office.com/sharepoint/docs/spfx/set-up-your-developer-tenant)
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Solution|Author(s)
|
||||||
|
--------|---------
|
||||||
|
aad-api-spo-cookie|Waldek Mastykarz (MVP, Rencore, @waldekm)
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
Version|Date|Comments
|
||||||
|
-------|----|--------
|
||||||
|
1.0.0|May 5, 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
|
||||||
|
|
||||||
|
### Create custom API secured with AAD
|
||||||
|
|
||||||
|
- create an API and secure it with AAD
|
||||||
|
- in the API enable receiving credentials from cross-domain origins
|
||||||
|
- if you want to test the API from the local workbench, add **https://localhost:4321** as valid origin
|
||||||
|
- the API should return the following data (JSON):
|
||||||
|
|
||||||
|
```json
|
||||||
|
[{
|
||||||
|
id: 1,
|
||||||
|
orderDate: new Date(2016, 0, 6),
|
||||||
|
region: "east",
|
||||||
|
rep: "Jones",
|
||||||
|
item: "Pencil",
|
||||||
|
units: 95,
|
||||||
|
unitCost: 1.99,
|
||||||
|
total: 189.05
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
orderDate: new Date(2016, 0, 23),
|
||||||
|
region: "central",
|
||||||
|
rep: "Kivell",
|
||||||
|
item: "Binder",
|
||||||
|
units: 50,
|
||||||
|
unitCost: 19.99,
|
||||||
|
total: 999.50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
orderDate: new Date(2016, 1, 9),
|
||||||
|
region: "central",
|
||||||
|
rep: "Jardine",
|
||||||
|
item: "Pencil",
|
||||||
|
units: 36,
|
||||||
|
unitCost: 4.99,
|
||||||
|
total: 179.64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
orderDate: new Date(2016, 1, 26),
|
||||||
|
region: "central",
|
||||||
|
rep: "Gill",
|
||||||
|
item: "Pen",
|
||||||
|
units: 27,
|
||||||
|
unitCost: 19.99,
|
||||||
|
total: 539.73
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
orderDate: new Date(2016, 2, 15),
|
||||||
|
region: "west",
|
||||||
|
rep: "Sorvino",
|
||||||
|
item: "Pencil",
|
||||||
|
units: 56,
|
||||||
|
unitCost: 2.99,
|
||||||
|
total: 167.44
|
||||||
|
}]
|
||||||
|
```
|
||||||
|
Here is the complete code if you want to implement this API using a Node.js Azure Function:
|
||||||
|
|
||||||
|
```js
|
||||||
|
module.exports = function (context, req) {
|
||||||
|
context.res = {
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
orderDate: new Date(2016, 0, 6),
|
||||||
|
region: "east",
|
||||||
|
rep: "Jones",
|
||||||
|
item: "Pencil",
|
||||||
|
units: 95,
|
||||||
|
unitCost: 1.99,
|
||||||
|
total: 189.05
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
orderDate: new Date(2016, 0, 23),
|
||||||
|
region: "central",
|
||||||
|
rep: "Kivell",
|
||||||
|
item: "Binder",
|
||||||
|
units: 50,
|
||||||
|
unitCost: 19.99,
|
||||||
|
total: 999.50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
orderDate: new Date(2016, 1, 9),
|
||||||
|
region: "central",
|
||||||
|
rep: "Jardine",
|
||||||
|
item: "Pencil",
|
||||||
|
units: 36,
|
||||||
|
unitCost: 4.99,
|
||||||
|
total: 179.64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
orderDate: new Date(2016, 1, 26),
|
||||||
|
region: "central",
|
||||||
|
rep: "Gill",
|
||||||
|
item: "Pen",
|
||||||
|
units: 27,
|
||||||
|
unitCost: 19.99,
|
||||||
|
total: 539.73
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
orderDate: new Date(2016, 2, 15),
|
||||||
|
region: "west",
|
||||||
|
rep: "Sorvino",
|
||||||
|
item: "Pencil",
|
||||||
|
units: 56,
|
||||||
|
unitCost: 2.99,
|
||||||
|
total: 167.44
|
||||||
|
}],
|
||||||
|
headers: {
|
||||||
|
"Access-Control-Allow-Credentials" : "true",
|
||||||
|
"Access-Control-Allow-Origin" : "https://contoso.sharepoint.com"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
context.done();
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Important:** The **Access-Control-Allow-Origin** header specifies the origin that is allowed to call the API. If you want to test the web part in the hosted workbench, this URL should be set to the URL of your SharePoint tenant. For testing the web part in the local workbench this URL should be set to **https://localhost:4321**.
|
||||||
|
|
||||||
|
### Update web part
|
||||||
|
|
||||||
|
- in the **./src/webparts/latestOrders/LatestOrdersWebPart.ts** file:
|
||||||
|
- in line 21, update the value of the **src** attribute with the URL where your API is hosted. Navigating to that URL in the browser should trigger Azure Active Directory sign in page
|
||||||
|
- in line 51, update the URL to call your custom API secured with AAD and returning orders
|
||||||
|
|
||||||
|
### Run web part
|
||||||
|
|
||||||
|
- in the command line run `npm i` to restore project dependencies
|
||||||
|
- to test web part in local workbench
|
||||||
|
- in the command line run `gulp serve`
|
||||||
|
- if after adding the web part you get an error similar to the following:
|
||||||
|
![Error when loading orders](./assets/orders-fetch-error.png)
|
||||||
|
you should navigate to the URL you entered in the main web part file in line 21 to sign in to Azure AD with your organizational account. After signing in, the web part should display orders as expected.
|
||||||
|
- to test web part in hosted workbench
|
||||||
|
- in the command line run `gulp serve --nobrowser`
|
||||||
|
- in the web browser navigate to **https://contoso.sharepoint.com/_layouts/workbench.aspx**
|
||||||
|
|
||||||
|
If you're testing the web part in Internet Explorer and get an error similar to following:
|
||||||
|
|
||||||
|
![Error when testing the web part in Internet Explorer](./assets/orders-ie-error.png)
|
||||||
|
|
||||||
|
it means that the page where the web part is hosted and the Azure Active Directory sign in page are in different security zones. Update the security settings in Internet Explorer and add to the **Local intranet** zone the URL of your SharePoint tenant, the URL where your custom API is hosted and **https://login.microsoftonline.com**.
|
||||||
|
|
||||||
|
![Microsoft Internet Explorer security zones settings](./assets/orders-ie-zones-settings.png)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
This project contains sample client-side web part built on the SharePoint Framework illustrating how to access a custom API secured with AAD without using ADAL JS.
|
||||||
|
|
||||||
|
This sample illustrates the following concepts on top of the SharePoint Framework:
|
||||||
|
|
||||||
|
- using iframe to seamlessly authenticate to with Azure AD using the SharePoint Online authentication cookie
|
||||||
|
- executing cross-domain web requests with passing credentials
|
||||||
|
- communicating errors to users with the standard SharePoint Framework indicator
|
||||||
|
- communicating progress to users with the standard SharePoint Framework indicator
|
||||||
|
- manipulating DOM without using JavaScript libraries
|
||||||
|
- chaining promises
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/aad-api-spo-cookie" />
|
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
Binary file not shown.
After Width: | Height: | Size: 50 KiB |
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"entry": "./lib/webparts/latestOrders/LatestOrdersWebPart.js",
|
||||||
|
"manifest": "./src/webparts/latestOrders/LatestOrdersWebPart.manifest.json",
|
||||||
|
"outputPath": "./dist/latest-orders.bundle.js"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"externals": {},
|
||||||
|
"localizedResources": {
|
||||||
|
"latestOrdersStrings": "webparts/latestOrders/loc/{locale}.js"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"deployCdnPath": "temp/deploy"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"workingDir": "./temp/deploy/",
|
||||||
|
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||||
|
"container": "aad-api-spo-cookie",
|
||||||
|
"accessKey": "<!-- ACCESS KEY -->"
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"solution": {
|
||||||
|
"name": "aad-api-spo-cookie-client-side-solution",
|
||||||
|
"id": "ae8e010a-71b4-479b-904b-15405d951d65",
|
||||||
|
"version": "1.0.0.0"
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"zippedPackage": "solution/aad-api-spo-cookie.sppkg"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"port": 4321,
|
||||||
|
"initialPage": "https://localhost:5432/workbench",
|
||||||
|
"https": true,
|
||||||
|
"api": {
|
||||||
|
"port": 5432,
|
||||||
|
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
// 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-unused-imports": 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,3 @@
|
||||||
|
{
|
||||||
|
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const gulp = require('gulp');
|
||||||
|
const build = require('@microsoft/sp-build-web');
|
||||||
|
|
||||||
|
build.initialize(gulp);
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"name": "aad-api-spo-cookie",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"name": "Waldek Mastykarz",
|
||||||
|
"url": "https://rencore.com"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@microsoft/sp-client-base": "~1.0.0",
|
||||||
|
"@microsoft/sp-core-library": "~1.0.0",
|
||||||
|
"@microsoft/sp-webpart-base": "~1.0.0",
|
||||||
|
"@types/webpack-env": ">=1.12.1 <1.14.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@microsoft/sp-build-web": "~1.0.1",
|
||||||
|
"@microsoft/sp-module-interfaces": "~1.0.0",
|
||||||
|
"@microsoft/sp-webpart-workbench": "~1.0.0",
|
||||||
|
"gulp": "~3.9.1",
|
||||||
|
"@types/chai": ">=3.4.34 <3.6.0",
|
||||||
|
"@types/mocha": ">=2.2.33 <2.6.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "gulp bundle",
|
||||||
|
"clean": "gulp clean",
|
||||||
|
"test": "gulp test"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export interface ILatestOrdersWebPartProps {
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
export interface IOrder {
|
||||||
|
id: number;
|
||||||
|
orderDate: Date;
|
||||||
|
region: Region;
|
||||||
|
rep: string;
|
||||||
|
item: string;
|
||||||
|
units: number;
|
||||||
|
unitCost: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Region = "east" | "central" | "west";
|
|
@ -0,0 +1,32 @@
|
||||||
|
.latestOrders {
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
color: #333;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead tr {
|
||||||
|
border-bottom: 1px solid #767676;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead th {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #666;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr {
|
||||||
|
border-bottom: 1px solid #c8c8c8;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding: 10px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
|
||||||
|
|
||||||
|
"id": "63d86d5a-2e61-4bff-82e5-0d05509f939a",
|
||||||
|
"alias": "LatestOrdersWebPart",
|
||||||
|
"componentType": "WebPart",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"manifestVersion": 2,
|
||||||
|
|
||||||
|
"preconfiguredEntries": [{
|
||||||
|
"groupId": "63d86d5a-2e61-4bff-82e5-0d05509f939a",
|
||||||
|
"group": { "default": "Under Development" },
|
||||||
|
"title": { "default": "Latest orders" },
|
||||||
|
"description": { "default": "Shows latest orders" },
|
||||||
|
"officeFabricIconFontName": "Page",
|
||||||
|
"properties": {
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
import { Version } from '@microsoft/sp-core-library';
|
||||||
|
import {
|
||||||
|
BaseClientSideWebPart,
|
||||||
|
IPropertyPaneConfiguration
|
||||||
|
} from '@microsoft/sp-webpart-base';
|
||||||
|
import { HttpClient, HttpClientResponse } from "@microsoft/sp-http";
|
||||||
|
|
||||||
|
import styles from './LatestOrders.module.scss';
|
||||||
|
import { ILatestOrdersWebPartProps } from './ILatestOrdersWebPartProps';
|
||||||
|
import { IOrder, Region } from './IOrder';
|
||||||
|
|
||||||
|
export default class LatestOrdersWebPart extends BaseClientSideWebPart<ILatestOrdersWebPartProps> {
|
||||||
|
private remotePartyLoaded: boolean = false;
|
||||||
|
private orders: IOrder[];
|
||||||
|
|
||||||
|
public render(): void {
|
||||||
|
this.domElement.innerHTML = `
|
||||||
|
<div class="${styles.latestOrders}">
|
||||||
|
<iframe src="https://contoso.azurewebsites.net/"
|
||||||
|
style="display:none;"></iframe>
|
||||||
|
<div class="ms-font-xxl">Recent orders</div>
|
||||||
|
<div class="loading"></div>
|
||||||
|
<table class="data" style="display:none;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Region</th>
|
||||||
|
<th>Rep</th>
|
||||||
|
<th>Item</th>
|
||||||
|
<th>Units</th>
|
||||||
|
<th>Unit cost</th>
|
||||||
|
<th>Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
this.context.statusRenderer.displayLoadingIndicator(
|
||||||
|
this.domElement.querySelector(".loading"), "orders");
|
||||||
|
|
||||||
|
this.domElement.querySelector("iframe").addEventListener("load", (): void => {
|
||||||
|
this.remotePartyLoaded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.executeOrDelayUntilRemotePartyLoaded((): void => {
|
||||||
|
this.context.httpClient.get("https://contoso.azurewebsites.net/api/orders",
|
||||||
|
HttpClient.configurations.v1, {
|
||||||
|
credentials: "include"
|
||||||
|
})
|
||||||
|
.then((response: HttpClientResponse): Promise<IOrder[]> => {
|
||||||
|
if (response.ok) {
|
||||||
|
return response.json();
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((orders: IOrder[]): void => {
|
||||||
|
this.orders = orders;
|
||||||
|
this.context.statusRenderer.clearLoadingIndicator(
|
||||||
|
this.domElement.querySelector(".loading"));
|
||||||
|
this.renderData();
|
||||||
|
})
|
||||||
|
.catch((error: any): void => {
|
||||||
|
this.context.statusRenderer.clearLoadingIndicator(
|
||||||
|
this.domElement.querySelector(".loading"));
|
||||||
|
this.context.statusRenderer.renderError(this.domElement, "Error loading orders: " + (error ? error.message : ""));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderData(): void {
|
||||||
|
if (this.orders === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ordersString: string = "";
|
||||||
|
this.orders.forEach(order => {
|
||||||
|
ordersString += `
|
||||||
|
<tr>
|
||||||
|
<td class="${styles.number}">${order.id}</td>
|
||||||
|
<td class="${styles.number}">${new Date(order.orderDate).toLocaleDateString()}</td>
|
||||||
|
<td>${order.region.toString()}</td>
|
||||||
|
<td>${order.rep}</td>
|
||||||
|
<td>${order.item}</td>
|
||||||
|
<td class="${styles.number}">${order.units}</td>
|
||||||
|
<td class="${styles.number}">$${order.unitCost.toFixed(2)}</td>
|
||||||
|
<td class="${styles.number}">$${order.total.toFixed(2)}</td>
|
||||||
|
</tr>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const table: Element = this.domElement.querySelector(".data");
|
||||||
|
table.removeAttribute("style");
|
||||||
|
table.querySelector("tbody").innerHTML = ordersString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private executeOrDelayUntilRemotePartyLoaded(func: Function): void {
|
||||||
|
if (this.remotePartyLoaded) {
|
||||||
|
func();
|
||||||
|
} else {
|
||||||
|
setTimeout((): void => { this.executeOrDelayUntilRemotePartyLoaded(func); }, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get dataVersion(): Version {
|
||||||
|
return Version.parse('1.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
|
return {
|
||||||
|
pages: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
define([], function() {
|
||||||
|
return {
|
||||||
|
"PropertyPaneDescription": "Description",
|
||||||
|
"BasicGroupName": "Group Name",
|
||||||
|
"DescriptionFieldLabel": "Description Field"
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,10 @@
|
||||||
|
declare interface ILatestOrdersStrings {
|
||||||
|
PropertyPaneDescription: string;
|
||||||
|
BasicGroupName: string;
|
||||||
|
DescriptionFieldLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'latestOrdersStrings' {
|
||||||
|
const strings: ILatestOrdersStrings;
|
||||||
|
export = strings;
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
/// <reference types="mocha" />
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
|
||||||
|
describe('LatestOrdersWebPart', () => {
|
||||||
|
it('should do something', () => {
|
||||||
|
assert.ok(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"jsx": "react",
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"types": [
|
||||||
|
"es6-promise",
|
||||||
|
"es6-collections",
|
||||||
|
"webpack-env"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
// 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;
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference path="@ms/odsp.d.ts" />
|
Loading…
Reference in New Issue