added New webpart sample
|
@ -0,0 +1,5 @@
|
|||
require('@rushstack/eslint-config/patch/modern-module-resolution');
|
||||
module.exports = {
|
||||
extends: ['@microsoft/eslint-config-spfx/lib/profiles/react'],
|
||||
parserOptions: { tsconfigRootDir: __dirname }
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
!dist
|
||||
config
|
||||
|
||||
gulpfile.js
|
||||
|
||||
release
|
||||
src
|
||||
temp
|
||||
|
||||
tsconfig.json
|
||||
tslint.json
|
||||
|
||||
*.log
|
||||
|
||||
.yo-rc.json
|
||||
.vscode
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"plusBeta": false,
|
||||
"isCreatingSolution": true,
|
||||
"version": "1.15.0",
|
||||
"libraryName": "react-add-formcustomizer-to-list",
|
||||
"libraryId": "93fb58e4-6db3-4559-b98c-bd42f10a5fc6",
|
||||
"environment": "spo",
|
||||
"packageManager": "npm",
|
||||
"solutionName": "react-add-formcustomizer-to-list",
|
||||
"solutionShortDescription": "react-add-formcustomizer-to-list description",
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
# react-add-formcustomizer-to-list
|
||||
|
||||
A react based SPFx utility web part which will help admins/user(s) to associate and remove association of the list form customizer extension to a particular list.
|
||||
|
||||
With SPFx version 1.15.1, we can now create new type of Extension as Form customizer which allows use to associate custom forms to SharePoint List.
|
||||
As of writing this webpart, this no direct way to associate this form customizer to SP list. We will have to either write PowerShell or Use REST API to associate it with the list.
|
||||
|
||||
This webpart serves as utility so the developers can use to associate single form customizer with multiple lists with control over option to associate New/Edit/View form seperately.
|
||||
|
||||
Note - This webpart only serve to associate the Form customizer, so it is required that the actual SPFx Form Customizer solution is deployed and installed to targeted Site before association.
|
||||
|
||||
WebPart in Action
|
||||
|
||||
![Web part in action](assets/webpartinaction-form.gif "Webpart in action")
|
||||
|
||||
### Highlights
|
||||
|
||||
* Option to Select Site->List->Content Type
|
||||
* Option to choose asssociate either with one or more type of forms(New/Edit/View)
|
||||
* Option to remove association of form
|
||||
* Associate single customizer with mutiple lists/forms
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
|
||||
![1.15.0](https://img.shields.io/badge/version-1.15.0-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
|
||||
* [Office 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-developer-tenant)
|
||||
|
||||
### Package and Deploy
|
||||
|
||||
Note - If you don't want to build and package on your own, you can directly download package at this [location](https://github.com/siddharth-vaghasia/public-docs/blob/master/react-add-formcustomizer-to-list.sppkg) and upload to app catalog and install app on required site collection. Skip below steps and directly go to How to use section.
|
||||
Clone the solution and make sure there is no error before packaging. Try first on local work bench.
|
||||
|
||||
Change the `pageURL` property in `/config/serve.json` - This should be a valid modern page on your site collection.
|
||||
|
||||
```bash
|
||||
git clone the repo
|
||||
npm i
|
||||
gulp serve
|
||||
```
|
||||
- Execute the following gulp task to bundle your solution. This executes a release build of your project by using a dynamic label as the host URL for your assets. This URL is automatically updated based on your tenant CDN settings:
|
||||
```bash
|
||||
gulp bundle --ship
|
||||
```
|
||||
- Execute the following task to package your solution. This creates an updated `react-add-formcustomizer-to-list.sppkg` package on the `sharepoint/solution` folder.
|
||||
```bash
|
||||
gulp package-solution --ship
|
||||
```
|
||||
- Upload or drag and drop the newly created client-side solution package to the app catalog in your tenant.
|
||||
- Based on your tenant settings, if you would not have CDN enabled in your tenant, and the `includeClientSideAssets` setting would be true in the `package-solution.json`, the loading URL for the assets would be dynamically updated and pointing directly to the `ClientSideAssets` folder located in the app catalog site collection.
|
||||
|
||||
### How to Use Solution
|
||||
|
||||
* Once app is deployed to app catalog successfully
|
||||
* Install app to required site collection
|
||||
* Create new modern page. Add **react-add-formcustomizer-to-list** web part to page.
|
||||
* Publish the page.
|
||||
|
||||
To do the association or removing the assoication , user needs to follow the below steps:
|
||||
|
||||
* Select the site from available sites
|
||||
* Choose a list from the available options
|
||||
* Choose the content type to which the form customizer needs to associate/remove association if its already associated
|
||||
* User needs to grab client component id present in form customizer manifest json file (Information is added in the client component id section with image)
|
||||
* Select the required check box option New Form/Edit Form/View Form
|
||||
* After filling the required values, click on Associate/Remove Association button
|
||||
* Once it is successful then go to respective list and check the forms
|
||||
* Users can only associate/remove association of the form customizer to lists of the sites that they have access
|
||||
|
||||
You can copy the actual component ID of form customizer with from its manifest.json file.
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-add-formcustomizer-to-list | [Siddharth Vaghasia](https://www.linkedin.com/in/siddharthvaghasia/)
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0.0|Septemeber 04, 2022|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.**
|
||||
|
||||
For any issue or help, Buzz me on twitter:([siddh_me](https://twitter.com/siddh_me/))
|
||||
|
||||
> Sharing is caring!
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-add-formcustomizer-to-list" />
|
After Width: | Height: | Size: 1.0 MiB |
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"react-addformcustomizertolist-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/reactAddformcustomizertolist/ReactAddformcustomizertolistWebPart.js",
|
||||
"manifest": "./src/webparts/reactAddformcustomizertolist/ReactAddformcustomizertolistWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"ReactAddformcustomizertolistWebPartStrings": "lib/webparts/reactAddformcustomizertolist/loc/{locale}.js",
|
||||
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./release/assets/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-add-formcustomizer-to-list",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-add-formcustomizer-to-list-client-side-solution",
|
||||
"id": "93fb58e4-6db3-4559-b98c-bd42f10a5fc6",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
"developer": {
|
||||
"name": "",
|
||||
"websiteUrl": "",
|
||||
"privacyUrl": "",
|
||||
"termsOfUseUrl": "",
|
||||
"mpnId": "Undefined-1.15.0"
|
||||
},
|
||||
"metadata": {
|
||||
"shortDescription": {
|
||||
"default": "react-add-formcustomizer-to-list description"
|
||||
},
|
||||
"longDescription": {
|
||||
"default": "react-add-formcustomizer-to-list description"
|
||||
},
|
||||
"screenshotPaths": [],
|
||||
"videoUrl": "",
|
||||
"categories": []
|
||||
},
|
||||
"features": [
|
||||
{
|
||||
"title": "react-add-formcustomizer-to-list Feature",
|
||||
"description": "The feature that activates elements of the react-add-formcustomizer-to-list solution.",
|
||||
"id": "b16b826f-d607-4f4a-a67e-0f42ed12f031",
|
||||
"version": "1.0.0.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-add-formcustomizer-to-list.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://xr4vy.sharepoint.com/sites/Retail/_layouts/workbench.aspx"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
'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.`);
|
||||
|
||||
var getTasks = build.rig.getTasks;
|
||||
build.rig.getTasks = function () {
|
||||
var result = getTasks.call(build.rig);
|
||||
|
||||
result.set('serve', result.get('serve-deprecated'));
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
build.initialize(require('gulp'));
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "react-add-formcustomizer-to-list",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.15.0",
|
||||
"@microsoft/sp-lodash-subset": "1.15.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.15.0",
|
||||
"@microsoft/sp-property-pane": "1.15.0",
|
||||
"@microsoft/sp-webpart-base": "1.15.0",
|
||||
"@pnp/logging": "^3.5.1",
|
||||
"@pnp/sp": "^3.5.1",
|
||||
"@pnp/spfx-controls-react": "3.9.0",
|
||||
"office-ui-fabric-react": "7.185.7",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"tslib": "2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/rush-stack-compiler-4.5": "0.2.2",
|
||||
"@rushstack/eslint-config": "2.5.1",
|
||||
"@microsoft/eslint-plugin-spfx": "1.15.0",
|
||||
"@microsoft/eslint-config-spfx": "1.15.0",
|
||||
"@microsoft/sp-build-web": "1.15.0",
|
||||
"@types/webpack-env": "~1.15.2",
|
||||
"ajv": "^6.12.5",
|
||||
"gulp": "4.0.2",
|
||||
"@types/react": "16.9.51",
|
||||
"@types/react-dom": "16.9.8",
|
||||
"eslint-plugin-react-hooks": "4.3.0",
|
||||
"@microsoft/sp-module-interfaces": "1.15.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "b07808d4-15db-4988-be7b-73c5777838d7",
|
||||
"alias": "ReactAddformcustomizertolistWebPart",
|
||||
"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", "TeamsPersonalApp", "TeamsTab", "SharePointFullPage"],
|
||||
"supportsThemeVariants": true,
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "react-addformcustomizertolist" },
|
||||
"description": { "default": "react-addformcustomizertolist description" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"description": "react-addformcustomizertolist"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-property-pane';
|
||||
import { BaseClientSideWebPart, WebPartContext } from '@microsoft/sp-webpart-base';
|
||||
import { IReadonlyTheme } from '@microsoft/sp-component-base';
|
||||
|
||||
import * as strings from 'ReactAddformcustomizertolistWebPartStrings';
|
||||
import ReactAddformcustomizertolist from './components/ReactAddformcustomizertolist';
|
||||
import { IReactAddformcustomizertolistProps } from './components/IReactAddformcustomizertolistProps';
|
||||
|
||||
import { getSP } from './pnpjsConfig';
|
||||
|
||||
export interface IReactAddformcustomizertolistWebPartProps {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default class ReactAddformcustomizertolistWebPart extends BaseClientSideWebPart<IReactAddformcustomizertolistWebPartProps> {
|
||||
|
||||
private _isDarkTheme: boolean = false;
|
||||
private _environmentMessage: string = '';
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IReactAddformcustomizertolistProps> = React.createElement(
|
||||
ReactAddformcustomizertolist,
|
||||
{
|
||||
description: this.properties.description,
|
||||
isDarkTheme: this._isDarkTheme,
|
||||
environmentMessage: this._environmentMessage,
|
||||
hasTeamsContext: !!this.context.sdks.microsoftTeams,
|
||||
userDisplayName: this.context.pageContext.user.displayName,
|
||||
context: this.context
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected async onInit(): Promise<void> {
|
||||
this._environmentMessage = this._getEnvironmentMessage();
|
||||
|
||||
await super.onInit();
|
||||
getSP(this.context);
|
||||
}
|
||||
|
||||
private _getEnvironmentMessage(): string {
|
||||
if (!!this.context.sdks.microsoftTeams) { // running in Teams
|
||||
return this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment;
|
||||
}
|
||||
|
||||
return this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentSharePoint : strings.AppSharePointEnvironment;
|
||||
}
|
||||
|
||||
protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void {
|
||||
if (!currentTheme) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isDarkTheme = !!currentTheme.isInverted;
|
||||
const {
|
||||
semanticColors
|
||||
} = currentTheme;
|
||||
|
||||
if (semanticColors) {
|
||||
this.domElement.style.setProperty('--bodyText', semanticColors.bodyText || null);
|
||||
this.domElement.style.setProperty('--link', semanticColors.link || null);
|
||||
this.domElement.style.setProperty('--linkHovered', semanticColors.linkHovered || null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 167 KiB |
After Width: | Height: | Size: 73 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,10 @@
|
|||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
|
||||
export interface IReactAddformcustomizertolistProps {
|
||||
description: string;
|
||||
isDarkTheme: boolean;
|
||||
environmentMessage: string;
|
||||
hasTeamsContext: boolean;
|
||||
userDisplayName: string;
|
||||
context: any;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { ISite } from "@pnp/spfx-controls-react/lib/controls/sitePicker/ISitePicker";
|
||||
import { IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
|
||||
|
||||
export interface IReactAddformcustomizertolistState {
|
||||
siteUrl: string;
|
||||
sites: ISite[];
|
||||
errors: string[];
|
||||
contentTypes: IDropdownOption[];
|
||||
NewForm: boolean;
|
||||
EditForm: boolean;
|
||||
ViewForm: boolean;
|
||||
disabled: boolean;
|
||||
selectedContnetType: string;
|
||||
selectedList: string;
|
||||
clientComponentID: string;
|
||||
isCalloutVisible: boolean;
|
||||
userMessage: string;
|
||||
hideDialog: boolean;
|
||||
chkCustomSiteUrl: boolean;
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||
|
||||
.reactAddformcustomizertolist {
|
||||
overflow: hidden;
|
||||
padding: 1em;
|
||||
color: "[theme:bodyText, default: #323130]";
|
||||
color: var(--bodyText);
|
||||
|
||||
&.teams {
|
||||
font-family: $ms-font-family-fallbacks;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.welcomeImage {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
}
|
||||
|
||||
.links {
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: "[theme:link, default:#03787c]";
|
||||
color: var(--link); // note: CSS Custom Properties support is limited to modern browsers only
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
color: "[theme:linkHovered, default: #014446]";
|
||||
color: var(--linkHovered); // note: CSS Custom Properties support is limited to modern browsers only
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.headeClass {
|
||||
text-align: center;
|
||||
font-Weight: bold;
|
||||
}
|
|
@ -0,0 +1,447 @@
|
|||
import * as React from 'react';
|
||||
import styles from './ReactAddformcustomizertolist.module.scss';
|
||||
import { IReactAddformcustomizertolistProps } from './IReactAddformcustomizertolistProps';
|
||||
import { escape } from '@microsoft/sp-lodash-subset';
|
||||
|
||||
import { SPFI, SPFx } from '@pnp/sp';
|
||||
import { getSP } from '../pnpjsConfig';
|
||||
import { Logger, LogLevel } from "@pnp/logging";
|
||||
import { IItemUpdateResult } from "@pnp/sp/items";
|
||||
import { Label, PrimaryButton } from '@microsoft/office-ui-fabric-react-bundle';
|
||||
import "@pnp/sp/content-types/list";
|
||||
import "@pnp/sp/webs";
|
||||
import "@pnp/sp/lists";
|
||||
import { SPHttpClient, SPHttpClientResponse, SPHttpClientConfiguration, ISPHttpClientOptions } from '@microsoft/sp-http';
|
||||
|
||||
|
||||
import { SitePicker } from "@pnp/spfx-controls-react/lib/SitePicker";
|
||||
import { ListPicker } from "@pnp/spfx-controls-react/lib/ListPicker";
|
||||
import { IReactAddformcustomizertolistState } from './IReactAddformcustomizertolistState';
|
||||
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
|
||||
import { IContentTypeInfo } from '@pnp/sp/content-types/types';
|
||||
import { Web } from '@pnp/sp/webs';
|
||||
import { ITextFieldProps, TextField } from 'office-ui-fabric-react/lib/TextField';
|
||||
import { IStackStyles, IStackTokens, Stack } from 'office-ui-fabric-react/lib/Stack';
|
||||
import { DefaultButton, IButtonStyles, IconButton } from 'office-ui-fabric-react/lib/Button';
|
||||
import { Callout } from 'office-ui-fabric-react/lib/Callout';
|
||||
import { useBoolean, useId } from '@fluentui/react-hooks';
|
||||
import { FontIcon, IIconStyles } from 'office-ui-fabric-react/lib/Icon';
|
||||
import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox';
|
||||
import { mergeStyles } from 'office-ui-fabric-react/lib/Styling';
|
||||
import { Image, IImageProps } from 'office-ui-fabric-react/lib/Image';
|
||||
import Dialog, { DialogFooter, DialogType } from 'office-ui-fabric-react/lib/Dialog';
|
||||
|
||||
const iconClass = mergeStyles({
|
||||
fontSize: 20,
|
||||
height: 12,
|
||||
width: 12,
|
||||
margin: '5px 25px',
|
||||
});
|
||||
|
||||
const stackTokens: IStackTokens = {
|
||||
childrenGap: 10,
|
||||
};
|
||||
|
||||
const chkstackTokens: IStackTokens = {
|
||||
childrenGap: 6,
|
||||
};
|
||||
|
||||
const labelCalloutStackStyles: Partial<IStackStyles> = { root: { padding: 20 } };
|
||||
const iconButtonStyles: Partial<IButtonStyles> = { root: { marginBottom: -3 } };
|
||||
const iconProps = { iconName: 'Info' };
|
||||
|
||||
const modelProps = {
|
||||
isBlocking: false,
|
||||
styles: { main: { maxWidth: 450 } },
|
||||
};
|
||||
|
||||
|
||||
export default class ReactAddformcustomizertolist extends React.Component<IReactAddformcustomizertolistProps, IReactAddformcustomizertolistState> {
|
||||
|
||||
private LOG_SOURCE = "ReactAddformcustomizertolist";
|
||||
private _sp: SPFI;
|
||||
|
||||
private dialogContentProps = {
|
||||
type: DialogType.largeHeader,
|
||||
title: 'Information!',
|
||||
subText: "",
|
||||
};
|
||||
constructor(props: IReactAddformcustomizertolistProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
sites: [],
|
||||
siteUrl: "",
|
||||
errors: [],
|
||||
contentTypes: [],
|
||||
NewForm: false,
|
||||
EditForm: false,
|
||||
ViewForm: false,
|
||||
disabled: false,
|
||||
selectedContnetType: "",
|
||||
selectedList: "",
|
||||
clientComponentID: "",
|
||||
isCalloutVisible: false,
|
||||
userMessage: "",
|
||||
hideDialog: true,
|
||||
chkCustomSiteUrl: false,
|
||||
};
|
||||
this._sp = getSP();
|
||||
this.onListPickerChange = this.onListPickerChange.bind(this);
|
||||
this.CTTypeChanged = this.CTTypeChanged.bind(this);
|
||||
this.onNewFormChange = this.onNewFormChange.bind(this);
|
||||
this.onEditFormChange = this.onEditFormChange.bind(this);
|
||||
this.onViewFormChange = this.onViewFormChange.bind(this);
|
||||
this.addFormCustomizer = this.addFormCustomizer.bind(this);
|
||||
this.removeFormCustomizer = this.removeFormCustomizer.bind(this);
|
||||
this.handleCCIDChange = this.handleCCIDChange.bind(this);
|
||||
this.toggleIsCalloutVisible = this.toggleIsCalloutVisible.bind(this);
|
||||
this.toggleHideDialog = this.toggleHideDialog.bind(this);
|
||||
this.onCustomSiteUrlChange = this.onCustomSiteUrlChange.bind(this);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IReactAddformcustomizertolistProps> {
|
||||
try {
|
||||
const {
|
||||
hasTeamsContext
|
||||
} = this.props;
|
||||
return (
|
||||
<section className={`${styles.reactAddformcustomizertolist} ${hasTeamsContext ? styles.teams : ''}`} >
|
||||
<h1 className={styles.headeClass}>Add form customizer to list</h1>
|
||||
<Stack tokens={stackTokens}>
|
||||
{
|
||||
!this.state.chkCustomSiteUrl && (
|
||||
<SitePicker
|
||||
context={this.props.context}
|
||||
label={'Select the site'}
|
||||
mode={'site'}
|
||||
allowSearch={true}
|
||||
multiSelect={false}
|
||||
onChange={(sites) => {
|
||||
console.log(sites);
|
||||
this.setState({ siteUrl: sites[0].url });
|
||||
this.setState({ sites: sites });
|
||||
this.setState({ contentTypes: [] });
|
||||
}}
|
||||
placeholder={'Select the site'}
|
||||
searchPlaceholder={'Choose the site'}
|
||||
selectedSites={this.state.sites}
|
||||
initialSites={this.state.sites}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
this.state.siteUrl && (<Label>{`Selected site url: ${this.state.siteUrl}`}</Label>)
|
||||
}
|
||||
|
||||
<Checkbox label="Custom Site" value={"Custom Site"} checked={this.state.chkCustomSiteUrl} onChange={this.onCustomSiteUrlChange} />
|
||||
{
|
||||
this.state.chkCustomSiteUrl && (
|
||||
<>
|
||||
<Label>Enter site url</Label>
|
||||
<TextField
|
||||
value={this.state.siteUrl}
|
||||
onChange={(e) => { this.handleCustomSiteUrlChange(e) }}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
<ListPicker context={this.props.context}
|
||||
label="Select the list"
|
||||
placeHolder="Select the list"
|
||||
baseTemplate={100}
|
||||
includeHidden={false}
|
||||
multiSelect={false}
|
||||
webAbsoluteUrl={this.state.siteUrl}
|
||||
onSelectionChanged={this.onListPickerChange}
|
||||
selectedList={this.state.selectedList}
|
||||
disabled={!(this.state.siteUrl)}
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
label="Select a content type"
|
||||
placeholder="Select a content type..."
|
||||
onChange={this.CTTypeChanged}
|
||||
options={this.state.contentTypes}
|
||||
required={true}
|
||||
selectedKey={this.state.selectedContnetType}
|
||||
/>
|
||||
|
||||
|
||||
<Stack horizontal tokens={chkstackTokens}>
|
||||
<Label required={true}>Client Component ID </Label>
|
||||
<IconButton
|
||||
id={"iconButtonId"}
|
||||
iconProps={iconProps}
|
||||
title="Info"
|
||||
ariaLabel="Info"
|
||||
onClick={this.toggleIsCalloutVisible}
|
||||
styles={iconButtonStyles}
|
||||
/>
|
||||
{this.state.isCalloutVisible && (
|
||||
<Callout
|
||||
target={'#' + "iconButtonId"}
|
||||
setInitialFocus
|
||||
onDismiss={this.toggleIsCalloutVisible}
|
||||
ariaDescribedBy={"description"}
|
||||
role="alertdialog"
|
||||
>
|
||||
<Stack tokens={stackTokens} horizontalAlign="start" styles={labelCalloutStackStyles}>
|
||||
<Image src={require('../assets/ClientComponentID.png')} alt='Client Component ID' height={400} width={500}></Image>
|
||||
<span id={"description"}>Enter the 'Client Component ID' present in form customizer manifest json file.</span>
|
||||
<DefaultButton onClick={this.toggleIsCalloutVisible}>Close</DefaultButton>
|
||||
</Stack>
|
||||
</Callout>
|
||||
)}
|
||||
</Stack>
|
||||
<TextField
|
||||
value={this.state.clientComponentID}
|
||||
onChange={(e) => { this.handleCCIDChange(e) }}
|
||||
/>
|
||||
|
||||
<Label>Select the required form to associate the customizer</Label>
|
||||
<Stack horizontal tokens={chkstackTokens} >
|
||||
<Checkbox label="New Form" value={"New Form"} checked={this.state.NewForm} onChange={this.onNewFormChange} />
|
||||
<Checkbox label="Edit Form" value={"Edit Form"} checked={this.state.EditForm} onChange={this.onEditFormChange} />
|
||||
<Checkbox label="View Form" value={"View Form"} checked={this.state.ViewForm} onChange={this.onViewFormChange} />
|
||||
</Stack>
|
||||
|
||||
|
||||
<Stack horizontal tokens={stackTokens}>
|
||||
<DefaultButton text="Associate" onClick={this.addFormCustomizer} allowDisabledFocus disabled={this.state.disabled} />
|
||||
<DefaultButton text="Remove Association" onClick={this.removeFormCustomizer} allowDisabledFocus disabled={this.state.disabled} />
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Dialog
|
||||
hidden={this.state.hideDialog}
|
||||
onDismiss={this.toggleHideDialog}
|
||||
dialogContentProps={this.dialogContentProps}
|
||||
modalProps={modelProps}
|
||||
>
|
||||
<Label>{this.state.userMessage}</Label>
|
||||
<DialogFooter>
|
||||
<DefaultButton onClick={this.toggleHideDialog} text="Close" />
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</section >
|
||||
);
|
||||
|
||||
} catch (err) {
|
||||
Logger.write(`${this.LOG_SOURCE} (render) - ${JSON.stringify(err)} - `, LogLevel.Error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private toggleHideDialog() {
|
||||
//this.dialogContentProps.subText = this.state.userMessage;
|
||||
this.setState({ hideDialog: !this.state.hideDialog });
|
||||
|
||||
}
|
||||
private toggleIsCalloutVisible() {
|
||||
this.setState({ isCalloutVisible: !this.state.isCalloutVisible })
|
||||
}
|
||||
|
||||
|
||||
private handleCustomSiteUrlChange(e: any) {
|
||||
this.setState({ siteUrl: e.target.value });
|
||||
}
|
||||
private handleCCIDChange(e: any) {
|
||||
this.setState({ clientComponentID: e.target.value });
|
||||
}
|
||||
|
||||
private onListPickerChange(list: string) {
|
||||
try {
|
||||
this.setState({ selectedList: list });
|
||||
this.setState({ contentTypes: [] });
|
||||
this._getContentTypes(list);
|
||||
}
|
||||
catch (err) {
|
||||
Logger.write(`${this.LOG_SOURCE} (onListPickerChange) - ${JSON.stringify(err)} - `, LogLevel.Error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private _getContentTypes = async (listNameorListId: string): Promise<void> => {
|
||||
try {
|
||||
|
||||
var ctTypes: { key: string, text: string }[] = [];
|
||||
ctTypes.push({ key: '', text: '' });
|
||||
const web = Web([this._sp.web, this.state.siteUrl]);
|
||||
const list = web.lists.getById(listNameorListId);
|
||||
const listCTTypes: IContentTypeInfo[] = await list.contentTypes();
|
||||
|
||||
for await (var currentCTType of listCTTypes) {
|
||||
var id = currentCTType.Id.StringValue.toString();
|
||||
ctTypes.push({ key: id, text: currentCTType.Name });
|
||||
}
|
||||
this.setState({ contentTypes: ctTypes });
|
||||
}
|
||||
catch (err) {
|
||||
Logger.write(`${this.LOG_SOURCE} (_getContentTypes) - ${JSON.stringify(err)} - `, LogLevel.Error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private CTTypeChanged(ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void {
|
||||
this.setState({ selectedContnetType: item.key ? item.key.toString() : "" })
|
||||
}
|
||||
|
||||
|
||||
private reloadWebpart = (): void => {
|
||||
this.setState({
|
||||
sites: [],
|
||||
siteUrl: "",
|
||||
errors: [],
|
||||
contentTypes: [],
|
||||
NewForm: false,
|
||||
EditForm: false,
|
||||
ViewForm: false,
|
||||
disabled: false,
|
||||
selectedContnetType: null,
|
||||
selectedList: null,
|
||||
clientComponentID: "",
|
||||
isCalloutVisible: false,
|
||||
chkCustomSiteUrl: false,
|
||||
//userMessage: "",
|
||||
//hideDialog: true,
|
||||
});
|
||||
}
|
||||
|
||||
private async addFormCustomizer() {
|
||||
var isValid = this.validedFormFields();
|
||||
|
||||
try {
|
||||
|
||||
if (isValid) {
|
||||
|
||||
var result = await this.addremoveFormCustomizer("add");
|
||||
if (!result.ok) {
|
||||
Logger.write(`Could not update content type - ${this.LOG_SOURCE}`, LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
//alert("Associated the form customiser with the selected list");
|
||||
//this.dialogContentProps.subText = "Associated the form customiser with the selected list.";
|
||||
this.setState({
|
||||
hideDialog: false,
|
||||
userMessage: "Associated the form customiser with the selected list."
|
||||
});
|
||||
//Modal dialog
|
||||
this.reloadWebpart();
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
//alert("Enter all the required fields");
|
||||
// this.dialogContentProps.subText = "Enter all the required fields.";
|
||||
this.setState({
|
||||
hideDialog: false,
|
||||
userMessage: "Enter all the required fields."
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
Logger.write(`${this.LOG_SOURCE} (addFormCustomizer) - ${JSON.stringify(err)} - `, LogLevel.Error);
|
||||
}
|
||||
}
|
||||
private validedFormFields() {
|
||||
var isFormValid = false;
|
||||
|
||||
if ((this.state.siteUrl && this.state.selectedList && this.state.clientComponentID &&
|
||||
this.state.selectedContnetType) &&
|
||||
(this.state.NewForm || this.state.EditForm || this.state.ViewForm)) {
|
||||
isFormValid = true;
|
||||
}
|
||||
|
||||
return isFormValid;
|
||||
}
|
||||
|
||||
private async removeFormCustomizer() {
|
||||
var isValid = this.validedFormFields();
|
||||
try {
|
||||
|
||||
if (isValid) {
|
||||
|
||||
var result = await this.addremoveFormCustomizer("remove");
|
||||
if (!result.ok) {
|
||||
Logger.write(`Could not update content type - ${this.LOG_SOURCE}`, LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
//alert("Removed the associated form customiser from the selected list");
|
||||
// this.dialogContentProps.subText = "Removed the associated form customiser from the selected list.";
|
||||
this.setState({
|
||||
hideDialog: false,
|
||||
userMessage: "Removed the associated form customiser from the selected list."
|
||||
});
|
||||
this.reloadWebpart();
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
// alert("Enter all the required fields");
|
||||
//this.dialogContentProps.subText = "Enter all the required fields.";
|
||||
this.setState({
|
||||
hideDialog: false,
|
||||
userMessage: "Enter all the required fields."
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
Logger.write(`${this.LOG_SOURCE} (addFormCustomizer) - ${JSON.stringify(err)} - `, LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async addremoveFormCustomizer(addorremove: string) {
|
||||
const web = Web([this._sp.web, this.state.siteUrl]);
|
||||
//conext
|
||||
const ctUrl = await web.lists.getById(this.state.selectedList).contentTypes.getById(this.state.selectedContnetType).toUrl();
|
||||
let bodyObject = {};
|
||||
|
||||
if (this.state.NewForm) {
|
||||
addorremove == "add" ? bodyObject["NewFormClientSideComponentId"] = this.state.clientComponentID : bodyObject["NewFormClientSideComponentId"] = "";
|
||||
}
|
||||
if (this.state.EditForm) {
|
||||
addorremove == "add" ? bodyObject["EditFormClientSideComponentId"] = this.state.clientComponentID : bodyObject["EditFormClientSideComponentId"] = "";
|
||||
}
|
||||
if (this.state.ViewForm) {
|
||||
addorremove == "add" ? bodyObject["DisplayFormClientSideComponentId"] = this.state.clientComponentID : bodyObject["DisplayFormClientSideComponentId"] = "";
|
||||
}
|
||||
|
||||
let result: SPHttpClientResponse = null;
|
||||
result = await this.props.context.spHttpClient.fetch(`${ctUrl}`, SPHttpClient.configurations.v1, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(bodyObject)
|
||||
});
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private onCustomSiteUrlChange(ev?: React.FormEvent<HTMLElement | HTMLInputElement>, isChecked?: boolean) {
|
||||
this.setState({ chkCustomSiteUrl: isChecked });
|
||||
}
|
||||
|
||||
private onNewFormChange(ev?: React.FormEvent<HTMLElement | HTMLInputElement>, isChecked?: boolean) {
|
||||
this.setState({ NewForm: isChecked });
|
||||
}
|
||||
private onEditFormChange(ev?: React.FormEvent<HTMLElement | HTMLInputElement>, isChecked?: boolean) {
|
||||
this.setState({ EditForm: isChecked });
|
||||
}
|
||||
|
||||
private onViewFormChange(ev?: React.FormEvent<HTMLElement | HTMLInputElement>, isChecked?: boolean) {
|
||||
this.setState({ ViewForm: isChecked });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// create File item to work with it internally
|
||||
export interface IFile {
|
||||
Id: number;
|
||||
Title: string;
|
||||
Name: string;
|
||||
Size: number;
|
||||
}
|
||||
|
||||
// create PnP JS response interface for File
|
||||
export interface IResponseFile {
|
||||
Length: number;
|
||||
}
|
||||
|
||||
// create PnP JS response interface for Item
|
||||
export interface IResponseItem {
|
||||
Id: number;
|
||||
File: IResponseFile;
|
||||
FileLeafRef: string;
|
||||
Title: string;
|
||||
}
|
11
samples/react-add-formcustomizer-to-list/src/webparts/reactAddformcustomizertolist/loc/en-us.js
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field",
|
||||
"AppLocalEnvironmentSharePoint": "The app is running on your local environment as SharePoint web part",
|
||||
"AppLocalEnvironmentTeams": "The app is running on your local environment as Microsoft Teams app",
|
||||
"AppSharePointEnvironment": "The app is running on SharePoint page",
|
||||
"AppTeamsTabEnvironment": "The app is running in Microsoft Teams"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
declare interface IReactAddformcustomizertolistWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
AppLocalEnvironmentSharePoint: string;
|
||||
AppLocalEnvironmentTeams: string;
|
||||
AppSharePointEnvironment: string;
|
||||
AppTeamsTabEnvironment: string;
|
||||
}
|
||||
|
||||
declare module 'ReactAddformcustomizertolistWebPartStrings' {
|
||||
const strings: IReactAddformcustomizertolistWebPartStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
|
||||
// import pnp and pnp logging system
|
||||
import { spfi, SPFI, SPFx } from "@pnp/sp";
|
||||
import { LogLevel, PnPLogging } from "@pnp/logging";
|
||||
import "@pnp/sp/webs";
|
||||
import "@pnp/sp/lists";
|
||||
import "@pnp/sp/items";
|
||||
import "@pnp/sp/batching";
|
||||
|
||||
var _sp: SPFI = null;
|
||||
|
||||
export const getSP = (context?: WebPartContext): SPFI => {
|
||||
if (_sp === null && context != null) {
|
||||
//You must add the @pnp/logging package to include the PnPLogging behavior it is no longer a peer dependency
|
||||
// The LogLevel set's at what level a message will be written to the console
|
||||
_sp = spfi().using(SPFx(context)).using(PnPLogging(LogLevel.Warning));
|
||||
}
|
||||
return _sp;
|
||||
};
|
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 542 B |
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-4.5/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": [
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection",
|
||||
"es2015.promise"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx"
|
||||
]
|
||||
}
|