Introduction
This webpart will retrieve SharePoint list items beyond the threshold limit and will display the results using DetailsList
Office UI fabric component. Due to SharePoint list view threshold limit, users without admin rights will not be able to browse through the SharePoint list items. Adding and configuring this webpart will let users with at least read permissions to the site to view all items from the list .
Pre-requisites
- Create project
ViewAllItemsWebPart
by running Yeoman SharePoint Generator - Install react Paging component
- Install jQuery
Using the Code
Listed below are the components that were created as part of this webpart:
- Custom property pane - A custom property pane control to show a multiselect dropdown to populate the columns when the
list
property is selected. Please refer to this link to create Custom property pane control. - Config - Config component will be rendered if the webpart properties are not configured.
- Paging - This is react paging component. This is used to add pagination to the list view.
- ViewAllItems - The component that renders the
DetailsList
Office UI fabric component and binds the list items to it. - services - This contains the methods to get the Lists and Columns from SharePoint using Rest API.
Let's start off with modifying the scaffolding that was generated by the Yeoman Generator.
WebPart.manifest.json
Edit the webpart manifest json file properties section to include three properties List name (listName
), Column names (selectedIds
), Page size (pageSize
) and set the default values as shown.
"properties": {
"listName": "",
"selectedIds":[],
"pageSize": 100
}
Define Webpart Property Constants and Property Types
Under loc folder, update en-us.js and mystrings.js to include the below script. These is to define the property constants and property types that are required in webpart property pane configuration.
en-us.js
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"ListNameFieldLabel": "Select List",
"ColumnFieldLabel": "Select Columns",
"PageSizeFieldLabel": "Select Page Size"
}
});
mystrings.d.ts
declare interface IViewAllItemsWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
ListNameFieldLabel: string;
ColumnFieldLabel: string;
PageSizeFieldLabel: string;
}
declare module 'ViewAllItemsWebPartStrings' {
const strings: IViewAllItemsWebPartStrings;
export = strings;
}
Webpart.ts
Add below import
s to webpart.ts file:
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneDropdown,
IPropertyPaneDropdownOption
} from '@microsoft/sp-webpart-base';
import * as strings from 'ViewAllItemsWebPartStrings';
import ViewAllItems from './components/ViewAllItems';
import { IViewAllItemsProps } from './components/IViewAllItemsProps';
import { IList } from './services/IList';
import { IListColumn } from './services/IListColumn';
import { ListService } from './services/ListService';
import { MultiSelectBuilder, IItemProp, PropertyPaneMultiSelect }
from './CustomPropertyPane/PropertyPaneMultiSelect';
import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';
Remove the existing lines of code in getPropertyPaneConfiguration
method and add the below lines of code:
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneDropdown('listName', {
label: strings.ListNameFieldLabel,
options: this.lists,
disabled: this.listsDropdownDisabled,
}),
PropertyPaneMultiSelect("selectedIds", {
label: "Select Columns",
selectedItemIds: this.properties.selectedIds,
onload: () => this.loadColumns(),
onPropChange: this.onPropertyPaneFieldChanged,
properties: this.properties,
key: "targetkey",
disabled: !this.properties.listName
}),
PropertyPaneDropdown('pageSize',{
label: strings.PageSizeFieldLabel,
options:[
{key: '10', text: '10'},
{key: '25', text: '25'},
{key: '50', text: '50'},
{key: '100', text: '100'}
]
})
]
}
]
}
]
};
}
PropertyPaneMultiselect
is the custom property pane component. Please follow the link that is provided above to add it to this webpart project.
Add methods onPropertyPaneConfigurationStart
and onPropertyPaneFieldChanged
to the webpart
class to create cascading dropdown and load the property pane controls with data.
protected onPropertyPaneConfigurationStart(): void {
this.listsDropdownDisabled = !this.lists;
if (this.lists) {
return;
}
this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'lists');
this.loadLists()
.then((listOptions: IPropertyPaneDropdownOption[]): void => {
this.lists = listOptions;
this.listsDropdownDisabled = false;
this.context.propertyPane.refresh();
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
this.render();
});
}
protected onPropertyPaneFieldChanged(propertyPath: string,
oldValue: any, newValue: any): void {
if (propertyPath === 'listName' && newValue) {
super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
const previousItem: string[] = this.properties.selectedIds;
this.properties.selectedIds = [];
this.onPropertyPaneFieldChanged('selectedIds', previousItem,
this.properties.selectedIds);
this.render();
this.context.propertyPane.refresh();
}
else {
super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
}
}
If you notice the above methods are calling methods, loadLists
and loadColumns
to get the data, add the below methods to the webpart
class.
private loadLists(): Promise<IPropertyPaneDropdownOption[]> {
const dataService = new ListService(this.context);
return new Promise<IPropertyPaneDropdownOption[]>(resolve => {
dataService.getLists()
.then((response: IList[]) => {
var options : IPropertyPaneDropdownOption[] = [];
response.forEach((item: IList) => {
options.push({"key": item.Title, "text": item.Title});
});
resolve(options);
});
});
}
private loadColumns(): Promise<IItemProp[]> {
if (!this.properties.listName) {
return Promise.resolve();
}
const dataService = new ListService(this.context);
return new Promise<IItemProp[]>(resolve => {
dataService.getColumns(this.properties.listName)
.then((response) => {
var options : IItemProp[] = [];
this.properties.selectedColumnsAndType = [];
response.forEach((column: IListColumn) => {
options.push({"key": column.StaticName, "text": column.Title});
this.properties.selectedColumnsAndType.push
({"key": column.StaticName, "text": column.TypeDisplayName});
});
resolve(options);
});
});
}
In turn, these methods are referring to ListService
class to get the data. Add new folder services to the solutions and add files ILists.ts, IListColumn.ts, ListService.ts.
ILists.ts
Interface IList.ts defines the list metadata that will be requested from the rest API request. This interface will get the list id
and title
.
export interface IList {
Id: string;
Title: string;
}
IListColumns.ts
Interface IListColumns
defines the column metadata that will be requested from the API request. This will get the column title, Internal name and column type.
export interface IListColumn {
Title: string;
StaticName: string;
TypeDisplayName: string;
}
ListService.ts
Add the below script to ListService.ts file. This file will the methods getLists
and GetColumns
methods that will get the Lists
and Columns
information from the SharePoint by passing the current context of the site appended with the SharePoint REST API as url
parameter to the HTTP request.
export class ListService {
constructor(private context: IWebPartContext) {
}
public getLists(): Promise<IList[]> {
var httpClientOptions : ISPHttpClientOptions = {};
httpClientOptions.headers = {
'Accept': 'application/json;odata=nometadata',
'odata-version': ''
};
return new Promise<IList[]>((resolve: (results: IList[]) => void,
reject: (error: any) => void): void => {
this.context.spHttpClient.get(this.context.pageContext.web.serverRelativeUrl +
`/_api/web/lists?$select=id,title&$filter=Hidden eq false`,
SPHttpClient.configurations.v1,
httpClientOptions
)
.then((response: SPHttpClientResponse): Promise<{ value: IList[] }> => {
return response.json();
})
.then((lists: { value: IList[] }): void => {
resolve(lists.value);
}, (error: any): void => {
reject(error);
});
});
}
public getColumns(listName: string): Promise<IListColumn[]> {
var httpClientOptions : ISPHttpClientOptions = {};
httpClientOptions.headers = {
'Accept': 'application/json;odata=nometadata',
'odata-version': ''
};
return new Promise<IListColumn[]>((resolve: (results: IListColumn[]) => void,
reject: (error: any) => void): void => {
this.context.spHttpClient.get(this.context.pageContext.web.serverRelativeUrl +
`/_api/web/lists/GetByTitle('${listName}')/fields?
$filter=TypeDisplayName ne
'Attachments' and Hidden eq false and ReadOnlyField eq false`,
SPHttpClient.configurations.v1,
httpClientOptions
)
.then((response: SPHttpClientResponse): Promise<{ value: IListColumn[] }> => {
return response.json();
})
.then((listColumns: { value: IListColumn[] }): void => {
resolve(listColumns.value);
}, (error: any): void => {
reject(error);
});
});
}
}
Add below methods to the webpart
class. These methods will be passed as properties to the ReactDOM
element when the webpart render
method is called.
needsCofiguration
- This determines whether the webpart
properties that are required to render the view is configured. selectedColumns
- This will return columns that are selected from the custom property pane control. configureWebPart
- This method will open the webpart
property pane when it is called.
private needsConfiguration(): boolean {
return this.properties.listName === null ||
this.properties.listName === undefined ||
this.properties.listName.trim().length === 0 ||
this.properties.selectedIds === null ||
this.properties.selectedIds === undefined ||
this.properties.selectedIds.length === 0;
}
private selectedColumns(): IItemProp[] {
if(this.properties.selectedColumnsAndType === null ||
this.properties.selectedColumnsAndType===undefined ||
this.properties.selectedColumnsAndType.length === 0){
return [];
}
else{
return this.properties.selectedColumnsAndType.filter
(obj => this.properties.selectedIds.indexOf(obj.key) !== -1);
}
}
private configureWebPart(): void {
this.context.propertyPane.open();
}
Remove the existing code in the webpart render
method, copy and paste the below code:
public render(): void {
const element: React.ReactElement<IViewAllItemsProps> = React.createElement(
ViewAllItems,
{
spHttpClient: this.context.spHttpClient,
siteUrl: this.context.pageContext.web.absoluteUrl,
listName: this.properties.listName,
needsConfiguration: this.needsConfiguration(),
configureWebPart: this.configureWebPart,
displayMode: this.displayMode,
selectedColumns: this.selectedColumns(),
pageSize: this.properties.pageSize
}
);
ReactDom.render(element, this.domElement);
}
The render
method will load the JSX from ViewAllItems.tsx component. Add files ViewAllItems.tsx, IViewAllItemsProps.ts, ViewAllItems.module.scss to the solution.
IViewAllItemsProps.ts
This interface file defines all the properties that are required to load the component to ReactDOM.
export interface IViewAllItemsProps {
spHttpClient: SPHttpClient;
siteUrl: string;
listName: string;
selectedColumns: IItemProp[];
needsConfiguration:boolean;
configureWebPart: () => void;
displayMode: DisplayMode;
pageSize: number;
}
ViewAllItems.module.scss
These files contain all the styles that are used in ViewAllItems.tsx file.
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.viewAllItems {
.container {
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.status{
float:left
}
.button {
text-decoration: none;
height: 32px;
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,
BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}
ViewAllItems.tsx
ViewAllItems
class inherits from React.Component
which expects Props
and State
as arguments. We have defined the Props
(IViewAllItemsProps.ts). Define state
as shown below in the tsx file above the class.
export interface IViewAllItemsState {
items?: any[];
columns?:IColumn[];
status?: string;
currentPage?: number;
itemCount?: number;
pageSize?: number;
}
Inside the class, add the definition for Constructor. This sets the default props and state when the component is loaded initially.
private selectQuery: string[] = [];
private expandQuery: string[] = [];
constructor(props: IViewAllItemsProps){
super(props);
this.state ={
items:[],
columns: this.buildColumns(this.props),
currentPage:1,
pageSize: this.props.pageSize
};
this._onPageUpdate = this._onPageUpdate.bind(this);
this.getListItemsCount(`${this.props.siteUrl}/_api/web/lists/GetByTitle
('${props.listName}')/ItemCount`);
const queryParam = this.buildQueryParams(props);
this.readItems(`${this.props.siteUrl}/_api/web/lists/GetByTitle
('${props.listName}')/items${queryParam}`);
}
Add the method componentWillReceiveProps
to the ViewAllItems
class. This is the react life cycle method that will render the JSX if the properties supplied for this component changes.
public componentWillReceiveProps(nextProps: IViewAllItemsProps): void{
this.setState({
columns:this.buildColumns(nextProps),
pageSize: nextProps.pageSize
});
this.getListItemsCount(`${this.props.siteUrl}/_api/web/lists/GetByTitle
('${this.props.listName}')/ItemCount`);
const queryParam = this.buildQueryParams(nextProps);
this.readItems(`${this.props.siteUrl}/_api/web/lists/GetByTitle
('${nextProps.listName}')/items${queryParam}`);
}
Add render
method to the class with the below code snippet. When the JSX element is loaded to DOM, it will check if the webpart needsConfiguration
by calling this method which on the webpart
class which returns a boolean. if the method returns true
, it will load the config component JSX to DOM else it will load the current JSX to the DOM.
public render(): JSX.Element {
const { needsConfiguration, configureWebPart} = this.props;
let {items, columns, pageSize} = this.state;
return (
<div className={styles.viewAllItems}>
<div>
{needsConfiguration &&
<Config configure={configureWebPart} {...this.props}/>
}
{ needsConfiguration === false &&
<div>
<div>
<div>
<div className={styles.status}>{this.state.status}</div>
<Paging
totalItems={ this.state.itemCount }
itemsCountPerPage={ this.state.pageSize }
onPageUpdate={ this._onPageUpdate }
currentPage={ this.state.currentPage }/>
<div></div>
<DetailsList
items = {items}
columns = {columns}
isHeaderVisible = {true}
layoutMode = {LayoutMode.justified}
constrainMode ={ConstrainMode.unconstrained}
checkboxVisibility={CheckboxVisibility.hidden}
onColumnHeaderClick={ this._onColumnClick }
/>
</div>
</div>
</div>
}
</div>
</div>
);
}
Add method readItems
to ViewAllItems
class. This method will return the list items from SharePoint.
private readItems(url: string) {
this.setState({
items: [],
status: 'Loading all items...'
});
this.props.spHttpClient.get(url,
SPHttpClient.configurations.v1,
{
headers: {
'Accept': 'application/json;odata=nometadata',
'odata-version': ''
}
}).then((response: SPHttpClientResponse): Promise<{value: any[]}> =>{
return response.json();
}).then((response: {value: any[]}): void => {
this.setState({
items: response.value,
status: `Showing items ${(this.state.currentPage - 1)*this.props.pageSize +1} -
${(this.state.currentPage -1) * this.props.pageSize +
response.value.length}
of ${this.state.itemCount}`
});
}, (error: any): void => {
this.setState({
items: [],
status: 'Loading all items failed with error: ' + error
});
});
}
Add method getListItemsCount
which returns the list items count which is required by the Paging component to build pagination.
Private getListItemsCount(url: string) {
this.props.spHttpClient.get(url,SPHttpClient.configurations.v1,
{
headers: {
'Accept': 'application/json;odata=nometadata',
'odata-version':''
}
}).then((response: SPHttpClientResponse): Promise<{value: number}> =>{
return response.json();
}).then((response: {value: number}): void => {
this.setState({
itemCount: response.value
});
});
}
Add method _onPageUpdate
method to ViewAllItems
class. This method will be triggered when page numbers are clicked on the view. This method in turn calls the readItems
method by passing the updated url
as argument. Url
is built with skiptoken
, page ID and page size as parameters to get the right items from the list when the page number is clicked.
private _onPageUpdate(pageNumber: number) {
this.setState({
currentPage: pageNumber,
});
const p_ID = (pageNumber - 1)*this.props.pageSize;
const selectColumns = '&$select='+this.selectQuery;
const expandColumns = '&$expand='+this.expandQuery;
const queryParam = `%24skiptoken=Paged%3dTRUE%26p_ID=${p_ID}&
$top=${this.props.pageSize}`;
var url = `${this.props.siteUrl}/_api/web/lists/GetByTitle
('${this.props.listName}')/items?`+
queryParam + selectColumns+expandColumns;
this.readItems(url);
}
Add method _onColumnClick
to the ViewAllItems
class. This method is triggered when user clicks on the headers of the column in the list view. This method does the sorting for the results that are displayed.
private _onColumnClick(event: React.MouseEvent<HTMLElement>, column: IColumn) {
let { items, columns } = this.state;
let isSortedDescending = column.isSortedDescending;
if (column.isSorted) {
isSortedDescending = !isSortedDescending;
}
items = items!.concat([]).sort((a, b) => {
let firstValue = a[column.fieldName];
let secondValue = b[column.fieldName];
if (isSortedDescending) {
return firstValue > secondValue ? -1 : 1;
} else {
return firstValue > secondValue ? 1 : -1;
}
});
this.setState({
items: items,
columns: columns!.map(col => {
col.isSorted = (col.key === column.key);
if (col.isSorted) {
col.isSortedDescending = isSortedDescending;
}
return col;
})
});
}
Add methods buildQueryParams
and buildColumns
to the ViewAllItems
Class.
buildQueryParams
- This method returns url
query string that is required by the SharePoint Rest API to select and expand the columns that are selected on the webpart
property pane. buildColumns
- This method returns array of columns that is required by the DetailsList
office UI fabric component to build the view.
private buildQueryParams(props: IViewAllItemsProps): string{
this.selectQuery = [];
this.expandQuery = [];
props.selectedColumns.forEach(element => {
if(element.text === "Person or Group" || element.text === "Lookup"){
this.selectQuery.push(element.key+"/Title");
this.expandQuery.push(element.key);
}
else{
this.selectQuery.push(element.key);
}
});
const queryParam = `?%24skiptoken=Paged%3dTRUE%26p_ID=1&$top=${props.pageSize}`;
const selectColumns = this.selectQuery === null || this.selectQuery===undefined ||
this.selectQuery.length === 0? "" : '&$select='+this.selectQuery.join();
const expandColumns = this.expandQuery === null || this.expandQuery===undefined ||
this.expandQuery.length === 0? "" : '&$expand='+this.expandQuery.join();
return queryParam+selectColumns+expandColumns;
}
private buildColumns(props: IViewAllItemsProps): IColumn[]{
const columns: IColumn[]=[];
props.selectedColumns.forEach(element => {
if(element.text === "Person or Group" || element.text === "Lookup"){
const column: IColumn ={
key: element.key,
name: element.key.indexOf("_x0020_") !== -1?
element.key.replace("_x0020_"," "):element.key,
fieldName: element.key,
minWidth: 100,
maxWidth: 350,
isResizable: true,
data: 'string',
onRender: (item: any) => {
return (
<span>
{ item[element.key]["Title"] }
</span>
);
}
};
columns.push(column);
}
else{
const column: IColumn ={
key: element.key,
name: element.key.indexOf("_x0020_") !== -1?
element.key.replace("_x0020_"," "):element.key,
fieldName: element.key,
minWidth: 100,
maxWidth: 350,
isResizable: true,
data: 'string',
isMultiline: element.text === "Multiple lines of text" ? true:false
};
columns.push(column);
}
});
return columns;
}
Config Component
Add folder Config to the solution and then add files IConfigProp.ts, Config.module.scss and Config.tsx files to this folder.
IConfigProp.ts
Interface IConfigProp.ts defines the properties that are required to load Config.tsx component.
import { DisplayMode } from '@microsoft/sp-core-library';
export interface IConfigProps {
displayMode: DisplayMode;
configure: () => void;
}
Config.module.scss
This file contains the style that is required to style the Config.tsx Component.
.placeholder {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
.placeholderContainer {
-webkit-box-align: center;
-ms-flex-align: center;
-ms-grid-row-align: center;
align-items: center;
color: "[theme:neutralSecondary, default: #666666]";
background-color: "[theme:neutralLighter, default: #f4f4f4]";
width: 100%;
padding: 80px 0;
.placeholderHead {
color: "[theme:neutralPrimary, default: #333333]";
.placeholderHeadContainer {
height: 100%;
white-space: nowrap;
text-align: center;
}
.placeholderIcon {
display: inline-block;
vertical-align: middle;
white-space: normal;
}
.placeholderText {
display: inline-block;
vertical-align: middle;
white-space: normal
}
}
.placeholderDescription {
width: 65%;
vertical-align: middle;
margin: 0 auto;
text-align: center;
.placeholderDescriptionText {
color: "[theme:neutralSecondary, default: #666666]";
font-size: 17px;
display: inline-block;
margin: 24px 0;
font-weight: 100;
}
button {
font-size: 14px;
font-weight: 400;
box-sizing: border-box;
display: inline-block;
text-align: center;
cursor: pointer;
vertical-align: top;
min-width: 80px;
height: 32px;
background-color: "[theme:themePrimary, default: #0078d7]";
color: #fff;
user-select: none;
outline: transparent;
border-width: 1px;
border-style: solid;
border-color: transparent;
border-image: initial;
text-decoration: none;
}
}
}
}
[dir=ltr] .placeholder,
[dir=rtl] .placeholder {
.placeholderContainer {
.placeholderHead {
.placeholderText {
padding-left: 20px;
}
}
}
}
.placeholderOverlay {
position: relative;
height: 100%;
z-index: 1;
.placeholderSpinnerContainer {
position: relative;
width: 100%;
margin: 164px 0
}
}
Config.tsx
Replace class with the below code snippet in the Config.tsx. The _handleButtonClick
event will trigger the configureWebPart
method on the webpart.ts file which is passed to this component as a property.
import * as React from 'react';
import { Fabric } from 'office-ui-fabric-react';
import { DisplayMode } from '@microsoft/sp-core-library';
import { IConfigProps } from './IConfigProps';
import styles from './Config.module.scss';
import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';
export class Config extends React.Component<IConfigProps, {}> {
constructor(props: IConfigProps){
super(props);
this._handleBtnClick = this._handleBtnClick.bind(this);
}
public render(): JSX.Element {
return (
<Fabric>
{ this.props.displayMode === DisplayMode.Edit &&
<div className={`${styles.placeholder}`}>
<div className={styles.placeholderContainer}>
<div className={styles.placeholderHead}>
<div className={styles.placeholderHeadContainer}>
<i className={`${styles.placeholderIcon} ms-fontSize-su
ms-Icon ms-ICon--CheckboxComposite`}></i>
<span className={`${styles.placeholderText} ms-fontWeight-light
ms-fontSize-xxl`}>Configure your web part</span>
</div>
</div>
<div className={styles.placeholderDescription}>
<span className={styles.placeholderDescriptionText}>
Please configure the web part.</span>
</div>
<div className={styles.placeholderDescription}>
<PrimaryButton
text="Configure"
ariaLabel="Configure"
ariaDescription="Please configure the web part"
onClick={this._handleBtnClick} />
</div>
</div>
</div>
}
{ this.props.displayMode === DisplayMode.Read &&
<div className={`${styles.placeholder}`}>
<div className={styles.placeholderContainer}>
<div className={styles.placeholderHead}>
<div className={styles.placeholderHeadContainer}>
<i className={`${styles.placeholderIcon} ms-fontSize-su ms-Icon
ms-ICon--CheckboxComposite`}></i>
<span className={`${styles.placeholderText}
ms-fontWeight-light ms-fontSize-xxl`}>
Configure your web part</span>
</div>
</div>
<div className={styles.placeholderDescription}>
<span className={styles.placeholderDescriptionText}>
Please configure the web part.</span>
</div>
</div>
</div>
}
</Fabric>
);
}
private _handleBtnClick(event?: React.MouseEvent<HTMLButtonElement>) {
this.props.configure();
}
}
Paging Component
Add folder Paging to the solution and then add files IPagingProp.ts, Paging.module.scss and Paging.tsx files to this folder.
IPagingProp.ts
Interface IPagingProp.ts defines the properties that are required to load Paging.tsx component.
export interface IPagingProps {
totalItems: number;
itemsCountPerPage: number;
onPageUpdate: (pageNumber: number) => void;
currentPage: number;
}
Paging.module.scss
This file contains the style that is required to style the Paging.tsx Component.
.paginationContainer {
text-align: right;
.searchWp__paginationContainer__pagination {
display: inline-block;
text-align: center;
ul {
display: inline-block;
padding-left: 0;
border-radius: 4px;
li {
display: inline;
a {
float: left;
padding: 5px 10px;
text-decoration: none;
border-radius: 15px;
i {
font-size: 10px;
}
}
a:visited {
color: inherit;
}
a.active {
background-color: #0078d7!important;
color: white!important;
}
}
}
}
}
Paging.tsx
Replace class with the below code snippet in the Paging.tsx. The _onPageUpdate
event will trigger the _onPageUpdate
method on the ViewAllItems.tsx file which is passed as a property to this component.
import * as React from "react";
import {IPagingProps} from "./IPagingProps";
import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';
import Pagination from "react-js-pagination";
import styles from './Paging.module.scss';
export default class Paging extends React.Component<IPagingProps, null> {
constructor(props: IPagingProps) {
super(props);
this._onPageUpdate = this._onPageUpdate.bind(this);
}
public render(): React.ReactElement<IPagingProps> {
return(
<div className={`${styles.paginationContainer}`}>
<div className={`${styles.searchWp__paginationContainer__pagination}`}>
<Pagination
activePage={this.props.currentPage}
firstPageText={<i className="ms-Icon
ms-Icon--ChevronLeftEnd6" aria-hidden="true"></i>}
lastPageText={<i className="ms-Icon
ms-Icon--ChevronRightEnd6" aria-hidden="true"></i>}
prevPageText={<i className="ms-Icon
ms-Icon--ChevronLeft" aria-hidden="true"></i>}
nextPageText={<i className="ms-Icon
ms-Icon--ChevronRight" aria-hidden="true"></i>}
activeLinkClass={ `${styles.active}` }
itemsCountPerPage={ this.props.itemsCountPerPage }
totalItemsCount={ this.props.totalItems }
pageRangeDisplayed={10}
onChange={this.props.onPageUpdate}
/>
</div>
</div>
);
}
private _onPageUpdate(pageNumber: number): void {
this.props.onPageUpdate(pageNumber);
}
Package and Deploy
- Execute the following
gulp
task to bundle your solution. This executes a release build of your project by using a dynamic label as the host URL for your assets. This URL is automatically updated based on your tenant CDN settings:
gulp bundle --ship
- Execute the following task to package your solution. This creates an updated
webpart.sppkg
package on the sharepoint/solution folder.
gulp package-solution --ship
- Upload or drag and drop the newly created client-side solution package to the app catalog in your tenant.
- Based on your tenant settings, if you would not have CDN enabled in your tenant, and the
includeClientSideAssets
setting would be true
in the package-solution.json, the loading URL for the assets would be dynamically updated and pointing directly to the ClientSideAssets folder located in the app catalog site collection.
History
- 20th March, 2018 - Article published