I’m using spfx extensions to customize header and footer, but not customize body content or main content of page in sharepoint online.
HI..HERE U HAVE A SOLUTION
So, let’s get started…
System Requirements:
- Node .js
- Npm package manager
- Yeoman and gulp
Install and setup the environment as mentioned in this link.
Setup your Project;
- Create Project Folder
md TestForm
- Go to project folder
cd TestForm
- Create a new web part by running the Yeoman SharePoint Generator
yo @microsoft/SharePoint
- When prompted, select/fill as follows:
- After this, it takes some time for yeoman to setup the project depending on the connection speed.
- Once the project is setup successfully, you should see a screen similar to the one below:
- Now you can use any code editor of your choice to edit the webpart code. I prefer using Visual Studio Code for the purpose. It can be downloaded from this link.
- Install gulp dev certificate (This needs to done only once in your system)
gulp trust-dev-cert
- After the dev certificate is installed, run the following to start a local node server with an instance of SharePoint workbench to deploy and preview the webpart.
gulp serve
- Once the workbench is up, click on the Add button to open the webparts list available and select your web part to add to the page.
- Add Webpart for page preview
- To open the webpart for preview directly in your SharePoint tenant, go to:
https://<<Sharepoint site URL>>/_layouts/15/workbench.aspx
This will open an instance of then SharePoint workbench like the one above and you can add your webpart there to directly interact with your SharePoint lists and libraries. But unlike the local workbench which automatically refreshes each time the page code changes (mind you, gulp serve should be running), the SharePoint workbench must be manually refreshed to see changes.
- Let’s start customizing our webpart.
Test Webpart Requirements:
- Custom form
- Textbox for Activity Name
- Date picker for activity date
- People picker control for selecting user assigned with activity
- 2 dropdowns for selecting Category and Sub Category
Customizing the webpart:
- Open the solution using Visual Studio Code:
- I won’t be going through the details of the solution structure and so on. Details on that can be found here.
- For our webpart we will be using jQuery, Bootstrap (for UI) and sp-pnp.js for interaction with SharePoint lists.
- Install sp-pnp.js using:
npm install sp-pnp-js –save
- Install jquery using :
npm install –save @types/jquery@3 -D
- We also need the following for our webpart:
- Sp.peoplepicker.js and other required files from here
- Jquery-ui from here
- Create a folder for css and one for scripts under src/webparts/testform and copy all css and js files into the respective folders.
These directories with files will be copied automatically to lib under same hierarchy when project is built for distribution.
Open config.json under config and add the following in externals attribute:
- If gulp serve is running, stop and restart as any changes in config.json need gulp serve to be rerun .
- Open TestFormWebPart.ts file under src/webparts/testForm and add the following lines to import various dependencies:
<code>import { SPComponentLoader } from '@microsoft/sp-loader'; import pnp, { sp, Item, ItemAddResult, ItemUpdateResult, Web } from 'sp-pnp-js'; import * as $ from 'jquery'; require('bootstrap'); require('./css/jquery-ui.css'); let cssURL = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"; SPComponentLoader.loadCss(cssURL); SPComponentLoader.loadScript("https://ajax.aspnetcdn.com/ajax/4.0/1/MicrosoftAjax.js"); require('appjs'); require('sppeoplepicker'); require('jqueryui');</code>
- Also modify the sp-core library import to fetch URL parameters, if required:
<code>import { UrlQueryParameterCollection, Version } from '@microsoft/sp-core-library';</code>
- Overwrite the render() method with the following:
<code>public render(): void { this.domElement.innerHTML = ` <div id="container" class="container"> <div class="panel"> <div class="panel-body"> <div class="row"> <div class="col-lg-4 control-padding"> <label>Activity</label> <input type='textbox' name='txtActivity' id='txtActivity' class="form-control" value="" placeholder="" > </div> <div class="col-lg-4 control-padding"> <label>Activity Performed By</label> <div id="ppDefault"></div> </div> <div class="col-lg-4 control-padding"> <label>Activity Date</label> <div class="input-group date" data-provide="datepicker"> <input type="text" class="form-control" id="txtDate" name="txtDate"> </div> </div> </div> <div class="row"> <div class="col-lg-6 control-padding"> <label>Category</label> <select name="ddlCategory" id="ddlCategory" class="form-control"> </select> </div> <div class="col-lg-6 control-padding"> <label>Sub Category</label> <select name="ddlSubCategory" id="ddlSubCategory" class="form-control"> </select> </div> </div> <div class="row"> <div class="col col-lg-12"> <button type="button" class="btn btn-primary buttons" id="btnSubmit">Save</button> <button type="button" class="btn btn-default buttons" id="btnCancel">Cancel</button> </div> </div> </div> </div>`; (<any>$("#txtDate")).datepicker( { changeMonth: true, changeYear: true, dateFormat: "mm/dd/yy" } ); (<any>$('#ppDefault')).spPeoplePicker({ minSearchTriggerLength: 2, maximumEntitySuggestions: 10, principalType: 1, principalSource: 15, searchPrefix: '', searchSuffix: '', displayResultCount: 6, maxSelectedUsers: 1 }); this.AddEventListeners(); this.getCategoryData(); } </code>
In the render() method, the following code, initializes the datepicker control:
<code>(<any>$("#txtDate")).datepicker( { changeMonth: true, changeYear: true, dateFormat: "mm/dd/yy" } ); </code>
And the following code initializes the people picker:
<code>(<any>$('#ppDefault')).spPeoplePicker({ minSearchTriggerLength: 2, maximumEntitySuggestions: 10, principalType: 1, principalSource: 15, searchPrefix: '', searchSuffix: '', displayResultCount: 6, maxSelectedUsers: 1 }); </code>
Note that I have used <any> before initializing both the controls. I found in SPFx that if I try to initialize them without <any> it gave “method not found” error.
- Implement AddEventListeners() method to attach event listeners to controls:
<code>private AddEventListeners(): any { document.getElementById('btnSubmit').addEventListener('click', () => this.SubmitData()); document.getElementById('btnCancel').addEventListener('click', () => this.CancelForm()); document.getElementById('ddlSysWorked').addEventListener('change', () => this.PopulateSubCategory()); } </code>
- Implement getCategoryData() method to populate category dropdown from master list.
<code>private _getCategoryData(): any { return pnp.sp.web.lists.getByTitle("Category").items.select("Category").getAll().then((response) => { return response; }); } private getCategoryData(): any { this._getCategoryData() .then((response) => { this._renderCategoryList(response); }); } private _renderCategoryList(items: any): void { let html: string = ''; html += `<option value="Select Category" selected>Select Category</option>`; items.forEach((item: any) => { html += ` <option value="${item.Category}">${item.Category}</option>`; }); const listContainer1: Element = this.domElement.querySelector('#ddlCategory'); listContainer1.innerHTML = html; } </code>
- On change event of category should populate the subcategory dropdown control with requisite values from master list. Added the change listener on category dropdown to call PopulateSubCategory() method.
<code>public PopulateSubCategory() { this.getSubCategoryData($("#ddlCategory").val().toString()); } private _getSubCategoryData(category): any { return pnp.sp.web.lists.getByTitle("SubCategory").items.select("SubCategory").filter("Category eq '" + category + "'").getAll().then((response) => { return response; }); } private getSubCategoryData(category): any { this._getSubCategoryData(category) .then((response) => { this._renderSubCategoryList(response); }); } private _renderSubCategoryList(items: any): void { let html: string = ''; html += `<option value="Select Sub Category" selected>Select Sub Category</option>`; items.forEach((item: any) => { html += ` <option value="${item.SubCategory}">${item.SubCategory}</option>`; }); const listContainer1: Element = this.domElement.querySelector('#ddlSubCategory'); listContainer1.innerHTML = html; } </code>
- Now that the form is created and initialized with controls, we need to implement the Submit and Cancel functionalities.
- To implement Cancel, we will read the URL from where this page was opened, search for the Source attribute and redirect the page there.
<code>private CancelForm() { window.location.href = this.GetQueryStringByParameter("Source"); } private GetQueryStringByParameter(name) { name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), results = regex.exec(location.search); return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); } </code>
- Finally, let’s implement the Submit functionality and save the data to the Sharepoint list.
<code>private SubmitData(){ var userinfo = (<any>$('#ppDefault')).spPeoplePicker('get'); var userId; var userDetails = this.GetUserId(userinfo[0].email.toString()); console.log(JSON.stringify(userDetails)); userId = userDetails.d.Id; pnp.sp.web.lists.getByTitle('RigActiveList_Job_Cards_Area').items.add({ Title: "Test", Activity: $("#txtActivity").val().toString(), Activity_Date: $("#txtDate").val().toString(), Activity_ById : userId, Category: $("#ddlCategory").val().toString(), SubCategory: $("#ddlSubCategory").val().toString(), }); } private GetUserId(userName) { var siteUrl = this.context.pageContext.web.absoluteUrl; var call = $.ajax({ url: siteUrl + "/_api/web/siteusers/getbyloginname(@v)?@v=%27i:0%23.f|membership|" + userName + "%27", method: "GET", headers: { "Accept": "application/json; odata=verbose" }, async: false, dataType: 'json' }).responseJSON; return call; } </code>
Note that I have used a separate GetUSerID method. The sp.peoplepicker.js control returns an userinfo object and not the Id of the user selected. But to save the user into SharePoint list as a person or group object, we need the Id. So I am passing the userinfo object to the GetUserId method and returning the Id to be saved to Sharepoint list.
This is the final layout of the form:
That’s about it guys. That’s how I managed to get my first SPFx webpart up and running.
I hope it helps others looking for similar solutions. I will be working on SPFx with ReactJs and posting the solution soon. Have a great time coding.