Manual merge - step 1

This commit is contained in:
VesaJuvonen 2018-09-10 13:33:25 +03:00
commit ffdbbfd3e5
117 changed files with 24572 additions and 4490 deletions

View File

@ -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

32
samples/react-accordion/.gitignore vendored Normal file
View File

@ -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

View File

@ -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"
}
}

View File

@ -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

View File

@ -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"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "temp/deploy"
}

View File

@ -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 -->"
}

View File

@ -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"
}
}

View File

@ -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/"
}
}

View File

@ -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

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

7
samples/react-accordion/gulpfile.js vendored Normal file
View File

@ -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);

View File

@ -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"
}
}

View File

@ -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
}
}
]
}

View File

@ -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
}),
]
}
]
}
]
};
}
}

View File

@ -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;
}

View File

@ -0,0 +1,9 @@
import IAccordionListItem from '../models/IAccordionListItem';
export interface IReactAccordionState {
status: string;
items: IAccordionListItem[];
listItems: IAccordionListItem[];
isLoading: boolean;
loaderMessage: string;
}

View File

@ -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;
}
}
}

View File

@ -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>
);
}
}

View File

@ -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;
}

View File

@ -0,0 +1,8 @@
define([], function () {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"ListNameLabel": "List Name",
"MaxItemsPerPageLabel": "Max number of items per page"
}
});

View File

@ -0,0 +1,11 @@
declare interface IReactAccordionWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
ListNameLabel: string;
MaxItemsPerPageLabel: string
}
declare module 'ReactAccordionWebPartStrings' {
const strings: IReactAccordionWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,6 @@
interface IAccordionListItem {
Id: number;
Title: string;
Description: string;
}
export default IAccordionListItem;

View File

@ -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"
]
}
}

View File

@ -0,0 +1,3 @@
{
"rulesDirectory": "./config"
}

View File

@ -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.**

View File

@ -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",

View File

@ -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",

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,5 @@
{
"recommendations": [
"msjsdiag.debugger-for-chrome"
]
}

View File

@ -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"
]
}
]
}

View File

@ -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"
}

View File

@ -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"
}
}

View File

@ -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

View File

@ -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": {}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "temp/deploy"
}

View File

@ -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 -->"
}

View File

@ -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"
}
}

View File

@ -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/"
}
}

View File

@ -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
}
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -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

View File

@ -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"
}
}

View File

@ -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
}
}]
}

View File

@ -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
})
]
}]
}]
};
}
}

View File

@ -0,0 +1,7 @@
export interface IFacebookPageWebPartProps {
company: string;
height: string;
smallHeader?: boolean;
hideCover?: boolean;
showFacepile?: boolean;
}

View File

@ -0,0 +1,11 @@
.facebookPageContainer {
display: flex;
justify-content: center;
> iframe {
margin: 0 auto;
max-width: 500px;
}
}

View File

@ -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`;
}
}

View File

@ -0,0 +1,7 @@
export interface IFacebookPageProps {
company: string;
height: number;
smallHeader?: boolean;
hideCover?: boolean;
showFacepile?: boolean;
}

View File

@ -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"
};
});

View File

@ -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;
}

View File

@ -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"
]
}
}

View File

@ -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
}
}

View File

@ -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
},

View File

@ -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);
build.initialize(gulp);

View File

@ -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
}
}
},

View File

@ -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",

View File

@ -35,8 +35,11 @@ export default class ScriptEditorWebPart extends BaseClientSideWebPart<IScriptEd
this.domElement.innerHTML = this.properties.script;
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" });
// Dynamically load the editor pane to reduce overall bundle size
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: [

View File

@ -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"
}
}

View File

@ -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.**

View File

@ -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": {

View File

@ -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"
}

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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 -->"
}

View File

@ -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"
}
}

View File

View File

@ -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",

View File

@ -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);
}
}

View File

@ -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"
]
}

View File

@ -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
}
}

View File

@ -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.**

View File

@ -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",

View File

@ -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,

View File

@ -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);
}
}
}

View File

@ -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({
SelectedTerms:selectedTerms,
PickerText:this._getPickerTextString(this.state.SelectedTerms)
showPanel: false
})
}
// 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
})
}
}
// Generates the ';' separated string for selected terms
private _getPickerTextString(selectedTerms: any[]): string {
// 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
})
}
let pickerTextString: string = "";
// Generates the ';' separated string for selected terms
private _getPickerTextString(selectedTerms:any[]):string{
if (selectedTerms.length > 0) {
pickerTextString = selectedTerms.map(trm => {
return trm.name
}).join('; ')
}
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:[]
})
}
}
}

View File

@ -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

32
samples/react-visio/.gitignore vendored Normal file
View File

@ -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

View File

@ -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"
}
}

View File

@ -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

View File

@ -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"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "temp/deploy"
}

View File

@ -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 -->"
}

View File

@ -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"
}
}

View File

@ -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/"
}
}

View File

@ -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
}
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

7
samples/react-visio/gulpfile.js vendored Normal file
View File

@ -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);

View File

@ -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"
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1 @@
export * from "./VisioService";

Some files were not shown because too many files have changed in this diff Show More