Create jQuery Accordion using SharePoint Framework (spfx)

Today, I will be outlining steps to create an accordion SPFX webpart using jQuery in Sharepoint.
Final Output  (click to open)
 
Lets start with creating a new spfx project
 
1. Open terminal and create a folder for new project. 
  1. md jQwebpart
2. Navigate to the directory  
  1. cd jQwebpart  
 3. Run Yeoman command to generate a SharePoint project.
  1. yo @microsoft/sharepoint   
Add the below options for the prompts. 
Solution Name: JqWebPart
SharePoint Online only (latest)
Use the current folder
N to require the extension to be installed on each site explicitly when it's being used.
N on the question if solution contains unique permissions.
WebPart as the client-side component type to be created.
jQWebPart for the web part name, and select Enter.
jQuery Accodrion Web Part as the description of the web part, and select Enter.
No JavaScript framework option for the framework, and select Enter to continue.
 
 4. Install jquery and jqueryUI components
 
  1. npm install jquery@2 --save  
  2. npm install jquery-ui-dist  
  3. npm install bootstrap --save   
 
 5. Open vscode in the current folder from the terminal typing 
  1. code .  
 6. Open config\config.json file and paste below code, this will add references to javascript libraries.
 
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"people-search-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/peopleSearch/PeopleSearchWebPart.js",
"manifest": "./src/webparts/peopleSearch/PeopleSearchWebPart.manifest.json"
}
]
}
},
// "externals": {},
//reference to external library
"externals": {
"jquery": "node_modules/jquery/dist/jquery.min.js",
"jqueryui": "node_modules/jquery-ui-dist/jquery-ui.min.js",
"bootstrap": {
"path": "node_modules/bootstrap/dist/js/bootstrap.min.js",
"globalName": "bootstrap"
}
},
"localizedResources": {
"PeopleSearchWebPartStrings": "lib/webparts/jQWebPart/loc/{locale}.js"
}
}
Here in the externals section add the reference to jquery, jqueryUI, and bootstrap.
 
 7.  Go to src\webparts\jQWebPart\jQWebPart.ts
 Add below interfaces to work with list data from a Sharepoint list. 
// list models to start working with SharePoint list data
//The ISPList interface holds the SharePoint list information that we are connecting to.
export interface ISPLists {
value: ISPList[];
}

export interface ISPList {
Header: string;
Description: string;
ID?: number;
edit?: string;
}
 
8. Interface for definfing webpart property pane properties 
//creating a interface that will be used for assigning properties and type checking
export interface IPeopleSearchWebPartProps {
webpartTitle: string;
siteURL: string;
sourceList: string;
headerColumnName: string;
contentColumnName: string;
noOfItems: number;
}
 
9. Import Jquery, jqueryui and bootstrap to the module.

//import jquery and jquerry UI
import * as jQuery from "jquery";
import "jqueryui";
import * as bootstrap from "bootstrap";
 
10. Import the property pane field types.
import {
IPropertyPaneConfiguration,
PropertyPaneTextField, //import single line and multiline text field
PropertyPaneButton, //buttons
PropertyPaneCheckbox, //check boxes
PropertyPaneDropdown, //dropdown menu
PropertyPaneToggle, //toggle button
PropertyPaneLabel,
PropertyPaneSlider,
IPropertyPaneDropdownOption,
PropertyPaneLink,
PropertyPaneHorizontalRule,
PropertyPaneButtonType

//* tip, click ctrl +space to get the intelisense /suggestions
} from "@microsoft/sp-property-pane";
 
 
11. Add default values of the properties in jQWebPart.manifest.json file  
 
"preconfiguredEntries": [
{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "HR" }, //This is the group name that webpart appears on when you are adding webpart on the page
"title": { "default": "PeopleSearch" },
"description": { "default": "Employee Directory" },
// "officeFabricIconFontName": "ATPLogo",
"iconImageUrl": "https://static2.sharepointonline.com/files/fabric/assets/brand-icons/product-fluent/svg/onedrive_48x1.svg",

"properties": {
"webpartTitle": "SharePoint News",
"siteURL": "https://demo.sharepoint.com/sites/demosite/",
"sourceList": "Accordion",
"headerColumnName": "Header",
"contentColumnName": "Description"
}
}
]
}

//Notes:
//We define the default property of webpart properties fields in this file
 
12. Import SPComponentLoader to load external css files
//Load some external CSS files by using the module loader. Add the following import:
import { SPComponentLoader } from "@microsoft/sp-loader";
 
 13. Add a public constructor to load css from public cdn using SPComponentLoader
public constructor() {
super();

SPComponentLoader.loadCss(
"//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css"
);
SPComponentLoader.loadCss(
"//stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
);
SPComponentLoader.loadCss(
"//use.fontawesome.com/releases/v5.8.2/css/all.css"
);
}
 
14. Create a mock repository for testing in local workbench
In jQWebPart directory create a new file called MockHttpClient.ts and add below contents 
 
//import interface ISPList
//You do not need to type the file extension(.ts) when importing from the default module
import { ISPList } from "./PeopleSearchWebPart";

//It exports the MockHttpClient class as a default module so that it can be imported in other files
export default class MockHttpClient {
private static _items: ISPList[] = [
{
Header: "Working with SharePoint is fun",
Description: `Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit
amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut
odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.`,
ID: 1
},

{
Header: "Sharepoint development using SPFX",
Description: `Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet
purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor
velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In
suscipit faucibus urna. `,
ID: 2
},
{
Header: "Create jQuery Accordion using SPFX",
Description: `<p>
Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis.
Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero
ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis
lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui.
</p>
<ul>
<li>List item one</li>
<li>List item two</li>
<li>List item three</li>
</ul> `,
ID: 3
}
];
// It builds the initial ISPList mock array and returns.
public static get(): Promise<ISPList[]> {
return new Promise<ISPList[]>(resolve => {
resolve(MockHttpClient._items);
});
}
}

// You first need to import the MockHttpClient module to the default webpart module.
15. Import Mock data from MockHttpClient.ts fle . Add below code to the jQWebPart.ts file to import
//importing the mock array of items
import MockHttpClient from "./MockHttpClient";
16. Add below method to retrieve data from the mock list.
//private method that mocks the list retrieval
private _getMockListData(): Promise<ISPLists> {
return MockHttpClient.get().then((data: ISPList[]) => {
var listData: ISPLists = { value: data };
return listData;
}) as Promise<ISPLists>;
}
 
17. Import helper class for executing REST API calls. 
//helper class SPHttpClient is provided by SharePoint to execute REST API Requests.
import { SPHttpClient, SPHttpClientResponse } from "@microsoft/sp-http";
 
18. Add method to pull data from sharepoint list using rest API using above helper class
//private method to pull data from accordian list in sharepoint online
private _getListData(): Promise<ISPLists> {
return this.context.spHttpClient
.get(
this.context.pageContext.web.absoluteUrl +
`/_api/web/lists/GetByTitle('${
this.properties.sourceList
}')/items?top=${this.properties.noOfItems}&$select=ID,${
this.properties.headerColumnName
},${this.properties.contentColumnName}`,
SPHttpClient.configurations.v1
)
.then((response: SPHttpClientResponse) => {
return response.json();
});
}
 
19. Add method to render list items into html and initialize accordion. 
//private method to render list information received from REST API
private _renderList(items: ISPList[]): void {
let html: string = ``;

items.forEach((item: ISPList) => {
var url = `${this.context.pageContext.web.absoluteUrl}/lists/${
this.properties.sourceList
}/EditForm.aspx?ID=${item.ID}`;
html += `
<h3>${item.Header}</h3>
<div>
<p>${
item.Description
}<a class="float-right" target=\"_self"\ href=\"${url}"><i class="fas fa-edit"></i></a>
</p>
</div>`;
});
const listContainer: Element = this.domElement.querySelector(
"#spListContainer"
);
listContainer.innerHTML = html;

//add options
const accordionOptions: JQueryUI.AccordionOptions = {
animate: true,
collapsible: false,
icons: {
header: "ui-icon-circle-arrow-e",
activeHeader: "ui-icon-circle-arrow-s"
}
};
//initialize the accordian, class is .accordian and pass above options
jQuery(".accordion", this.domElement).accordion(accordionOptions);
}
 
20. Add below code to import the environemnt type module
//SharePoint Framework aids this capability by helping you understand which
//environment your web part is running from by using the EnvironmentType module.
import { Environment, EnvironmentType } from "@microsoft/sp-core-library";
21. Add method to call getMockListData or getListData from Sharepoint depending on the environment.
1//private method to call the respective methods to retrieve list data:
private _renderListAsync(): void {
// Local environment
if (Environment.type === EnvironmentType.Local) {
this._getMockListData().then(response => {
this._renderList(response.value);
});
} else if (
Environment.type == EnvironmentType.SharePoint ||
Environment.type == EnvironmentType.ClassicSharePoint
) {
this._getListData().then(response => {
this._renderList(response.value);
});
}
}
 
22. Replace the render method with below code
public render(): void {
this.domElement.innerHTML = `
<h3 class="">${escape(
this.context.pageContext.web.title
)}</h3>
<h4 class="p-3 mb-2 bg-dark text-white rounded">${
this.properties.webpartTitle
}</h4>
<div id="spListContainer" class="accordion"/>
</div>`;

this._renderListAsync();
}
 
23. Add Property pane fields validation method under the class
//method for validations
private validateFields(value: string): string {
if (value === null || value.trim().length === 0) {
return "Provide a value";
}

if (value.length > 40) {
return "Value should not be longer than 40 characters";
}

return "";
}
private cobWPPropButtonClick() {
alert("Property pane horozontal rule");
}
 
24.Replace the getPropertyPaneConfiguration method
 
//In this method we add new properties to pane and mamp them to their typed objects
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
//we can add them to multiple pages
pages: [
{
header: {
description: "SharePoint News WebPart"
},
//properties can be defined into groups,
groups: [
{
groupName: "News Settings",
groupFields: [
PropertyPaneTextField("webpartTitle", {
label: "Web Part Title",
onGetErrorMessage: this.validateFields

//where is this coming from?

//ctrl + space gives all possible ptions you can use here
}),

PropertyPaneTextField("siteURL", {
label: "Site URL",
onGetErrorMessage: this.validateFields
}),
PropertyPaneTextField("sourceList", {
label: "Source List Name",
onGetErrorMessage: this.validateFields
}),

PropertyPaneTextField("headerColumnName", {
label: "Field Name for accordion header",
onGetErrorMessage: this.validateFields
}),
PropertyPaneTextField("contentColumnName", {
label: "Field Name for accordion body",
onGetErrorMessage: this.validateFields
}),
// ^ note both multine line and single line use same field type

PropertyPaneSlider("noOfItems", {
label: "Max no of items to display",
min: 1,
max: 20,
step: 2,
showValue: true
//ctrl + space gives all possible options you can use here
}),
PropertyPaneHorizontalRule(),
PropertyPaneButton("", {
text: "View Details",
buttonType: PropertyPaneButtonType.Normal,
onClick: this.cobWPPropButtonClick
})
]
},
{
groupName: "Contact Us",
groupFields: [
PropertyPaneLink("", {
href: "https://www.google.com",
text: "Connect with us",
target: "_blank",
popupWindowProps: {
height: 500,
width: 500,
positionWindowPosition: 2,
title: "COB blog"
}
})
]
}
]
}
]
};
}
 
 
Below is the complete module file jQwebPart.ts Please use this to verify you did not miss any part and verify each piece of code goes where it is supposed to go. 
 
import { Version } from "@microsoft/sp-core-library";
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";

//import jquery and jquerry UI
import * as jQuery from "jquery";
import "jqueryui";
import * as bootstrap from "bootstrap";

//Load some external CSS files by using the module loader. Add the following import:
import { SPComponentLoader } from "@microsoft/sp-loader";
//We need to import all the field types we want to use for Web Part pane properties
import {
IPropertyPaneConfiguration,
PropertyPaneTextField, //import single line and multiline text field
PropertyPaneButton, //buttons
PropertyPaneCheckbox, //check boxes
PropertyPaneDropdown, //dropdown menu
PropertyPaneToggle, //toggle button
PropertyPaneLabel,
PropertyPaneSlider,
IPropertyPaneDropdownOption,
PropertyPaneLink,
PropertyPaneHorizontalRule,
PropertyPaneButtonType

//* tip, click ctrl +space to get the intelisense /suggestions
} from "@microsoft/sp-property-pane";

// we will use this escape function from lodash to escape html special characters
import { escape } from "@microsoft/sp-lodash-subset";

//importing css
import styles from "./jQWebPart.module.scss";
import * as strings from "jQWebPartStrings";

//importing the mock array of items
import MockHttpClient from "./MockHttpClient";

//helper class SPHttpClient is provided by SharePoint to execute REST API Requests.
import { SPHttpClient, SPHttpClientResponse } from "@microsoft/sp-http";

//SharePoint Framework aids this capability by helping you understand which
//environment your web part is running from by using the EnvironmentType module.
import { Environment, EnvironmentType } from "@microsoft/sp-core-library";

//creating a interface that will be used for assigning properties and type checking
export interface IjQWebPartProps {
webpartTitle: string;
siteURL: string;
sourceList: string;
headerColumnName: string;
contentColumnName: string;
noOfItems: number;
}

// list models to start working with SharePoint list data
//The ISPList interface holds the SharePoint list information that we are connecting to.
export interface ISPLists {
value: ISPList[];
}

export interface ISPList {
Header: string;
Description: string;
ID?: number;
}

//jQWebPart extends from class BaseClientSideWebPart with interface IjQWebPartProps using <>
export default class jQWebPart extends BaseClientSideWebPart<
IjQWebPartProps
> {
public constructor() {
super();

SPComponentLoader.loadCss(
"//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css"
);
SPComponentLoader.loadCss(
"//stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
);
SPComponentLoader.loadCss(
"//use.fontawesome.com/releases/v5.8.2/css/all.css"
);
}
//private method that mocks the list retrieval
private _getMockListData(): Promise<ISPLists> {
return MockHttpClient.get().then((data: ISPList[]) => {
var listData: ISPLists = { value: data };
return listData;
}) as Promise<ISPLists>;
}
//private method to pull data from accordian list in sharepoint online
private _getListData(): Promise<ISPLists> {
return this.context.spHttpClient
.get(
this.context.pageContext.web.absoluteUrl +
`/_api/web/lists/GetByTitle('${
this.properties.sourceList
}')/items?top=${this.properties.noOfItems}&$select=ID,${
this.properties.headerColumnName
},${this.properties.contentColumnName}`,
SPHttpClient.configurations.v1
)
.then((response: SPHttpClientResponse) => {
return response.json();
});
}
//private method to render list information received from REST API
private _renderList(items: ISPList[]): void {
let html: string = ``;

items.forEach((item: ISPList) => {
var url = `${this.context.pageContext.web.absoluteUrl}/lists/${
this.properties.sourceList
}/EditForm.aspx?ID=${item.ID}`;
html += `
<h3>${item.Header}</h3>
<div>
<p>${
item.Description
}<a class="float-right" target=\"_self"\ href=\"${url}"><i class="fas fa-edit"></i></a>
</p>
</div>`;
});
const listContainer: Element = this.domElement.querySelector(
"#spListContainer"
);
listContainer.innerHTML = html;

//add options
const accordionOptions: JQueryUI.AccordionOptions = {
animate: true,
collapsible: false,
icons: {
header: "ui-icon-circle-arrow-e",
activeHeader: "ui-icon-circle-arrow-s"
}
};
//initialize the accordian, class is .accordian and pass above options
jQuery(".accordion", this.domElement).accordion(accordionOptions);
}

//private method to call the respective methods to retrieve list data:
private _renderListAsync(): void {
// Local environment
if (Environment.type === EnvironmentType.Local) {
this._getMockListData().then(response => {
this._renderList(response.value);
});
} else if (
Environment.type == EnvironmentType.SharePoint ||
Environment.type == EnvironmentType.ClassicSharePoint
) {
this._getListData().then(response => {
this._renderList(response.value);
});
}
}

public render(): void {
this.domElement.innerHTML = `
<h3 class="">${escape(
this.context.pageContext.web.title
)}</h3>
<h4 class="p-3 mb-2 bg-dark text-white rounded">${
this.properties.webpartTitle
}</h4>
<div id="spListContainer" class="accordion"/>
</div>`;

this._renderListAsync();
}
//properties defined in IjQWebPartProps can be accessed using $(this.properties.nameOfProperty} )

//Note Above escape function imported from lodash library
// Notice that we are performing an HTML escape on the property's value to ensure a valid string
//escape Converts the characters "&", "<", ">", '"', and "'" in string to their corresponding HTML entities.

protected get dataVersion(): Version {
return Version.parse("1.0");
}

//method for validations
private validateFields(value: string): string {
if (value === null || value.trim().length === 0) {
return "Provide a value";
}

if (value.length > 40) {
return "Value should not be longer than 40 characters";
}

return "";
}
private cobWPPropButtonClick() {
alert("Property pane horozontal rule");
}

//In this method we add new properties to pane and mamp them to their typed objects
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
//we can add them to multiple pages
pages: [
{
header: {
description: "SharePoint News WebPart"
},
//properties can be defined into groups,
groups: [
{
groupName: "News Settings",
groupFields: [
PropertyPaneTextField("webpartTitle", {
label: "Web Part Title",
onGetErrorMessage: this.validateFields

//where is this coming from?

//ctrl + space gives all possible ptions you can use here
}),

PropertyPaneTextField("siteURL", {
label: "Site URL",
onGetErrorMessage: this.validateFields
}),
PropertyPaneTextField("sourceList", {
label: "Source List Name",
onGetErrorMessage: this.validateFields
}),

PropertyPaneTextField("headerColumnName", {
label: "Field Name for accordion header",
onGetErrorMessage: this.validateFields
}),
PropertyPaneTextField("contentColumnName", {
label: "Field Name for accordion body",
onGetErrorMessage: this.validateFields
}),
// ^ note both multine line and single line use same field type

PropertyPaneSlider("noOfItems", {
label: "Max no of items to display",
min: 1,
max: 20,
step: 2,
showValue: true
//ctrl + space gives all possible options you can use here
}),
PropertyPaneHorizontalRule(),
PropertyPaneButton("", {
text: "View Details",
buttonType: PropertyPaneButtonType.Normal,
onClick: this.cobWPPropButtonClick
})
]
},
{
groupName: "Contact Us",
groupFields: [
PropertyPaneLink("", {
href: "https://www.google.com",
text: "Connect with us",
target: "_blank",
popupWindowProps: {
height: 500,
width: 500,
positionWindowPosition: 2,
title: "COB blog"
}
})
]
}
]
}
]
};
}
}
 
 
25. Now your webpart is ready to be bundled and shipped. Run gulp serve and open in local workbench and live workbench. 
In local workbench it will fetch data from mock list and in live sharepoint, you need to create a custom list and add few contents.
  1. gulp serve  
Details for Custom List 
Name of the list: Accordion
Fields:
1. Header (single line)
2. Description (multiline)
 
And add a few new items to the list that you want to display.
 
 
26.Configuring the webpart properties:
 
You can change the name of the list, column name for header and description and number of list items to be fetched.
 
27. Packaging and bundling the solution
 
  1. gulp bundle --ship  
  2. gulp package-solution --ship or   
  3. gulp package-solution --production   
gulp bundle --ship: builds the minified assets required to upload to the CDN provider. The --ship indicates the build tool to build for distribution. You should also notice that the output of the build tools indicate the Build Target is SHIP.
The minified assets can be found under the temp\deploy directory.
 
 28. You can upload .sppkg file from jQWebPart\sharepoint\solution to App Catalog in SharePoint online
.
Thanks for following, please leave comments if you have any questions or suggestions.