Category: SharePoint Framework

Office UI Fabric People Picker in Property Pane – SharePoint Framework (SPFx)

As we know Office UI Fabric which official front-end framework is for building a user interface that fits seamlessly into SharePoint modern experience. In this article, we going to see more detail about the adding custom controls like office UI fabric people picker and Persona in the property pane.

Creating a new SharePoint Framework web-part project

The first step we have to create a new SharePoint framework react web-part project, I have used SharePoint Framework version 1.8.2, if you have any question regarding set-up new SharePoint framework development environment then you can refer my one of the previous article where I explained simple steps to set up a new development environment and create the new project. While creating a project in the PowerShell you must select react framework.

Important files

In the new SharePoint framework web part solution contains many files, but in this article, we are going take look only files which are under src/webpart

Web part TS file (PropertyPaneSpFxWebPart.ts)

In this file, we create react element and created element assigned to the react DOM, also we are passing web part context as props so we can access these props in while create react component.

import * as React from 'react';
import * as ReactDom from 'react-dom';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import {
  IPropertyPaneConfiguration
} from '@microsoft/sp-property-pane';
import * as strings from 'PropertyPaneSpFxWebPartStrings';
import PropertyPaneSpFx from './components/PropertyPaneSpFx';
import { PropertyPanetextboxcustom } from './components/PropertyPaneControl';

export interface IPropertyPaneSpFxWebPartProps {
  selectedusers: any;
  onchange(stringvalue:string):void;
}

export default class PropertyPaneSpFxWebPart extends BaseClientSideWebPart<IPropertyPaneSpFxWebPartProps> {

  public render(): void {
    if(this.properties.selectedusers == "[]")
    {
      this.properties.selectedusers=[];
    }
    const element: React.ReactElement<IPropertyPaneSpFxWebPartProps> = React.createElement(
      PropertyPaneSpFx,
      {
        selectedusers: this.properties.selectedusers,
        onchange:this.onChanged
      }
    );
    ReactDom.render(element, this.domElement);
  }

  private onChanged(stringvalue:any): void {
    this.properties.selectedusers=stringvalue;
    this.render();
  }


  protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPanetextboxcustom(this.properties.selectedusers,this.onChanged.bind(this), this.context)
              ]
            }
          ]
        }
      ]
    };
  }
}

React component (CustomPeoplePicker.tsx)

In this file we creating the office UI fabric people picker component, So created component will be turned as property pane custom field in another file.

import * as React from 'react';
import { IPersonaProps } from 'office-ui-fabric-react/lib/Persona';
import { IPropertyFieldMessageBarPropsInternal } from './PropertyPaneControl';  
import { NormalPeoplePicker } from 'office-ui-fabric-react/lib/Pickers';
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http'; 
import { autobind } from 'office-ui-fabric-react/lib//Utilities'; 

export default class customtext extends React.Component<IPropertyFieldMessageBarPropsInternal, {}> {

  public render(): React.ReactElement<{}> {
    return (
      <div>
          <NormalPeoplePicker onResolveSuggestions={this._onFilterChanged}  resolveDelay={200}  onChange={this._onChange1}/>
      </div>
    );
  }

  private _onChange1 = (items?:IPersonaProps[]) => {
    this.props.onPropertyChange(items);
  };

  @autobind 
  private _onFilterChanged(filterText: string) { 
    if (filterText) { 
      if (filterText.length > 2) { 
        return this.searchPeople(filterText);         
      } 
    } else { 
      return []; 
    } 
  } 

  private searchPeople(terms: string): IPersonaProps[] | Promise<IPersonaProps[]> { 
    return new Promise<IPersonaProps[]>((resolve, reject) => 
      this.props.spcontect.spHttpClient.get(`${this.props.spcontect.pageContext.web.absoluteUrl}/_api/search/query?querytext='*${terms}*'&rowlimit=10&sourceid='b09a7990-05ea-4af9-81ef-edfab16c4e31'`, 
        SPHttpClient.configurations.v1, 
        { 
          headers: { 
            'Accept': 'application/json;odata=nometadata', 
            'odata-version': '' 
          } 
        }).then((response: SPHttpClientResponse): Promise<{ PrimaryQueryResult: any }> => { 
          return response.json(); 
        }).then((response: { PrimaryQueryResult: any }): void => { 
          let relevantResults: any = response.PrimaryQueryResult.RelevantResults; 
          let resultCount: number = relevantResults.TotalRows; 
          let people = []; 
          if (resultCount > 0) { 
            relevantResults.Table.Rows.forEach(function (row) { 
              let persona: IPersonaProps = {}; 
              row.Cells.forEach(function (cell) { 
                if (cell.Key === 'JobTitle') 
                  persona.secondaryText = cell.Value; 
                if (cell.Key === 'PictureURL') 
                  persona.imageUrl = cell.Value; 
                if (cell.Key === 'PreferredName') 
                  persona.primaryText = cell.Value; 
              }); 
              people.push(persona); 
            }); 
          } 
          resolve(people); 
        }, (error: any): void => { 
          reject(); 
        })); 
  }
}

Property pane component file (PropertyPaneControl.ts)

In this file, we implement the generic interface of IPropertyPaneField and rendering the office UI fabric people picker component which we created. at the end of the file, we also created one function for calling the property pane field, basically this will help us to call without calling as a new class we can directly call this function.  

import * as React from 'react';
import * as ReactDom from 'react-dom';
import {  
  IPropertyPaneCustomFieldProps,  
  IPropertyPaneField,  
  PropertyPaneFieldType  
} from '@microsoft/sp-webpart-base';  
import customtext from './CustomPeoplePicker';

export interface IPropertyFieldMessageBarPropsInternal extends IPropertyPaneCustomFieldProps {  
  onPropertyChange(items:any[]): void;    
  onRender(elem: HTMLElement): void;
  onDispose(elem: HTMLElement): void;
  key: string;  
  spcontect?:any|null;
}  


export default class propertypanecontrol implements IPropertyPaneField<IPropertyFieldMessageBarPropsInternal> {
  public properties: IPropertyFieldMessageBarPropsInternal;  
  public targetProperty: string;  
  public type: PropertyPaneFieldType = PropertyPaneFieldType.Custom;  
 // private onPropertyChange: (propertyPath: string, oldValue: any, newValue: any) => void;  
  private key: string;  
  private elem: HTMLElement;

  constructor(targetProperty: string,  userproperties: IPropertyFieldMessageBarPropsInternal) {
    this.targetProperty = targetProperty;
    this.render = this.render.bind(this);  
    this.properties = userproperties;  
    this.properties.onDispose = this.dispose;  
    this.properties.onRender = this.render;  
    this.properties.spcontect=userproperties.spcontect;
  //  this.onPropertyChange = userproperties.onPropertyChange;  
    this.key = userproperties.key;  
  }

  public render(elem: HTMLElement): void {
    if (!this.elem) {
      this.elem = elem;
    }
    const element: React.ReactElement<IPropertyFieldMessageBarPropsInternal> = React.createElement(customtext, {  
      onDispose: null,  
      onRender: null,  
      onPropertyChange: this.onChanged.bind(this),
      key: this.key,
      spcontect: this.properties.spcontect 
    });
    ReactDom.render(element, elem);
  }

  private onChanged(selectedusers: any): void {
    this.properties.onPropertyChange(selectedusers);
  }

  private dispose(elem: HTMLElement): void {  
  }
}

export function PropertyPanetextboxcustom(selectedusers:any, onChanged,spcontect?:any|null): IPropertyPaneField<IPropertyPaneCustomFieldProps> { 
  var newProperties: IPropertyFieldMessageBarPropsInternal = {  
    onPropertyChange: onChanged,  
    onDispose: null,  
    onRender: null,  
    key: 'test',
    spcontect:spcontect
  };  
  return new propertypanecontrol(selectedusers,newProperties);  
} 

Web part body react component (PropertyPaneSpFx.tsx)

In this file we creating web parts body part as react component, we used the office UI fabric Persona component to list the selected users 

import * as React from 'react';
import styles from './PropertyPaneSpFx.module.scss';
import { IPropertyPaneSpFxWebPartProps } from '../PropertyPaneSpFxWebPart';
import { escape } from '@microsoft/sp-lodash-subset';
import { Persona } from 'office-ui-fabric-react/lib/Persona';

export default class PropertyPaneSpFx extends React.Component<IPropertyPaneSpFxWebPartProps, {}> {
  public render(): React.ReactElement<IPropertyPaneSpFxWebPartProps> {
    return (
      <div className={ styles.propertyPaneSpFx }>
        <div className={ styles.container }>
          <div className={ styles.row }>
            <div className={ styles.column }>
              <span className={ styles.title }>Welcome to Ravichandran blog!</span>
              <p className={ styles.subTitle }>Seleted Users</p>
                {this.props.selectedusers.map((row, index) => (
                        <Persona {...row}  />
                ))}
            </div>
          </div>
        </div>
      </div>
    );
  }
}

Click below button to redirect Microsoft code galley to download this project

If you have any questions, feel free to let me know in the comments section. Good Luck!!!

Office UI fabric Callout in SharePoint Framework (SPFx)

Office UI fabric Callout in SharePoint Framework (SPFx)

As we know Office UI Fabric which official front-end framework is for building a user interface that fits seamlessly into SharePoint modern experience. In this article, we going to see more detail about the Callout component. The Callout is a powerful way to simplify a user interface. Also, we going to see how to update the title for a selected list item in the SharePoint. At the end of the article, you can find the downlink to download this whole project.

Creating a new SharePoint Framework extension project

The first step we have to create a new SharePoint framework extension project, I have used SharePoint Framework version 1.8.2, if you have any question regarding set-up new SharePoint framework development environment then you can refer my one of the previous article where I explained simple steps to set up a new development environment and create the new project. please refer below image for select input while creating project.

Creating Office UI Fabric Callout component

In order to create new react component we have to create a new folder in the name of components under src\extensions, in that folder we have to create three files,

  1. src\extensions\components\Callout.module.scss
  2. src\extensions\components\Callout.tsx
  3. src\extensions\components\ICalloutProps.ts

In the SCSS file we just used for styling purpose, Its almost same like CSS but using scss we can access CSS classes and functions, tsx is the react component file, here we managing to create control and the events.

Here we creating a component and pushing that using SharePoint base dialog. We also updating list item’s title, for that we passing this.context from the List view commend set file in the react component file, if you have any question about the code logic flow, please post into the comments section below.

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { ICalloutProps, ICalloutState } from './ICalloutProps';
import { Callout } from 'office-ui-fabric-react/lib/Callout';
import styles01 from './Callout.module.scss';
import { BaseDialog, IDialogConfiguration } from '@microsoft/sp-dialog';
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http'


export default class CalloutComponent extends BaseDialog {
  public itemTitle: string;
  public itemID: number;
  public spcontext?: any | null;

  public render(): void {
    ReactDOM.render(<Cillout itemID={this.itemID} spcontext={this.spcontext} Title={this.itemTitle} domElement={document.activeElement.parentElement} onDismiss={this.onDismiss.bind(this)} />,
      this.domElement);
  }

  public getConfig(): IDialogConfiguration {
    return {
      isBlocking: false
    };
  }

  private onDismiss() {
    ReactDOM.unmountComponentAtNode(this.domElement);
  }
}

class Cillout extends React.Component<ICalloutProps, ICalloutState> {

  constructor(props: ICalloutProps) {
    super(props);
    this.state = {
      Title: this.props.Title
    };

    this.setState({ Title: this.props.Title });
    this._saveClicked = this._saveClicked.bind(this);
    this._onChangedTitle = this._onChangedTitle.bind(this);
  }

  public render(): JSX.Element {
    return (
      <div>
        <Callout
          className={styles01["ms-CalloutExample-callout"]}
          role="alertdialog"
          gapSpace={0}
          target={this.props.domElement}
          onDismiss={this.onDismiss.bind(this)}
          setInitialFocus={true}
          hidden={false}
        >
          <div className={styles01["ms-CalloutExample-header"]}>
            <p className={styles01["ms-CalloutExample-title"]}>
              Property panel
            </p>
          </div>
          <div className={styles01["ms-CalloutExample-inner"]}>
            <div className={styles01["ms-CalloutExample-content"]}>
              <p className={styles01["ms-CalloutExample-subText"]}>
                <TextField label="Title" value={this.state.Title} underlined onChanged={this._onChangedTitle} />
              </p>
            </div>
            <div className={styles01["ms-CalloutExample-actions"]}>
              <PrimaryButton text="Save" onClick={this._saveClicked} />
            </div>
          </div>
        </Callout>
      </div>
    );
  }
  private onDismiss(ev: any) {
    this.props.onDismiss();
  }

  private _onChangedTitle(newValue: string): void {
    this.setState({ Title: newValue });
  }

  private _saveClicked() {
    const body: string = JSON.stringify({
      '__metadata': {
        'type': 'SP.Data.' + this.props.spcontext.pageContext.list.title + 'ListItem'
      },
      'Title': this.state.Title
    });
    this.props.spcontext.spHttpClient.get(this.props.spcontext.pageContext.web.absoluteUrl + `/_api/web/lists/getbytitle('${this.props.spcontext.pageContext.list.title}')/items(` + this.props.itemID + ')', SPHttpClient.configurations.v1).then
      ((Response: SPHttpClientResponse) => {
        this.props.spcontext.spHttpClient.post(this.props.spcontext.pageContext.web.absoluteUrl + `/_api/web/lists/getbytitle('${this.props.spcontext.pageContext.list.title}')/items(` + this.props.itemID + ')', SPHttpClient.configurations.v1,
          {
            headers: {
              'Accept': 'application/json;odata=nometadata',
              'Content-type': 'application/json;odata=verbose',
              'odata-version': '',
              'IF-MATCH': Response.headers.get('ETag'),
              'X-HTTP-Method': 'MERGE'
            },
            body: body
          }).then((response: SPHttpClientResponse) => {
            console.log(`Status code: ${response.status}`);
            console.log(`Status text: ${response.statusText}`);
            this.props.onDismiss();
          });
      });
  }
}

And then we have typescript file for property interface, in the file, we managing all property interfaces which is required for our extension and the react components.

export interface ICalloutProps {
  isCalloutVisible?: boolean;
  onDismiss: () => void;
  domElement: any;
  Title:string;
  spcontext?:any|null;
  itemID:number;
}

export interface ICalloutState {
  Title:string;
}

Mapping react component into list view command set

List view commend set files are created by the yeoman generator while creating the project, we just wanted to map ours react component into the this.

  1. \src\extensions\fabricCallout\FabricCalloutCommandSet.ts
  2. \src\extensions\fabricCallout\FabricCalloutCommandSet.manifest.json
import { override } from '@microsoft/decorators';
import {
  BaseListViewCommandSet,
  Command,
  IListViewCommandSetListViewUpdatedParameters,
  IListViewCommandSetExecuteEventParameters
} from '@microsoft/sp-listview-extensibility';
import Callout from '../components/Callout'; 

export default class FabricCalloutCommandSet extends BaseListViewCommandSet<{}> {
  @override
  public onInit(): Promise<void> {
    return Promise.resolve();
  }

  @override
  public onListViewUpdated(event: IListViewCommandSetListViewUpdatedParameters): void {
    const compareOneCommand: Command = this.tryGetCommand('COMMAND_1');
    if (compareOneCommand) {
      // This command should be hidden unless exactly one row is selected.
      compareOneCommand.visible = event.selectedRows.length === 1;
    }
  }

  @override
  public onExecute(event: IListViewCommandSetExecuteEventParameters): void {
    switch (event.itemId) {
      case 'COMMAND_1':
        const callout: Callout = new Callout();
        callout.itemTitle=event.selectedRows[0].getValueByName('Title');
        callout.itemID=event.selectedRows[0].getValueByName('ID');
        callout.spcontext= this.context;
        callout.show();
        break;
      default:
        throw new Error('Unknown command');
    }
  }
}

We can change the commend set title and the icon the commend set manifest file,

Also, we can manage where we want to show out custom commend set, for that we have to edit the elements.xml file under \sharepoint\assets\ for more detail about this file please refer the Microsoft documentation

If you have any questions, feel free to let me know in the comments section. Good Luck!!!

Fabric React DatePicker in SPFx

Fabric React DatePicker in SPFx

As we know Office UI Fabric which is official front-end framework for building a user interface that fits seamlessly into SharePoint modern experience. In this article, we going to see more detail about the Date picker component. Also, we saving and retrieving data into the SharePoint list. Herewith you can find the complete project download link.

Fabric React DatePicker in SPFx

Creating a new SharePoint Framework web part
The first step we have to create new SharePoint framework web part project, I have used SharePoint Framework version 1.8, if you have any question for you then you can refer my one of the previous article where I explained simple steps to set up a new development environment and create the new project. While creating a project in the PowerShell you have to select react framework.

Fabric React DatePicker in SPFx create web part

Important files
In the new SharePoint framework web part solution contains tons of files, understanding each file’s purpose is really awesome, but in this articale we going take look only four files, because making changes in those four files is enough
1. src\webparts\fabricDatePicker\FabricDatePickerWebPart.ts
2. src\webparts\fabricDatePicker\components\IFabricDatePickerProps.ts
3. src\webparts\fabricDatePicker\components\FabricDatePicker.tsx
4. src\webparts\fabricDatePicker\components\FabricDatePicker.module.scss

Web part TS file (FabricDatePickerWebPart.ts)
In this file, we just calling create react element and created element assigned to the react DOM, also we passing web part context as props so we can access this props in while create react component.

 

import * as React from 'react';
import * as ReactDom from 'react-dom';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import FabricDatePicker from './components/FabricDatePicker';
import { IFabricDatePickerProps, IFabricDatePickerWebpartProps } from './components/IFabricDatePickerProps';
export default class FabricPeoplePickerWebPart extends BaseClientSideWebPart<IFabricDatePickerWebpartProps> {
    public render(): void {
        const element: React.ReactElement<IFabricDatePickerProps> = React.createElement(
          FabricDatePicker,
            {
                spcontect: this.context
            }
        );
        ReactDom.render(element, this.domElement);
    }
}

 

Property file (IFabricDatePickerProps.ts)
In the props file we just created two interfaces, one is IFabricDatePickerProps will pass the props which are read-only, so we only passing context and another one is IFabricDatePickerWebpartProps will carry all states for the react components. so we can set and get values to the controls.

//Set all read only values
export interface IFabricDatePickerProps {
  spcontect?:any|null
}

//Set all read and write only values
export interface IFabricDatePickerWebpartProps {
  birthday?:any|null;
  message:string
}

Component file (FabricDatePicker.tsx)
In this file we do most of the things like building date picker, retiring data from SharePoint and assign that value to the date picker and save changes back into the SharePoint list.
In the constructor, we retrieve the value from SharePoint and assign the value to the date picker also we injecting props to the button click event for accessing all props inside the click event.
In the render event, we returning the HTML content for the user interface,
In the select date event, we receive the value and assign back to the component state
In the format date event, we can set which format date picker have to display the value,
In the button click event, we updating value into the SharePoint list

import * as React from 'react';
import { IFabricDatePickerProps, IFabricDatePickerWebpartProps } from './IFabricDatePickerProps';
import { Environment, EnvironmentType } from '@microsoft/sp-core-library';
import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions, IHttpClientOptions } from '@microsoft/sp-http'
import { DatePicker } from 'office-ui-fabric-react/lib/DatePicker';
import { Label } from 'office-ui-fabric-react/lib/Label';
import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';
import styles from './FabricDatePicker.module.scss';

export default class FabricDatePicker extends React.Component<IFabricDatePickerProps, IFabricDatePickerWebpartProps> {

    private etag: String = undefined;
    public constructor(props: IFabricDatePickerProps, state: IFabricDatePickerWebpartProps) {
        super(props);
        this.state = {
            birthday: null,
            message:''
        };

        if (Environment.type === EnvironmentType.SharePoint) {
            this.props.spcontect.spHttpClient.get(this.props.spcontect.pageContext.web.absoluteUrl + '/_api/web/lists/getbytitle(\'sampleLIST\')/items(1)', SPHttpClient.configurations.v1).then
                ((Response: SPHttpClientResponse) => {
                   // this.etag = Response.headers.get('ETag');
                    Response.json().then((listItem: any) => {
                        this.setState({ birthday: new Date(listItem.Birthday) });
                    });
                });

        }
        else if (Environment.type === EnvironmentType.Local) {
            // return (<div>Whoops! you are using local host...</div>);
        }

        this._alertClicked = this._alertClicked.bind(this);
    }

    public render(): React.ReactElement<IFabricDatePickerProps> {
        return (
            <div className={styles.fabricDatePicker}>
                <div id="DivLocalHost"></div>
                <div className={styles.container}>
                    <div className={styles.row}>
                        <Label>Birthday</Label>
                        <DatePicker placeholder="Select a date..."
                            onSelectDate={this._onSelectDate}
                            value={this.state.birthday}
                            formatDate={this._onFormatDate}
                        />
                        <div>
                        <div className={styles.label}>
                        <label>{this.state.message}</label>
                        </div>
                        <div className={styles.button}>                     
                            <PrimaryButton data-automation-id="test"
                                text="Save"
                                onClick={this._alertClicked} />
                        </div>
                        </div>
                    </div>
                </div>
            </div>
        );

    }

    private _onSelectDate = (date: Date | null | undefined): void => {
        this.setState({ birthday: date });
    };

    private _onFormatDate = (date: Date): string => {
        return date.getDate() + '/' + (date.getMonth() + 1) + '/' + date.getFullYear();
    };

    private _alertClicked(): void {
        const body: string = JSON.stringify({
            '__metadata': {
                'type': 'SP.Data.SampleLISTListItem'
            },
            'Birthday': this.state.birthday
        });
        this.props.spcontect.spHttpClient.get(this.props.spcontect.pageContext.web.absoluteUrl + '/_api/web/lists/getbytitle(\'sampleLIST\')/items(1)', SPHttpClient.configurations.v1).then
        ((Response: SPHttpClientResponse) => {
          this.props.spcontect.spHttpClient.post(this.props.spcontect.pageContext.web.absoluteUrl + `/_api/web/lists/getbytitle('sampleLIST')/items(1)`,
          SPHttpClient.configurations.v1,
          {
              headers: {
                  'Accept': 'application/json;odata=nometadata',
                  'Content-type': 'application/json;odata=verbose',
                  'odata-version': '',
                  'IF-MATCH': Response.headers.get('ETag'),
                  'X-HTTP-Method': 'MERGE'
              },
              body: body
          }).then((response: SPHttpClientResponse) => {
              // Access properties of the response object. 
              this.setState({ message: 'Successfully saved' });
              console.log(`Status code: ${response.status}`);
              console.log(`Status text: ${response.statusText}`);

              //response.json() returns a promise so you get access to the json in the resolve callback.
              response.json().then((responseJSON: JSON) => {
                  console.log(responseJSON);
              });
          });
        });

    }

}

Style file (FabricDatePicker.module.scss)
This file contains the CSS module, but we using sass because we wanted to call the CSS class name from javascript as a javascript object.

Date Picker properties
We can customize the date picker using date picker properties, you can find the many properties in the official documentation, here we take look some of them, by default date picker looks like this,

isMonthPickerVisible true

isMonthPickerVisible
this property by default is true, we can make false if we don’t need month picker in the calendar. example

<DatePicker placeholder="Select a date..." isMonthPickerVisible={false}/>

isMonthPickerVisible

minDate
Using this property we can set the minimum date for the date picker, so date picker does not allow to chose less than that. Example

<DatePicker placeholder="Select a date..." minDate={new Date(2000,12,30)}/>

minDate

Please feel free to let me know if you have any queries in the comment section, I’m happy to help you!!
Source Code Download link

174https://code.msdn.microsoft.com/Fabric-React-DatePicker-in-d25382bb