Development, SharePoint, SPFx

Create a SPFx web part to display SharePoint list data based on locale and Graph API data

This is an example of how I used the SharePoint Framework (SPFx) to create a modern page/modern experience web part that shows SharePoint list data based on a user’s Language/Locale/Country and some other user profile information from Azure Active Directory using the Graph API.

We onboard guest users using Azure AD B2B to our SharePoint Online tenant using a PowerShell script I documented before. We add some additional profile properties in the CSV when we onboard them, such as Office Location (Which we put whatever we want in here), etc. The issue is, SharePoint Online SPFx web parts do not have a way to pull this information. So, we use the Graph API.

User Data

SPFx Web Part

SharePoint List data

Comparison

Display comparison results in web part

This code will even create the SharePoint list for you using an elements.xml/schema.xml file.

There are some holes in the data structures and list columns since I removed some that are client specific. I did my best to match them up.

Solution

    1. Setup your SPFx environment https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-development-environment
  1. Install dependencies for jQuery and Bootstrap (if needed). You only need to run npm install if you don’t use the Yeoman generator (this creates the node_modules folder in your solution. Since these files are not in TFS, I run npm install on new environments when grabbing from source control):
npm install

npm install --save jquery@2

npm install --save bootstrap
  1. Open SPFXProject1\config\config.json and add the path for jQuery and Bootstrap.
...
 "externals": {
 "jquery": {
 "path": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js",
 "globalName": "jquery"
 },
 "bootstrap": {
 "path": "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js",
 "globalName": "bootstrap"
 }
 },
...
  1. Open SPFXProject1\config\package-solution.json and add the Elements/schema assets reference so the SharePoint list can be created. This creates a feature that deploys the list, which is good to know for uninstalling/troubleshooting.
...
"features": [
 {
 "title": "My Web Part Create List Feature",
 "description": "Deploys a list named My List for the slider web part, a content type and site columns",
 "id": "88d9ea22-0988-4c58-b551-b1c5dc92e211",
 "version": "1.0.0.0",
 "assets": {
 "elementManifests": [ "elements.xml" ],
 "elementFiles": [ "schema.xml" ]
 }
 }
 ]
...
  1. Open SPFXProject1\src\webparts\mywebpartname\MySliderWebPart.manifest.json file and add the full width web part zone hack from https://blog.velingeorgiev.com/how-add-spfx-webpart-full-width-column
...

 "manifestVersion": 2,
 "supportsFullBleed": true,

...

  1. Create the elements.xml file in SPFXProject1\SharePoint\assets\elements.xml
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<CustomAction
Title="MyWebPart"
Location="ClientSideExtension.ApplicationCustomizer"
ClientSideComponentId="8ec40281-8c15-4a97-ab64-cc1feeb7a50c"
ClientSideComponentProperties="{"testMessage":"Test message"}">
</CustomAction>
<Field ID="{3246929A-5ECA-4E1E-BC8F-4ED878F3A33A}"
Name="SPFxSliderImage"
DisplayName="Slider Image"
Type="Image"
RichText="TRUE"
RichTextMode="FullHtml"
Group="Homepage Slider"/>
<Field ID="{1837EB2A-0A2B-494B-9509-61AEDF7BB2CB}"
Name="SPFxSliderBGImage"
DisplayName="Slider BG Image"
Type="Image"
RichText="TRUE"
RichTextMode="FullHtml"
Group="Homepage Slider"/>
<Field ID="{CEEFD3D4-212F-48D7-AA8F-B9A23A7C0FFC}"
Name="SPFxOrder"
DisplayName="Order"
Type="Number"
Group="Homepage Slider" />
<Field ID="{1F2EEECC-5AB3-4D46-8656-E54FDEA3A08D}"
Name="SPFxDesc"
DisplayName="Description"
Type="Text"
Group="Homepage Slider" />
<Field ID="{683FD699-2404-4E6B-848C-100E8CF8D38E}"
Name="SPFxBtnLeftTxt"
DisplayName="Button Left Text"
Type="Text"
Group="Homepage Slider" />
<Field ID="{E3312F89-8492-4A54-9129-16170D64DD6F}"
Name="SPFxBtnLeftURL"
DisplayName="Button Left URL"
Type="Text"
Group="Homepage Slider" />
<Field ID="{9801966F-7B8F-4214-83CA-35C83D41A04A}"
Name="SPFxBtnRightTxt"
DisplayName="Button Right Text"
Type="Text"
Group="Homepage Slider" />
<Field ID="{1ECB4C39-8E31-491C-8A5C-6701C458518B}"
Name="SPFxBtnRightURL"
DisplayName="Button Right URL"
Type="Text"
Group="Homepage Slider" />
<Field ID="{8A327CB8-578A-433F-9F00-75D92B13825C}"
Name="SPFxSliderExpire"
DisplayName="Expires"
Type="DateTime"
Format="DateOnly"
Group="Homepage Slider" />
<ContentType ID="0x01009FAF6E59182F44F6AABD475FDC8F883A"
Name="Slider"
Group="My Company Content Types"
Description="Sample content type from web part solution">
<FieldRefs>
<FieldRef ID="{3246929A-5ECA-4E1E-BC8F-4ED878F3A33A}" />
<FieldRef ID="{1837EB2A-0A2B-494B-9509-61AEDF7BB2CB}" />
<FieldRef ID="{CEEFD3D4-212F-48D7-AA8F-B9A23A7C0FFC}" />
<FieldRef ID="{1F2EEECC-5AB3-4D46-8656-E54FDEA3A08D}" />
<FieldRef ID="{683FD699-2404-4E6B-848C-100E8CF8D38E}" />
<FieldRef ID="{E3312F89-8492-4A54-9129-16170D64DD6F}" />
<FieldRef ID="{9801966F-7B8F-4214-83CA-35C83D41A04A}" />
<FieldRef ID="{1ECB4C39-8E31-491C-8A5C-6701C458518B}" />
<FieldRef ID="{8A327CB8-578A-433F-9F00-75D92B13825C}" />
</FieldRefs>
</ContentType>
<ListInstance
CustomSchema="schema.xml"
FeatureId="00bfea71-de22-43b2-a848-c05709900100"
Title="My List"
Description="My List for site"
TemplateType="100"
Url="Lists/MyList">
</ListInstance>
</Elements>

The above template Types are 100 for Custom List.

 

  1. Create schema.xml with your list columns from the above elements.xml SPFXProject1\SharePoint\assets\schema.xml
<List xmlns:ows="Microsoft SharePoint" Title="Basic List" EnableContentTypes="TRUE" FolderCreation="FALSE" Direction="$Resources:Direction;" Url="Lists/Basic List" BaseType="0" xmlns="http://schemas.microsoft.com/sharepoint/">

<MetaData>
<ContentTypes>
<ContentTypeRef ID="0x01009FAF6E59182F44F6AABD475FDC8F883A" />
</ContentTypes>
<Fields></Fields>
<Views>
<View BaseViewID="1" Type="HTML" WebPartZoneID="Main" DisplayName="$Resources:core,objectiv_schema_mwsidcamlidC24;" DefaultView="TRUE" MobileView="TRUE" MobileDefaultView="TRUE" SetupPath="pages\viewpage.aspx" ImageUrl="/_layouts/images/generic.png" Url="AllItems.aspx">
<XslLink Default="TRUE">main.xsl</XslLink>
<RowLimit Paged="TRUE">30</RowLimit>
<Toolbar Type="Standard" />
<ViewFields>
<FieldRef Name="LinkTitle"></FieldRef>
<FieldRef Name="SPFxSliderImage"></FieldRef>
<FieldRef Name="SPFxSliderBGImage"></FieldRef>
<FieldRef Name="SPFxOrder"></FieldRef>
<FieldRef Name="SPFxDesc"></FieldRef>
<FieldRef Name="SPFxBtnLeftTxt"></FieldRef>
<FieldRef Name="SPFxBtnLeftURL"></FieldRef>
<FieldRef Name="SPFxBtnRightTxt"></FieldRef>
<FieldRef Name="SPFxBtnRightURL"></FieldRef>
<FieldRef Name="SPFxSliderExpire"></FieldRef>
</ViewFields>
<Query>
<OrderBy>
<FieldRef Name="ID" />
</OrderBy>
</Query>
</View>
</Views>
<Forms>
<Form Type="DisplayForm" Url="DispForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />
<Form Type="EditForm" Url="EditForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />
<Form Type="NewForm" Url="NewForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />
</Forms>
</MetaData></List>

  1. Add import statements to your SPFXProject1\src\webparts\mywebpartname\MySliderWebPart.ts code in Visual Studio so we can reference the above jQuery and Bootstrap code:
import * as jQuery from 'jquery';
import * as bootstrap from 'bootstrap';
  1. Import IWebPartContext from the @microsoft/sp-webpart-base so we can get the users locale from their browser session:
import {
 BaseClientSideWebPart,
 IPropertyPaneConfiguration,
 PropertyPaneTextField,
 IWebPartContext
} from '@microsoft/sp-webpart-base';
import { SPComponentLoader } from '@microsoft/sp-loader';
  1. Import intersection from lodash so we can compare where two arrays intersect easily and the Graph API. Note, the day after I wrote this web part, MS released a new Graph API for SPFx. See link in comment to do it the newer way (recommended):
// GraphHttpClient is in preview and will be depricated soon. Use new method https://docs.microsoft.com/en-us/sharepoint/dev/spfx/use-msgraph
import { SPHttpClient, SPHttpClientResponse, GraphHttpClient, GraphHttpClientResponse } from '@microsoft/sp-http';
import { escape, intersection } from '@microsoft/sp-lodash-subset';
  1. Now we can jump into setting up the data structures:

// All of the list data we get back from SharePoint, REST API response format

export interface ISlides {
value: ISlide[];
}

// Each item in the list from REST API response
export interface ISlide {
ID: number;
Title: string;
SPFxDesc: string;
SPFxBtnLeftTxt: string;
SPFxBtnLeftURL: string;
SPFxBtnRightTxt: string;
SPFxBtnRightURL: string;
SPFxSliderExpire: Date;
SPFxOrder: number;
Office: string;
Language: string;
Region: string[];
}

// Each item in the list's Publishing HTML Image, since its an extended property we need a second rest call
export interface ISlideItem {
SPFxSliderImage: string;
SPFxSliderBGImage: string;
}

export interface IItemGuid {
value: string;
}

// each actual list item data
export interface slides {
ID: number;
Title: string;
UrlSlideImg: string;
UrlSlideBGImg: string;
SPFxDesc: string;
SPFxBtnLeftTxt: string;
SPFxBtnLeftURL: string;
SPFxBtnRightTxt: string;
SPFxBtnRightURL: string;
SPFxSliderExpire: Date;
SPFxOrder: number;
Office: string;
Language: string;
Region: string[];
}

export interface IUserProfile {
Office: Array<string>;
OfficeString: string;
Locale: string;
Language: string;
Region: string;
CountryCode: string;
}

// User profile info from Graph API
export interface IUserProfileResponse {
[property: string]: string;
}
  1. On the export statement, load your jQuery from CDN. These lines give some warnings during the gulp bundle/package process. Let me know if you know how I can fix them, but this way works.

private _slides: slides[] = [];
public constructor(context: IWebPartContext) {
super();
// FIX LATER- redundant to load jquery twice, but this will load bootstrap JS and CSS. Fix this and top jQuery import
SPComponentLoader.loadCss('https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css');
SPComponentLoader.loadCss('https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css');

SPComponentLoader.loadScript('https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js', { globalExportsName: 'jQuery' }).then((jQuery: any): void => {
 SPComponentLoader.loadScript('https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js', { globalExportsName: 'jQuery' }).then((): void => {});
});

}

  1. Now create your REST API call methods (Replace Some List with your SharePoint list name)
private _getCurrentUserProfile(): Promise<IUserProfileResponse> {
// Query user properties from office graph API
// Tutorial: https://docs.microsoft.com/en-us/sharepoint/dev/spfx/call-microsoft-graph-using-graphhttpclient
// Graph API properties https://github.com/microsoftgraph/microsoft-graph-docs/blob/master/api-reference/v1.0/api/user_list.md#user-content-example-2-users-request-using-select
let promise: Promise<IUserProfileResponse> = new Promise<IUserProfileResponse>((resolve, reject) => {
// Call the Rest API for current user, get AAD properties.
this.context.graphHttpClient.get("v1.0/me?$select=displayName, officeLocation, preferredLanguage, givenName, country", GraphHttpClient.configurations.v1)
.then((response: GraphHttpClientResponse) => {
//console.log("response.json(): " + response.json());
resolve(response.json());
}).catch((error: any) => {
reject(error);
});

return promise;
});
return promise;

}

private _getSlides(): Promise<ISlides> {
return this.context.spHttpClient.get(`${this.context.pageContext.web.absoluteUrl}/_api/web/lists/getByTitle('My List')/items?select=Title,SPFxDesc,SPFxBtnLeftURL,SPFxBtnLeftTxt,SPFxBtnRightURL,SPFxBtnRightTxt,SPFxSliderExpire,SPFxOrder,Office,Language,Region&$orderBy=SPFxOrder asc`,
SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
});
}

private _getImage(id: number): Promise<ISlideItem> {
return this.context.spHttpClient.get(`${this.context.pageContext.web.absoluteUrl}/_api/web/lists/getByTitle('My List')/items('${id}')/FieldValuesAsHtml`,
SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
});
}

//private _getImageUrl(url: string): Promise<string> {
// return this.context.spHttpClient.get(`${this.context.pageContext.web.absoluteUrl}/_api/web/GetFileByServerRelativeUrl('${this.context.pageContext.web.serverRelativeUrl}${url}')/ListItemAllFields/ServerRelatveUrl`,
// SPHttpClient.configurations.v1)
// .then((response: SPHttpClientResponse) => {
// return response.json();
// })
// .then((item: IItemGuid) => {
// return item.value;
// });
//}

private _itemCarouselIndicators(index: number): string {
if (index == 0) {
return `<li data-target="#myCarousel" data-slide-to="${index}" class="active" style="background-color:#d7d7d7"></li>`;
}
else {
return `<li data-target="#myCarousel" data-slide-to="${index}" class=""></li>`;
}
}

private _userProfile(aadquery: IUserProfileResponse) {
let userProfile: IUserProfile = { Office: [""], CountryCode: "", Language: "", Locale: "", OfficeString: "", Region: "" };
//console.log(userProfile);
// Store result for each property
// console.log("Graph API current user: " + result.displayName + result.officeLocation + result.preferredLanguage + result.givenName + result.country);

// 1. Office (Office)
userProfile.OfficeString = aadquery.officeLocation;
if (!userProfile.OfficeString) {
userProfile.OfficeString = "Los Angeles";
}
//console.log("Office: " + me._userprofile.OfficeString);
userProfile.Office = userProfile.OfficeString.split(",");
console.log("Users Office:" + userProfile.Office);

// 2. Locale (preferredLanguage) - OLD WAY from AAD, not good.
//userProfile.Locale = aadquery.preferredLanguage;
//if (!userProfile.Locale) {
// userProfile.Locale = "en-US";
//}
//console.log("Users Locale: " + me._userprofile.Locale);

// 3. Country Code (country)
userProfile.CountryCode = aadquery.country;
if (!userProfile.CountryCode) {
userProfile.CountryCode = "US";
}
//console.log("Users Country: " + me._userprofile.CountryCode);

// Final formatting of data

// Select current users Lanaguage (spelled out)
// from en-US, just keep the language. We use Country from AAD to determine Country/Region, not locale
let userLang: string = this.context.pageContext.cultureInfo.currentUICultureName;

userLang = userLang.substring(0, 2);

switch (userLang) {
case "en":
userProfile.Language = "English";
break;
case "es":
userProfile.Language = "Spanish";
break;
default:
userProfile.Language = "English";
}
console.log("Users Language: " + userProfile.Language);

// Select current users Region
// Define all US and CA regions, default any other country to Latin America
// Phase 2 or so, make this read from a SP lookup list.
switch (userProfile.CountryCode) {
// USA
case "US":
userProfile.Region = "USA";
break;

// United Kingdom
case "GB":
userProfile.Region = "United Kingdom";
break;

// Mexico (not really needed since default is Latin America)
case "MX":
userProfile.Region = "Latin America";
break;

// Latin America - Default anyone not US or GB to Latin America for now, since thats the 3rd region and is currently the only region with multiple countries (cases)
default:
userProfile.Region = "Latin America";
}
console.log("Users Region: " + userProfile.Region);

return userProfile;
}
private _itemSlideWrapper(item: slides, index: number): string {
// if the buttons are empty, dont display them
let leftButtonHTML: string = "";
let rightButtonHTML: string = "";
let SPFxBtnLeftTxt = item.SPFxBtnLeftTxt ? item.SPFxBtnLeftTxt : '';
let SPFxBtnRightTxt = item.SPFxBtnRightTxt ? item.SPFxBtnRightTxt : '';
if (SPFxBtnLeftTxt.length > 0) {
leftButtonHTML = `<a href="${item.SPFxBtnLeftURL ? item.SPFxBtnLeftURL : '#'}" style= "margin-right: 20px;" target= "_blank" > <div class="slider-button ${styles.sliderButton}" > ${SPFxBtnLeftTxt} </div></a>`;
}
if (SPFxBtnRightTxt.length > 0) {
rightButtonHTML = `<a href="${item.SPFxBtnRightURL ? item.SPFxBtnRightURL : '#'}"><div class="slider-button ${styles.sliderButton}" target="_blank"> ${SPFxBtnRightTxt} </div></a>`;
}
//check if slide is the first (active) or not
let activeFlag: string = "";
if (index == 0) {
activeFlag = "active";
}
//return slide HTML
return `
<div class="${styles.item} item ${activeFlag}" style="background-image: url(${item.UrlSlideBGImg ? item.UrlSlideBGImg : ''})">
<div class="row">
<div class="col col-xs-12 col-sm-6 col-md-6 carousel-image ${styles.carouselImage}">
<img src="${item.UrlSlideImg ? item.UrlSlideImg : ''}" alt="${item.Title ? item.Title : ''}">
</div>
<div class="col col-xs-12 col-sm-6 col-md-6 carousel-text ${styles.carouselText}">
<h1>${item.Title ? item.Title : ''}</h1>
<p>${item.SPFxDesc ? item.SPFxDesc : ''}</p>
<div class="slider-buttons ${styles.sliderButtons}">
${leftButtonHTML}
${rightButtonHTML}
</div>
</div>
</div>
</div>`;
}
  1. Finally, the big puppy, the render() method
public render(): void {

// only render this once
if (!this.renderedOnce) {
var me = this;
// Get users profile object
this._getCurrentUserProfile()
.then((_userprofileresponse: IUserProfileResponse) => {
//console.log("userprofileresponse: ");
//console.log(_userprofileresponse);

// format the users profile to compare to news
let userProfile: IUserProfile = this._userProfile(_userprofileresponse);

//console.log("userProfile: " + userProfile);

//Go get the slider. Compare to users profile object and determine to display it or not

this._getSlides()
.then((response: ISlides): void => {
// get slider images
response.value.forEach((slide: ISlide): void => {
this._getImage(slide.ID)
.then((data: ISlideItem): void => {
//Fix these image queries to be failsafe if nothing comes back for each slide image
//Slider Image
// get the image out of the FieldValuesAsHtml
let divSliderImg = document.createElement('div');
divSliderImg.innerHTML = data.SPFxSliderImage;
let imgSlider: HTMLImageElement = divSliderImg.firstChild as HTMLImageElement;
// need to do string split inorder to make the url relative
const imgSliderUrl: string = imgSlider.src.split(this.context.pageContext.web.serverRelativeUrl)[1];
//Slider BG Image
// get the image out of the FieldValuesAsHtml
let divSliderBGImg = document.createElement('div');
divSliderBGImg.innerHTML = data.SPFxSliderBGImage;
let imgSliderBG: HTMLImageElement = divSliderBGImg.firstChild as HTMLImageElement;
// need to do string split inorder to make the url relative
const imgSliderBGUrl: string = imgSliderBG.src.split(this.context.pageContext.web.serverRelativeUrl)[1];
//Continue to store data from query for each slide
const item: slides = {
ID: slide.ID,
Title: slide.Title,
UrlSlideImg: `${this.context.pageContext.web.absoluteUrl}/_layouts/15/getpreview.ashx?resolution=2&path=${this.context.pageContext.web.serverRelativeUrl}${imgSliderUrl}&clientType=modernWebPart`,
UrlSlideBGImg: `${this.context.pageContext.web.absoluteUrl}/_layouts/15/getpreview.ashx?resolution=2&path=${this.context.pageContext.web.serverRelativeUrl}${imgSliderBGUrl}&clientType=modernWebPart`,
SPFxDesc: slide.SPFxDesc,
SPFxBtnLeftTxt: slide.SPFxBtnLeftTxt,
SPFxBtnLeftURL: slide.SPFxBtnLeftURL,
SPFxBtnRightTxt: slide.SPFxBtnRightTxt,
SPFxBtnRightURL: slide.SPFxBtnRightURL,
SPFxSliderExpire: slide.SPFxSliderExpire,
SPFxOrder: slide.SPFxOrder,
Language: slide.Language,
Region: slide.Region,
Office: slide.Office

};
console.log("Slide ID:" + item.ID);
let slideOfficeString: string = item.Office + '';

let slideOffice: Array<string> = slideOfficeString.split(","); //["Los Angeles", "Value 1", "Value 2"];
console.log("Slide Office" + slideOffice);
let userOffice: Array<string> = userProfile.Office; //["Los Angeles", "Value 1"];

let slideLanguage: string = item.Language; //"English";
let userLanguage: string = userProfile.Language; //should this support multiple?;
console.log("Slide Language:" + slideLanguage);

let slideRegion: Array<string> = item.Region; //["USA", "United Kingdom", "Latin America"];
let userRegion: string = userProfile.Region;
console.log("Slide Region:" + slideRegion);

// reset each news items flag for users filter

let flag1: boolean = false;
let flag2: boolean = false;
let flag3: boolean = false;

// compare each user variable to slide item column

// see if users Office is in the slider item
// make case upper
for (let i = 0; i < slideOffice.length; i++) {
slideOffice[i] = slideOffice[i].toUpperCase();
}
for (let i = 0; i < userOffice.length; i++) {
userOffice[i] = userOffice[i].toUpperCase();
}
let resultcompare: Array<string> = intersection(userOffice, slideOffice);
if (resultcompare.length > 0) {
flag1 = true;
}

// see if users language is in the slider item
if (slideLanguage.toUpperCase() === userLanguage.toUpperCase()) {
flag2 = true;
}

//See if users region is in the slider item
// FIX- This should be list driven from the region/country list and UPS.
for (let i = 0; i < slideRegion.length; i++) {
slideRegion[i] = slideRegion[i].toUpperCase();
}
if (slideRegion.indexOf(userRegion.toUpperCase()) > -1) {
flag3 = true;
}

console.log("Compare Result- Office: " + flag1);
console.log("Compare Result- Language: " + flag2);
console.log("Compare Result- Region: " + flag3);

// if all 3 filters match, show the news item to the user

if (flag1 && flag2 && flag3) {
// Print IT! Append the item to the News object to be pushed to the DOM!

this._slides.push(item);
//console.log("Print the slider item!");
}
else {
console.log("Skipping Slide " + item.ID + ". There are " + this._slides.length + " slides that have been added.");
}

})
.then((): void => {
this.domElement.innerHTML = `
<div class="carousel-container">
<div style="height: 15px; width: 100%; background: #333333;"></div>

<div id="myCarousel" class="carousel slide ${styles.carousel}" data-ride="carousel">
<!-- Indicators -->
<ol class="${styles.carouselIndicators} carousel-indicators"></ol>
<!-- Wrapper For Slides -->
<div class="${styles.carouselInner} carousel-inner" role="listbox"></div>
<!-- Side Controls -->
<a class="${styles.carouselControl} carousel-control left" href="#myCarousel" data-slide="prev"><span class="glyphicon ${styles.glyphiconChevronLeft} glyphicon glyphicon-chevron-left"></span></a>
<a class="${styles.carouselControl} carousel-control right" href= "#myCarousel" data-slide="next" > <span class="glyphicon ${styles.glyphiconChevronRight} glyphicon glyphicon-chevron-right"></span></a>
</div>
</div>`;

// add news items to domElement
if (this._slides.length > 0) {

//Quick fix for edit mode saving not doubling query to slides
jQuery('.carousel-indicators').empty();
var today = new Date();

// Sort by date again
var sortResults: slides[] = this._slides.sort(function compare(a, b) {
if (a.SPFxOrder > b.SPFxOrder) {
return 1;
}
if (a.SPFxOrder < b.SPFxOrder) {
return -1;
}
return 0;
});
sortResults.forEach((item: slides, index: number): void => {
if (index <= 3 && item.SPFxSliderExpire <= today) {

jQuery('.carousel-indicators', this.domElement).append(this._itemCarouselIndicators(index));
jQuery('.carousel-inner', this.domElement).append(this._itemSlideWrapper(item, index));
console.log("Slide "+index + " ID: " + item.ID + " appended to DOM.");
}
else {
console.log("Skipping Slide " + index + " ID: " + item.ID + " Expires: " + item.SPFxSliderExpire + " " + item.SPFxOrder + " Total Slides in list:" + this._slides.length + " and today's date is: " + today);
}
});
}
else {
// no slide items case here
}
// initialize the slider
jQuery('#myCarousel').carousel('pause');

});
});
});
});
}
}
  1. Add the SASS (I removed some of the colors so it might not be perfect):
$white: #fff;
$black: #000;
$blue: blue;

@import url('https://fonts.googleapis.com/css?family=Open+Sans:300,400,600');

@mixin transition() {
-webkit-transition: 0.5s;
-moz-transition: 0.5s;
-ms-transition: 0.5s;
-o-transition: 0.5s;
transition: 0.5s;
}

.carousel {
background-color: #3279c3;
font-weight: 100;

.item {
min-height: 400px; /* Prevent carousel from being distorted if for some reason image doesn't load */
background-repeat: no-repeat;
background-size: 45%;

img {
margin: 0 auto; /* Align slide image horizontally center */
}
}

.carouselInner {
.carouselImage {
text-align: center;
}

img {
width: 100%;
max-height: 400px;
max-width: 500px;
padding: 10px;
}

.col {
@media screen and (max-width: 767px) {
text-align: center;
}
}

.carouselText {
max-width: 510px;
color: $white;
font-size: 14px;
font-weight: 300;
line-height: 25px;
letter-spacing: 1px;
padding: 0 30px;

@media screen and (max-width: 767px) {
text-align: center;
max-width: 100%;
}

h1 {
font-size: 28px;
font-weight: 100;

@media screen and (max-width: 767px) {
font-size: 48px;
font-weight: 300;
}
}

p {
margin-bottom: 30px;
color: $white;

@media screen and (max-width: 767px) {
font-size: 12px;
line-height: 18px;
}
}

.sliderButtons {
display: flex;

a {
background: $white;
border: 0;
border-radius: 4px;
font-family: 'Segoe UI', Arial, sans-serif;
font-size: 14px;
font-weight: 400;
margin-bottom: 20px;
text-align: center;
text-decoration: none;
text-transform: none;
width: 50%;

@media screen and (max-width: 767px) {
min-width: 100%;
}

&:hover {
background-color: #394a4f;
border: 0;
text-decoration: none;
}
}

.sliderButton {
border: 0;
border-radius: 4px;
color: $blue;
display: block;
font-size: 14px;
font-weight: 400;
letter-spacing: normal;
padding: 6px 10px;
text-align: center;
text-transform: none;

&:hover {
background-color: #394a4f;
border: 0;
color: $white;
}
}
}
}
}

.carouselIndicators {
/*position: relative;
display: inline-block;
width: 100%;
margin-left: 0;
left: 0;
@media screen and (max-width: 767px) {
margin-top: 50px;*/
bottom: 6px;

li {
background: $white;
width: 10px;
height: 10px;
margin: 0 8px;
border: none;
}

li {
.active {
background: #c0c0c0 !important;
border: none;
width: 10px;
height: 10px;
}
}
}

@media screen and (max-width: 767px) {
.carouselIndicators {
display: none;
}
}

.carouselControl .glyphiconChevronLeft, .carouselControl .glyphiconChevronRight, .carouselControl .iconNext, .carouselControl .iconPrev {
width: 40px;
height: 40px;
margin-top: -10px;
font-size: 40px;

@media screen and (max-width: 767px) {
display: none;
}
}
}

  1. Now we are ready to build and deploy! Run these node.js commands to package. Then install your app package file and assets. (I deploy mine to CDN, see the SPFX Web Part CDN tutorial on the MS website https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/hosting-webpart-from-office-365-cdn ):
gulp bundle --ship
gulp package-solution --ship
  1. Add the app to your site
  2. On the new My List that is created, add the Language site column (OOTB).
  3. Add some list data to your My List and tag data. (below is a similar list I added the site columns to, its not the same for the slider since this list was for news. That’s all I had for sample data sorry)

  1. Add the web part to your modern page:

  1. It will appear here, if not, ctrl+f5 or check for build errors:

  1. View the web part:

  1. Use F12 Chrome to debug web part

Please comment below any enhancements to the code, or big areas I may have messed up on when sanitizing the data.

Thanks!

1 thought on “Create a SPFx web part to display SharePoint list data based on locale and Graph API data”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s