Merge pull request #1241 from siddharth-vaghasia/react-js-css-ref-sample

Added new sample webpart which add js, css file references to modern pages.
This commit is contained in:
Hugo Bernier 2020-04-24 22:05:23 -04:00 committed by GitHub
commit 59111889cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 19675 additions and 0 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-add-js-css-ref/.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,12 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": false,
"environment": "spo",
"version": "1.9.1",
"libraryName": "react-add-js-css-ref",
"libraryId": "d9c30e1a-bf06-46fa-807d-ce5182d9c91c",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,96 @@
## SPFx webpart to add JS and CSS reference on Modern Pages via SPFx application customizer extension
This repo is a react based SPFx web part and extension that allows users to add/modify/delete custom js and css file references using SPFx application customizer extension all modern pages within SP online site. This web part provides an interface to JS and CSS file references so that we don't have to modify code when we need to change references or add new references in the future. As part of security measures, this actions on web part can be only accessed by users who have Manage web permission on site.
WebPart in Action
![Webpart in action](assets/webpartinaction.gif?raw=true "Webpart in action")
Challanges/Drawback with ONLY using SPFx extension for adding js and css file references.
* JS and CSS file refereces links needs to be hardcoded in solution
* Changes to code required if we need to change add new reference or remove existing reference.
* Redeployment of package and installation
* Diffrent solution would be required for diffrent site collections as we would defintely need diffrent header js and css file refereces for each site collection(most of case)
* High maintananence and time consuming for simple task.
To overcome this drawbacks, this solution comes handy. This is resuable component which can be used by developers to eliminate creating Extension on thier own. Feel free to connect on twitter:@siddh_me for any details.
### Features of solution
* WebPart to configure JS and CSS file reference.
* Edit functionality if atleast one JS or CSS reference is already added via this solution
* Completely remove all the references added via this solution
* Support for relative url also, if your js and css file is refered from some document library in same site collection.
Path can be '/sites/mysc/style library/js/custom.js' or '/sites/mysc/style library/css/custom.css'
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-1.9.1-green.svg)
## Applies to
* [SharePoint Framework](http://dev.office.com/sharepoint/docs/spfx/sharepoint-framework-overview)
* [Office 365 tenant](http://dev.office.com/sharepoint/docs/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](./sharepoint/solutions/react-add-js-css-ref.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 webpart.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 sucessfully.
* Install app to required site collection
* Create new modern page. Add 'addJsCssReference' webpart to page. Publish the page.
* Use grid to add js and css file references, both are seperate sections.
* On Success message - Refresh the page and you would see your js and css files will be loaded.
* To Edit/Remove, go to same page again and Use 'Activate' or 'Deactivate'.
* Only Users with Manage Web permission will be able to access webpart and add/modify references.
### High level design of Solution
* SPFx solution with 2 components 1. SPFx Webpart 2. SPFx Extension Application Customizer
* Disables Automatic activation of SPFx extension when app is installed.
* React based solution
* Register Custom action with ClientSideComponentId of Extension component
* Passes parameters to Extension with ClientSideComponentProperties
## Solution
Solution|Author(s)
--------|---------
react-add-js-css-ref | [Siddharth Vaghasia](https://www.linkedin.com/in/siddharthvaghasia/)
## Version history
Version|Date|Comments
-------|----|--------
1.0.0|Apr 24, 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.**
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-js-css-ref" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

View File

@ -0,0 +1,28 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"reference-injector-application-customizer": {
"components": [
{
"entrypoint": "./lib/extensions/referenceInjector/ReferenceInjectorApplicationCustomizer.js",
"manifest": "./src/extensions/referenceInjector/ReferenceInjectorApplicationCustomizer.manifest.json"
}
]
},
"add-js-css-reference-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/addJsCssReference/AddJsCssReferenceWebPart.js",
"manifest": "./src/webparts/addJsCssReference/AddJsCssReferenceWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"ReferenceInjectorApplicationCustomizerStrings": "lib/extensions/referenceInjector/loc/{locale}.js",
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js",
"AddJsCssReferenceWebPartStrings": "lib/webparts/addJsCssReference/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-add-js-css-ref",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-add-js-css-ref-client-side-solution",
"id": "d9c30e1a-bf06-46fa-807d-ce5182d9c91c",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"isDomainIsolated": false,
"features": [
{
"title": "Application Extension - Deployment of custom action.",
"description": "Deploys a custom action with ClientSideComponentId association",
"id": "201c7969-a60d-492a-83d2-db3088515c51",
"version": "1.0.0.0",
"assets": {
"elementManifests": [
"elements.xml",
"clientsideinstance.xml"
]
}
}
]
},
"paths": {
"zippedPackage": "solution/react-add-js-css-ref.sppkg"
}
}

View File

@ -0,0 +1,34 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"https": true,
"serveConfigurations": {
"default": {
"pageUrl": "https://contoso.sharepoint.com/sites/mysc/_layouts/15/viewlsts.aspx",
"customActions": {
"38afa8d7-b498-4529-9f99-6279392f9309": {
"location": "ClientSideExtension.ApplicationCustomizer",
"properties": {
"testMessage": "Test message"
}
}
}
},
"referenceInjector": {
"pageUrl": "https://contoso.sharepoint.com/sites/mysc/_layouts/15/viewlsts.aspx",
"customActions": {
"38afa8d7-b498-4529-9f99-6279392f9309": {
"location": "ClientSideExtension.ApplicationCustomizer",
"properties": {
"testMessage": "Test message"
}
}
}
}
},
"initialPage": "https://localhost:5432/workbench",
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

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,46 @@
{
"name": "react-add-js-css-ref",
"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": {
"@microsoft/decorators": "1.9.1",
"@microsoft/sp-application-base": "1.9.1",
"@microsoft/sp-core-library": "1.9.1",
"@microsoft/sp-dialog": "1.9.1",
"@microsoft/sp-lodash-subset": "1.9.1",
"@microsoft/sp-office-ui-fabric-core": "1.9.1",
"@microsoft/sp-webpart-base": "1.9.1",
"@pnp/sp": "^2.0.3",
"@pnp/spfx-controls-react": "1.16.0",
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"office-ui-fabric-react": "6.189.2",
"react": "16.8.5",
"react-dom": "16.8.5"
},
"devDependencies": {
"@microsoft/sp-build-web": "1.9.1",
"@microsoft/sp-tslint-rules": "1.9.1",
"@microsoft/sp-module-interfaces": "1.9.1",
"@microsoft/sp-webpart-workbench": "1.9.1",
"@microsoft/rush-stack-compiler-2.9": "0.7.16",
"gulp": "~3.9.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2"
},
"resolutions": {
"@types/react": "16.8.8"
}
}

View File

@ -0,0 +1,17 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-extension-manifest.schema.json",
"id": "38afa8d7-b498-4529-9f99-6279392f9309",
"alias": "ReferenceInjectorApplicationCustomizer",
"componentType": "Extension",
"extensionType": "ApplicationCustomizer",
// 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
}

View File

@ -0,0 +1,56 @@
import { override } from '@microsoft/decorators';
import { Log } from '@microsoft/sp-core-library';
import {
BaseApplicationCustomizer
} from '@microsoft/sp-application-base';
import { Dialog } from '@microsoft/sp-dialog';
import * as strings from 'ReferenceInjectorApplicationCustomizerStrings';
const LOG_SOURCE: string = 'ReferenceInjectorApplicationCustomizer';
/**
* If your command set uses the ClientSideComponentProperties JSON input,
* it will be deserialized into the BaseExtension.properties object.
* You can define an interface to describe it.
*/
export interface IReferenceInjectorApplicationCustomizerProperties {
// This is an example; replace with your own property
jsfiles:any[];
cssfiles:any[];
}
/** A Custom Action which can be run during execution of a Client Side Application */
export default class ReferenceInjectorApplicationCustomizer
extends BaseApplicationCustomizer<IReferenceInjectorApplicationCustomizerProperties> {
@override
public onInit(): Promise<void> {
Log.info(LOG_SOURCE, `Initialized ${strings.Title}`);
if(this.properties.jsfiles)
{
this.properties.jsfiles.forEach(element => {
let JsTag: HTMLScriptElement = document.createElement("script");
JsTag.src = element.FilePath;
JsTag.type = "text/javascript";
document.body.appendChild(JsTag);
});
}
if(this.properties.cssfiles){
this.properties.cssfiles.forEach(element => {
let cssLink: HTMLLinkElement = document.createElement("link");
cssLink.href = element.FilePath;
cssLink.type = "text/css";
cssLink.rel = "stylesheet";
document.body.appendChild(cssLink);
});
}
return Promise.resolve();
}
}

View File

@ -0,0 +1,5 @@
define([], function() {
return {
"Title": "ReferenceInjectorApplicationCustomizer"
}
});

View File

@ -0,0 +1,8 @@
declare interface IReferenceInjectorApplicationCustomizerStrings {
Title: string;
}
declare module 'ReferenceInjectorApplicationCustomizerStrings' {
const strings: IReferenceInjectorApplicationCustomizerStrings;
export = strings;
}

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "9fa13c84-2736-4413-be49-e163b796b143",
"alias": "AddJsCssReferenceWebPart",
"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": "AddJsCssReference" },
"description": { "default": "AddJsCssReference description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "AddJsCssReference"
}
}]
}

View File

@ -0,0 +1,61 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import * as strings from 'AddJsCssReferenceWebPartStrings';
import AddJsCssReference from './components/AddJsCssReference';
import { IAddJsCssReferenceProps } from './components/IAddJsCssReferenceProps';
export interface IAddJsCssReferenceWebPartProps {
description: string;
}
export default class AddJsCssReferenceWebPart extends BaseClientSideWebPart<IAddJsCssReferenceWebPartProps> {
public render(): void {
const element: React.ReactElement<IAddJsCssReferenceProps > = React.createElement(
AddJsCssReference,
{
description: this.properties.description,
context:this.context
}
);
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
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,91 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.addJsCssReference {
.container {
// max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
padding: 30px;
}
.row {
@include ms-Grid-row;
@include ms-fontColor-black;
// background-color: $ms-color-themeDark;
// padding: 20px;
}
.column {
// @include ms-Grid-col;
@include ms-lg12;
// @include ms-xl8;
// @include ms-xlPush2;
// @include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-black;
font-weight: 500;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-black;
}
.description {
@include ms-font-l;
@include ms-fontColor-black;
}
.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;
}
}
.customicons{
font-size: 14px;
color:$ms-color-themePrimary;
}
.filepath{
font-size: 14px;
}
.ms-DetailsList{
font-size: 14px;
}
}

View File

@ -0,0 +1,491 @@
import * as React from 'react';
import styles from './AddJsCssReference.module.scss';
import { IAddJsCssReferenceProps } from './IAddJsCssReferenceProps';
import { escape } from '@microsoft/sp-lodash-subset';
import { TextField, MaskedTextField } from 'office-ui-fabric-react/lib/TextField';
import { ListView, IViewField, SelectionMode} from "@pnp/spfx-controls-react/lib/ListView";
import {MessageBarType,Link,Separator, CommandBarButton,IStackStyles,Text,MessageBar,PrimaryButton,DefaultButton,Dialog,DialogFooter,DialogType,Stack, IStackTokens, updateA } from 'office-ui-fabric-react';
import { sp} from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/user-custom-actions";
import "@pnp/sp/presets/all";
import {TypedHash} from "@pnp/common";
import { IUserCustomActionAddResult,IUserCustomActionUpdateResult,IUserCustomAction } from '@pnp/sp/user-custom-actions';
import { createTheme, ITheme } from 'office-ui-fabric-react/lib/Styling';
import { mergeStyleSets } from 'office-ui-fabric-react/lib/Styling';
import { PermissionKind } from '@pnp/sp/presets/all';
const stackTokens: IStackTokens = { childrenGap: 40 };
const CustomActionTitle = 'JSCssAppCustomizer';
const ApplicationCustomizerComponentID = '38afa8d7-b498-4529-9f99-6279392f9309';
const description = 'This user action is of type application customizer to custom js and css file references via SFPx extension';
const theme: ITheme = createTheme({
fonts: {
medium: {
// fontFamily: 'Monaco, Menlo, Consolas',
fontSize: '18px'
}
}
});
const stackStyles: Partial<IStackStyles> = { root: { height: 30 } };
export interface IAddJsCssReferenceState {
disableRegisterButton: boolean;
disableRemoveButton: boolean;
jsfiles:any[];
cssfiles:any[];
currentjsRef:string;
currentcssRef:string;
hideJSDailog:boolean;
hideCSSDailog:boolean;
currentCustomAction:any;
isEdit:boolean;
editIndex:number;
showMesssage:boolean;
successmessage:string;
userHasPermission:boolean;
}
export default class AddJsCssReference extends React.Component<IAddJsCssReferenceProps, IAddJsCssReferenceState> {
private viewFields: any[] = [
{
name: "Type",
displayName: "Action",
minWidth: 60,
maxWidth:60,
render: (item,index) =>{
console.log(item);
return (
<React.Fragment>
<Stack horizontal tokens={{childrenGap:20}}>
<i className={"ms-Icon ms-Icon--Edit " + styles.customicons} onClick={()=> this.editClicked(item,index)} aria-hidden="true"></i>
<i className={"ms-Icon ms-Icon--Delete " + styles.customicons} onClick={()=> this.deleteClicked(item,index)} aria-hidden="true"></i>
</Stack>
</React.Fragment>
);
},
className:"test"
},
{
name: "FilePath",
displayName: "FilePath",
minWidth:600,
render: (item,index) =>{
console.log(item);
return (
<React.Fragment>
<span className={styles.filepath}>
{item.FilePath}
</span>
</React.Fragment>
);
}
// maxWidth:800
}
];
constructor(props: IAddJsCssReferenceProps,state:IAddJsCssReferenceProps) {
super(props);
this.state = {
disableRegisterButton:false,
disableRemoveButton:false,
jsfiles:[],
cssfiles:[],
currentjsRef:"",
currentcssRef:"",
hideJSDailog:true,
hideCSSDailog:true,
currentCustomAction:null,
isEdit:false,
editIndex:-1,
showMesssage:false,
successmessage:"",
userHasPermission:false
};
sp.setup(this.props.context);
}
public render(): React.ReactElement<IAddJsCssReferenceProps> {
return (
<React.Fragment>
{this.state.userHasPermission &&
<div className={styles.addJsCssReference}>
<div className={ styles.container }>
<div className={ styles.row }>
<div className={ styles.column }>
<span className={ styles.title }>SPFx JS CSS References WebPart</span>
<p className={ styles.subTitle }>This webpart can be used to add reference to custom js files and css files via SPFx extension application customizer.</p>
</div>
<div className={ styles.row }>
<div className={ styles.column }>
{this.state.showMesssage &&
<MessageBar dismissButtonAriaLabel="Close" onDismiss={()=>{ this.setState({showMesssage:false});}} messageBarType={MessageBarType.success}>
{this.state.successmessage}
</MessageBar>
}
{this.state.currentCustomAction && this.state.showMesssage != true &&
<MessageBar >
We found you already have some custom js and css files references added via this customizer. Feel free to Edit or Remove references.
</MessageBar>
}
<div id="jsfiles">
<Separator></Separator>
<Stack horizontal styles={stackStyles} tokens={stackTokens}>
<Text theme={theme}>Javascript Files</Text>
<CommandBarButton iconProps={{iconName: 'Add'}} text="Add JS Link" onClick={()=>this.openAddJSDailog()} />
</Stack>
<Separator></Separator>
{/* <PrimaryButton text="Add New Item" } /> */}
{this.state.jsfiles.length === 0 &&
<React.Fragment>
<MessageBar>
No References Found.
<Link href="#" onClick={()=>this.openAddJSDailog()}>
Click here
</Link>
<Text> to add new.</Text>
</MessageBar>
<br/>
</React.Fragment>
}
{this.state.jsfiles.length >0 &&
<ListView
items={this.state.jsfiles}
viewFields={this.viewFields}
/>
}
<Dialog
minWidth={600}
maxWidth={900}
hidden={this.state.hideJSDailog}
onDismiss={this._closeJSDialog}
dialogContentProps={{
type: DialogType.normal,
title: 'Add JS Reference',
// subText: 'Enter a valid JS file link.'
}}
modalProps={{
isBlocking: false,
// styles: { main: { maxWidth: 450 } }
}}
>
<TextField required onChange={evt => this.updateJSValue(evt)} value={this.state.currentjsRef} label="URL" resizable={false} />
<DialogFooter>
<PrimaryButton onClick={()=>this._addJsReference()} text="Add" />
<DefaultButton onClick={this._closeJSDialog} text="Cancel" />
</DialogFooter>
</Dialog>
</div>
<div id="cssfiles">
<br/>
<Stack horizontal styles={stackStyles} tokens={stackTokens}>
<Text theme={theme}>CSS Files</Text>
<CommandBarButton iconProps={{iconName: 'Add'}} text="Add CSS Link" onClick={()=>this.openAddCSSDailog()} />
</Stack>
{/* <PrimaryButton text="Add New Item" onClick={()=>this.openAddCSSDailog()} /> */}
<Separator></Separator>
{this.state.cssfiles.length === 0 &&
<React.Fragment>
<MessageBar>
No References Found.
<Link href="#" onClick={()=>this.openAddCSSDailog()}>
Click here
</Link>
<Text> to add new.</Text>
</MessageBar>
<br/>
</React.Fragment>
}
{this.state.cssfiles.length > 0 &&
<ListView
items={this.state.cssfiles}
viewFields={this.viewFields}
// iconFieldName="ServerRelativeUrl"
// selectionMode={SelectionMode.multiple}
// selection={this._getSelection}
/>
}
<Dialog
minWidth={600}
maxWidth={900}
hidden={this.state.hideCSSDailog}
onDismiss={this._closeCSSDialog}
dialogContentProps={{
type: DialogType.normal,
title: 'Add CSS Reference',
// subText: 'Enter a valid CSS file link.'
}}
modalProps={{
isBlocking: false,
// styles: { main: { minWidth: 500 !important ,width:500} }
}}
>
<TextField required onChange={evt => this.updateCSSValue(evt)} value={this.state.currentcssRef} label="URL" />
<DialogFooter>
<PrimaryButton onClick={()=>this._addCSSReference()} text="Add" />
<DefaultButton onClick={this._closeCSSDialog} text="Cancel" />
</DialogFooter>
</Dialog>
</div>
<br/>
<Stack horizontal tokens={stackTokens}>
<PrimaryButton text="Activate" onClick={()=>this._registerClicked()} disabled={(this.state.jsfiles.length>0 || this.state.cssfiles.length>0 )?false:true} />
<DefaultButton text="Deactivate" onClick={()=> this._removeClicked()} disabled={this.state.currentCustomAction==null?true:false} />
</Stack>
</div>
</div>
</div>
</div>
<div>
</div>
</div>
}
{
<MessageBar messageBarType={MessageBarType.severeWarning}>
Access denied, you do not have permission to access this section. Please connect with your site admins.
</MessageBar>
}
</React.Fragment>
);
}
public componentDidMount() {
this.checkPermisson();
}
private async checkPermisson(){
const perms2 = await sp.web.currentUserHasPermissions(PermissionKind.ManageWeb);
console.log(perms2);
var temp = false;
if(temp){
this.getCustomAction();
}
}
private _registerClicked(): void {
this.setCustomAction();
}
private _removeClicked(): void {
const uca = sp.web.userCustomActions.getById(this.state.currentCustomAction.Id);
const response = uca.delete();
console.log("removed custom action");
console.log(response);
this.setState({currentCustomAction:null,jsfiles:[],cssfiles:[],
showMesssage:true,successmessage:"Application Customizer deactivated sucessfully."});
}
private updateJSValue(evt) {
this.setState({
currentjsRef: evt.target.value
});
}
private updateCSSValue(evt) {
this.setState({
currentcssRef: evt.target.value
});
}
private editClicked (item,index){
if(item.Type == "js") {
this.setState({hideJSDailog:false,
currentjsRef:item.FilePath,
isEdit:true,
editIndex:index
});
}
if(item.Type == "css") {
this.setState({hideCSSDailog:false,
currentcssRef:item.FilePath,
isEdit:true,
editIndex:index
});
}
}
private deleteClicked (item,index){
if(item.Type == "css") {
let currentitems = this.state.cssfiles.map((x) => x);
currentitems.splice(index,1);
this.setState({cssfiles:currentitems});
}
else if(item.Type == "js"){
let currentitems = this.state.jsfiles.map((x) => x);
currentitems.splice(index,1);
this.setState({jsfiles:currentitems});
}
}
private openAddJSDailog (){
this.setState({hideJSDailog:false});
}
private openAddCSSDailog (){
this.setState({hideCSSDailog:false});
}
private _addJsReference (){
if(!this.state.isEdit){
var item = {
FilePath:this.state.currentjsRef,
Type: "js"
};
let currentitems = this.state.jsfiles.map((x) => x);
currentitems.push(item);
currentitems[this.state.jsfiles.length] = item;
this.setState({jsfiles:currentitems,
hideJSDailog:true,currentjsRef:""});
}
else{
item = {
FilePath:this.state.currentjsRef,
Type: "js"
};
let currentitems = this.state.jsfiles.map((x) => x);
currentitems[this.state.editIndex] = item;
this.setState({jsfiles:currentitems,
hideJSDailog:true,currentjsRef:"",
isEdit:false,editIndex:-1});
}
}
private _addCSSReference (){
if(!this.state.isEdit){
console.log("add item to grid");
var item = {
FilePath:this.state.currentcssRef,
Type:"css"
};
let currentitems = this.state.cssfiles.map((x) => x);
currentitems.push(item);
currentitems[this.state.cssfiles.length] = item;
this.setState({cssfiles:currentitems,
hideCSSDailog:true,
currentcssRef:""});
}
else{
console.log("add item to grid");
item = {
FilePath:this.state.currentcssRef,
Type:"css"
};
let currentitems = this.state.cssfiles.map((x) => x);
currentitems[this.state.editIndex] = item;
this.setState({cssfiles:currentitems,
hideCSSDailog:true,
currentcssRef:"",
editIndex:-1,isEdit:false});
}
}
private _closeJSDialog = (): void => {
this.setState({ hideJSDailog: true });
}
private _closeCSSDialog = (): void => {
this.setState({ hideCSSDailog: true });
}
private async getCustomAction(){
var web = await sp.web.get();
console.log(web);
var customactions:any = await sp.web.userCustomActions.get();
console.log(customactions);
var found = customactions.filter(item => item.Title == CustomActionTitle);
if (found.length > 0) {
this.setState({currentCustomAction:found[0]});
var jsonproperties = found[0].ClientSideComponentProperties;
var jsfileArray = JSON.parse(jsonproperties).jsfiles;
var cssfileArray = JSON.parse(jsonproperties).cssfiles;
console.log(jsfileArray);
console.log(cssfileArray);
this.setState({jsfiles:jsfileArray,cssfiles:cssfileArray});
}
}
protected async setCustomAction() {
try {
const payload: TypedHash<string> = {
"Title": CustomActionTitle,
"Description": description,
"Location": 'ClientSideExtension.ApplicationCustomizer',
ClientSideComponentId:ApplicationCustomizerComponentID,
ClientSideComponentProperties: JSON.stringify({jsfiles:this.state.jsfiles,cssfiles:this.state.cssfiles }),
};
if(this.state.currentCustomAction == null) {
const response : IUserCustomActionAddResult = await sp.web.userCustomActions.add(payload);
console.log(response);
const uca = await sp.web.userCustomActions.getById(response.data.Id);
this.setState({currentCustomAction: uca,showMesssage:true,successmessage:"Application customizer activated sucessfully."});
}
else{
const uca = sp.web.userCustomActions.getById(this.state.currentCustomAction.Id);
const response: IUserCustomActionUpdateResult =await uca.update(payload);
const ucaupdated = await sp.web.userCustomActions.getById(response.data.Id);
this.setState({currentCustomAction: ucaupdated,showMesssage:true,successmessage:"Application customizer updated sucessfully."});
}
} catch (error) {
console.error(error);
}
}
}

View File

@ -0,0 +1,6 @@
import { WebPartContext } from "@microsoft/sp-webpart-base";
export interface IAddJsCssReferenceProps {
description: string;
context:WebPartContext;
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field"
}
});

View File

@ -0,0 +1,10 @@
declare interface IAddJsCssReferenceWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'AddJsCssReferenceWebPartStrings' {
const strings: IAddJsCssReferenceWebPartStrings;
export = strings;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,38 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-2.9/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"
],
"exclude": [
"node_modules",
"lib"
]
}

View File

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