GoDaddy’s domain expiration process explained

I had a good call with GoDaddy today to explain the process of what happens to a domain when it is deleted. I was interested in purchasing a domain that I know was deleted today. Of course, the easiest way is to have the account holder un-delete the domain and transfer it, but in this case the user who deleted it no longer wants to deal with the domain, or me asking to have it transferred (it’s a two-step process and takes time).

When a domain is deleted, the account holder has a few weeks to undo that action. After this time, the domain goes to an expired domains auction. If no one buys it, GoDaddy backorder customers ($25-$35 cost for a backorder, good for a 1-year registration) will purchase it. If you do not have a backorder, you cannot purchase the domain during this time since you are just a normal person. The domain then goes to a secondary auction. The secondary auction expires about 84 days from when the domain was first deleted by the user. That’s why the GoDaddy support team says “about 90 days the domain could be released back for normal purchase” if no one buys it at auction. GoDaddy monitoring on a domain just sends you an email of the status. I suppose you could order a backorder during the auction process if you see no one is purchasing it, then when it closes and goes to backorders you might be able to get it that way.

Here is the time-table for the scenario of what happens to a domain when a user deletes it:

Day Status

0

User deletes domain

26

Expired Domain Auction starts

36

If someone bids, done, domain is sold.
If no one bids, it goes to the first person who Backordered the domain. You cannot simply buy the domain for $9.99, you need to purchase a backorder.

41

3-day floating period the domain is in limbo. Nothing happens here.

43

Secondary Domain Auction starts

84

The domain is released for normal purchase if it didn’t sell

 

So, if the domain you are looking for is any good, it might sell at auction. Otherwise, purchase a backorder for $25-$35 and hope you get it! If it sells in the auction, you are out the $ for the backorder, so it’s a gamble. In this case, I don’t want to pay (gamble) that no one bids and I will get the domain, since I am just interested in trying to list it for sale myself, so I am passing on purchasing a backorder and just letting it go. I might check back in 84 days but I have a felling it will be purchased.

A good rule of thumb I have noticed is if the domain is worth anything, some domain selling company will snatch it up and you will never see it again. Still waiting for ericschrader.com, I have been waiting for this to fall out of auctions and auction resellers for about 5 years now.

But hey, you might get lucky. I let SharePointEric.com expire a few years ago and it was bought at auction by a reseller, and now it’s back for normal purchase for $11.99 from GoDaddy:

GoDaddy has a cool apprasal tool to check the monetary value of a domain: https://www.godaddy.com/domain-value-appraisal/appraisal/?checkAvail=1&tmskey=&domainToCheck=sharepointeric.com

If you have any tips, post them below.

There is so much to purchasing domains now days, its not like the old days (back before emoji’s).

Updating Visual Studio 2017 Enterprise

Yesterday I noticed a release (VS 2017 Update 6) was released, but my Visual Studio was still running Update 3:

The newest update is version 15.6.

While reading how to update Visual Studio, the article mentioned Visual Studio Installer.

https://docs.microsoft.com/en-us/visualstudio/install/update-visual-studio

So I launched Visual Studio Installer and clicked “Update“. Note, the installer did not tell me what version will be installed, but I knew the latest GA release update is 15.6 (VS current release version https://docs.microsoft.com/en-us/visualstudio/releasenotes/vs2017-relnotes)

Now my Visual Studio shows version 15.6, which is the latest current release:

Microsoft Visual Studio Enterprise 2017 (3)

Version 15.6.0

VisualStudio.15.Release/15.6.0+27428.1

Microsoft .NET Framework

Version 4.7.02556

Installed Version: Enterprise

 

All done!

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):

[code language=”bash”]
npm install

npm install –save jquery@2

npm install –save bootstrap
[/code]

  1. Open SPFXProject1\config\config.json and add the path for jQuery and Bootstrap.

[code language=”text”]

"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"
}
},

[/code]

  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.

[code language=”text”]…
"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" ]
}
}
]
…[/code]

  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

[code language=”text”]…

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

[/code]

  1. Create the elements.xml file in SPFXProject1\SharePoint\assets\elements.xml

[code language=”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>[/code]

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

[code language=”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>

[/code]

  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:

[code language=”javascript”]import * as jQuery from ‘jquery’;
import * as bootstrap from ‘bootstrap’;[/code]

  1. Import IWebPartContext from the @microsoft/sp-webpart-base so we can get the users locale from their browser session:

[code language=”javascript”]import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField,
IWebPartContext
} from ‘@microsoft/sp-webpart-base’;
import { SPComponentLoader } from ‘@microsoft/sp-loader’;[/code]

  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):

[code language=”javascript”]// 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’;[/code]

  1. Now we can jump into setting up the data structures:

[code language=”javascript”]

// 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;
}[/code]

  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.

[code language=”javascript”]

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 => {});
});

}

[/code]

  1. Now create your REST API call methods (Replace Some List with your SharePoint list name)

[code language=”javascript”]
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>`;
}
[/code]

  1. Finally, the big puppy, the render() method

[code language=”javascript”]
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’);

});
});
});
});
}
}
[/code]

  1. Add the SASS (I removed some of the colors so it might not be perfect):

[code language=”css”]
$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;
}
}
}

[/code]

  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 ):

[code language=”powershell”]gulp bundle –ship
gulp package-solution –ship[/code]

  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!