Dynamic Single Select ListBox In SharePoint Framework (SPFx)

In this blog, we will create a single selection listbox as a React component in Sharepoint framework (SPFx).
 
Child Component
 
I have created a React component and named it as "Dialog.tsx" as shown below.
 
Import the below node modules to your solution. Here, i have used UrlQueryParameterCollection to retrieve the ListItemId which is passed as a querystring parameter. 
  1. import * as React from 'react';    
  2. import { ISpFxRichTextEditorProps } from '../ISpFxRichTextEditorProps';    
  3. import { PrimaryButton, DefaultButton, DialogType, Dialog, DialogFooter, TextField, Label } from 'office-ui-fabric-react';    
  4. import * as $ from "jquery";    
  5. import { UrlQueryParameterCollection } from '@microsoft/sp-core-library';   
Declare the state variables as shown below. 
  1. export interface IDialogState {    
  2.       hideDialog: boolean;    
  3.       spanID:string;    
  4.       dialogID: string;    
  5.       standard:string;    
  6.       isDraggable: boolean;    
  7.       areaTemparr:any[];  
  8.      multiSelectAreaarr:string[];  
  9. }  
Declare constant values that will be populated in List Box.
  1. const areaConstantValues=["Bangalore","Mysore","Kerala","Chennai","Delhi","Mumbai"];  
Initiate the state variables. 
  1. public state: IDialogState = {    
  2.           hideDialog: this.props.dialogOpen,    
  3.           isDraggable: false,    
  4.           spanID: this.props.spanID,    
  5.           dialogID: this.props.id,    
  6.           standard:this.props.standard,   
  7.           areaTemparr : [],           multiSelectAreaarr:$('#' + this.props.spanID).html() != "" ? this.RemovedPtagString($('#' + this.props.spanID).html()).split(",") : []           
  8.     };  
Get the values of Dialog Label and sub text label from parent component. 
  1. private labelId: string = getId('dialogLabel');    
  2. private subTextId: string = getId('subTextLabel');    
Insert the select box as shown below. 
  1. public render() {  
  2.         const { hideDialog } = this.state;  
  3.   
  4.         let inputchkbox;  
  5.         let inputcontrols;  
  6.         let selectBox;  
  7.                   
  8.         if(this.state.spanID == "spnAreaVal"){  
  9.             inputchkbox = this.state.areaTemparr.map((element: string) => {  
  10.                 let result = this.state.multiSelectAreaarr.indexOf(element);  
  11.                     return (  
  12.                         (result > -1) ? <option value={element} selected>{element}</option>  
  13.                             : <option value={element}>{element}</option>  
  14.                     );  
  15.             });  
  16.         }  
  17.           
  18.         if(this.state.spanID == "spnAreaVal"){  
  19.             selectBox = <select id={"AreaSelect" + this.props.id} style={{ width: 250, height: 200 }} size={6} >  
  20.                      {inputchkbox}  
  21.             </select>  
  22.         }  
  23.   
  24.         return (  
  25.             <div>  
  26.                 <Dialog  
  27.                     hidden={hideDialog}  
  28.                     onDismiss={this.closeDialog}  
  29.                     dialogContentProps={{  
  30.                         type: DialogType.normal,  
  31.                         title: this.props.value,  
  32.                         subText: "",  
  33.                         styles: { title: { backgroundColor: "blue", height: 10, marginBottom: 10, paddingBottom: 22 } }  
  34.                     }}  
  35.                     modalProps={{  
  36.                         titleAriaId: this.labelId,  
  37.                         subtitleAriaId: this.subTextId,  
  38.                         isBlocking: false,  
  39.                         styles: { main: { height: 350, width: 500 } },  
  40.                     }}>  
  41.                     {selectBox}  
  42.                       
  43.                     <DialogFooter>  
  44.                         <PrimaryButton onClick={this.saveDialog} text="Save" />  
  45.                         <DefaultButton onClick={this.closeDialog} text="Cancel" />  
  46.                     </DialogFooter>  
  47.                 </Dialog>  
  48.             </div>  
  49.   
  50.         );  
  51.     }  
Reload the container on componemtDidMount. 
  1. public componentDidMount(): void {    
  2.         if(this.state.spanID == "spnAreaVal"){  
  3.             this.ReLoadAreaContainer();  
  4.         }           
  5. }   
  1. /** Load Area container based on values */  
  2.     public ReLoadAreaContainer() {  
  3.         try {  
  4.             this.setState({areaTemparr:areaConstantValues});  
  5.         } catch (error) {  
  6.             console.log("Error in ReLoadDepartamentContainer : " + error);  
  7.         }  
  8.     }  
This function is to close the dialog window.
  1. /*Close dialog window*/    
  2. private closeDialog = (): void => {    
  3.           this.setState({ hideDialog: true });    
  4.           this.props.onUpdate();    
  5. }   
Save the selected data and callback to parent as shown below. 
  1. private saveDialog = (): void => {  
  2.         if(this.state.spanID == "spnAreaVal"){  
  3.             this.SaveArea();  
  4.         }               
  5. }  
  6.   
  7. /*save Area on save button click */  
  8.     private SaveArea(){  
  9.         try{  
  10.             let selValues = "";  
  11.             $("#AreaSelect" + this.props.id + " option:selected").each(function () {  
  12.                 var $this = $(this);  
  13.                 if ($this.length) {  
  14.                     var selText = $this.text();  
  15.                     if (selValues) {  
  16.                         selValues = selValues + "<p>" + selText + "</p>";  
  17.                     }  
  18.                     else {  
  19.                         selValues = "<p>" + selText + "</p>";  
  20.                     }  
  21.                 }  
  22.             });  
  23.             $("#" + this.state.spanID).html(selValues);              
  24.             this.setState({ hideDialog: true });  
  25.             this.props.onUpdate(this.props.value);  
  26.         }catch(error){  
  27.             console.log("Error in save area to div : " + error);  
  28.         }  
  29. }  
Remove HTML <P> tag from HTML string
  1. /*Remove p tag from html string */     
  2.     
  3.     private RemovedPtagString(removedPString): string {    
  4.     
  5.         let newString;    
  6.     
  7.         try {              
  8.     
  9.           let item = removedPString.replace(/<p[^>]*>/g, "").replace(/<\/p>/g, ",");    
  10.     
  11.           item = item.replace(",,"",");    
  12.     
  13.           newString = item.indexOf(",") == 0 ? item.substring(1) : item;    
  14.     
  15.           newString = newString.slice(0, -1);    
  16.     
  17.         } catch (error) { console.log("Error in removedPtagString : " + error ); }    
  18.     
  19.         return newString;    
  20.     
  21.     }   
Use "DialogBox.tsx" component in parent.
 
Parent Component
 
Import "DialogBox.tsx" reusable component in your parent file as shown below.
 
Note
I have placed the DialoBox.tsx file under ReactDialogBox folder and hence the below path.
 
If you are not using Folder structure you can directly import the child component without giving the folder name.
  1. import DialogBox from './ReactDialogBox/DialogBox';
Declare the state variable in your parent state as shown below. 
  1. export interface ISpFxRichTextEditorState {
  2. clicked: boolean;
  3. listName: string;
  4. dialogTitle: string;
  5. spanID: string;
  6. DialogId: string;
  7. standard:string;
  8. isValidArea:string;
  9. toolpriorityinput:string;
  10. Safety:string;
  11. }
Declare the props in your parent props as shown below.
  1. export interface ISpFxRichTextEditorProps {
  2. id?:string;
  3. value?:string;
  4. dialogOpen?:boolean;
  5. spanID?:string;
  6. listName?:string;
  7. standardText?:string;
  8. onUpdate?:any;
  9. standard?:string;
  10. }
In the parent constructor class, declare the below state variables
  1. constructor(props: ISpFxRichTextEditorProps, state: ISpFxRichTextEditorState) {
  2. super(props);
  3. this.state = {
  4. clicked: false,
  5. listName: "",
  6. dialogTitle: "",
  7. spanID: "",
  8. DialogId: "",
  9. standard:"",
  10. isValidArea:"",
  11. toolpriorityinput:"",
  12. Safety:""
  13. };
  14. }
Call the dialog box in your render method of parent component. 
  1. public render(){  
  2.     return (  
  3.       <div>  
  4.         <div className="width3">  
  5.           <div className="modal-area">  
  6.             <div className="modal-heading">  
  7.               <span>  
  8.                 <b>Area</b>  
  9.                 {/* <span className="mandatary">*</span> */}  
  10.               </span>  
  11.               <span  
  12.                 className="add-area float-right document-icons-area "  
  13.                 onClick={() =>  
  14.                   this.handleClick(  
  15.                     "Areas",  
  16.                     "Select Area",  
  17.                     "spnAreaVal",  
  18.                     "areaDlgId"  
  19.                   )  
  20.                 }  
  21.               >  
  22.                 <Icon  
  23.                   iconName="CalculatorAddition"  
  24.                   className="ms-IconExample"  
  25.                 />  
  26.                 <label>Add</label>  
  27.               </span>  
  28.             </div>  
  29.             <div className="modal-adding">  
  30.               <div id="spnAreaVal" />  
  31.             </div>  
  32.             <div className="modal-box">  
  33.                 {this.state.clicked ? (  
  34.                   <DialogBox  
  35.                     id={this.state.DialogId}  
  36.                     value={this.state.dialogTitle}  
  37.                     dialogOpen={false}  
  38.                     onUpdate={this.onUpdate}  
  39.                     spHttpClient={this.props.spHttpClient}  
  40.                     siteUrl={this.props.siteUrl}  
  41.                     listName={this.state.listName}  
  42.                     spanID={this.state.spanID}  
  43.                     standardText={this.state.standard}  
  44.                   />  
  45.                 ) : null}  
  46.               </div>  
  47.           </div>  
  48.         </div>  
  49.       </div>  
  50.     );      
  51.   }  
Handle click event as shown below.
  1. /* Open dialog on click event */    
  2.   private handleClick(    
  3.     listName: string,    
  4.     dialogTitle: string,    
  5.     spanID: string,    
  6.     DlgId: string    
  7.   ): void {    
  8.     this.setState({    
  9.       clicked: true,    
  10.       listName: listName,    
  11.       dialogTitle: dialogTitle,    
  12.       spanID: spanID,    
  13.       DialogId: DlgId    
  14.     });    
  15.   }    
"onUpdate" function acts as call back from child component to parent component, which usually transfers the data from child to parent.
 
In the below function, I am storing the selected checkbox values in state variable named "Safety".
  1. /*Dialog on update function*/    
  2.   private onUpdate = dlgOutput => {    
  3.     try{    
  4.       this.setState({    
  5.         clicked: false    
  6.       });    
  7.           
  8.       this.setState({Safety:$("#spnAreaVal").text()});     
  9.           
  10.     }catch(error){    
  11.       console.log("Error in onUpdate function : " + error);    
  12.     }    
  13.   }    
Complete code of a child component "DialogBox.tsx" is depicted below,
  1. import * as React from 'react';    
  2. import { ISpFxRichTextEditorProps } from '../ISpFxRichTextEditorProps';    
  3. import { PrimaryButton, DefaultButton, DialogType, Dialog, DialogFooter, TextField, Label } from 'office-ui-fabric-react';    
  4. import * as $ from "jquery";    
  5. import { UrlQueryParameterCollection } from '@microsoft/sp-core-library';    
  6.     
  7. export interface IDialogState {    
  8.     hideDialog: boolean;    
  9.     spanID:string;    
  10.     dialogID: string;    
  11.     standard:string;    
  12.     isDraggable: boolean;    
  13.     areaTemparr:any[];  
  14.     multiSelectAreaarr:string[];  
  15. }    
  16.     
  17. /*Select Area Constant Values */  
  18. const areaConstantValues=["Bangalore","Mysore","Kerala","Chennai","Delhi","Mumbai"];  
  19.     
  20.     
  21. export default class DialogBox extends React.Component<ISpFxRichTextEditorProps, any>    
  22. {    
  23.         
  24.     public constructor(props: ISpFxRichTextEditorProps) {    
  25.         super(props);
  26.     }    
  27.       
  28.     public state: IDialogState = {    
  29.         hideDialog: this.props.dialogOpen,    
  30.         isDraggable: false,    
  31.         spanID: this.props.spanID,    
  32.         dialogID: this.props.id,    
  33.         standard:this.props.standard,    
  34.         areaTemparr : [],  
  35.         multiSelectAreaarr:$('#' + this.props.spanID).html() != "" ? this.RemovedPtagString($('#' + this.props.spanID).html()).split(",") : []  
  36.     };    
  37.     
  38.         
  39.     private labelId: string = getId('dialogLabel');    
  40.     private subTextId: string = getId('subTextLabel');    
  41.         
  42.     public render() {    
  43.         const { hideDialog } = this.state;    
  44.         let inputchkbox;    
  45.         let inputcontrols;    
  46.         let selectBox;    
  47.             
  48.         /* input control ends here*/    
  49.         if(this.state.spanID == "spnAreaVal"){  
  50.             inputchkbox = this.state.areaTemparr.map((element: string) => {  
  51.                 let result = this.state.multiSelectAreaarr.indexOf(element);  
  52.                     return (  
  53.                         (result > -1) ? <option value={element} selected>{element}</option>  
  54.                             : <option value={element}>{element}</option>  
  55.                     );  
  56.             });  
  57.         }   
  58.         /* input control ends here*/    
  59.     
  60.         /* select control starts here*/    
  61.         if(this.state.spanID == "spnAreaVal"){  
  62.             selectBox = <select id={"AreaSelect" + this.props.id} style={{ width: 250, height: 200 }} size={6} >  
  63.                      {inputchkbox}  
  64.             </select>  
  65.         }    
  66.         /* select control ends here*/    
  67.     
  68.         return (    
  69.             <div>    
  70.                 <Dialog    
  71.                     hidden={hideDialog}    
  72.                     onDismiss={this.closeDialog}    
  73.                     dialogContentProps={{    
  74.                         type: DialogType.normal,    
  75.                         title: this.props.value,    
  76.                         subText: "",    
  77.                         styles: { title: { backgroundColor: "blue", height: 10, marginBottom: 10, paddingBottom: 22 } }    
  78.                     }}    
  79.                     modalProps={{    
  80.                         titleAriaId: this.labelId,    
  81.                         subtitleAriaId: this.subTextId,    
  82.                         isBlocking: false,    
  83.                         styles: { main: { height: 350, width: 500 } },    
  84.                     }}>    
  85.                     {selectBox}    
  86.                         
  87.                     <DialogFooter>    
  88.                         <PrimaryButton onClick={this.saveDialog} text="Save" />    
  89.                         <DefaultButton onClick={this.closeDialog} text="Cancel" />    
  90.                     </DialogFooter>    
  91.                 </Dialog>    
  92.             </div>    
  93.     
  94.         );    
  95.     }    
  96.       
  97.     /* Initiate load containers */    
  98.     public componentDidMount(): void {    
  99.         if(this.state.spanID == "spnAreaVal"){  
  100.             this.ReLoadAreaContainer();  
  101.         }    
  102.     }    
  103.        
  104.     /** Area Selection Starts */    
  105.     /** Load Area container based on values */    
  106.     public ReLoadAreaContainer() {  
  107.         try {  
  108.             this.setState({areaTemparr:areaConstantValues});  
  109.         } catch (error) {  
  110.             console.log("Error in ReLoadAreaContainer : " + error);  
  111.         }  
  112.     }    
  113.     /** Area Ends */    
  114.     
  115.     /*Close dialog window*/    
  116.     private closeDialog = (): void => {    
  117.         this.setState({ hideDialog: true });    
  118.         this.props.onUpdate();    
  119.     }    
  120.                
  121.     /*Save operation based on span id selection*/    
  122.     private saveDialog = (): void => {    
  123.         if(this.state.spanID == "spnAreaVal"){  
  124.             this.SaveArea();  
  125.         }         
  126.     }    
  127.     
  128.     /*save Area on save button click */  
  129.     private SaveArea(){  
  130.         try{  
  131.             let selValues = "";  
  132.             $("#AreaSelect" + this.props.id + " option:selected").each(function () {  
  133.                 var $this = $(this);  
  134.                 if ($this.length) {  
  135.                     var selText = $this.text();  
  136.                     if (selValues) {  
  137.                         selValues = selValues + "<p>" + selText + "</p>";  
  138.                     }  
  139.                     else {  
  140.                         selValues = "<p>" + selText + "</p>";  
  141.                     }  
  142.                 }  
  143.             });  
  144.             $("#" + this.state.spanID).html(selValues);              
  145.             this.setState({ hideDialog: true });  
  146.             this.props.onUpdate(this.props.value);  
  147.         }catch(error){  
  148.             console.log("Error in save area to div : " + error);  
  149.         }  
  150.     }  
  151.         
  152.     /*Other Functions*/    
  153.     /*Remove p tag from html string */     
  154.     private RemovedPtagString(removedPString): string {    
  155.         let newString;    
  156.         try {              
  157.           let item = removedPString.replace(/<p[^>]*>/g, "").replace(/<\/p>/g, ",");    
  158.           item = item.replace(",,"",");    
  159.           newString = item.indexOf(",") == 0 ? item.substring(1) : item;    
  160.           newString = newString.slice(0, -1);    
  161.         } catch (error) { console.log("Error in removedPtagString : " + error ); }    
  162.         return newString;    
  163.     }      
  164.     
  165. }    
Complete code of a parent component "Parent.tsx" is depicted below,
  1. import * as React from 'react';    
  2. import { ISpFxRichTextEditorProps } from './ISpFxRichTextEditorProps';    
  3. import { ISpFxRichTextEditorState } from './ISpFxRichTextEditorState';    
  4. import { UrlQueryParameterCollection } from '@microsoft/sp-core-library';    
  5. import { css, DefaultButton, IButtonProps, IStyle, Label, PrimaryButton, DialogType, Dialog, DialogFooter, format, Icon, TextField } from 'office-ui-fabric-react';    
  6. import DialogBox from './ReactDialogBox/DialogBox';    
  7.     
  8. export default class SpFxRichTextEditor extends React.Component<ISpFxRichTextEditorProps, ISpFxRichTextEditorState> {    
  9.         
  10.   constructor(props:  ISpFxRichTextEditorProps, state: ISpFxRichTextEditorState) {    
  11.     super(props);    
  12.     this.state = {    
  13.         clicked: false,    
  14.         listName: "",    
  15.         dialogTitle: "",    
  16.         spanID: "",    
  17.         DialogId: "",    
  18.         standard:"",    
  19.         isValidArea:"",    
  20.         toolpriorityinput:"",    
  21.         Safety:""    
  22.             
  23.     };    
  24.                
  25.   }      
  26.      
  27.   /* Open dialog on click event */    
  28.   private handleClick(    
  29.     listName: string,    
  30.     dialogTitle: string,    
  31.     spanID: string,    
  32.     DlgId: string    
  33.   ): void {    
  34.     this.setState({    
  35.       clicked: true,    
  36.       listName: listName,    
  37.       dialogTitle: dialogTitle,    
  38.       spanID: spanID,    
  39.       DialogId: DlgId    
  40.     });    
  41.   }    
  42.     
  43.   public render(){    
  44.     return (    
  45.       <div>    
  46.          <div className="width3">    
  47.           <div className="modal-area">    
  48.             <div className="modal-heading">    
  49.               <span>    
  50.                 <b>Area</b>    
  51.                 {/* <span className="mandatary">*</span> */}    
  52.               </span>    
  53.               <span    
  54.                 className="add-area float-right document-icons-area "    
  55.                 onClick={() =>    
  56.                   this.handleClick(    
  57.                     "Areas",    
  58.                     "Select Area",    
  59.                     "spnAreaVal",    
  60.                     "areaDlgId"    
  61.                   )    
  62.                 }    
  63.               >    
  64.                 <Icon    
  65.                   iconName="CalculatorAddition"    
  66.                   className="ms-IconExample"    
  67.                 />    
  68.                 <label>Add</label>    
  69.               </span>    
  70.             </div>    
  71.             <div className="modal-adding">    
  72.               <div id="spnAreaVal" />    
  73.             </div>    
  74.             <div className="modal-box">    
  75.                 {this.state.clicked ? (    
  76.                   <DialogBox    
  77.                     id={this.state.DialogId}    
  78.                     value={this.state.dialogTitle}    
  79.                     dialogOpen={false}    
  80.                     onUpdate={this.onUpdate}    
  81.                     spHttpClient={this.props.spHttpClient}    
  82.                     siteUrl={this.props.siteUrl}    
  83.                     listName={this.state.listName}    
  84.                     spanID={this.state.spanID}    
  85.                     standardText={this.state.standard}    
  86.                   />    
  87.                 ) : null}    
  88.               </div>    
  89.           </div>    
  90.         </div>    
  91.       </div>    
  92.     );        
  93.   }    
  94.     
  95.        
  96.   /*Dialog on update function*/    
  97.   private onUpdate = dlgOutput => {    
  98.     try{    
  99.       this.setState({    
  100.         clicked: false    
  101.       });    
  102.       
  103.       this.setState({Safety:$("#spnAreaVal").text()});     
  104.           
  105.     }catch(error){    
  106.       console.log("Error in onUpdate function : " + error);    
  107.     }    
  108.   }    
  109.     
  110.   /*Replace semicolon with paragraph */    
  111.   public ReplaceSemiColon(stringValue):string{    
  112.     try{    
  113.       var htmlString='';    
  114.       var temp = [];    
  115.       if(stringValue !== ""){    
  116.         if(stringValue.indexOf(';') > -1){    
  117.           temp = stringValue.split(';')    
  118.           $.each(temp,function(index,value){    
  119.             htmlString = htmlString + "<p>" + value + "</p>"    
  120.           });    
  121.               
  122.         }    
  123.       }    
  124.       return htmlString;    
  125.     }catch(error){    
  126.       console.log("Error in ReplaceSemiColon : " +  error);    
  127.     }    
  128.   }    
  129.     
  130.   /*Replace paragraph with semicolon */    
  131.   public ReplaceParaWithSemiColon(stringValue):string{    
  132.     try{    
  133.       if(stringValue !== ""){    
  134.         if(stringValue.indexOf('<p>') >-1 ){    
  135.           stringValue = stringValue.replace(/<p>/g, "").replace(/<\/p>/g,";");    
  136.           stringValue = stringValue.substr(0, stringValue.length - 1);    
  137.         }            
  138.       }          
  139.       return stringValue;    
  140.     }catch(error){    
  141.       console.log("Error in ReplaceParaWithSemiColon : " +  error);    
  142.     }    
  143.   }    
  144.     
  145. }    
Below are the sample styles used to create multi-selection checkbox. 
 
CSS
  1. .ms-Dialog-header .ms-Dialog-title {    
  2.     background-color:#da291c;    
  3.     color: #fff;    
  4.     padding: 8px;    
  5.     height: 40px;    
  6.     box-sizing: border-box;    
  7.     line-height: 1;    
  8.     font-weight: bold;    
  9.               font-size: 20px;    
  10. }    
  11. .ms-Dialog-header .ms-Button {    
  12.     color: #fff;    
  13.     position: relative;    
  14.     top: -6px;    
  15.     float: right;    
  16.     right: 0px;    
  17.               min-width:0em;    
  18. }    
  19. .ms-Dialog-header .ms-Button:hover,.ms-Dialog-header .ms-Button:active, .ms-Dialog-header .ms-Button:focus  {    
  20.     background: none !important;    
  21. }    
  22. .ms-Dialog-header {    
  23.     margin-right: 40px;    
  24. }    
  25. .modal-heading {    
  26.     border: 1px solid #ccc;    
  27.     padding: 10px;    
  28.     background: #f1f1f1;    
  29. }    
  30.     
  31. .modal-adding {    
  32.     border: 1px solid #ccc;    
  33.     background: #fff;    
  34.     padding: 10px;    
  35.     min-height: 100px;    
  36. }    
  37.     
  38. .ms-Button-flexContainer {    
  39.     float: left;    
  40. }    
Output
 
 
 
Please feel free to share your comments.
 
I hope this helps!!!!