Manual merge - step 1
This commit is contained in:
commit
ffdbbfd3e5
|
@ -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,11 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.5.1",
|
||||
"libraryName": "react-accordion",
|
||||
"libraryId": "6d6cf05b-cfe5-4d12-af19-19ec3aedcaf9",
|
||||
"packageManager": "npm",
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
## Using React Accordion plugin with SPFx
|
||||
|
||||
## Summary
|
||||
|
||||
This is a sample web Part that illustrates the use of React Accessible Accordion plugin for building SharePoint Framework client-side web parts to show SharePoint list data in Accordion format.
|
||||
|
||||
![Sample Web Part built using SPFx with React Framework showing list data in accordion format](./assets/previewAccordion.PNG)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/drop-1.5.1-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework Developer Preview](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)
|
||||
--------|---------
|
||||
react-accordion | Gautam Sheth (SharePoint Consultant, RapidCircle)
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|August 17, 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 repo
|
||||
- in the command line run:
|
||||
- `npm i`
|
||||
- `gulp serve --nobrowser`
|
||||
- in your SharePoint Site create a custom list named FAQ
|
||||
- in the FAQ list, create a column Description(internal name) of type Enhanced rich text
|
||||
- add some list items with Title and Description values
|
||||
|
||||
- navigate to the hosted version of SharePoint workbench, eg. **https://contoso.sharepoint.com/sites/test/_layouts/15/workbench.aspx**
|
||||
- add the Web Part to canvas and in its configuration specify:
|
||||
- name of the list where list items are stored, eg. **FAQ**
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
This project contains sample client-side web part built on the SharePoint Framework illustrating how to show list data in Accordion format using React framework.
|
||||
|
||||
This sample illustrates the following concepts on top of the SharePoint Framework:
|
||||
|
||||
- general
|
||||
- performing SharePoint GET operation in React using inbuilt SP Http Client
|
||||
- Using Fabric UI button component for pagination
|
||||
- optimizing REST responses for performance using nometadata option of JSON light
|
||||
- using PnP Webpart title control of @pnp/spfx-controls-react library
|
||||
- showing SharePoint list data in Accordion format using React Accessible Accordion plugin
|
||||
- searching in the fetched data by making use of Search Box from Office Fabric UI
|
||||
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-accordion" />
|
||||
|
||||
### Build options
|
||||
|
||||
gulp clean - TODO
|
||||
gulp test - TODO
|
||||
gulp serve - TODO
|
||||
gulp bundle - TODO
|
||||
gulp package-solution - TODO
|
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"react-accordion-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/reactAccordion/ReactAccordionWebPart.js",
|
||||
"manifest": "./src/webparts/reactAccordion/ReactAccordionWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"ReactAccordionWebPartStrings": "lib/webparts/reactAccordion/loc/{locale}.js",
|
||||
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-accordion",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-accordion-client-side-solution",
|
||||
"id": "6d6cf05b-cfe5-4d12-af19-19ec3aedcaf9",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-accordion.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/core-build/tslint.schema.json",
|
||||
"$schema": "https://developer.microsoft.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
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
build.initialize(gulp);
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "react-accordion",
|
||||
"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.5.1",
|
||||
"@microsoft/sp-lodash-subset": "1.5.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.5.1",
|
||||
"@microsoft/sp-webpart-base": "1.5.1",
|
||||
"@pnp/spfx-controls-react": "1.7.0",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/react": "15.6.6",
|
||||
"@types/react-dom": "15.5.6",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"react": "15.6.2",
|
||||
"react-accessible-accordion": "1.0.2",
|
||||
"react-dom": "15.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.5.1",
|
||||
"@microsoft/sp-module-interfaces": "1.5.1",
|
||||
"@microsoft/sp-webpart-workbench": "1.5.1",
|
||||
"gulp": "~3.9.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "97a28c00-64ee-4ec7-b373-723e39069a96",
|
||||
"alias": "ReactAccordionWebPart",
|
||||
"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 Accordion App"
|
||||
},
|
||||
"description": {
|
||||
"default": "SPFx webpart which shows SharePoint list data in Accordion format"
|
||||
},
|
||||
"officeFabricIconFontName": "Questionnaire",
|
||||
"properties": {
|
||||
"description": "SPFx webpart which shows SharePoint list data in Accordion format",
|
||||
"listName": "FAQ",
|
||||
"maxItemsPerPage": 5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version, DisplayMode } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField,
|
||||
PropertyPaneSlider
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
|
||||
import * as strings from 'ReactAccordionWebPartStrings';
|
||||
import ReactAccordion from './components/ReactAccordion';
|
||||
import { IReactAccordionProps } from './components/IReactAccordionProps';
|
||||
|
||||
export interface IReactAccordionWebPartProps {
|
||||
listName: string;
|
||||
choice: string;
|
||||
title: string;
|
||||
displayMode: DisplayMode;
|
||||
maxItemsPerPage: number;
|
||||
updateProperty: (value: string) => void;
|
||||
}
|
||||
|
||||
export default class ReactAccordionWebPart extends BaseClientSideWebPart<IReactAccordionWebPartProps> {
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IReactAccordionProps> = React.createElement(
|
||||
ReactAccordion,
|
||||
{
|
||||
listName: this.properties.listName,
|
||||
spHttpClient: this.context.spHttpClient,
|
||||
siteUrl: this.context.pageContext.web.absoluteUrl,
|
||||
title: this.properties.title,
|
||||
displayMode: this.displayMode,
|
||||
maxItemsPerPage: this.properties.maxItemsPerPage,
|
||||
updateProperty: (value: string) => {
|
||||
this.properties.title = value;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField('listName', {
|
||||
label: strings.ListNameLabel
|
||||
}),
|
||||
PropertyPaneSlider('maxItemsPerPage', {
|
||||
label: strings.MaxItemsPerPageLabel,
|
||||
ariaLabel: strings.MaxItemsPerPageLabel,
|
||||
min: 3,
|
||||
max: 20,
|
||||
value: 5,
|
||||
showValue: true,
|
||||
step: 1
|
||||
}),
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { SPHttpClient } from '@microsoft/sp-http';
|
||||
import { DisplayMode } from '@microsoft/sp-core-library';
|
||||
|
||||
export interface IReactAccordionProps {
|
||||
listName: string;
|
||||
spHttpClient: SPHttpClient;
|
||||
siteUrl: string;
|
||||
title: string,
|
||||
displayMode: DisplayMode,
|
||||
maxItemsPerPage: number,
|
||||
updateProperty: (value: string) => void;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import IAccordionListItem from '../models/IAccordionListItem';
|
||||
|
||||
export interface IReactAccordionState {
|
||||
status: string;
|
||||
items: IAccordionListItem[];
|
||||
listItems: IAccordionListItem[];
|
||||
isLoading: boolean;
|
||||
loaderMessage: string;
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||
|
||||
.reactAccordion {
|
||||
.container {
|
||||
max-width: 100%;
|
||||
margin: 0px auto;
|
||||
}
|
||||
|
||||
.row {
|
||||
@include ms-Grid-row;
|
||||
@include ms-fontColor-white;
|
||||
background-color: $ms-color-themeDark;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.column {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg10;
|
||||
@include ms-xl8;
|
||||
@include ms-xlPush2;
|
||||
@include ms-lgPush1;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include ms-font-xl;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.description {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.button {
|
||||
// Our button
|
||||
text-decoration: none;
|
||||
height: 32px;
|
||||
|
||||
// Primary Button
|
||||
min-width: 80px;
|
||||
background-color: $ms-color-themePrimary;
|
||||
border-color: $ms-color-themePrimary;
|
||||
color: $ms-color-white;
|
||||
|
||||
// Basic Button
|
||||
outline: transparent;
|
||||
position: relative;
|
||||
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: $ms-font-size-m;
|
||||
font-weight: $ms-font-weight-regular;
|
||||
border-width: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 0 16px;
|
||||
|
||||
.label {
|
||||
font-weight: $ms-font-weight-semibold;
|
||||
font-size: $ms-font-size-m;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 4px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
import * as React from 'react';
|
||||
import styles from './ReactAccordion.module.scss';
|
||||
import { IReactAccordionProps } from './IReactAccordionProps';
|
||||
import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions } from '@microsoft/sp-http';
|
||||
import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';
|
||||
import { SearchBox } from 'office-ui-fabric-react/lib/SearchBox';
|
||||
import {
|
||||
Spinner,
|
||||
SpinnerSize
|
||||
} from 'office-ui-fabric-react/lib/Spinner';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionItemTitle,
|
||||
AccordionItemBody,
|
||||
} from 'react-accessible-accordion';
|
||||
import 'react-accessible-accordion/dist/react-accessible-accordion.css';
|
||||
import { IReactAccordionState } from "./IReactAccordionState";
|
||||
import IAccordionListItem from "../models/IAccordionListItem";
|
||||
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
|
||||
import './accordion.css';
|
||||
|
||||
export default class ReactAccordion extends React.Component<IReactAccordionProps, IReactAccordionState> {
|
||||
|
||||
constructor(props: IReactAccordionProps, state: IReactAccordionState) {
|
||||
super(props);
|
||||
this.state = {
|
||||
status: this.listNotConfigured(this.props) ? 'Please configure list in Web Part properties' : 'Ready',
|
||||
items: [],
|
||||
listItems: [],
|
||||
isLoading: false,
|
||||
loaderMessage: ''
|
||||
};
|
||||
|
||||
if (!this.listNotConfigured(this.props)) {
|
||||
this.readItems();
|
||||
}
|
||||
|
||||
this.searchTextChange = this.searchTextChange.bind(this);
|
||||
|
||||
}
|
||||
|
||||
private listNotConfigured(props: IReactAccordionProps): boolean {
|
||||
return props.listName === undefined ||
|
||||
props.listName === null ||
|
||||
props.listName.length === 0;
|
||||
}
|
||||
|
||||
private searchTextChange(event) {
|
||||
|
||||
if (event === undefined ||
|
||||
event === null ||
|
||||
event === "") {
|
||||
let listItemsCollection = [...this.state.listItems];
|
||||
this.setState({ items: listItemsCollection.splice(0, this.props.maxItemsPerPage) });
|
||||
}
|
||||
else {
|
||||
var updatedList = [...this.state.listItems];
|
||||
updatedList = updatedList.filter((item) => {
|
||||
return item.Title.toLowerCase().search(
|
||||
event.toLowerCase()) !== -1 || item.Description.toLowerCase().search(
|
||||
event.toLowerCase()) !== -1;
|
||||
});
|
||||
this.setState({ items: updatedList });
|
||||
}
|
||||
}
|
||||
|
||||
private readItems(): void {
|
||||
let restAPI = this.props.siteUrl + `/_api/web/Lists/GetByTitle('${this.props.listName}')/items?$select=Title,Description`;
|
||||
|
||||
this.props.spHttpClient.get(restAPI, SPHttpClient.configurations.v1, {
|
||||
headers: {
|
||||
'Accept': 'application/json;odata=nometadata',
|
||||
'odata-version': ''
|
||||
}
|
||||
})
|
||||
.then((response: SPHttpClientResponse): Promise<{ value: IAccordionListItem[] }> => {
|
||||
return response.json();
|
||||
})
|
||||
.then((response: { value: IAccordionListItem[] }): void => {
|
||||
|
||||
let listItemsCollection = [...response.value];
|
||||
|
||||
this.setState({
|
||||
status: "",
|
||||
items: listItemsCollection.splice(0, this.props.maxItemsPerPage),
|
||||
listItems: response.value,
|
||||
isLoading: false,
|
||||
loaderMessage: ""
|
||||
});
|
||||
}, (error: any): void => {
|
||||
this.setState({
|
||||
status: 'Loading all items failed with error: ' + error,
|
||||
items: [],
|
||||
isLoading: false,
|
||||
loaderMessage: ""
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IReactAccordionProps> {
|
||||
let displayLoader;
|
||||
let faqTitle;
|
||||
let { listItems } = this.state;
|
||||
let pageCountDivisor: number = this.props.maxItemsPerPage;
|
||||
let pageCount: number;
|
||||
let pageButtons = [];
|
||||
|
||||
let _pagedButtonClick = (pageNumber: number, listData: any) => {
|
||||
let startIndex: number = (pageNumber - 1) * pageCountDivisor;
|
||||
let listItemsCollection = [...listData];
|
||||
this.setState({ items: listItemsCollection.splice(startIndex, pageCountDivisor) });
|
||||
};
|
||||
|
||||
const items: JSX.Element[] = this.state.items.map((item: IAccordionListItem, i: number): JSX.Element => {
|
||||
return (
|
||||
<AccordionItem>
|
||||
<AccordionItemTitle className="accordion__title">
|
||||
<h3 className="u-position-relative ms-fontColor-white">{item.Title}</h3>
|
||||
<div className="accordion__arrow ms-fontColor-white" role="presentation" />
|
||||
</AccordionItemTitle>
|
||||
<AccordionItemBody className="accordion__body">
|
||||
<div className="" dangerouslySetInnerHTML={{ __html: item.Description }}>
|
||||
</div>
|
||||
</AccordionItemBody>
|
||||
</AccordionItem>
|
||||
);
|
||||
});
|
||||
|
||||
if (this.state.isLoading) {
|
||||
displayLoader = (<div className={`ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}`}>
|
||||
<div className='ms-Grid-col ms-u-lg12'>
|
||||
<Spinner size={SpinnerSize.large} label={this.state.loaderMessage} />
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
else {
|
||||
displayLoader = (null);
|
||||
}
|
||||
|
||||
if (this.state.listItems.length > 0) {
|
||||
pageCount = Math.ceil(this.state.listItems.length / pageCountDivisor);
|
||||
}
|
||||
for (let i = 0; i < pageCount; i++) {
|
||||
pageButtons.push(<PrimaryButton onClick={() => { _pagedButtonClick(i + 1, listItems); }}> {i + 1} </PrimaryButton>);
|
||||
}
|
||||
return (
|
||||
<div className={styles.reactAccordion}>
|
||||
<div className={styles.container}>
|
||||
{faqTitle}
|
||||
{displayLoader}
|
||||
<WebPartTitle displayMode={this.props.displayMode}
|
||||
title={this.props.title}
|
||||
updateProperty={this.props.updateProperty} />
|
||||
<div className='ms-Grid-row'>
|
||||
<div className='ms-Grid-col ms-u-lg12'>
|
||||
<SearchBox
|
||||
onChange={this.searchTextChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`ms-Grid-row`}>
|
||||
<div className='ms-Grid-col ms-u-lg12'>
|
||||
{this.state.status}
|
||||
<Accordion accordion={false}>
|
||||
{items}
|
||||
</Accordion>
|
||||
</div>
|
||||
</div>
|
||||
<div className='ms-Grid-row'>
|
||||
<div className='ms-Grid-col ms-u-lg12'>
|
||||
{pageButtons}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
.accordion__title > *:last-child,
|
||||
.accordion__body > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.accordion__arrow {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 24px;
|
||||
height: 12px;
|
||||
right: 10px;
|
||||
margin-top: -28px;
|
||||
color: white !important;
|
||||
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
.accordion__arrow::after,
|
||||
.accordion__arrow::before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 2px;
|
||||
background-color: currentColor;
|
||||
content: '';
|
||||
}
|
||||
|
||||
.accordion__arrow::before {
|
||||
left: 4px;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
[aria-expanded="true"] .accordion__arrow::before {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.accordion__arrow::after {
|
||||
right: 4px;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
[aria-expanded="true"] .accordion__arrow::after {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.accordion__arrow::before, .accordion__arrow::after {
|
||||
transition: transform .25s ease, -webkit-transform .25s ease;
|
||||
}
|
||||
|
||||
.accordion__item {
|
||||
background-color: "[theme: themePrimary, default: #0078d7]";
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.accordion {
|
||||
padding-top: 10px;
|
||||
|
||||
}
|
||||
.accordion__item .accordion__title {
|
||||
padding: 5px 20px;;
|
||||
background-color: "[theme: themePrimary, default: #0078d7]";
|
||||
}
|
||||
.accordion__item .accordion__title h3 {
|
||||
font-weight: normal;
|
||||
width: 88%;
|
||||
}
|
||||
.accordion__item .accordion__body {
|
||||
padding: 15px 20px;
|
||||
background-color: "[theme: themeLighterAlt, default: #0078d7]";
|
||||
color: "[theme: bodyText, default: #333333]";
|
||||
}
|
||||
|
||||
.accordion__item .accordion__body a {
|
||||
color: "[theme: themePrimary, default: #0078d7]" !important;
|
||||
}
|
||||
.accordion__item .accordion__body p {
|
||||
color: "[theme: bodyText, default: #333333]";
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
/* ---------------- Animation part ------------------ */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
@keyframes move-down {
|
||||
0% { transform: translateY(0); }
|
||||
10% { transform: translateY(0); }
|
||||
20% { transform: translateY(5px); }
|
||||
30% { transform: translateY(0); }
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes move-up {
|
||||
0% { transform: translateY(0); }
|
||||
10% { transform: translateY(0); }
|
||||
20% { transform: translateY(-5px); }
|
||||
30% { transform: translateY(0); }
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
|
||||
.accordion__title--animated:hover .accordion__arrow {
|
||||
animation-name: move-down;
|
||||
animation-duration: 1.5s;
|
||||
}
|
||||
|
||||
.accordion__title--animated[aria-expanded="true"]:hover .accordion__arrow {
|
||||
animation-name: move-up;
|
||||
animation-duration: 1.5s;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
define([], function () {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"ListNameLabel": "List Name",
|
||||
"MaxItemsPerPageLabel": "Max number of items per page"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
declare interface IReactAccordionWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
ListNameLabel: string;
|
||||
MaxItemsPerPageLabel: string
|
||||
}
|
||||
|
||||
declare module 'ReactAccordionWebPartStrings' {
|
||||
const strings: IReactAccordionWebPartStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
interface IAccordionListItem {
|
||||
Id: number;
|
||||
Title: string;
|
||||
Description: string;
|
||||
}
|
||||
export default IAccordionListItem;
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"rulesDirectory": "./config"
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
# React sample showing the use of sp-pnp-js with Async / Await
|
||||
# React sample showing the use of @pnp/js with Async / Await
|
||||
|
||||
## Summary
|
||||
This webpart demonstrates how to use [PnP JS Core](https://github.com/SharePoint/PnP-JS-Core) with Async functions into the SharePoint Framework as well as integrating [PnP JS and SPFx Logging systems](https://blog.josequinto.com/2017/04/30/how-to-integrate-pnp-js-core-and-sharepoint-framework-logging-systems/). Real example querying SharePoint library to show document sizes.
|
||||
This webpart demonstrates how to use [PnPJS](https://pnp.github.io/pnpjs/) with Async functions into the SharePoint Framework as well as integrating [PnP JS and SPFx Logging systems](https://blog.josequinto.com/2017/04/30/how-to-integrate-pnp-js-core-and-sharepoint-framework-logging-systems/). Real example querying SharePoint library to show document sizes.
|
||||
|
||||
![React-sp-pnp-js-async-await](./assets/async-await-sp-pnp-js.png)
|
||||
|
||||
|
@ -24,8 +24,9 @@ react-async-await-sp-pnp-js | Jose Quinto ([@jquintozamora](https://twitter.com/
|
|||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|May 1, 2017|Initial release
|
||||
1.2|Jul 20, 2018|Replaced deprecated sp-pnp-js with @pnp/js
|
||||
1.1|Mar 6, 2018|Update to 1.4.1
|
||||
1.0|May 1, 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.**
|
||||
|
|
|
@ -1265,6 +1265,73 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@pnp/common": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@pnp/common/-/common-1.1.2.tgz",
|
||||
"integrity": "sha1-AieN9cb8ds60eoHNBLxDANak5JI=",
|
||||
"requires": {
|
||||
"@types/adal-angular": "1.0.1",
|
||||
"adal-angular": "1.0.17",
|
||||
"tslib": "1.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"adal-angular": {
|
||||
"version": "1.0.17",
|
||||
"resolved": "https://registry.npmjs.org/adal-angular/-/adal-angular-1.0.17.tgz",
|
||||
"integrity": "sha1-bpNuDkH5HTsqiOf/ypwvb29WLMQ="
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.2.tgz",
|
||||
"integrity": "sha512-AVP5Xol3WivEr7hnssHDsaM+lVrVXWUvd1cfXTRkTj80b//6g2wIFEH6hZG0muGZRnHGrfttpdzRk3YlBkWjKw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pnp/logging": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@pnp/logging/-/logging-1.1.2.tgz",
|
||||
"integrity": "sha1-XxJmpCwA2wpPYyDNbg66s6wVqRA=",
|
||||
"requires": {
|
||||
"tslib": "1.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.2.tgz",
|
||||
"integrity": "sha512-AVP5Xol3WivEr7hnssHDsaM+lVrVXWUvd1cfXTRkTj80b//6g2wIFEH6hZG0muGZRnHGrfttpdzRk3YlBkWjKw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pnp/odata": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@pnp/odata/-/odata-1.1.2.tgz",
|
||||
"integrity": "sha1-RaKSK4NKsd5RPPwYyfN4ZiZbVAc=",
|
||||
"requires": {
|
||||
"tslib": "1.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.2.tgz",
|
||||
"integrity": "sha512-AVP5Xol3WivEr7hnssHDsaM+lVrVXWUvd1cfXTRkTj80b//6g2wIFEH6hZG0muGZRnHGrfttpdzRk3YlBkWjKw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pnp/sp": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@pnp/sp/-/sp-1.1.2.tgz",
|
||||
"integrity": "sha1-D6XT6QSxmTRKLd6L2ClbPrRkawQ=",
|
||||
"requires": {
|
||||
"tslib": "1.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.2.tgz",
|
||||
"integrity": "sha512-AVP5Xol3WivEr7hnssHDsaM+lVrVXWUvd1cfXTRkTj80b//6g2wIFEH6hZG0muGZRnHGrfttpdzRk3YlBkWjKw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/adal": {
|
||||
"version": "1.0.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/adal/-/adal-1.0.27.tgz",
|
||||
|
@ -1273,6 +1340,11 @@
|
|||
"@types/angular": "1.6.43"
|
||||
}
|
||||
},
|
||||
"@types/adal-angular": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/adal-angular/-/adal-angular-1.0.1.tgz",
|
||||
"integrity": "sha512-2sRGxJYrluhvIz8ae98i5k5woe9Fics4dMFHTcNfY2xAkj5QGZor+sfZzlgM58Fpw7Kklau9Gn6OhgJP25dKug=="
|
||||
},
|
||||
"@types/angular": {
|
||||
"version": "1.6.43",
|
||||
"resolved": "https://registry.npmjs.org/@types/angular/-/angular-1.6.43.tgz",
|
||||
|
@ -1429,11 +1501,6 @@
|
|||
"@types/node": "6.0.64"
|
||||
}
|
||||
},
|
||||
"@types/microsoft-ajax": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/microsoft-ajax/-/microsoft-ajax-0.0.33.tgz",
|
||||
"integrity": "sha512-R31YIw2FUZsx62yzH1LX9+SV0xCxRn+r6FpYhaEKHAms+3zozMYZZvsJO+I3za+wlE/z03PrePGgCO1rxv7dUg=="
|
||||
},
|
||||
"@types/mime": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.0.tgz",
|
||||
|
@ -1559,14 +1626,6 @@
|
|||
"@types/mime": "2.0.0"
|
||||
}
|
||||
},
|
||||
"@types/sharepoint": {
|
||||
"version": "2013.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/sharepoint/-/sharepoint-2013.1.10.tgz",
|
||||
"integrity": "sha512-rDsQNjMnRkzIU4Iz5GWHivR3CXZAbsGMAmPDakMZkgDSl2uzAFcm78ugMnbV0B6vi0ClDGiVrsiPCdkFExhVSw==",
|
||||
"requires": {
|
||||
"@types/microsoft-ajax": "0.0.33"
|
||||
}
|
||||
},
|
||||
"@types/sinon": {
|
||||
"version": "1.16.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-1.16.34.tgz",
|
||||
|
@ -15347,14 +15406,6 @@
|
|||
"integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
|
||||
"dev": true
|
||||
},
|
||||
"sp-pnp-js": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/sp-pnp-js/-/sp-pnp-js-2.0.6.tgz",
|
||||
"integrity": "sha1-zWeDkx30gT5EGZpw9HbnpiQNoSo=",
|
||||
"requires": {
|
||||
"@types/sharepoint": "2013.1.10"
|
||||
}
|
||||
},
|
||||
"sparkles": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz",
|
||||
|
|
|
@ -8,15 +8,18 @@
|
|||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "~1.4.1",
|
||||
"@microsoft/sp-webpart-base": "~1.4.1",
|
||||
"@pnp/common": "^1.1.2",
|
||||
"@pnp/logging": "^1.1.2",
|
||||
"@pnp/odata": "^1.1.2",
|
||||
"@pnp/sp": "^1.1.2",
|
||||
"@types/react": "0.14.46",
|
||||
"@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",
|
||||
"@types/react": "0.14.46",
|
||||
"@types/react-dom": "0.14.18",
|
||||
"@types/react-addons-shallow-compare": "0.14.17",
|
||||
"@types/react-addons-update": "0.14.14",
|
||||
"@types/react-addons-test-utils": "0.14.15",
|
||||
"sp-pnp-js": "2.0.6"
|
||||
"react-dom": "15.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "~1.4.1",
|
||||
|
|
|
@ -5,7 +5,9 @@ import styles from "./AsyncAwaitPnPJs.module.scss";
|
|||
import { IFile, IResponseItem } from "../interfaces";
|
||||
|
||||
// import pnp and pnp logging system
|
||||
import { Logger, FunctionListener, LogEntry, LogLevel, Web } from "sp-pnp-js";
|
||||
import { Web } from "@pnp/sp";
|
||||
import { Logger, LogLevel, LogEntry, FunctionListener } from "@pnp/logging";
|
||||
|
||||
// import SPFx Logging system
|
||||
import { Log } from "@microsoft/sp-core-library";
|
||||
|
||||
|
@ -102,7 +104,7 @@ export default class AsyncAwaitPnPJs extends React.Component<IAsyncAwaitPnPJsPro
|
|||
|
||||
// mapping betwween PnP JS Log types and SPFx logging methods
|
||||
// instead of using switch we use object easy syntax
|
||||
const logLevelConversion = { Verbose: "verbose", Info: "info", Warning: "warn", Error: "error" };
|
||||
const logLevelConversion = { 0: "verbose", 1: "info", 2: "warn", 3: "error" };
|
||||
|
||||
// create Message. Two importante notes here:
|
||||
// 1. Use JSON.stringify to output everything. It´s helpful when some internal exception comes thru.
|
||||
|
@ -116,7 +118,7 @@ export default class AsyncAwaitPnPJs extends React.Component<IAsyncAwaitPnPJsPro
|
|||
}
|
||||
|
||||
// [SPFx Logging] Calculate method to invoke verbose, info, warn or error
|
||||
const method = logLevelConversion[LogLevel[entry.level]];
|
||||
const method = logLevelConversion[entry.level];
|
||||
|
||||
// [SPFx Logging] Call SPFx Logging system with the message received from PnP JS Logging
|
||||
Log[method](componentName, formatedMessage);
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# 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,5 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"msjsdiag.debugger-for-chrome"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
/**
|
||||
* Install Chrome Debugger Extension for Visual Studio Code to debug your components with the
|
||||
* Chrome browser: https://aka.ms/spfx-debugger-extensions
|
||||
*/
|
||||
"version": "0.2.0",
|
||||
"configurations": [{
|
||||
"name": "Local workbench",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "https://localhost:4321/temp/workbench.html",
|
||||
"webRoot": "${workspaceRoot}",
|
||||
"sourceMaps": true,
|
||||
"sourceMapPathOverrides": {
|
||||
"webpack:///../../../src/*": "${webRoot}/src/*",
|
||||
"webpack:///../../../../src/*": "${webRoot}/src/*",
|
||||
"webpack:///../../../../../src/*": "${webRoot}/src/*"
|
||||
},
|
||||
"runtimeArgs": [
|
||||
"--remote-debugging-port=9222"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Hosted workbench",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "https://enter-your-SharePoint-site/_layouts/workbench.aspx",
|
||||
"webRoot": "${workspaceRoot}",
|
||||
"sourceMaps": true,
|
||||
"sourceMapPathOverrides": {
|
||||
"webpack:///../../../src/*": "${webRoot}/src/*",
|
||||
"webpack:///../../../../src/*": "${webRoot}/src/*",
|
||||
"webpack:///../../../../../src/*": "${webRoot}/src/*"
|
||||
},
|
||||
"runtimeArgs": [
|
||||
"--remote-debugging-port=9222",
|
||||
"-incognito"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
// Configure glob patterns for excluding files and folders in the file explorer.
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/bower_components": true,
|
||||
"**/coverage": true,
|
||||
"**/lib-amd": true,
|
||||
"src/**/*.scss.ts": true,
|
||||
"**/node_modules": true,
|
||||
"**/typings": true,
|
||||
"**/.editorconfig": true,
|
||||
"**/.yo-rc.json": true,
|
||||
"**/package-lock.json": true,
|
||||
"**/.vscode": true,
|
||||
"**/temp": true,
|
||||
"**/lib": true,
|
||||
"**/dist": true
|
||||
},
|
||||
"typescript.tsdk": ".\\node_modules\\typescript\\lib"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.5.1",
|
||||
"libraryName": "react-facebook-plugin",
|
||||
"libraryId": "2608d0f3-26fe-4142-941e-d5937bbbb952",
|
||||
"packageManager": "npm",
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
# SharePoint Framework Facebook Page Social Plugin web part sample
|
||||
|
||||
## Summary
|
||||
|
||||
This sample shows how to implement iFrame-based web parts with a dynamic responsive behavior on the example of Facebook Page Social Plugin.
|
||||
|
||||
The key differences between using Embed web part and current example are the strong parametrization of web part properties and responsive behavior of the iFrame depending on parent container width changes.
|
||||
|
||||
![preview](./assets/preview.png)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
|
||||
![drop](https://img.shields.io/badge/drop-1.5.1-blue.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)
|
||||
|
||||
## Solution
|
||||
|
||||
Solution | Author(s)
|
||||
---------|----------
|
||||
react-facebook-plugin | Andrew Koltyakov ([@AndrewKoltyakov](https://twitter.com/AndrewKoltyakov))
|
||||
|
||||
## Version history
|
||||
|
||||
Version | Date| Comments
|
||||
--------|-----|---------
|
||||
1.0 | August 26, 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
|
||||
|
||||
### Local testing
|
||||
|
||||
* Clone the repository
|
||||
* `cd` to web part's project folder
|
||||
* In the command line run:
|
||||
* `npm install`
|
||||
* `gulp serve`
|
||||
|
||||
### Deployment
|
||||
|
||||
Follow the usual SPFx deployment process up to your preferences.
|
||||
|
||||
## Features
|
||||
|
||||
This web part illustrates the following concepts on top of the SharePoint Framework:
|
||||
|
||||
* Observe parent container width and dynamic adaptation of absolute-width'ed HTML elements
|
||||
* React
|
Binary file not shown.
After Width: | Height: | Size: 176 KiB |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"facebook-page-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/facebookPage/FacebookPageWebPart.js",
|
||||
"manifest": "./src/webparts/facebookPage/FacebookPageWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"localizedResources": {
|
||||
"FacebookPageWebPartStrings": "lib/webparts/facebookPage/loc/{locale}.js"
|
||||
},
|
||||
"externals": {}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-facebook-plugin",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-facebook-plugin-client-side-solution",
|
||||
"id": "a395e8a8-07fd-4a20-bc10-ad1cd91fb5b9",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-facebook-plugin.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.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://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
build.initialize(gulp);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "react-facebook-plugin",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run clean && gulp build && gulp bundle && gulp package-solution",
|
||||
"build:prod": "npm run clean && gulp build --ship && gulp bundle --ship && gulp package-solution --ship",
|
||||
"dev": "gulp serve",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.5.1",
|
||||
"@microsoft/sp-lodash-subset": "1.5.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.5.1",
|
||||
"@microsoft/sp-webpart-base": "1.5.1",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/react": "15.6.6",
|
||||
"@types/react-dom": "15.5.6",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"on-el-resize": "0.0.4",
|
||||
"react": "15.6.2",
|
||||
"react-dom": "15.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.5.1",
|
||||
"@microsoft/sp-module-interfaces": "1.5.1",
|
||||
"@microsoft/sp-webpart-workbench": "1.5.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2",
|
||||
"gulp": "~3.9.1",
|
||||
"tslint-config-standard": "^7.1.0",
|
||||
"tslint-react": "^3.6.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "fada13df-a74a-4c80-8b56-5166e8ebf4f8",
|
||||
"alias": "FacebookPageWebPart",
|
||||
"componentType": "WebPart",
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
"requiresCustomScript": false,
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "Facebook Page" },
|
||||
"description": { "default": "Facebook Page Social Plugin webpart" },
|
||||
// "iconImageUrl": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDBweCIgaGVpZ2h0PSI0MHB4IiB2aWV3Qm94PSIwIDAgNjAgNjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPg0KICAgIDxwYXRoIGQ9Ik0wLjExMjI5MDM0NCwzMCBDMC4xMTIyOTAzNDQsMTMuNDMxNDU2NyAxMy4zNDI2NTA2LDAgMjkuNjYzMTI5LDAgQzQ1Ljk4MzYwNzMsMCA1OS4yMTM5Njc2LDEzLjQzMTQ1NjcgNTkuMjEzOTY3NiwzMCBDNTkuMjEzOTY3Niw0Ni41Njg1NDMzIDQ1Ljk4MzYwNzMsNjAgMjkuNjYzMTI5LDYwIEMxMy4zNDI2NTA2LDYwIDAuMTEyMjkwMzQ0LDQ2LjU2ODU0MzMgMC4xMTIyOTAzNDQsMzAgWiBNMC4xMTIyOTAzNDQsMzAiIGZpbGw9IiMzQjU5OTgiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4NCgk8cGF0aCBkPSJNMzIuMTM0MTQ1Nyw0Ni4zMTk2NzI5IEwzMi4xMzQxNDU3LDI5Ljk5ODA4OTEgTDM2LjU2NTc1NjUsMjkuOTk4MDg5MSBMMzcuMTUzMDQwNiwyNC4zNzM1ODA5IEwzMi4xMzQxNDU3LDI0LjM3MzU4MDkgTDMyLjE0MTY3NSwyMS41NTg0NjA0IEMzMi4xNDE2NzUsMjAuMDkxNTAyIDMyLjI3ODc3MDcsMTkuMzA1NDcyMiAzNC4zNTEyMDYsMTkuMzA1NDcyMiBMMzcuMTIxNjY4NiwxOS4zMDU0NzIyIEwzNy4xMjE2Njg2LDEzLjY4MDMyNzEgTDMyLjY4OTQzMDQsMTMuNjgwMzI3MSBDMjcuMzY1NTk5NSwxMy42ODAzMjcxIDI1LjQ5MTc0OSwxNi40MDg4MTg3IDI1LjQ5MTc0OSwyMC45OTcyODM1IEwyNS40OTE3NDksMjQuMzc0MjE3OSBMMjIuMTczMjE3MywyNC4zNzQyMTc5IEwyMi4xNzMyMTczLDI5Ljk5ODcyNiBMMjUuNDkxNzQ5LDI5Ljk5ODcyNiBMMjUuNDkxNzQ5LDQ2LjMxOTY3MjkgTDMyLjEzNDE0NTcsNDYuMzE5NjcyOSBaIE0zMi4xMzQxNDU3LDQ2LjMxOTY3MjkiIGlkPSJQYXRoIiBmaWxsPSIjRkZGRkZGIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+DQoJPHBhdGggZD0iTTU5LjIxMzk2NzYsMzAgQzU5LjIxMzk2NzYsNDYuNTY4NTQzMyA0NS45ODM2MDczLDYwIDI5LjY2MzEyOSw2MCBDMjMuNjEwMjUwMiw2MCAxNy45ODI0MTQ3LDU4LjE1MjUxMzQgMTMuMjk3MDM0MSw1NC45ODI3NzU0IEw0Ny4xNTcyNTU0LDUuODE5NDExMDMgQzU0LjQ2OTE1MzQsMTEuMjgwNjUwMyA1OS4yMTM5Njc2LDIwLjA3Nzc5NzMgNTkuMjEzOTY3NiwzMCBaIE01OS4yMTM5Njc2LDMwIiBpZD0icmVmbGVjIiBmaWxsLW9wYWNpdHk9IjAuMDgiIGZpbGw9IiMwMDAwMDAiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4NCjwvc3ZnPg==",
|
||||
"iconImageUrl": "https://spoprod-a.akamaihd.net/files/sp-client-prod_2018-08-17.009/facebook_cdd011c89467d86664bb8331411d5ef5.svg",
|
||||
"properties": {
|
||||
"company" : "Microsoft",
|
||||
"height" : 600
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField,
|
||||
PropertyPaneToggle
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
|
||||
import * as strings from 'FacebookPageWebPartStrings';
|
||||
import FacebookPage from './components/FacebookPage';
|
||||
import { IFacebookPageProps } from './components/IFacebookPageProps';
|
||||
import { IFacebookPageWebPartProps } from './IFacebookPageWebPart';
|
||||
|
||||
export default class FacebookPageWebPart extends BaseClientSideWebPart<IFacebookPageWebPartProps> {
|
||||
|
||||
public render(): void {
|
||||
const props: IFacebookPageProps = {
|
||||
company: this.properties.company,
|
||||
height: Number(this.properties.height),
|
||||
smallHeader: this.properties.smallHeader,
|
||||
hideCover: this.properties.hideCover,
|
||||
showFacepile: this.properties.showFacepile
|
||||
};
|
||||
const element = React.createElement(FacebookPage, props);
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField('company', {
|
||||
label: strings.CompanyFieldLabel
|
||||
}),
|
||||
PropertyPaneTextField('height', {
|
||||
label: strings.HeightFieldLabel
|
||||
}),
|
||||
PropertyPaneToggle('smallHeader', {
|
||||
label: strings.SmallHeaderFieldLabel
|
||||
}),
|
||||
PropertyPaneToggle('hideCover', {
|
||||
label: strings.HideCoverFieldLabel
|
||||
}),
|
||||
PropertyPaneToggle('showFacepile', {
|
||||
label: strings.ShowFacepileFieldLabel
|
||||
})
|
||||
]
|
||||
}]
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
export interface IFacebookPageWebPartProps {
|
||||
company: string;
|
||||
height: string;
|
||||
smallHeader?: boolean;
|
||||
hideCover?: boolean;
|
||||
showFacepile?: boolean;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
.facebookPageContainer {
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
> iframe {
|
||||
margin: 0 auto;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
import * as React from 'react';
|
||||
import { Resizable } from 'on-el-resize/lib/components';
|
||||
import { IFacebookPageProps } from './IFacebookPageProps';
|
||||
import styles from './FacebookPage.module.scss';
|
||||
|
||||
export default class FacebookPage extends React.Component<IFacebookPageProps, {}> {
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<Resizable
|
||||
className={styles.facebookPageContainer}
|
||||
render={({ width }) => {
|
||||
return (
|
||||
<iframe
|
||||
src={this.buildIFrameUrl(width)}
|
||||
width={width}
|
||||
height={this.props.height || 500}
|
||||
style={{
|
||||
border: 'none',
|
||||
overflow: 'hidden',
|
||||
width: '100%'
|
||||
}}
|
||||
scrolling='no'
|
||||
allowTransparency={true}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private buildIFrameUrl(width: number): string {
|
||||
return `https://www.facebook.com/plugins/page.php?` +
|
||||
`href=${encodeURIComponent(`https://www.facebook.com/${this.props.company || 'Microsoft'}`)}&` +
|
||||
`width=${width}&` +
|
||||
`height=${this.props.height || 500}&` +
|
||||
`small_header=${typeof this.props.smallHeader !== 'undefined' ? this.props.smallHeader : false}&` +
|
||||
`hide_cover=${typeof this.props.hideCover !== 'undefined' ? this.props.hideCover : false}&` +
|
||||
`show_facepile=${typeof this.props.showFacepile !== 'undefined' ? this.props.showFacepile : false}&` +
|
||||
`adapt_container_width=true&` +
|
||||
`tabs=timeline`;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
export interface IFacebookPageProps {
|
||||
company: string;
|
||||
height: number;
|
||||
smallHeader?: boolean;
|
||||
hideCover?: boolean;
|
||||
showFacepile?: boolean;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Facebook Page Social Plugin webpart",
|
||||
"BasicGroupName": "Webpart settings",
|
||||
"CompanyFieldLabel": "Company page (uri)",
|
||||
"HeightFieldLabel": "Webpart height",
|
||||
"SmallHeaderFieldLabel": "Small header",
|
||||
"HideCoverFieldLabel": "Hide cover",
|
||||
"ShowFacepileFieldLabel": "Show facepile"
|
||||
};
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
declare interface IFacebookPageWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
BasicGroupName: string;
|
||||
CompanyFieldLabel: string;
|
||||
HeightFieldLabel: string;
|
||||
SmallHeaderFieldLabel: string;
|
||||
HideCoverFieldLabel: string;
|
||||
ShowFacepileFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'FacebookPageWebPartStrings' {
|
||||
const strings: IFacebookPageWebPartStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": ["tslint-config-standard", "tslint-react", "./config/tslint.json"],
|
||||
"rules": {
|
||||
"space-before-function-paren": false,
|
||||
"semicolon": [ true, "always", "ignore-interfaces" ],
|
||||
"jsx-no-multiline-js": false,
|
||||
"jsx-no-lambda": false
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
"solution": {
|
||||
"name": "Modern Script Editor web part by Puzzlepart",
|
||||
"id": "1425175f-3ed8-44d2-8fc4-dd1497191294",
|
||||
"version": "1.0.0.8",
|
||||
"version": "1.0.0.9",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": false
|
||||
},
|
||||
|
|
|
@ -22,13 +22,4 @@ build.configureWebpack.mergeConfig({
|
|||
}
|
||||
});
|
||||
|
||||
let copyDynamic = build.subTask('copy-dynamic-load-files', function (gulp, buildOptions, done) {
|
||||
gulp.src('./node_modules/sharepoint-modern-script-editor-propertypane/bundles/editor-pop-up.min.js')
|
||||
.pipe(gulp.dest('./temp/deploy'))
|
||||
.pipe(gulp.dest('./dist'));
|
||||
|
||||
done();
|
||||
});
|
||||
build.rig.addPostBuildTask(copyDynamic);
|
||||
|
||||
build.initialize(gulp);
|
|
@ -688,8 +688,7 @@
|
|||
"@microsoft/load-themed-styles": {
|
||||
"version": "1.7.53",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/load-themed-styles/-/load-themed-styles-1.7.53.tgz",
|
||||
"integrity": "sha1-J7KoW8wJdoyOwrZS/9mIBCeah8U=",
|
||||
"dev": true
|
||||
"integrity": "sha1-J7KoW8wJdoyOwrZS/9mIBCeah8U="
|
||||
},
|
||||
"@microsoft/loader-cased-file": {
|
||||
"version": "1.5.1",
|
||||
|
@ -2463,19 +2462,33 @@
|
|||
}
|
||||
},
|
||||
"@uifabric/merge-styles": {
|
||||
<<<<<<< .mine
|
||||
"version": "5.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/merge-styles/-/merge-styles-5.17.1.tgz",
|
||||
"integrity": "sha512-4/EtO6Ns7kNtKxC+6InShwVQeNQEDT5H8Ex7m/i4OrT9i7csje4YwBQPkkpm31qJwEZEyD7bbAwyLezI63sLhg==",
|
||||
"dev": true,
|
||||
=======
|
||||
"version": "5.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/merge-styles/-/merge-styles-5.17.1.tgz",
|
||||
"integrity": "sha512-4/EtO6Ns7kNtKxC+6InShwVQeNQEDT5H8Ex7m/i4OrT9i7csje4YwBQPkkpm31qJwEZEyD7bbAwyLezI63sLhg==",
|
||||
|
||||
>>>>>>> .theirs
|
||||
"requires": {
|
||||
"tslib": "1.8.1"
|
||||
}
|
||||
},
|
||||
"@uifabric/styling": {
|
||||
<<<<<<< .mine
|
||||
"version": "5.31.2",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-5.31.2.tgz",
|
||||
"integrity": "sha512-G7grRENxdB4NcUEtQmSisRDnIeQHQmAfcpIe5AWmVQxnB/e8U/jT+SzYqK6V12SnLf4ufN5nepZkdOjgbtuA1Q==",
|
||||
"dev": true,
|
||||
=======
|
||||
"version": "5.31.2",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-5.31.2.tgz",
|
||||
"integrity": "sha512-G7grRENxdB4NcUEtQmSisRDnIeQHQmAfcpIe5AWmVQxnB/e8U/jT+SzYqK6V12SnLf4ufN5nepZkdOjgbtuA1Q==",
|
||||
|
||||
>>>>>>> .theirs
|
||||
"requires": {
|
||||
"@microsoft/load-themed-styles": "1.7.53",
|
||||
"@uifabric/merge-styles": "5.17.1",
|
||||
|
@ -2484,10 +2497,17 @@
|
|||
}
|
||||
},
|
||||
"@uifabric/utilities": {
|
||||
<<<<<<< .mine
|
||||
"version": "5.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/utilities/-/utilities-5.34.1.tgz",
|
||||
"integrity": "sha512-xUZ+KlCxmA9PCEOtutfeyYnPmySkEVA/ak3fb8Uj54684GlbrnlqrfNEencVG5YgQDcNEaztTwxW3I7jSCrYJQ==",
|
||||
"dev": true,
|
||||
=======
|
||||
"version": "5.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/utilities/-/utilities-5.34.1.tgz",
|
||||
"integrity": "sha512-xUZ+KlCxmA9PCEOtutfeyYnPmySkEVA/ak3fb8Uj54684GlbrnlqrfNEencVG5YgQDcNEaztTwxW3I7jSCrYJQ==",
|
||||
|
||||
>>>>>>> .theirs
|
||||
"requires": {
|
||||
"@uifabric/merge-styles": "5.17.1",
|
||||
"prop-types": "15.6.0",
|
||||
|
@ -13661,15 +13681,24 @@
|
|||
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-5.118.3.tgz",
|
||||
"integrity": "sha512-MYRah+boLs75UXkTjh1gYORitjtt7ixrfl5BFtFWkX8AGSau2ulZT4XQ0gOyKumvPfCqLzlNKXv//84tptUm0g==",
|
||||
"requires": {
|
||||
<<<<<<< .mine
|
||||
"@microsoft/load-themed-styles": "1.7.74",
|
||||
"@uifabric/icons": "5.8.0",
|
||||
"@uifabric/merge-styles": "5.17.1",
|
||||
"@uifabric/styling": "5.31.2",
|
||||
"@uifabric/utilities": "5.34.1",
|
||||
=======
|
||||
"@microsoft/load-themed-styles": "1.7.53",
|
||||
"@uifabric/icons": "5.8.0",
|
||||
"@uifabric/merge-styles": "5.17.1",
|
||||
"@uifabric/styling": "5.31.2",
|
||||
"@uifabric/utilities": "5.34.1",
|
||||
>>>>>>> .theirs
|
||||
"prop-types": "15.6.0",
|
||||
"tslib": "1.8.1"
|
||||
},
|
||||
"dependencies": {
|
||||
<<<<<<< .mine
|
||||
"@microsoft/load-themed-styles": {
|
||||
"version": "1.7.74",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/load-themed-styles/-/load-themed-styles-1.7.74.tgz",
|
||||
|
@ -13696,12 +13725,47 @@
|
|||
"version": "5.31.2",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-5.31.2.tgz",
|
||||
"integrity": "sha512-G7grRENxdB4NcUEtQmSisRDnIeQHQmAfcpIe5AWmVQxnB/e8U/jT+SzYqK6V12SnLf4ufN5nepZkdOjgbtuA1Q==",
|
||||
=======
|
||||
"@uifabric/icons": {
|
||||
"version": "5.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/icons/-/icons-5.8.0.tgz",
|
||||
"integrity": "sha512-EUhKxYlIPJshg4fQvCNTYSk0p7RhzEWeEAJBV4sao1SKmN0/pZBnkLbDqWjU5VUfdwZZYiIdaLRpM+pyzhniZw==",
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
>>>>>>> .theirs
|
||||
"requires": {
|
||||
<<<<<<< .mine
|
||||
"@microsoft/load-themed-styles": "1.7.74",
|
||||
"@uifabric/merge-styles": "5.17.1",
|
||||
"@uifabric/utilities": "5.34.1",
|
||||
=======
|
||||
"@uifabric/styling": "5.31.2",
|
||||
|
||||
|
||||
>>>>>>> .theirs
|
||||
"tslib": "1.8.1"
|
||||
}
|
||||
<<<<<<< .mine
|
||||
},
|
||||
"@uifabric/utilities": {
|
||||
"version": "5.34.1",
|
||||
|
@ -13712,6 +13776,18 @@
|
|||
"prop-types": "15.6.0",
|
||||
"tslib": "1.8.1"
|
||||
}
|
||||
=======
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
>>>>>>> .theirs
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -6,11 +6,24 @@
|
|||
"node": ">=0.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
<<<<<<< .mine
|
||||
"@microsoft/sp-core-library": "1.5.1",
|
||||
"@microsoft/sp-webpart-base": "1.5.1",
|
||||
|
||||
=======
|
||||
"@microsoft/sp-core-library": "1.5.1",
|
||||
"@microsoft/sp-webpart-base": "1.5.1",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
>>>>>>> .theirs
|
||||
"@types/react": "15.6.6",
|
||||
"@types/react-dom": "15.5.6",
|
||||
<<<<<<< .mine
|
||||
"@types/webpack-env": "1.13.1",
|
||||
|
||||
=======
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"office-ui-fabric-react": "^5.118.3",
|
||||
>>>>>>> .theirs
|
||||
"react": "15.6.2",
|
||||
"react-dom": "15.6.2",
|
||||
"@types/es6-promise": "0.0.33"
|
||||
|
@ -23,8 +36,13 @@
|
|||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2",
|
||||
"gulp": "~3.9.1",
|
||||
<<<<<<< .mine
|
||||
"sharepoint-modern-script-editor-propertypane": "^1.0.5",
|
||||
"webpack-bundle-analyzer": "^2.13.1"
|
||||
=======
|
||||
"webpack-bundle-analyzer": "^2.13.1"
|
||||
|
||||
>>>>>>> .theirs
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
|
|
|
@ -36,7 +36,10 @@ export default class ScriptEditorWebPart extends BaseClientSideWebPart<IScriptEd
|
|||
this.executeScript(this.domElement);
|
||||
} else {
|
||||
// Dynamically load the editor pane to reduce overall bundle size
|
||||
const editorPopUp: any = await SPComponentLoader.loadScript(this.getScriptRoot() + '/editor-pop-up.min.js', { globalExportsName: "EditorPopUp" });
|
||||
const editorPopUp = await import(
|
||||
/* webpackChunkName: 'scripteditor' */
|
||||
'./components/ScriptEditor'
|
||||
);
|
||||
const element: React.ReactElement<IScriptEditorProps> = React.createElement(
|
||||
editorPopUp.default,
|
||||
{
|
||||
|
@ -49,27 +52,10 @@ export default class ScriptEditorWebPart extends BaseClientSideWebPart<IScriptEd
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a dummy bundled image to get the path from where the bundle is served.
|
||||
*/
|
||||
private getScriptRoot(): string {
|
||||
const runtimePath: string = require('./1x1.png');
|
||||
const scriptRoot = runtimePath.substr(0, runtimePath.lastIndexOf("/"));
|
||||
return scriptRoot;
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
protected renderLogo(domElement: HTMLElement) {
|
||||
domElement.innerHTML = `
|
||||
<div style="margin-top: 30px">
|
||||
<div style="float:right">Author: <a href="mailto:mikael.svenson@puzzlepart.com" tabindex="-1">Mikael Svenson</a></div>
|
||||
<div style="float:right"><a href="https://www.puzzlepart.com/" target="_blank"><img src="//www.puzzlepart.com/wp-content/uploads/2017/08/Pzl-LogoType-200.png" onerror="this.style.display = \'none\'";"></a></div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"version": "1.4.0",
|
||||
"version": "1.6.0",
|
||||
"libraryName": "react-slide-swiper",
|
||||
"libraryId": "c7fdd51c-469e-432f-bdde-930294d3a133",
|
||||
"environment": "spo"
|
||||
"environment": "spo",
|
||||
"isCreatingSolution": true,
|
||||
"packageManager": "npm",
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ This SPFx React web part sample demonstrates mobile touch slide swiper. By defau
|
|||
This sample uses the Swiper JavaScript library thought the npm packages, but just for the purpose of the sample so it can quicky be run without addional setup. It is highly recomended to add the Swiper library as SPFx solution [external reference](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/basics/add-an-external-library) to resize the solution bundle size and improve Site Page load times.
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/drop-1.4-green.svg)
|
||||
![drop](https://img.shields.io/badge/drop-1.6.0-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
|
@ -34,6 +34,7 @@ react-slide-swiper | Velin Georgiev ([@VelinGeorgiev](https://twitter.com/veling
|
|||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
0.0.1|February 08, 2018 | Initial commit
|
||||
0.0.2|September 07, 2018 | Upgrade to 1.6.0
|
||||
|
||||
## 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.**
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"react-slide-swiper-web-part": {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-slide-swiper",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-slide-swiper-client-side-solution",
|
||||
"id": "c7fdd51c-469e-432f-bdde-930294d3a133",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/core-build/serve.schema.json",
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -11,25 +11,27 @@
|
|||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "15.6.2",
|
||||
"react-dom": "15.6.2",
|
||||
"@microsoft/sp-core-library": "1.6.0",
|
||||
"@microsoft/sp-lodash-subset": "1.6.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.6.0",
|
||||
"@microsoft/sp-webpart-base": "1.6.0",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/react": "15.6.6",
|
||||
"@types/react-dom": "15.5.6",
|
||||
"@microsoft/sp-core-library": "~1.4.0",
|
||||
"@microsoft/sp-webpart-base": "~1.4.0",
|
||||
"@microsoft/sp-lodash-subset": "~1.4.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "~1.4.0",
|
||||
"@types/webpack-env": ">=1.12.1 <1.14.0",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"react": "15.6.2",
|
||||
"react-dom": "15.6.2",
|
||||
"swiper": "4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "~1.4.0",
|
||||
"@microsoft/sp-module-interfaces": "~1.4.0",
|
||||
"@microsoft/sp-webpart-workbench": "~1.4.0",
|
||||
"gulp": "~3.9.1",
|
||||
"@types/chai": ">=3.4.34 <3.6.0",
|
||||
"@types/mocha": ">=2.2.33 <2.6.0",
|
||||
"@microsoft/sp-build-web": "1.6.0",
|
||||
"@microsoft/sp-module-interfaces": "1.6.0",
|
||||
"@microsoft/sp-webpart-workbench": "1.6.0",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2",
|
||||
"gulp": "~3.9.1",
|
||||
"tslint-microsoft-contrib": "5.0.0",
|
||||
"webpack-bundle-analyzer": "2.9.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "6d0ab1aa-db7a-4e80-a2b5-c83fd820f165",
|
||||
"alias": "ReactSlideSwiperWebPart",
|
||||
"componentType": "WebPart",
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import * as React from 'react';
|
||||
import styles from './ReactSlideSwiper.module.scss';
|
||||
import { IReactSlideSwiperProps } from './IReactSlideSwiperProps';
|
||||
import { escape } from '@microsoft/sp-lodash-subset';
|
||||
import { IReactSlideSwiperState } from './IReactSlideSwiperState';
|
||||
import { ListItem } from '../services/ListItem';
|
||||
import Card from './Card/Card';
|
||||
|
||||
const Swiper = require('swiper/dist/js/swiper.min');
|
||||
const Swiper: any = require('swiper/dist/js/swiper.min');
|
||||
|
||||
export default class ReactSlideSwiper extends React.Component<IReactSlideSwiperProps, IReactSlideSwiperState> {
|
||||
|
||||
|
@ -118,6 +117,7 @@ export default class ReactSlideSwiper extends React.Component<IReactSlideSwiperP
|
|||
};
|
||||
}
|
||||
|
||||
return new Swiper(`.container-${this.uniqueId}`, options);
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
new Swiper(`.container-${this.uniqueId}`, options);
|
||||
}
|
||||
}
|
|
@ -2,12 +2,14 @@
|
|||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "commonjs",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "lib",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
|
@ -21,5 +23,12 @@
|
|||
"dom",
|
||||
"es2015.collection"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"rulesDirectory": [
|
||||
"tslint-microsoft-contrib"
|
||||
],
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
"forin": false,
|
||||
"label-position": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": false,
|
||||
"no-function-expression": true,
|
||||
"no-internal-module": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
# SPFx webpart with Taxonomy picker Office UI Fabric Panel.
|
||||
|
||||
## Summary
|
||||
Sample webpart with Single and Multi-select taxonomy pickers using Office UI Fabric panel. The webpart uses the Taxonomy API support available in the SP-PNP-JS.
|
||||
Sample webpart with Single and Multi-select taxonomy pickers using Office UI Fabric panel. The webpart uses the Taxonomy API support available in the @pnp/taxonomy.
|
||||
|
||||
|
||||
![TaxonomyPickerPanel webpart](https://github.com/vipulkelkar/sp-dev-fx-webparts/blob/TaxonomyPanelPicker/samples/react-taxonomypicker-panel/assets/TaxonomyPicker.gif)
|
||||
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/version-GA-green.svg)
|
||||
![drop](https://img.shields.io/badge/drop-1.5.1-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
|
@ -17,7 +17,7 @@ Sample webpart with Single and Multi-select taxonomy pickers using Office UI Fab
|
|||
|
||||
## Prerequisites
|
||||
|
||||
- sp-pnp-js
|
||||
- @pnp/sp
|
||||
- Office UI Fabric React
|
||||
|
||||
## Solution
|
||||
|
@ -31,6 +31,7 @@ react-taxonomypicker-panel | Vipul Kelkar @vipulkelkar
|
|||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|June 22, 2018|Initial release
|
||||
1.1|August 20, 2018|Bug fix and version upgrade
|
||||
|
||||
## 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.**
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.5.0",
|
||||
"@microsoft/sp-lodash-subset": "1.5.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.5.0",
|
||||
"@microsoft/sp-webpart-base": "1.5.0",
|
||||
"@microsoft/sp-core-library": "1.5.1",
|
||||
"@microsoft/sp-lodash-subset": "1.5.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.5.1",
|
||||
"@microsoft/sp-webpart-base": "1.5.1",
|
||||
"@pnp/common": "^1.1.1",
|
||||
"@pnp/logging": "^1.1.1",
|
||||
"@pnp/odata": "^1.1.1",
|
||||
|
@ -26,13 +26,12 @@
|
|||
"@types/react-dom": "15.5.6",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"react": "15.6.2",
|
||||
"react-dom": "15.6.2",
|
||||
"sp-pnp-js": "^3.0.9"
|
||||
"react-dom": "15.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.5.0",
|
||||
"@microsoft/sp-module-interfaces": "1.5.0",
|
||||
"@microsoft/sp-webpart-workbench": "1.5.0",
|
||||
"@microsoft/sp-build-web": "1.5.1",
|
||||
"@microsoft/sp-module-interfaces": "1.5.1",
|
||||
"@microsoft/sp-webpart-workbench": "1.5.1",
|
||||
"gulp": "~3.9.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
import * as strings from 'ReactTaxonomypickerPanelWebPartStrings';
|
||||
import ReactTaxonomypickerPanel from './components/ReactTaxonomypickerPanel';
|
||||
import { IReactTaxonomypickerPanelProps } from './components/IReactTaxonomypickerPanelProps';
|
||||
import { setup as pnpSetup } from "@pnp/common";
|
||||
|
||||
export interface IReactTaxonomypickerPanelWebPartProps {
|
||||
description: string;
|
||||
|
@ -17,6 +18,15 @@ export interface IReactTaxonomypickerPanelWebPartProps {
|
|||
|
||||
export default class ReactTaxonomypickerPanelWebPart extends BaseClientSideWebPart<IReactTaxonomypickerPanelWebPartProps> {
|
||||
|
||||
public onInit(): Promise<void> {
|
||||
|
||||
return super.onInit().then(_ => {
|
||||
pnpSetup({
|
||||
spfxContext: this.context
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IReactTaxonomypickerPanelProps > = React.createElement(
|
||||
ReactTaxonomypickerPanel,
|
||||
|
|
|
@ -1,71 +1,70 @@
|
|||
import * as React from 'react';
|
||||
import styles from './ReactTaxonomypickerPanel.module.scss';
|
||||
import { IReactTaxonomypickerPanelProps } from './IReactTaxonomypickerPanelProps';
|
||||
import { escape } from '@microsoft/sp-lodash-subset';
|
||||
|
||||
// Controls
|
||||
import TermsPickerComponent, { ITaxonomyTerm } from './TermsPickerComponent';
|
||||
import { DefaultButton, IButtonProps, Button } from 'office-ui-fabric-react/lib/Button';
|
||||
|
||||
export interface ITaxonomyPickerWebpartState{
|
||||
SingleSelectFieldTerms:ITaxonomyTerm[],
|
||||
MultiSelectFieldTerms:ITaxonomyTerm[]
|
||||
export interface ITaxonomyPickerWebpartState {
|
||||
SingleSelectFieldTerms: ITaxonomyTerm[],
|
||||
MultiSelectFieldTerms: ITaxonomyTerm[]
|
||||
}
|
||||
|
||||
export default class ReactTaxonomypickerPanel extends React.Component<IReactTaxonomypickerPanelProps, ITaxonomyPickerWebpartState> {
|
||||
constructor(props, state:ITaxonomyPickerWebpartState){
|
||||
constructor(props, state: ITaxonomyPickerWebpartState) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
SingleSelectFieldTerms:[],
|
||||
MultiSelectFieldTerms:[]
|
||||
SingleSelectFieldTerms: [],
|
||||
MultiSelectFieldTerms: []
|
||||
|
||||
// Supply array in the below format for a pre-populated control.
|
||||
//SingleSelectFieldTerms:[{name:"<Term-Label>", key="<Term-GUID>"}],
|
||||
//MultiSelectFieldTerms:[{name:"<Term-Label>", key="<Term-GUID>"}, {name:"<Term-Label>", key="<Term-GUID>"}]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IReactTaxonomypickerPanelProps> {
|
||||
return (
|
||||
<div>
|
||||
<div className="ms-Grid">
|
||||
<div className="ms-Grid-row">
|
||||
<div className="ms-Grid-col ms-sm12 ms-lg12">
|
||||
<TermsPickerComponent IsMultiValue={false} TermSetId='<TERM-SET-ID>' LabelText='Single-select field' SelectedTerms={this.state.SingleSelectFieldTerms}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ms-Grid-row">
|
||||
<div className="ms-Grid-col ms-sm12 ms-lg12">
|
||||
<TermsPickerComponent IsMultiValue={true} TermSetId='<TERM-SET-ID>' LabelText='Multi-select field' SelectedTerms={this.state.MultiSelectFieldTerms}/>
|
||||
</div>
|
||||
<div className="ms-Grid">
|
||||
<div className="ms-Grid-row">
|
||||
<div className="ms-Grid-col ms-sm12 ms-lg12">
|
||||
<TermsPickerComponent IsMultiValue={false} TermSetId='<TERM-SET-ID>' LabelText='Single-select field' SelectedTerms={this.state.SingleSelectFieldTerms} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="ms-Grid-row">
|
||||
<div className="ms-Grid-col ms-sm12 ms-lg12">
|
||||
<TermsPickerComponent IsMultiValue={true} TermSetId='<TERM-SET-ID>' LabelText='Multi-select field' SelectedTerms={this.state.MultiSelectFieldTerms} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
<DefaultButton
|
||||
primary={true}
|
||||
text="Show selected values"
|
||||
onClick={this._showTaxonomyControlValues.bind(this)}
|
||||
primary={true}
|
||||
text="Show selected values"
|
||||
onClick={this._showTaxonomyControlValues.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private _showTaxonomyControlValues(){
|
||||
private _showTaxonomyControlValues() {
|
||||
|
||||
if(this.state.SingleSelectFieldTerms.length > 0){
|
||||
alert("Single-Select term Label and GUID : \n" + this.state.SingleSelectFieldTerms[0].name + " - " + this.state.SingleSelectFieldTerms[0].key);
|
||||
if (this.state.SingleSelectFieldTerms.length > 0) {
|
||||
alert("Single-Select term Label and GUID : \n" + this.state.SingleSelectFieldTerms[0].name + " - " + this.state.SingleSelectFieldTerms[0].key);
|
||||
}
|
||||
if(this.state.MultiSelectFieldTerms.length > 0){
|
||||
let multiSelectValues = this.state.MultiSelectFieldTerms.map(trm => {
|
||||
return trm.name + " - " + trm.key
|
||||
}).join(' | ');
|
||||
if (this.state.MultiSelectFieldTerms.length > 0) {
|
||||
let multiSelectValues = this.state.MultiSelectFieldTerms.map(trm => {
|
||||
return trm.name + " - " + trm.key
|
||||
}).join(' | ');
|
||||
|
||||
alert("Multi-select term Label and GUID : \n" + multiSelectValues);
|
||||
alert("Multi-select term Label and GUID : \n" + multiSelectValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,340 +1,339 @@
|
|||
import * as React from 'react';
|
||||
import { escape } from '@microsoft/sp-lodash-subset';
|
||||
import { taxonomy, ITermStore, ITermSet, ITerms, ITerm, ITermData, ITermStoreData} from "@pnp/sp-taxonomy";
|
||||
import { taxonomy, ITermStore } from "@pnp/sp-taxonomy";
|
||||
|
||||
// Office ui fabric react controls
|
||||
import { TagPicker, ITag } from 'office-ui-fabric-react/lib/components/pickers/TagPicker/TagPicker';
|
||||
import { DefaultButton, IButtonProps, Button, ActionButton } from 'office-ui-fabric-react/lib/Button';
|
||||
import { TagPicker } from 'office-ui-fabric-react/lib/components/pickers/TagPicker/TagPicker';
|
||||
import { DefaultButton, ActionButton } from 'office-ui-fabric-react/lib/Button';
|
||||
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
|
||||
import { Label } from 'office-ui-fabric-react/lib/Label';
|
||||
import { ChoiceGroup, IChoiceGroupOption } from 'office-ui-fabric-react/lib/ChoiceGroup';
|
||||
import { TextField } from 'office-ui-fabric-react/lib/TextField';
|
||||
|
||||
// Represents a taxonomy term.
|
||||
export interface ITaxonomyTerm{
|
||||
name:string;
|
||||
key:string;
|
||||
export interface ITaxonomyTerm {
|
||||
name: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
// Represents the properties of the term picker component.
|
||||
export interface ITermPickerProps{
|
||||
IsMultiValue:boolean;
|
||||
TermSetId:string;
|
||||
LabelText:string;
|
||||
SelectedTerms:any[]
|
||||
export interface ITermPickerProps {
|
||||
IsMultiValue: boolean;
|
||||
TermSetId: string;
|
||||
LabelText: string;
|
||||
SelectedTerms: any[]
|
||||
}
|
||||
|
||||
// Represents the local state of the term picker component.
|
||||
export interface ISelectTermsState{
|
||||
TaxonomyTerms:ITaxonomyTerm[];
|
||||
showPanel:boolean;
|
||||
SelectedChoiceGroupTerm:ITaxonomyTerm;
|
||||
SelectedTerms:any[];
|
||||
PickerText:string;
|
||||
export interface ISelectTermsState {
|
||||
TaxonomyTerms: ITaxonomyTerm[];
|
||||
showPanel: boolean;
|
||||
SelectedChoiceGroupTerm: ITaxonomyTerm;
|
||||
SelectedTerms: any[];
|
||||
PickerText: string;
|
||||
}
|
||||
|
||||
let taxonomyOptions:IChoiceGroupOption[] = [];
|
||||
let taxonomyOptions: IChoiceGroupOption[] = [];
|
||||
|
||||
export default class TermsPickerComponent extends React.Component<ITermPickerProps, ISelectTermsState> {
|
||||
|
||||
constructor(props, state:ISelectTermsState){
|
||||
super(props);
|
||||
constructor(props, state: ISelectTermsState) {
|
||||
super(props);
|
||||
|
||||
let initialTaxonomyTermsArray:ITaxonomyTerm[] = [];
|
||||
let initialTaxonomyTermsArray: ITaxonomyTerm[] = [];
|
||||
|
||||
this.state = {
|
||||
TaxonomyTerms: initialTaxonomyTermsArray,
|
||||
showPanel: false,
|
||||
SelectedChoiceGroupTerm: { name: null, key: null },
|
||||
SelectedTerms: [],
|
||||
PickerText: "",
|
||||
}
|
||||
|
||||
this.state = {
|
||||
TaxonomyTerms:initialTaxonomyTermsArray,
|
||||
showPanel:false,
|
||||
SelectedChoiceGroupTerm:{name:null,key:null},
|
||||
SelectedTerms:[],
|
||||
PickerText:"",
|
||||
}
|
||||
|
||||
}
|
||||
// Creates the choice group to be displayed in the pciker panel.
|
||||
public createTaxonomyChoiceGroup() {
|
||||
|
||||
// Creates the choice group to be displayed in the pciker panel.
|
||||
public createTaxonomyChoiceGroup(){
|
||||
taxonomyOptions = [];
|
||||
if (this.state.TaxonomyTerms.length > 0) {
|
||||
this.state.TaxonomyTerms.forEach(trm => {
|
||||
taxonomyOptions.push(
|
||||
{
|
||||
key: trm.key,
|
||||
text: trm.name
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
taxonomyOptions = [];
|
||||
if(this.state.TaxonomyTerms.length > 0){
|
||||
this.state.TaxonomyTerms.forEach(trm=>{
|
||||
taxonomyOptions.push(
|
||||
{
|
||||
key:trm.key,
|
||||
text:trm.name
|
||||
}
|
||||
)
|
||||
public render(): React.ReactElement<ITermPickerProps> {
|
||||
|
||||
this.createTaxonomyChoiceGroup();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="ms-Grid">
|
||||
<div className="ms-Grid-row">
|
||||
<div className="ms-Grid-col ms-sm12 ms-lg12">
|
||||
<Label>{this.props.LabelText}</Label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ms-Grid-row">
|
||||
<div className="ms-Grid-col ms-sm8 ms-lg8">
|
||||
<TagPicker
|
||||
onResolveSuggestions={this._onFilterChanged.bind(this)}
|
||||
selectedItems={this.state.SelectedTerms}
|
||||
getTextFromItem={this._getTextFromItem.bind(this)}
|
||||
itemLimit={this.props.IsMultiValue ? 100 : 1}
|
||||
disabled={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="ms-Grid-col ms-sm4 ms-lg4">
|
||||
<ActionButton
|
||||
primary={true}
|
||||
onClick={this._onShowPanel.bind(this)}
|
||||
iconProps={{ iconName: 'MultiSelectMirrored' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Panel
|
||||
isOpen={this.state.showPanel}
|
||||
type={PanelType.smallFixedFar}
|
||||
onDismiss={this._handlePanelDismiss.bind(this)}
|
||||
headerText="Select terms"
|
||||
closeButtonAriaLabel="Close"
|
||||
>
|
||||
<div style={this.state.TaxonomyTerms.length == 0 ? { display: "none" } : {}}>
|
||||
<ChoiceGroup
|
||||
options={taxonomyOptions}
|
||||
onChange={this._onTaxonomyChoiceChange.bind(this)}
|
||||
required={true}
|
||||
selectedKey={this.state.SelectedChoiceGroupTerm.key}
|
||||
/>
|
||||
<br />
|
||||
<DefaultButton
|
||||
primary={true}
|
||||
text="Select"
|
||||
onClick={this._handleSelectedTaxonomyTerm.bind(this)}
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
<TextField label="Selected tags : " value={this.state.PickerText} multiline rows={4} onChanged={this._handlePickerTextChange.bind(this)} />
|
||||
<br />
|
||||
<br />
|
||||
<DefaultButton
|
||||
primary={true}
|
||||
text="Done"
|
||||
onClick={this._handlePickerDone.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
<div style={this.state.TaxonomyTerms.length == 0 ? {} : { display: "none" }}>
|
||||
<Label>No terms available....</Label>
|
||||
</div>
|
||||
</Panel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Component did mount - fetches the available terms from the term set.
|
||||
// If existing terms are passed to the component through the 'SelectedTerms' property, it resolves
|
||||
// the terms from the available set and adds them to the local state.
|
||||
public componentDidMount() {
|
||||
|
||||
this.GetTerms().then(resp => {
|
||||
let setSelectedTerms: ITaxonomyTerm[] = [];
|
||||
if (this.props.SelectedTerms.length > 0 && this.state.TaxonomyTerms.length > 0) {
|
||||
this.props.SelectedTerms.forEach(selectedTrm => {
|
||||
|
||||
// Checks if the selected terms that was send as a property is valid (ie present in the available terms from termstore)
|
||||
var checkForExistingValidTerm = this.state.TaxonomyTerms.filter(trm => {
|
||||
return (trm.name.toLowerCase() === selectedTrm.name.toLowerCase() && trm.key === selectedTrm.key)
|
||||
})
|
||||
// If valid, add it to the selected terms of LOCAL STATE
|
||||
if (checkForExistingValidTerm.length > 0) {
|
||||
setSelectedTerms.push(selectedTrm);
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
this.setState({
|
||||
SelectedTerms: setSelectedTerms,
|
||||
PickerText: this._getPickerTextString(setSelectedTerms)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<ITermPickerProps> {
|
||||
// Fetches the terms from the term set and sets the TaxonomyTerms (available terms) in the local state.
|
||||
public async GetTerms(): Promise<any> {
|
||||
|
||||
this.createTaxonomyChoiceGroup();
|
||||
try {
|
||||
const store: ITermStore = await taxonomy.termStores.getByName("<TERM_STORE_NAME>");
|
||||
const setWithData = await store.getTermSetById(this.props.TermSetId);
|
||||
const terms = await setWithData.terms.get();
|
||||
let taxonomyTerms: ITaxonomyTerm[] = new Array();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="ms-Grid">
|
||||
<div className="ms-Grid-row">
|
||||
<div className="ms-Grid-col ms-sm12 ms-lg12">
|
||||
<Label>{this.props.LabelText}</Label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ms-Grid-row">
|
||||
<div className="ms-Grid-col ms-sm8 ms-lg8">
|
||||
<TagPicker
|
||||
onResolveSuggestions={this._onFilterChanged.bind(this)}
|
||||
selectedItems={this.state.SelectedTerms}
|
||||
getTextFromItem={this._getTextFromItem.bind(this)}
|
||||
itemLimit={this.props.IsMultiValue ? 100 : 1}
|
||||
disabled={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="ms-Grid-col ms-sm4 ms-lg4">
|
||||
<ActionButton
|
||||
primary={true}
|
||||
onClick={this._onShowPanel.bind(this)}
|
||||
iconProps={{ iconName: 'MultiSelectMirrored' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Panel
|
||||
isOpen={this.state.showPanel}
|
||||
type={PanelType.smallFixedFar}
|
||||
onDismiss={this._handlePanelDismiss.bind(this)}
|
||||
headerText="Select terms"
|
||||
closeButtonAriaLabel="Close"
|
||||
>
|
||||
<div style={this.state.TaxonomyTerms.length == 0 ? {display:"none"} : {}}>
|
||||
<ChoiceGroup
|
||||
options={taxonomyOptions}
|
||||
onChange={this._onTaxonomyChoiceChange.bind(this)}
|
||||
required={true}
|
||||
selectedKey={this.state.SelectedChoiceGroupTerm.key}
|
||||
/>
|
||||
<br/>
|
||||
<DefaultButton
|
||||
primary={true}
|
||||
text="Select"
|
||||
onClick={this._handleSelectedTaxonomyTerm.bind(this)}
|
||||
/>
|
||||
<br/>
|
||||
<br/>
|
||||
<TextField label="Selected tags : " value={this.state.PickerText} multiline rows={4} onChanged={this._handlePickerTextChange.bind(this)}/>
|
||||
<br/>
|
||||
<br/>
|
||||
<DefaultButton
|
||||
primary={true}
|
||||
text="Done"
|
||||
onClick={this._handlePickerDone.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
<div style={this.state.TaxonomyTerms.length == 0 ? {} : {display:"none"}}>
|
||||
<Label>No terms available....</Label>
|
||||
</div>
|
||||
</Panel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (terms.length > 0) {
|
||||
terms.forEach(trm => {
|
||||
taxonomyTerms.push({
|
||||
name: trm.Name,
|
||||
key: trm.Id.split('(')[1].replace(')/', '')
|
||||
})
|
||||
});
|
||||
|
||||
// Component did mount - fetches the available terms from the term set.
|
||||
// If existing terms are passed to the component through the 'SelectedTerms' property, it resolves
|
||||
// the terms from the available set and adds them to the local state.
|
||||
public componentDidMount(){
|
||||
|
||||
this.GetTerms().then(resp => {
|
||||
let setSelectedTerms : ITaxonomyTerm[] = [];
|
||||
if(this.props.SelectedTerms.length > 0 && this.state.TaxonomyTerms.length > 0){
|
||||
this.props.SelectedTerms.forEach(selectedTrm => {
|
||||
|
||||
// Checks if the selected terms that was send as a property is valid (ie present in the available terms from termstore)
|
||||
var checkForExistingValidTerm = this.state.TaxonomyTerms.filter(trm => {
|
||||
return (trm.name.toLowerCase() === selectedTrm.name.toLowerCase() && trm.key === selectedTrm.key)
|
||||
this.setState({
|
||||
TaxonomyTerms: taxonomyTerms
|
||||
})
|
||||
// If valid, add it to the selected terms of LOCAL STATE
|
||||
if(checkForExistingValidTerm.length > 0){
|
||||
setSelectedTerms.push(selectedTrm);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
catch (error) {
|
||||
console.log("An error occurred while fetching the terms from the term store....");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public _onShowPanel() {
|
||||
this.setState({ showPanel: true })
|
||||
}
|
||||
|
||||
|
||||
// This method is called when the panel is dismissed. So on the "DONE" button click,
|
||||
// this method is automatically called. Also on the 'X' button click.
|
||||
// On DONE button click in the picker panel, we only set the showPanel value to false.
|
||||
private _handlePanelDismiss() {
|
||||
|
||||
// Push the data into component property.
|
||||
if (this.state.SelectedTerms.length > 0) {
|
||||
this.props.SelectedTerms.length = 0;
|
||||
this.state.SelectedTerms.forEach(trm => {
|
||||
this.props.SelectedTerms.push(trm);
|
||||
})
|
||||
}
|
||||
|
||||
// Reset the picker text in case it was modified by the user manually.
|
||||
// The next time it is opened, it will show the text of the selected terms.
|
||||
this.setState({
|
||||
SelectedTerms:setSelectedTerms,
|
||||
PickerText:this._getPickerTextString(setSelectedTerms)
|
||||
PickerText: this._getPickerTextString(this.state.SelectedTerms),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Fetches the terms from the term set and sets the TaxonomyTerms (available terms) in the local state.
|
||||
public async GetTerms():Promise<any>{
|
||||
private _onFilterChanged = (filterText: string, tagList: { key: string; name: string }[]): { key: string; name: string }[] => {
|
||||
return filterText ? this.state.TaxonomyTerms.filter(tag => tag.name.toLowerCase().indexOf(filterText.toLowerCase()) === 0) : [];
|
||||
};
|
||||
|
||||
try{
|
||||
const store: ITermStore = await taxonomy.termStores.getByName("<TERM_STORE_NAME>");
|
||||
const setWithData = await store.getTermSetById(this.props.TermSetId);
|
||||
const terms = await setWithData.terms.get();
|
||||
let taxonomyTerms:ITaxonomyTerm[] = new Array();
|
||||
public _getTextFromItem(item: any): any {
|
||||
return item.Text;
|
||||
}
|
||||
|
||||
if(terms.length > 0){
|
||||
terms.forEach(trm => {
|
||||
taxonomyTerms.push({
|
||||
name:trm.Name,
|
||||
key:trm.Id.split('(')[1].replace(')/','')
|
||||
})
|
||||
});
|
||||
// Sets the selected term on change of term in the picker panel.
|
||||
private _onTaxonomyChoiceChange = (ev: React.FormEvent<HTMLInputElement>, option: any): void => {
|
||||
|
||||
this.setState({
|
||||
SelectedChoiceGroupTerm: { name: option.text, key: option.key }
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
private _handleSelectedTaxonomyTerm = (): void => {
|
||||
|
||||
let selectedTerms: ITaxonomyTerm[] = this.state.SelectedTerms;
|
||||
|
||||
// Handles the state and picker textbox if the field is multi-select
|
||||
if (this.props.IsMultiValue) {
|
||||
selectedTerms = this.state.SelectedTerms;
|
||||
|
||||
// Check if the term is already selected
|
||||
let existingTerm = selectedTerms.filter(trm => {
|
||||
return trm.name.toLowerCase() === this.state.SelectedChoiceGroupTerm.name.toLowerCase()
|
||||
})
|
||||
if (existingTerm.length == 0) {
|
||||
selectedTerms.push(
|
||||
{
|
||||
name: this.state.SelectedChoiceGroupTerm.name,
|
||||
key: this.state.SelectedChoiceGroupTerm.key
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
this.setState({
|
||||
TaxonomyTerms:taxonomyTerms
|
||||
SelectedTerms: selectedTerms,
|
||||
PickerText: this._getPickerTextString(this.state.SelectedTerms)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
catch(error){
|
||||
console.log("An error occurred while fetching the terms from the term store....");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public _onShowPanel(){
|
||||
this.setState({ showPanel:true })
|
||||
}
|
||||
|
||||
|
||||
// This method is called when the panel is dismissed. So on the "DONE" button click,
|
||||
// this method is automatically called. Also on the 'X' button click.
|
||||
// On DONE button click in the picker panel, we only set the showPanel value to false.
|
||||
private _handlePanelDismiss(){
|
||||
|
||||
// Push the data into component property.
|
||||
if(this.state.SelectedTerms.length > 0){
|
||||
this.props.SelectedTerms.length = 0;
|
||||
this.state.SelectedTerms.forEach(trm => {
|
||||
this.props.SelectedTerms.push(trm);
|
||||
})
|
||||
}
|
||||
|
||||
// Reset the picker text in case it was modified by the user manually.
|
||||
// The next time it is opened, it will show the text of the selected terms.
|
||||
this.setState({
|
||||
PickerText:this._getPickerTextString(this.state.SelectedTerms),
|
||||
})
|
||||
}
|
||||
|
||||
private _onFilterChanged = (filterText: string,tagList: {key: string; name: string }[]): {key: string; name: string }[] => {
|
||||
return filterText ? this.state.TaxonomyTerms.filter(tag => tag.name.toLowerCase().indexOf(filterText.toLowerCase()) === 0) : [];
|
||||
};
|
||||
|
||||
public _getTextFromItem(item:any):any{
|
||||
return item.Text;
|
||||
}
|
||||
|
||||
// Sets the selected term on change of term in the picker panel.
|
||||
private _onTaxonomyChoiceChange = (ev: React.FormEvent<HTMLInputElement>, option: any): void => {
|
||||
|
||||
this.setState({
|
||||
SelectedChoiceGroupTerm:{ name:option.text, key:option.key }
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
private _handleSelectedTaxonomyTerm = (): void => {
|
||||
|
||||
let selectedTerms:ITaxonomyTerm[] = this.state.SelectedTerms;
|
||||
|
||||
// Handles the state and picker textbox if the field is multi-select
|
||||
if(this.props.IsMultiValue){
|
||||
selectedTerms = this.state.SelectedTerms;
|
||||
|
||||
// Check if the term is already selected
|
||||
let existingTerm = selectedTerms.filter(trm => {
|
||||
return trm.name.toLowerCase() === this.state.SelectedChoiceGroupTerm.name.toLowerCase()
|
||||
})
|
||||
if(existingTerm.length == 0){
|
||||
// Handles the state and picker textbox if the field is single-select
|
||||
else {
|
||||
selectedTerms = [];
|
||||
selectedTerms.push(
|
||||
{
|
||||
name:this.state.SelectedChoiceGroupTerm.name,
|
||||
key:this.state.SelectedChoiceGroupTerm.key
|
||||
name: this.state.SelectedChoiceGroupTerm.name,
|
||||
key: this.state.SelectedChoiceGroupTerm.key
|
||||
}
|
||||
);
|
||||
|
||||
this.setState({
|
||||
SelectedTerms: selectedTerms,
|
||||
PickerText: selectedTerms[0].name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the close panel on "Done" button click
|
||||
// When the showPanel is set to false, the onDismiss method will be automatically executed.
|
||||
private _handlePickerDone = (): void => {
|
||||
this.setState({
|
||||
showPanel: false
|
||||
})
|
||||
}
|
||||
|
||||
// Generates the ';' separated string for selected terms
|
||||
private _getPickerTextString(selectedTerms: any[]): string {
|
||||
|
||||
let pickerTextString: string = "";
|
||||
|
||||
if (selectedTerms.length > 0) {
|
||||
pickerTextString = selectedTerms.map(trm => {
|
||||
return trm.name
|
||||
}).join('; ')
|
||||
}
|
||||
|
||||
this.setState({
|
||||
SelectedTerms:selectedTerms,
|
||||
PickerText:this._getPickerTextString(this.state.SelectedTerms)
|
||||
})
|
||||
}
|
||||
// Handles the state and picker textbox if the field is single-select
|
||||
else{
|
||||
selectedTerms = [];
|
||||
selectedTerms.push(
|
||||
{
|
||||
name:this.state.SelectedChoiceGroupTerm.name,
|
||||
key:this.state.SelectedChoiceGroupTerm.key
|
||||
}
|
||||
);
|
||||
|
||||
this.setState({
|
||||
SelectedTerms:selectedTerms,
|
||||
PickerText:selectedTerms[0].name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the close panel on "Done" button click
|
||||
// When the showPanel is set to false, the onDismiss method will be automatically executed.
|
||||
private _handlePickerDone = (): void => {
|
||||
this.setState({
|
||||
showPanel:false
|
||||
})
|
||||
}
|
||||
|
||||
// Generates the ';' separated string for selected terms
|
||||
private _getPickerTextString(selectedTerms:any[]):string{
|
||||
|
||||
let pickerTextString:string = "";
|
||||
|
||||
if(selectedTerms.length > 0){
|
||||
pickerTextString = selectedTerms.map(trm => {
|
||||
return trm.name
|
||||
}).join('; ')
|
||||
return pickerTextString;
|
||||
}
|
||||
|
||||
return pickerTextString;
|
||||
}
|
||||
// Handles manual change to the picker control.
|
||||
private _handlePickerTextChange(value) {
|
||||
|
||||
// Handles manual change to the picker control.
|
||||
private _handlePickerTextChange(value){
|
||||
if (value !== "") {
|
||||
|
||||
if(value !== ""){
|
||||
let selectedTerms: any[] = [];
|
||||
let pickerSelectedTerms: any[] = value.split(';');
|
||||
|
||||
let selectedTerms:any[] = [];
|
||||
let pickerSelectedTerms:any[] = value.split(';');
|
||||
pickerSelectedTerms.forEach((pickerSelectedTerm: string) => {
|
||||
pickerSelectedTerm = pickerSelectedTerm.trim();
|
||||
let filteredTerm = this.state.TaxonomyTerms.filter((trm) => {
|
||||
return trm.name.toLowerCase() == pickerSelectedTerm.toLowerCase()
|
||||
})
|
||||
|
||||
pickerSelectedTerms.forEach((pickerSelectedTerm:string) => {
|
||||
pickerSelectedTerm = pickerSelectedTerm.trim();
|
||||
let filteredTerm = this.state.TaxonomyTerms.filter((trm)=>{
|
||||
return trm.name.toLowerCase() == pickerSelectedTerm.toLowerCase()
|
||||
// Push the term only if it resolved.
|
||||
if (filteredTerm.length > 0) {
|
||||
selectedTerms.push(filteredTerm[0]);
|
||||
}
|
||||
})
|
||||
|
||||
// Push the term only if it resolved.
|
||||
if(filteredTerm.length > 0){
|
||||
selectedTerms.push(filteredTerm[0]);
|
||||
}
|
||||
})
|
||||
this.setState({
|
||||
PickerText: value,
|
||||
SelectedTerms: selectedTerms
|
||||
})
|
||||
}
|
||||
else {
|
||||
this.setState({
|
||||
PickerText: value,
|
||||
SelectedTerms: []
|
||||
})
|
||||
}
|
||||
|
||||
this.setState({
|
||||
PickerText:value,
|
||||
SelectedTerms:selectedTerms
|
||||
})
|
||||
}
|
||||
else{
|
||||
this.setState({
|
||||
PickerText:value,
|
||||
SelectedTerms:[]
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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,11 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.5.1",
|
||||
"libraryName": "react-visio",
|
||||
"libraryId": "0ad401f1-b158-4626-8e71-cf27b1f4848d",
|
||||
"packageManager": "npm",
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
# React Visio Embed
|
||||
|
||||
## Summary
|
||||
|
||||
This sample shows how the [Visio JavaScript APIs](https://dev.office.com/reference/add-ins/visio/visio-javascript-reference-overview) can be used within a web part. For sample purposes, this web part will display the name and the hyperlinks of a Visio shape when the user selects it.
|
||||
|
||||
To test the web part, upload the sample file provided to a SharePoint document library. Then open the file on the Visio web client and copy the Url from the browser. Add that url on the web part properties field and the EmbeddedSession will start and display the diagram.
|
||||
|
||||
![Demo](./assets/Preview.PNG)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
|
||||
![drop](https://img.shields.io/badge/drop-1.5.1-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||
* [Visio JavaScript APIs](https://dev.office.com/reference/add-ins/visio/visio-javascript-reference-overview)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* Office 365 subscription with SharePoint Online licence
|
||||
* SharePoint Framework [development environment](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment) already set up.
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-visio|Joel Rodrigues
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|August 23, 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 illustrates the following concepts on top of the SharePoint Framework:
|
||||
|
||||
* Using the Visio JavaScript APIs to embed a diagram on a page
|
||||
* Using the Visio JavaScript APIs to interact with the Visio diagram and data available
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"visio-sample-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/visioSample/VisioSampleWebPart.js",
|
||||
"manifest": "./src/webparts/visioSample/VisioSampleWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {
|
||||
"officejs": {
|
||||
"path": "https://appsforoffice.microsoft.com/embedded/1.0/visio-web-embedded.js",
|
||||
"globalName": "officejs"
|
||||
}
|
||||
},
|
||||
"localizedResources": {
|
||||
"VisioSampleWebPartStrings": "lib/webparts/visioSample/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-visio",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-visio-client-side-solution",
|
||||
"id": "0ad401f1-b158-4626-8e71-cf27b1f4848d",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-visio.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.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://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
build.initialize(gulp);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"name": "react-visio",
|
||||
"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.5.1",
|
||||
"@microsoft/sp-lodash-subset": "1.5.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.5.1",
|
||||
"@microsoft/sp-webpart-base": "1.5.1",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/office-js": "0.0.101",
|
||||
"@types/react": "15.6.6",
|
||||
"@types/react-dom": "15.5.6",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"react": "15.6.2",
|
||||
"react-dom": "15.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.5.1",
|
||||
"@microsoft/sp-module-interfaces": "1.5.1",
|
||||
"@microsoft/sp-webpart-workbench": "1.5.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2",
|
||||
"gulp": "~3.9.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,350 @@
|
|||
|
||||
import { find } from '@microsoft/sp-lodash-subset';
|
||||
import { IWebPartContext } from '@microsoft/sp-webpart-base';
|
||||
|
||||
export class VisioService {
|
||||
|
||||
private _webPartContext: IWebPartContext;
|
||||
|
||||
private _url = "";
|
||||
/**
|
||||
* gets the url of the Visio document to embed
|
||||
* @returns a string with the document url
|
||||
*/
|
||||
get url(): string {
|
||||
return this._url;
|
||||
}
|
||||
/**
|
||||
* sets the url of the Visio document to embed
|
||||
* @param url the url of the document
|
||||
*/
|
||||
set url(url: string) {
|
||||
// apis are enabled for EmbedView action only
|
||||
url = url.replace("action=view", "action=embedview");
|
||||
url = url.replace("action=interactivepreview", "action=embedview");
|
||||
url = url.replace("action=default", "action=embedview");
|
||||
url = url.replace("action=edit", "action=embedview");
|
||||
|
||||
this._url = url;
|
||||
}
|
||||
|
||||
private _session: OfficeExtension.EmbeddedSession = null;
|
||||
private _shapes: Visio.Shape[] = [];
|
||||
private _selectedShape: Visio.Shape;
|
||||
|
||||
private _documentLoadComplete = false;
|
||||
private _pageLoadComplete = false;
|
||||
/**
|
||||
* gets a pre-loaded collection of relevant shapes from the diagram
|
||||
*/
|
||||
public get shapes(): Visio.Shape[] {
|
||||
return this._shapes;
|
||||
}
|
||||
|
||||
// delegate functions passed from the react component
|
||||
public onSelectionChanged: (selectedShape: Visio.Shape) => void;
|
||||
public getAllShapes: (shapes: Visio.Shape[]) => void;
|
||||
|
||||
/**
|
||||
* class constructor
|
||||
* @param webPartContext the context of the web part
|
||||
*/
|
||||
constructor(webPartContext: IWebPartContext) {
|
||||
// set web part context
|
||||
this._webPartContext = webPartContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* initialize session by embedding the Visio diagram on the page
|
||||
* @returns returns a promise
|
||||
*/
|
||||
private async _init(): Promise<any> {
|
||||
// initialize communication between the developer frame and the Visio Online frame
|
||||
this._session = new OfficeExtension.EmbeddedSession(
|
||||
this._url, {
|
||||
id: "embed-iframe",
|
||||
container: document.getElementById("iframeHost"),
|
||||
width: "100%",
|
||||
height: "600px"
|
||||
}
|
||||
);
|
||||
await this._session.init();
|
||||
console.log("Session successfully initialized");
|
||||
}
|
||||
|
||||
/**
|
||||
* function to add custom event handlers
|
||||
* @returns returns a promise
|
||||
*/
|
||||
private _addCustomEventHandlers = async (): Promise<any> => {
|
||||
|
||||
try {
|
||||
await Visio.run(this._session, async (context: Visio.RequestContext) => {
|
||||
var doc: Visio.Document = context.document;
|
||||
|
||||
// on document load complete
|
||||
const onDocumentLoadCompleteEventResult: OfficeExtension.EventHandlerResult<Visio.DocumentLoadCompleteEventArgs> =
|
||||
doc.onDocumentLoadComplete.add(
|
||||
this._onDocumentLoadComplete
|
||||
);
|
||||
// on page load complete
|
||||
const onPageLoadCompleteEventResult: OfficeExtension.EventHandlerResult<Visio.PageLoadCompleteEventArgs> =
|
||||
doc.onPageLoadComplete.add(
|
||||
this._onPageLoadComplete
|
||||
);
|
||||
// on selection changed
|
||||
const onSelectionChangedEventResult: OfficeExtension.EventHandlerResult<Visio.SelectionChangedEventArgs> =
|
||||
doc.onSelectionChanged.add(
|
||||
this._onSelectionChanged
|
||||
);
|
||||
|
||||
await context.sync();
|
||||
console.log("Document Load Complete handler attached");
|
||||
});
|
||||
} catch (error) {
|
||||
this.logError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* method executed after a on document load complete event is triggered
|
||||
* @param args event arguments
|
||||
* @returns returns a promise
|
||||
*/
|
||||
private _onDocumentLoadComplete = async (args: Visio.DocumentLoadCompleteEventArgs): Promise<void> => {
|
||||
|
||||
// only execute if not executed yet
|
||||
if (!this._documentLoadComplete) {
|
||||
|
||||
try {
|
||||
console.log("Document Loaded Event: " + JSON.stringify(args));
|
||||
|
||||
// set internal flag to prevent event from running again if triggered twice
|
||||
this._documentLoadComplete = true;
|
||||
|
||||
await Visio.run(this._session, async (context: Visio.RequestContext) => {
|
||||
var doc: Visio.Document = context.document;
|
||||
// disable Hyperlinks on embed diagram
|
||||
doc.view.disableHyperlinks = true;
|
||||
// hide diagram boundary on embed diagram
|
||||
doc.view.hideDiagramBoundary = true;
|
||||
|
||||
await context.sync();
|
||||
});
|
||||
|
||||
|
||||
} catch (error) {
|
||||
this.logError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* method executed after a on page load event is triggered
|
||||
* @param args event arguments
|
||||
* @returns returns a promise
|
||||
*/
|
||||
private _onPageLoadComplete = async (args: Visio.PageLoadCompleteEventArgs): Promise<void> => {
|
||||
|
||||
// only execute if not executed yet
|
||||
if (!this._pageLoadComplete) {
|
||||
|
||||
try {
|
||||
console.log("Page Loaded Event: " + JSON.stringify(args));
|
||||
|
||||
// set internal flag to prevent event from running again if triggered twice
|
||||
this._pageLoadComplete = true;
|
||||
|
||||
// get all relevant shapes and populate the class variable
|
||||
this._shapes = await this._getAllShapes();
|
||||
|
||||
// call delegate function from the react component
|
||||
this.getAllShapes(this._shapes);
|
||||
|
||||
} catch (error) {
|
||||
this.logError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* method executed after a on selection change event is triggered
|
||||
* @param args event arguments
|
||||
* @returns returns a promise
|
||||
*/
|
||||
private _onSelectionChanged = async (args: Visio.SelectionChangedEventArgs): Promise<void> => {
|
||||
|
||||
try {
|
||||
console.log("Selection Changed Event " + JSON.stringify(args));
|
||||
|
||||
if (args.shapeNames.length > 0 && this._shapes && this._shapes.length > 0) {
|
||||
|
||||
// get name of selected item
|
||||
const selectedShapeText: string = args.shapeNames[0];
|
||||
|
||||
// find selected shape on the list of pre-loaded shapes
|
||||
this._selectedShape = find(this._shapes,
|
||||
s => s.name === selectedShapeText
|
||||
);
|
||||
|
||||
// call delegate function from the react component
|
||||
this.onSelectionChanged(this._selectedShape);
|
||||
|
||||
} else {
|
||||
// shape was deselected
|
||||
this._selectedShape = null;
|
||||
}
|
||||
} catch (error) {
|
||||
this.logError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* select a shape on the visio diagram
|
||||
* @param name the name of the shape to select
|
||||
*/
|
||||
public selectShape = async (name: string): Promise<void> => {
|
||||
|
||||
try {
|
||||
|
||||
// find the correct shape from the pre-loaded list of shapes
|
||||
// check the ShapeData item with the 'Name' key
|
||||
const shape: Visio.Shape = find(this._shapes,
|
||||
s => (find(s.shapeDataItems.items, i => i.label === "Name").value === name)
|
||||
);
|
||||
|
||||
// only select shape if not the currently selected one
|
||||
if (this._selectedShape === null
|
||||
|| this._selectedShape === undefined
|
||||
|| (this._selectedShape && this._selectedShape.name !== shape.name)) {
|
||||
|
||||
await Visio.run(this._session, async (context: Visio.RequestContext) => {
|
||||
const page: Visio.Page = context.document.getActivePage();
|
||||
const shapesCollection: Visio.ShapeCollection = page.shapes;
|
||||
shapesCollection.load();
|
||||
await context.sync();
|
||||
|
||||
const diagramShape: Visio.Shape = shapesCollection.getItem(shape.name);
|
||||
// select shape on diagram
|
||||
diagramShape.select = true;
|
||||
|
||||
await context.sync();
|
||||
console.log(`Selected shape '${shape.name}' in diagram`);
|
||||
this._selectedShape = shape;
|
||||
});
|
||||
} else {
|
||||
console.log(`Shape '${shape.name}' is already selected in diagram`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.logError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get all shapes from page
|
||||
* @returns returns a promise
|
||||
*/
|
||||
private _getAllShapes = async (): Promise<Visio.Shape[]> => {
|
||||
|
||||
console.log("Getting all shapes");
|
||||
|
||||
try {
|
||||
let shapes: Visio.Shape[];
|
||||
|
||||
await Visio.run(this._session, async (context: Visio.RequestContext) => {
|
||||
const page: Visio.Page = context.document.getActivePage();
|
||||
const shapesCollection: Visio.ShapeCollection = page.shapes;
|
||||
shapesCollection.load();
|
||||
await context.sync();
|
||||
|
||||
// load all required properties for each shape
|
||||
for (let i: number = 0; i < shapesCollection.items.length; i++) {
|
||||
shapesCollection.items[i].shapeDataItems.load();
|
||||
shapesCollection.items[i].hyperlinks.load();
|
||||
}
|
||||
await context.sync();
|
||||
|
||||
shapes = shapesCollection.items;
|
||||
|
||||
return shapes;
|
||||
});
|
||||
|
||||
return shapes;
|
||||
} catch (error) {
|
||||
this.logError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* initializes the embed session and attaches event handlers
|
||||
* this is the function that should be called to start the session
|
||||
* @param docUrl embed url of the document
|
||||
* @returns returns a promise
|
||||
*/
|
||||
public load = async (docUrl: string): Promise<void> => {
|
||||
console.log("Start loading Visio data");
|
||||
|
||||
try {
|
||||
|
||||
// sets the url, modifying it if required - uses set method to re-use logic
|
||||
this.url = docUrl;
|
||||
|
||||
// init
|
||||
await this._init();
|
||||
|
||||
// add custom onDocumentLoadComplete event handler
|
||||
await this._addCustomEventHandlers();
|
||||
|
||||
// trigger document and page loaded event handlers after 3 seconds in case Visio fails to trigger them
|
||||
// this is randomly happening on chrome, but seems to always fail on IE...
|
||||
setTimeout(() => {
|
||||
this._onDocumentLoadComplete(null);
|
||||
this._onPageLoadComplete(null);
|
||||
}, 3000);
|
||||
|
||||
} catch (error) {
|
||||
this.logError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* generate embed url for a document
|
||||
* @param docId the list item ID of the target document
|
||||
*/
|
||||
private generateEmbedUrl = async (itemProperties: any): Promise<string> => {
|
||||
let url: string = "";
|
||||
|
||||
try {
|
||||
// check if data was returned
|
||||
if (itemProperties) {
|
||||
// generate required URL
|
||||
const siteUrl: string = this._webPartContext.pageContext.site.absoluteUrl;
|
||||
const sourceDoc: string = encodeURIComponent(itemProperties.File.ContentTag.split(",")[0]);
|
||||
const fileName: string = encodeURIComponent(itemProperties.File.Name);
|
||||
|
||||
if (siteUrl && sourceDoc && fileName) {
|
||||
url = `${siteUrl}/_layouts/15/Doc.aspx?sourcedoc=${sourceDoc}&file=${fileName}&action=default`;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* log error
|
||||
* @param error error object
|
||||
*/
|
||||
private logError = (error: any): void => {
|
||||
console.error("Error");
|
||||
if (error instanceof OfficeExtension.Error) {
|
||||
console.error("Debug info: ", JSON.stringify(error.debugInfo));
|
||||
} else {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from "./VisioService";
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue