New Sample Web part to edit Application Customizer
New sample webpart named react-Edit-ApplicationCustomizer to help users to edit the title,json and description for their application customizer Co-Authored-By: Siddharth Vaghasia <siddh.vaghasia@gmail.com>
This commit is contained in:
parent
83ddd64e68
commit
4ebc9806d3
|
@ -0,0 +1,75 @@
|
|||
# react-edit-application-customizers
|
||||
|
||||
## Summary
|
||||
|
||||
This web part will allow users to view/update application customizers properties across any web where the current user has access to. This web part can be helpful when we require to update the properties for application customizer without using any powershell script or cli tool.
|
||||
|
||||
![Web part in action](assets/react-all-applicationcustomizers.gif?raw=true "Webpart in action")
|
||||
|
||||
## Idea behind this web part
|
||||
|
||||
- SPFx Application customizer can be used to add scripts, and add custom html to well known placeholder(header and footer)
|
||||
- We can use properties to pass data to Application customizers to make solution customizable.
|
||||
- To update properties of application customizer there is no UI based solution.
|
||||
- To update the title, details and other information of application customizer we use either powershell script or cli tool.
|
||||
- This webpart can be used at a central location where all the users have access and if they require to update title, description and properties.
|
||||
|
||||
## Features
|
||||
|
||||
- Webpart to view/update Application Customizers registered for a selected web
|
||||
- Provides two different UI Accordion or List based(configurable)
|
||||
- Provides a dropdown to select the web from where we would require to fetch application customizers
|
||||
- Allows to update application customizer properties which makes it easy to make reuseable application customizers
|
||||
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
|
||||
![version](https://img.shields.io/badge/version-1.11-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
- [SharePoint Framework](https://aka.ms/spfx)
|
||||
- [Microsoft 365 tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
|
||||
|
||||
> Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram)
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-edit-applicationcustomizers | [Kunj Sangani](https://www.linkedin.com/in/kunj-sangani/) and [Siddharth Vaghasia](https://www.linkedin.com/in/siddharthvaghasia/)
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|October 16, 2020|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
|
||||
- Ensure that you are at the solution folder
|
||||
- in the command-line run:
|
||||
- **npm install**
|
||||
- **gulp serve**
|
||||
|
||||
|
||||
For any issue or help, Buzz us on twitter:([sanganikunj](https://twitter.com/sanganikunj)) or ([siddh_me](https://twitter.com/siddh_me/))
|
||||
|
||||
> Sharing is caring!
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-edit-applicationcustomizers" />
|
||||
|
||||
## References
|
||||
|
||||
- [Getting started with SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
|
||||
- [Building for Microsoft teams](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-overview)
|
||||
- [Use Microsoft Graph in your solution](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis)
|
||||
- [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview)
|
||||
- [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development
|
Binary file not shown.
After Width: | Height: | Size: 2.3 MiB |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"application-customizers-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/applicationCustomizers/ApplicationCustomizersWebPart.js",
|
||||
"manifest": "./src/webparts/applicationCustomizers/ApplicationCustomizersWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"ApplicationCustomizersWebPartStrings": "lib/webparts/applicationCustomizers/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-all-application-customizers",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-all-application-customizers-client-side-solution",
|
||||
"id": "f560c809-b5a8-4320-abef-224f27d2d0f0",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"isDomainIsolated": false,
|
||||
"developer": {
|
||||
"name": "",
|
||||
"websiteUrl": "",
|
||||
"privacyUrl": "",
|
||||
"termsOfUseUrl": "",
|
||||
"mpnId": ""
|
||||
}
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-all-application-customizers.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const 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(require('gulp'));
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"name": "react-all-application-customizers",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@microsoft/sp-core-library": "1.11.0",
|
||||
"@microsoft/sp-lodash-subset": "1.11.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.11.0",
|
||||
"@microsoft/sp-property-pane": "1.11.0",
|
||||
"@microsoft/sp-webpart-base": "1.11.0",
|
||||
"@pnp/sp": "^2.0.10",
|
||||
"ace-builds": "^1.4.12",
|
||||
"office-ui-fabric-react": "6.214.0",
|
||||
"react": "16.8.5",
|
||||
"react-ace": "^9.1.3",
|
||||
"react-dom": "16.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "16.8.8",
|
||||
"@types/react-dom": "16.8.3",
|
||||
"@microsoft/sp-build-web": "1.11.0",
|
||||
"@microsoft/sp-tslint-rules": "1.11.0",
|
||||
"@microsoft/sp-module-interfaces": "1.11.0",
|
||||
"@microsoft/sp-webpart-workbench": "1.11.0",
|
||||
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
|
||||
"gulp": "~3.9.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"@types/es6-promise": "0.0.33"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "05567ead-69c5-4c36-958c-613a21cce18c",
|
||||
"alias": "ApplicationCustomizersWebPart",
|
||||
"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,
|
||||
"supportedHosts": [
|
||||
"SharePointWebPart"
|
||||
],
|
||||
"preconfiguredEntries": [
|
||||
{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": {
|
||||
"default": "Other"
|
||||
},
|
||||
"title": {
|
||||
"default": "ApplicationCustomizers"
|
||||
},
|
||||
"description": {
|
||||
"default": "ApplicationCustomizers description"
|
||||
},
|
||||
"officeFabricIconFontName": "CustomizeToolbar",
|
||||
"properties": {
|
||||
"description": "ApplicationCustomizers",
|
||||
"designType": "List"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField,
|
||||
PropertyPaneChoiceGroup
|
||||
} from '@microsoft/sp-property-pane';
|
||||
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||
|
||||
import * as strings from 'ApplicationCustomizersWebPartStrings';
|
||||
import ApplicationCustomizers from './components/ApplicationCustomizers';
|
||||
import { IApplicationCustomizersProps } from './components/IApplicationCustomizersProps';
|
||||
import { sp } from "@pnp/sp/presets/all";
|
||||
|
||||
|
||||
export interface IApplicationCustomizersWebPartProps {
|
||||
description: string;
|
||||
designType: string;
|
||||
}
|
||||
|
||||
export default class ApplicationCustomizersWebPart extends BaseClientSideWebPart<IApplicationCustomizersWebPartProps> {
|
||||
|
||||
protected onInit(): Promise<void> {
|
||||
|
||||
return super.onInit().then(_ => {
|
||||
|
||||
// other init code may be present
|
||||
|
||||
sp.setup({
|
||||
spfxContext: this.context
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IApplicationCustomizersProps> = React.createElement(
|
||||
ApplicationCustomizers,
|
||||
{
|
||||
description: this.properties.description,
|
||||
context: this.context,
|
||||
designType: this.properties.designType
|
||||
}
|
||||
);
|
||||
|
||||
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('description', {
|
||||
label: strings.DescriptionFieldLabel
|
||||
}),
|
||||
PropertyPaneChoiceGroup('designType', {
|
||||
label: strings.DesignFieldLabel,
|
||||
options: [
|
||||
{ key: 'Accordion', checked: true, text: 'Accordion', iconProps: { officeFabricIconFontName: 'AutoFillTemplate' } },
|
||||
{ key: 'List', text: 'List', iconProps: { officeFabricIconFontName: 'GroupedList' } }
|
||||
]
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||
|
||||
.applicationCustomizers {
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0px auto;
|
||||
}
|
||||
|
||||
.row {
|
||||
@include ms-Grid-row;
|
||||
padding: 20px;
|
||||
}
|
||||
.column2 {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg2;
|
||||
@include ms-xl2;
|
||||
margin-top: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.column {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg10;
|
||||
@include ms-xl10;
|
||||
margin-top: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.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;
|
||||
margin-top: 10px;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
:global{
|
||||
#workbenchPageContent{
|
||||
max-width: 1200px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,446 @@
|
|||
import * as React from 'react';
|
||||
import styles from './ApplicationCustomizers.module.scss';
|
||||
import { IApplicationCustomizersProps } from './IApplicationCustomizersProps';
|
||||
import { assign } from '@microsoft/sp-lodash-subset';
|
||||
import ApplicationCustomizersService from "../service/ApplicationCustomizersService";
|
||||
import { Dropdown, DropdownMenuItemType, IDropdownOption, IDropdownStyles } from 'office-ui-fabric-react/lib/Dropdown';
|
||||
import MuiAccordion from '@material-ui/core/Accordion';
|
||||
import MuiAccordionSummary from '@material-ui/core/AccordionSummary';
|
||||
import MuiAccordionDetails from '@material-ui/core/AccordionDetails';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { DefaultButton, TextField, thProperties, Dialog, DialogFooter, DialogType, List, mergeStyleSets, getFocusStyle, ITheme, getTheme, IconButton, Panel, PanelType } from 'office-ui-fabric-react';
|
||||
import AceEditor from "react-ace";
|
||||
|
||||
import "ace-builds/src-noconflict/mode-json";
|
||||
import "ace-builds/src-noconflict/theme-github";
|
||||
|
||||
const Accordion = withStyles({
|
||||
root: {
|
||||
border: '1px solid rgba(0, 0, 0, .125)',
|
||||
boxShadow: 'none',
|
||||
'&:not(:last-child)': {
|
||||
borderBottom: 0,
|
||||
},
|
||||
'&:before': {
|
||||
display: 'none',
|
||||
},
|
||||
'&$expanded': {
|
||||
margin: 'auto',
|
||||
},
|
||||
},
|
||||
expanded: {},
|
||||
})(MuiAccordion);
|
||||
|
||||
const AccordionSummary = withStyles({
|
||||
root: {
|
||||
backgroundColor: 'rgba(0, 0, 0, .03)',
|
||||
borderBottom: '1px solid rgba(0, 0, 0, .125)',
|
||||
marginBottom: -1,
|
||||
minHeight: 56,
|
||||
'&$expanded': {
|
||||
minHeight: 56,
|
||||
},
|
||||
},
|
||||
content: {
|
||||
'&$expanded': {
|
||||
margin: '12px 0',
|
||||
},
|
||||
},
|
||||
expanded: {},
|
||||
})(MuiAccordionSummary);
|
||||
|
||||
const AccordionDetails = withStyles((themes) => ({
|
||||
root: {
|
||||
padding: themes.spacing(2),
|
||||
display: 'block'
|
||||
},
|
||||
}))(MuiAccordionDetails);
|
||||
const theme: ITheme = getTheme();
|
||||
const { palette, semanticColors, fonts } = theme;
|
||||
const classNames = mergeStyleSets({
|
||||
container: {
|
||||
overflow: 'auto',
|
||||
maxHeight: 500,
|
||||
},
|
||||
itemCell: [
|
||||
getFocusStyle(theme, { inset: -1 }),
|
||||
{
|
||||
minHeight: 54,
|
||||
padding: 10,
|
||||
boxSizing: 'border-box',
|
||||
borderBottom: `1px solid ${semanticColors.bodyDivider}`,
|
||||
display: 'flex',
|
||||
selectors: {
|
||||
'&:hover': { background: palette.neutralLight },
|
||||
},
|
||||
},
|
||||
],
|
||||
itemImage: {
|
||||
flexShrink: 0,
|
||||
},
|
||||
itemContent: {
|
||||
marginLeft: 10,
|
||||
overflow: 'hidden',
|
||||
flexGrow: 1,
|
||||
},
|
||||
itemName: [
|
||||
fonts.xLarge,
|
||||
{
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
color: 'black'
|
||||
},
|
||||
],
|
||||
itemIndex: {
|
||||
fontSize: fonts.small.fontSize,
|
||||
marginBottom: 10,
|
||||
color: 'black'
|
||||
},
|
||||
chevron: {
|
||||
alignSelf: 'center',
|
||||
marginLeft: 10,
|
||||
color: palette.neutralTertiary,
|
||||
fontSize: fonts.large.fontSize,
|
||||
flexShrink: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const applicationCustomizersService = new ApplicationCustomizersService();
|
||||
|
||||
export interface IApplicationCustomizersState {
|
||||
selectedItem: IDropdownOption;
|
||||
dropdownSites: IDropdownOption[];
|
||||
expanded: string | false;
|
||||
allCustomizers: any;
|
||||
previousEditIndex?: number;
|
||||
editJSON?: { Title: string; Description: string; ClientSideComponentProperties: any };
|
||||
hideDialog: boolean;
|
||||
dialogContentProps?: any;
|
||||
isPanelOpen: boolean;
|
||||
itemInEdit?: number;
|
||||
isViewPanelOpen: boolean;
|
||||
viewJSON?: {
|
||||
Title: string; Description: string; ClientSideComponentId: any;
|
||||
ClientSideComponentProperties: any; Id: any;
|
||||
};
|
||||
}
|
||||
|
||||
export default class ApplicationCustomizers extends React.Component<IApplicationCustomizersProps, IApplicationCustomizersState> {
|
||||
|
||||
constructor(props: IApplicationCustomizersProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedItem: undefined,
|
||||
dropdownSites: undefined,
|
||||
expanded: 'panel1',
|
||||
allCustomizers: [],
|
||||
previousEditIndex: undefined,
|
||||
hideDialog: true,
|
||||
isPanelOpen: false,
|
||||
isViewPanelOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public componentDidMount() {
|
||||
applicationCustomizersService.fetchAllApplictionCustomizers(this.props.context.pageContext.web.absoluteUrl)
|
||||
.then((allCustomizers) => {
|
||||
allCustomizers = allCustomizers.map((cus) => { return assign(cus, { inEdit: false }); });
|
||||
this.setState({ allCustomizers: allCustomizers });
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
applicationCustomizersService.getAllSiteCollection()
|
||||
.then((allSites) => {
|
||||
let dropdownSites = allSites.PrimarySearchResults.map((val) => {
|
||||
val['key'] = val['SPSiteUrl'];
|
||||
val['text'] = `${val['Title']} - ${val['SPSiteUrl']}`;
|
||||
return val;
|
||||
});
|
||||
this.setState({ dropdownSites: dropdownSites });
|
||||
});
|
||||
}
|
||||
|
||||
private onChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
|
||||
this.setState({ selectedItem: item });
|
||||
applicationCustomizersService.fetchAllApplictionCustomizers(item.key as string)
|
||||
.then((allCustomizers) => {
|
||||
allCustomizers = allCustomizers.map((cus) => { return assign(cus, { inEdit: false }); });
|
||||
this.setState({ allCustomizers: allCustomizers });
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
public handleChange = (panel: string) => (event: React.ChangeEvent<{}>, newExpanded: boolean) => {
|
||||
this.setState({ expanded: newExpanded ? panel : false });
|
||||
}
|
||||
|
||||
public editCustomApplication = (index: number, inEdit: boolean) => {
|
||||
let allCustomizers = this.state.allCustomizers;
|
||||
allCustomizers[index].inEdit = inEdit;
|
||||
if (this.state.previousEditIndex !== undefined && inEdit) {
|
||||
allCustomizers[this.state.previousEditIndex].inEdit = false;
|
||||
}
|
||||
if (inEdit) {
|
||||
this.setState({
|
||||
isPanelOpen: this.props.designType === "List" ? true : false,
|
||||
itemInEdit: index,
|
||||
editJSON: {
|
||||
Title: allCustomizers[index].Title,
|
||||
Description: allCustomizers[index].Description,
|
||||
ClientSideComponentProperties: allCustomizers[index].ClientSideComponentProperties
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!inEdit) {
|
||||
this.setState({ isPanelOpen: false });
|
||||
}
|
||||
this.setState({ allCustomizers: allCustomizers, previousEditIndex: index });
|
||||
}
|
||||
|
||||
public onChangeJSON = (obj: string, newValue: string) => {
|
||||
let { editJSON } = this.state;
|
||||
editJSON[obj] = newValue;
|
||||
this.setState({ editJSON });
|
||||
}
|
||||
|
||||
public updateCustomizer = (index: number) => {
|
||||
let webURL = this.state.selectedItem ? this.state.selectedItem.key : this.props.context.pageContext.web.absoluteUrl;
|
||||
let { allCustomizers } = this.state;
|
||||
applicationCustomizersService.updateApplicationCustomizer(webURL, this.state.allCustomizers[index].Id, this.state.editJSON)
|
||||
.then(() => {
|
||||
allCustomizers[index].inEdit = false;
|
||||
this.setState({
|
||||
allCustomizers: allCustomizers,
|
||||
hideDialog: false,
|
||||
isPanelOpen: false,
|
||||
dialogContentProps: {
|
||||
type: DialogType.normal,
|
||||
title: 'Updated Successfully',
|
||||
closeButtonAriaLabel: 'Close',
|
||||
subText: 'Your Customizer is updated. Please refresh the page to look at the changes?'
|
||||
}
|
||||
});
|
||||
}).catch((err) => {
|
||||
this.setState({
|
||||
hideDialog: false,
|
||||
dialogContentProps: {
|
||||
type: DialogType.normal,
|
||||
title: 'Updat Error',
|
||||
closeButtonAriaLabel: 'Close',
|
||||
subText: 'There was some error while updating you customizer. Please try again'
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private toggleHideDialog = () => {
|
||||
applicationCustomizersService.fetchAllApplictionCustomizers(this.state.selectedItem ?
|
||||
this.state.selectedItem.key as string : this.props.context.pageContext.web.absoluteUrl)
|
||||
.then((allCustomizers) => {
|
||||
allCustomizers = allCustomizers.map((cus) => { return assign(cus, { inEdit: false }); });
|
||||
this.setState({ allCustomizers: allCustomizers, hideDialog: true });
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
private viewCustomApplication = (index: number) => {
|
||||
let { allCustomizers } = this.state;
|
||||
this.setState({
|
||||
isViewPanelOpen: true,
|
||||
viewJSON: {
|
||||
Title: allCustomizers[index].Title,
|
||||
ClientSideComponentId: allCustomizers[index].ClientSideComponentId,
|
||||
ClientSideComponentProperties: allCustomizers[index].ClientSideComponentProperties,
|
||||
Description: allCustomizers[index].Description,
|
||||
Id: allCustomizers[index].Id
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private onRenderCell = (item: any, index: number, isScrolling: boolean): JSX.Element => {
|
||||
return (
|
||||
<div className={classNames.itemCell} data-is-focusable={true}>
|
||||
<div className={classNames.itemContent}>
|
||||
<div className={classNames.itemName}>{item.Title}</div>
|
||||
<div className={classNames.itemIndex}>{item.Description}</div>
|
||||
</div>
|
||||
<IconButton iconProps={{ iconName: 'View' }} onClick={() => { this.viewCustomApplication(index); }} title="View" ariaLabel="View"></IconButton>
|
||||
<IconButton iconProps={{ iconName: 'Edit' }} onClick={() => { this.editCustomApplication(index, true); }} title="Edit" ariaLabel="Edit"></IconButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public render(): React.ReactElement<IApplicationCustomizersProps> {
|
||||
return (
|
||||
<div className={styles.applicationCustomizers}>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.row}>
|
||||
<h1>{this.props.description}</h1>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<Dropdown
|
||||
label="Select Web"
|
||||
selectedKey={this.state.selectedItem ? this.state.selectedItem.key : undefined}
|
||||
onChange={this.onChange}
|
||||
placeholder="Select an option"
|
||||
options={this.state.dropdownSites}
|
||||
/>
|
||||
</div>
|
||||
{this.props.designType === "Accordion" && this.state.allCustomizers.length !== 0 &&
|
||||
<div className={styles.row}>
|
||||
{this.state.allCustomizers.map((customizer, index) => {
|
||||
return (
|
||||
<Accordion square expanded={this.state.expanded === `panel${index + 1}`} onChange={this.handleChange(`panel${index + 1}`)}>
|
||||
<AccordionSummary aria-controls="panel1d-content" id="panel1d-header">
|
||||
<div>{customizer.Title}{customizer.Description && ` - ${customizer.Description}`}</div>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
{!customizer.inEdit ?
|
||||
<div>
|
||||
<div className={styles.column2}>Component ID</div>
|
||||
<div className={styles.column}>{customizer.ClientSideComponentId}</div>
|
||||
<div className={styles.column2}>ID</div>
|
||||
<div className={styles.column}>{customizer.Id}</div>
|
||||
<div className={styles.column2}>Properties</div>
|
||||
<div className={styles.column}>{customizer.ClientSideComponentProperties}</div>
|
||||
</div> :
|
||||
<div>
|
||||
<div className={styles.column2}>Title</div>
|
||||
<div className={styles.column}><TextField value={this.state.editJSON.Title}
|
||||
onChange={(ev, newVal) => {
|
||||
this.onChangeJSON("Title", newVal);
|
||||
}} />
|
||||
</div>
|
||||
<div className={styles.column2}>Description</div>
|
||||
<div className={styles.column}><TextField value={this.state.editJSON.Description} multiline rows={3}
|
||||
onChange={(ev, newVal) => {
|
||||
this.onChangeJSON("Description", newVal);
|
||||
}} /></div>
|
||||
<div className={styles.column2}>Properties</div>
|
||||
|
||||
<div className={styles.column}>
|
||||
<AceEditor
|
||||
placeholder="Placeholder Text"
|
||||
mode="json"
|
||||
theme="github"
|
||||
onChange={(val) => { this.onChangeJSON("ClientSideComponentProperties", val); }}
|
||||
fontSize={14}
|
||||
style={{ height: 200, width: 790 }}
|
||||
showPrintMargin={true}
|
||||
showGutter={true}
|
||||
highlightActiveLine={false}
|
||||
value={this.state.editJSON.ClientSideComponentProperties}
|
||||
setOptions={{
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: false,
|
||||
enableSnippets: false,
|
||||
showLineNumbers: true,
|
||||
tabSize: 2,
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{!customizer.inEdit ?
|
||||
<DefaultButton className={styles.button} text="Edit" onClick={() => { this.editCustomApplication(index, true); }} /> :
|
||||
[<DefaultButton className={styles.button} text="Update" onClick={() => { this.updateCustomizer(index); }} />,
|
||||
<DefaultButton style={{ marginLeft: 10, marginTop: 10 }} text="Cancel" onClick={() => { this.editCustomApplication(index, false); }} />]
|
||||
}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
{this.props.designType === "List" && this.state.allCustomizers.length !== 0 &&
|
||||
<div className={styles.row}>
|
||||
<List items={this.state.allCustomizers} onRenderCell={this.onRenderCell} />
|
||||
</div>
|
||||
}
|
||||
{this.state.allCustomizers.length === 0 &&
|
||||
<div className={styles.row}>We did not find any Application Customizers for the selected web</div>
|
||||
}
|
||||
<Dialog
|
||||
hidden={this.state.hideDialog}
|
||||
onDismiss={() => this.toggleHideDialog()}
|
||||
dialogContentProps={this.state.dialogContentProps}
|
||||
>
|
||||
<DialogFooter>
|
||||
<DefaultButton onClick={() => this.toggleHideDialog()} text="Cancel" />
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
<Panel
|
||||
headerText="Edit Application Customizer"
|
||||
isOpen={this.state.isPanelOpen}
|
||||
onDismiss={() => this.setState({ isPanelOpen: false })}
|
||||
closeButtonAriaLabel="Close"
|
||||
type={PanelType.large}
|
||||
>
|
||||
{this.state.editJSON &&
|
||||
<div className={styles.applicationCustomizers}>
|
||||
<div className={styles.column2}>Title</div>
|
||||
<div className={styles.column}><TextField value={this.state.editJSON.Title}
|
||||
onChange={(ev, newVal) => {
|
||||
this.onChangeJSON("Title", newVal);
|
||||
}} />
|
||||
</div>
|
||||
<div className={styles.column2}>Description</div>
|
||||
<div className={styles.column}><TextField value={this.state.editJSON.Description} multiline rows={3}
|
||||
onChange={(ev, newVal) => {
|
||||
this.onChangeJSON("Description", newVal);
|
||||
}} /></div>
|
||||
<div className={styles.column2}>Properties</div>
|
||||
|
||||
<div className={styles.column}>
|
||||
<AceEditor
|
||||
placeholder="Placeholder Text"
|
||||
mode="json"
|
||||
theme="github"
|
||||
onChange={(val) => { this.onChangeJSON("ClientSideComponentProperties", val); }}
|
||||
fontSize={14}
|
||||
style={{ height: 200, width: 800 }}
|
||||
showPrintMargin={true}
|
||||
showGutter={true}
|
||||
highlightActiveLine={false}
|
||||
value={this.state.editJSON.ClientSideComponentProperties}
|
||||
setOptions={{
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: false,
|
||||
enableSnippets: false,
|
||||
showLineNumbers: true,
|
||||
tabSize: 2,
|
||||
}} />
|
||||
</div>
|
||||
<DefaultButton style={{ marginLeft: 10, marginTop: 10 }} className={styles.button} text="Update" onClick={() => { this.updateCustomizer(this.state.itemInEdit); }} />
|
||||
<DefaultButton style={{ marginLeft: 10, marginTop: 10 }} text="Cancel" onClick={() => { this.editCustomApplication(this.state.itemInEdit, false); }} />
|
||||
</div>}
|
||||
</Panel>
|
||||
<Panel
|
||||
headerText="View Application Customizer"
|
||||
isOpen={this.state.isViewPanelOpen}
|
||||
onDismiss={() => this.setState({ isViewPanelOpen: false })}
|
||||
closeButtonAriaLabel="Close"
|
||||
type={PanelType.medium}
|
||||
>{this.state.viewJSON &&
|
||||
<div className={styles.applicationCustomizers}>
|
||||
<div className={styles.column2}>Title</div>
|
||||
<div className={styles.column}>{this.state.viewJSON.Title}</div>
|
||||
<div className={styles.column2}>Description</div>
|
||||
<div className={styles.column}>{this.state.viewJSON.Description ? this.state.viewJSON.Description : 'null'}</div>
|
||||
<div className={styles.column2}>ComponentID</div>
|
||||
<div className={styles.column}>{this.state.viewJSON.ClientSideComponentId}</div>
|
||||
<div className={styles.column2}>ID</div>
|
||||
<div className={styles.column}>{this.state.viewJSON.Id}</div>
|
||||
<div className={styles.column2}>Properties</div>
|
||||
<div className={styles.column}>{this.state.viewJSON.ClientSideComponentProperties}</div>
|
||||
</div>}</Panel>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
|
||||
export interface IApplicationCustomizersProps {
|
||||
description: string;
|
||||
context: WebPartContext;
|
||||
designType: string;
|
||||
}
|
8
samples/react-Edit-ApplicationCustomizer/src/webparts/applicationCustomizers/loc/en-us.js
vendored
Normal file
8
samples/react-Edit-ApplicationCustomizer/src/webparts/applicationCustomizers/loc/en-us.js
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
define([], function () {
|
||||
return {
|
||||
"PropertyPaneDescription": "",
|
||||
"BasicGroupName": "",
|
||||
"DescriptionFieldLabel": "Title",
|
||||
"DesignFieldLabel": "Choose design"
|
||||
}
|
||||
});
|
11
samples/react-Edit-ApplicationCustomizer/src/webparts/applicationCustomizers/loc/mystrings.d.ts
vendored
Normal file
11
samples/react-Edit-ApplicationCustomizer/src/webparts/applicationCustomizers/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
declare interface IApplicationCustomizersWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
DesignFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'ApplicationCustomizersWebPartStrings' {
|
||||
const strings: IApplicationCustomizersWebPartStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
import { sp } from "@pnp/sp";
|
||||
import "@pnp/sp/webs";
|
||||
import "@pnp/sp/user-custom-actions";
|
||||
import { ISearchQuery } from "@pnp/sp/search";
|
||||
import { Web } from "@pnp/sp/webs";
|
||||
|
||||
|
||||
export default class ApplicationCustomizersService {
|
||||
|
||||
/**
|
||||
* fetchAllApplictionCustomizers
|
||||
*/
|
||||
public fetchAllApplictionCustomizers = async (webURL: string) => {
|
||||
let web = Web(webURL);
|
||||
let response;
|
||||
try {
|
||||
response = await web.userCustomActions();
|
||||
console.log(response);
|
||||
//let temp = await sp.site.userCustomActions();
|
||||
//console.log(temp);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
response = error;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* getAllSiteCollection
|
||||
*/
|
||||
public getAllSiteCollection = async () => {
|
||||
let response;
|
||||
try {
|
||||
response = await sp.search(<ISearchQuery>{
|
||||
Querytext: "contentclass:STS_Site",
|
||||
SelectProperties: ["Title", "SPSiteUrl", "WebTemplate"],
|
||||
RowLimit: 1000,
|
||||
TrimDuplicates: true
|
||||
});
|
||||
console.log(response.PrimarySearchResults);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
response = error;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* updateApplicationCustomizer
|
||||
*/
|
||||
public updateApplicationCustomizer = async (webURL: string | number, selectedID: string, updateJSON: any) => {
|
||||
let web = Web(webURL as string);
|
||||
let response;
|
||||
try {
|
||||
response = await web.userCustomActions.getById(selectedID).update(updateJSON);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 383 B |
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "lib",
|
||||
"inlineSources": false,
|
||||
"strictNullChecks": false,
|
||||
"noUnusedLocals": false,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
],
|
||||
"types": [
|
||||
"es6-promise",
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||
"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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue