Custom Sharepoint Form using SPFx (No Javascript Framework)

In a recent requirement for a client, I had to develop a custom form as a web part using SPFx. The form required basic inputs like textboxes, Textarea, cascading dropdowns from master lists, date picker, and people picker, etc. The requirement was straightforward and seemed noncomplex at first. But as it was my very first encounter with the SPFx framework, I had a few hiccups on the way in getting the form done.
SPFx let you build client-side web part for SharePoint online leveraging modern technologies

Are you a SharePoint developer looking to build beyond “Hello World” SPFx client-side web-part?

Client-side web parts are client-side components that run inside the context of a SharePoint page. Client-side web parts can be deployed to SharePoint Online, and you can also use modern JavaScript tools and libraries to build them.

What is SPFx?

The SharePoint Framework (or SPFx) is a new development model for SharePoint user interface extensibility. It is used by first and third parties, complementing already existing user interface models such as the SharePoint Add-in model. The SharePoint Framework allows for a structured and supported approach to enrich and extend the user interface of SharePoint, by using client-side frameworks with initial support for client-side web parts. Based on modern web technology standards, it offers a unique set of features to make SharePoint customizations more broadly available for developers and enterprises, but at the same time aligns with previous models and patterns in SharePoint. 

Within multi-tenant SharePoint Online, full trust code has never been supported, and the sandboxed code service has been deprecated. The most common patterns for customizations of SharePoint Online have been either through add-ins, remote-code execution (code executing elsewhere, such as in Azure) through the standard APIs, and JavaScript embedding. Although JavaScript embedding has been a very powerful way of extending SharePoint, it has also proven difficult to keep up with the evergreen model of SharePoint Online. The SharePoint Framework aims to solve these issues by providing a standardized framework on how to create custom user interface extensions as well as building applications on top of SharePoint Online in a supported and future prepared way.

(courtesy: Enterprise Guidance)

When done right, SharePoint development is fun. This post is my first attempt to contribute to the ecosystem with my insights and learnings. The use of SPFx for user interface extensibility is fun. Like every new SharePoint developer, I struggled a bit initially to get the ‘Forms’ working – but, thanks to guidance documentation the first form using SPFx is out there working.

In a recent requirement for a client, I had to develop a custom form as a web part using SPFx. The form required basic inputs like textboxes, Textarea, cascading dropdowns from master lists, date picker, and people picker, etc. The requirement was straightforward and seemed noncomplex at first. But as it was my very first encounter with the SPFx framework, I had a few hiccups on the way in getting the form done.

In this post, I will try to take you through the process of developing a form using SPFx (No JavaScript Framework option). I plan on getting a form done using ReactJS or KnockoutJS in the near future.

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
  • 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:
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');
  • Also modify the sp-core library import to fetch URL parameters, if required:
import { UrlQueryParameterCollection, Version } from '@microsoft/sp-core-library';
  • Overwrite the render() method with the following:
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();

  }

In the render() method, the following code, initializes the datepicker control:

(<any>$("#txtDate")).datepicker(
      {
        changeMonth: true,
        changeYear: true,
        dateFormat: "mm/dd/yy"
      }
    );

And the following code initializes the people picker:

(<any>$('#ppDefault')).spPeoplePicker({
      minSearchTriggerLength: 2,
      maximumEntitySuggestions: 10,
      principalType: 1,
      principalSource: 15,
      searchPrefix: '',
      searchSuffix: '',
      displayResultCount: 6,
      maxSelectedUsers: 1
    });

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:
private AddEventListeners(): any {
    document.getElementById('btnSubmit').addEventListener('click', () => this.SubmitData());
    document.getElementById('btnCancel').addEventListener('click', () => this.CancelForm());   
    document.getElementById('ddlSysWorked').addEventListener('change', () => this.PopulateSubCategory());
  }
  • Implement getCategoryData() method to populate category dropdown from master list.
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;
  }
  • 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.
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;
  }
  • 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.
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, " "));
  }
  • Finally, let’s implement the Submit functionality and save the data to the Sharepoint list.
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;
}

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.

The project is available as a GitHub repository here.

Previous ArticleNext Article

6 Comments

      1. Hi Nalini,

        Thanks for reaching out. The jquery error comes if the location of jquery in config.json is not resolved.
        If jquery is not resolved, people picker also does not work as its is dependent on jquery.
        If you still have issues, please reach ot with specif errors and code snippets/logs/screenshots, if possible.

        Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Want to Accelerate Your Blockchain Adoption?

We are enabling businesses of all size to achieve Future Tech Adoption
DOWNLOAD BROCHURE
Close
DON’T MISS OUT!
Subscribe To
Newsletter
Be the first to get latest updates and exclusive content straight to your email inbox.
Stay Updated
Give it a try, you can unsubscribe anytime.
close-link

The future of Tech - delivered by our Team

We are enabling businesses of all size to achieve Future Tech Adoption
DOWNLOAD BROCHURE
Close