Building an App with SPFx, React, and TypeScript Part 3: Routing

Introduction

We’re switching gears in this post and moving away from components and data to revisit our navigation from part 1. In that post, we ended up with two stateless functional components to render our navigation. One issue that you’ll have with navigation in a web part is that you can’t change the url. If you do, you’ll end up on a new SharePoint site, instead of a new page in our app.

We added a hash to our url in part 1 to prevent it from leaving the page. In the example of the members page, we use an anchor tag and our url would look like https://localhost:4321/temp/workbench.html#/members

export interface NavigationItemProps {
    url: string,
    children: React.ReactNode
}

const navigationItem = (props:NavigationItemProps) => (
    <li className={styles.NavigationItem}>
        <a 
            href={'#' + props.url}
            >{props.children}</a>
        
    </li>
);

Replacing Anchors with NavLink

We’re going to make a few changes to NavigationItem.tsx. The NavigationItemProps interface will get a new options property called exact and we’ve replaced the anchor tag with NavLink which comes from react-router-dom. The NavLink doesn’t use an href attribute. Instead, it uses “to“. You’ll notice, in the next code snippet, that we removed the hash that we were appending to the URL.

One of the benefits of NavLink is that it automatically adds an active style to the element when the url matches it. So all we need to do is add a “.active” to our css. We can also change it to something other than “active” but that’s fine for us so we’ll leave it.

import * as React from 'react';
import { NavLink } from 'react-router-dom';

import styles from '../NavigationItem/NavigationItem.module.scss';

export interface NavigationItemProps {
    url: string,
    exact?: boolean,
    children: React.ReactNode
}

const navigationItem = (props:NavigationItemProps) => (
    <li className={styles.NavigationItem}>
        <NavLink 
            to={props.url}
            exact={props.exact}
            >{props.children}</NavLink>
        
    </li> 
);

export default navigationItem;

Routing

We currently have a new “exact” property in NavigationItemProps and we replaced the anchor tag with NavLink. These changes alone will break the app. We need to wire the app up to be able to know what to do with that NavLink tag. To do that, we have to go back to the EventHub.tsx where our app begins. We need to import HashRouter from react-router-dom and wrap our app with it. In this case, we wrap Chrome with HashRouter. At this point, the app will begin loading again, our url’s work, and an active style is added to the link that matches the current url, but we’re still not done. I want the app to load components based on the url instead of seeing everything all at once.

import * as React from 'react';
import styles from '../EventHub.module.scss';
import { IEventHubProps } from './IEventHubProps';
import { escape } from '@microsoft/sp-lodash-subset';

import Chrome from '../../statelessComponents/chrome/chrome';
import EventInfo from '../EventInfo/EventInfo';
import Members from '../Members/Members';
import { HashRouter } from 'react-router-dom';

export default class EventHub extends React.Component<IEventHubProps, {}> {
  public render(): React.ReactElement<IEventHubProps> {
    return (
      <HashRouter>
        <Chrome>
          <EventInfo />
          <Members />
        </Chrome>
      </HashRouter>
    );
  }
}

I want the contents of Chrome to be dynamic based on the URL so we’re going to add a Switch to load the proper component.

import * as React from 'react';
import styles from '../EventHub.module.scss';
import { IEventHubProps } from './IEventHubProps';
import { escape } from '@microsoft/sp-lodash-subset';

import Chrome from '../../statelessComponents/chrome/chrome';
import EventInfo from '../EventInfo/EventInfo';
import Members from '../Members/Members';
import { HashRouter, Switch, Route } from 'react-router-dom';

export default class EventHub extends React.Component<IEventHubProps, {}> {
  public render(): React.ReactElement<IEventHubProps> {
    return (
      <HashRouter>
        <Chrome>
          <Switch>
              <Route path="/members" component={Members} />
              <Route path="/" component={EventInfo} exact />
              <Route render={() => <h1>Page Not found</h1>} />
          </Switch>
        </Chrome>
      </HashRouter>
    );
  }
}

There are a few things about the previous code that we need to point out. We start by importing Switch and Route from react-router-dom. We eliminated the Members and EventInfo tags from within Chrome and added Switch. Inside of Switch, we have Route tags that define what components to load when the url matches the path. Switch will allow the proper route to be selected. So if the site url contains “/members”, we load members. If the url contains “/”, our EventInfo component loads. That route also uses exact because we don’t want other url’s to load the EventInfo. In other words, without “exact” EventInfo will load whenever if finds a “/” which would include “/members” and anything else for that matter.

Now, we never created a component for the About page and I did that to show how we handled unexpected URLs. The 3rd route in our switch renders an inline function that returns an h1 tag with Page Not Found. So since we don’t have a route to define what we want to do when we visit “/about”, the Switch will trigger the 3rd route and we’ll see a file not found. I threw in a style update to our navigation to add a border-bottom when the active style is added to the appropriate link. Let’s see what all of this looks like.

In this screenshot, we can see our hashroute and switch working. Our url contains a “#/” which tells it to only load our EventInfo component. Having the hash in the url allows us to append url paths that our app can read while staying on the same SharePoint site.

Here, we can see the Members component is loaded and EventInfo is gone when the url changes to “#/members.”

Finally, we see what happens when a route that wasn’t defined in the switch is met. Here we see our page not found.

Conclusion

In this post, we introduced React components designed to allow us to introduce navigation and dynamic component loading. In the next post, we’ll revisit EventInfo, and EventHub components, and we’ll also take a first look at the web part component. We’re going to make the web part configurable and use that configuration to get data from SharePoint to populate our components.

Building an App with SPFx, React, and TypeScript Part 2: Components

Introduction

In the previous post, we focused on Stateless Functional Components (SFC), which are functions that accept properties used to create elements that are returned by the function. Now we want to focus on Components which are classes where you can manage your application’s state and pass to an SFC as it’s properties.

We’ll create a few components. The main components will be the EventInfo, and Members to match the navigation menu that we created in the first part. We’re going to omit the About component.

Event Info

If you read the previous post, you’ll remember that we created a folder for each of our components and separated them further by differentiating whether they were class components or stateless functional components. We’ll start by creating an EventInfo folder and it’s class component inside. The shell of our code will include a class that extends React.Component and currently accepts any for it’s props and state. It contains a render function that will return a ReactElement and it also accepts any props but we won’t be using props here.

import * as React from 'react';

class EventInfo extends React.Component<any, any> {
    public render(): React.ReactElement<any> {
        return (
            <div>
                
            </div>
        );
    }
}

export default EventInfo;

Since we’ll be using State here, we’ll create an interface for state and put it in it’s own file named IEventInfoState.ts. Our EventInfo’s state will manage the name, location, organizers, and number of attendees.

export interface IEventInfoState {
    event: {
        name: string,
        location: string,
        organizers: string[],
        numOfAttendees: number
    }
}

Now we need to update the EventInfo.tsx to use the IEventInfoState state interface. The EventInfo class component will render an image and some information about the event that we will initialize with default values for now. We’ll come back in a future post and make it dynamic. I’m one of the organizers for the Tri-State Office 365 User Group and the information used in this example is the info from our meetup page.

import * as React from 'react';

import styles from './EventInfo.module.scss';
import { IEventInfoState } from './IEventInfoState';

const initialState: IEventInfoState = {
    event: {
        name: 'Tri-State Office 365 User Group',
        location: 'Malvern, PA',
        organizers: ['Jason', 'Michael'],
        numOfAttendees: 33
    }
}

class EventInfo extends React.Component<any, IEventInfoState> {

    readonly state: IEventInfoState = initialState;

    public render(): React.ReactElement<any> {
        return (
            <div className={styles.EventInfo}>
                <img src="https://secure.meetupstatic.com/photos/event/8/c/e/0/600_466836064.jpeg" alt="test" />
                <div className={styles.Details}>
                    <h2>{this.state.event.name}</h2>
                    <div>{this.state.event.location}</div>
                    <div>{'Organizers: ' + this.state.event.organizers.join(', ')}</div>
                    <div>{this.state.event.numOfAttendees + ' attendees'}</div>
                </div>
            </div>
        );
    }
}

export default EventInfo;

The class leverages some simple styles that we’ll skip over for now. We had a placeholder under our menu bar from the previous post. We now need to go back to our EventHub.tsx, import the EventInfo component, and add it to the JSX.

import * as React from 'react';
import styles from '../EventHub.module.scss';
import { IEventHubProps } from './IEventHubProps';
import { escape } from '@microsoft/sp-lodash-subset';

import Chrome from '../../statelessComponents/chrome/chrome';
import EventInfo from '../EventInfo/EventInfo';

export default class EventHub extends React.Component<IEventHubProps, {}> {
  public render(): React.ReactElement<IEventHubProps> {
    return (
      <Chrome>
        <EventInfo />
      </Chrome>
    );
  }
}

This is what our app currently looks like. If you remember, Chrome defines the menu bar which handles the logo and nav. Then there was a main element under the menu bar that contained {props.children} which indicates that we want to render anything that our component wraps. In this case, it’s the EventInfo component.

Members

The next component is Members which will display the profiles of each member. Same as before, we start by creating a Members directory under components and a Members.tsx within directory. There will be a slight difference between Members and EventInfo. EventInfo was fairly self contained but Members will use a stateless functional component to render the individual members. For now, let’s take a look at the Members.tsx starting point.

import * as React from 'react';

class Members extends React.Component<any, any> {
    public render(): React.ReactElement<any> {
        return (
            <div>Here is the members section!</div>
        );
    }
}

export default Members;

Next, we can wire Members up to the EventHub so that we can start seeing changes. We import Members and add the element under our EventInfo element.

import * as React from 'react';
import styles from '../EventHub.module.scss';
import { IEventHubProps } from './IEventHubProps';
import { escape } from '@microsoft/sp-lodash-subset';

import Chrome from '../../statelessComponents/chrome/chrome';
import EventInfo from '../EventInfo/EventInfo';
import Members from '../Members/Members';

export default class EventHub extends React.Component<IEventHubProps, {}> {
  public render(): React.ReactElement<IEventHubProps> {
    return (
      <Chrome>
        <EventInfo />
        <Members />
      </Chrome>
    );
  }
}

And when we take a look at our page, we can see the members component underneath the image.

So far so good. Next, I want the members section to be a list of people with at least an image and their name. I’m going to structure this one similar to how we did the navigation. We had navigation items (plural) which was our container and navigation item (singular) which represented our individual navigation items. This time, we’re going to have a Members class, which we already created, and a Member stateless functional component to define what each person object looks like.

The difference between Members and the Navigation Items is that members will be a class while navigation items was not. The reason I chose to do it this way is because navigation items had hard coded urls and members list will not have the people hard coded into the component… well, it will when we start, but we may circle back and change that. Members list should eventually pull the data from some repository and store it in state then pass the state values to the member SFC as properties.

I’ll start with the SFC by creating a Member folder and a Member.tsx inside of the statelessComponents folder. We’ll also use the Office Fabric UI persona. I’ll just pull some sample code from the page and hard code some values for now.

import React from 'react';

import { Persona, PersonaSize } from 'office-ui-fabric-react'

const member = () => (
    <div className="ms-PersonaExample">
        <Persona
            text="Jason Rivera"
            imageUrl={""}
            size={PersonaSize.small}
            secondaryText="SharePoint Architect"
        />
      </div>
);


export default member;

Let’s go back to the Members class and simply import this member SFC to confirm that it works. The updated Members.tsx looks like this:

import * as React from 'react';

import Member from '../../statelessComponents/Member/Member';

class Members extends React.Component<any, any> {
    public render(): React.ReactElement<any> {
        return (
            <div>
                <Member />
            </div>
        );
    }
}

export default Members;

Our app now looks like this:

Now let’s try to add more people to the list by storing the list in Members and updating the Member SFC to accept data. Our updated SFC will now accept props of any type and expects those props to have a name, imageUrl, and secondaryText. It may be a good idea to create an interface for the props that defines required fields and their type. I won’t do that here for this demo.

const member = (props:any) => (
    <div className="ms-PersonaExample">
        <Persona
            text={props.name}
            imageUrl={props.imageUrl}
            size={PersonaSize.small}
            secondaryText={props.secondaryText}
        />
    </div> 
);

Now we go back to Members and pass our data in. More baby steps. We’ll create a state object that will have our hard coded data and we will add a Member tag for each object. You’ll see that before the render function, we assign values to our state. State contains an array called person and it contains 3 items. Then we set up 3 member tags in the render function and assign the values of our array items to the props defined in Member (name, imageUrl, secondaryText).

import * as React from 'react';

import Member from '../../statelessComponents/Member/Member';

class Members extends React.Component<any, any> {
   state = {
        person: [
            {
                name: "Jason Rivera",
                imageUrl: "",
                secondaryText: "SharePoint Architect"
            },
            {
                name: "Adele Vance",
                imageUrl: "",
                secondaryText: "Business Analyst"
            },
            {
                name: "John Doe",
                imageUrl: "",
                secondaryText: "Manager"
            }
        ]
    }

 public render(): React.ReactElement<any> {

        return (
            <div>
                <Member 
                    name={this.state.person[0].name}
                    imageUrl={this.state.person[0].imageUrl}
                    secondaryText={this.state.person[0].secondaryText} />
                <Member 
                    name={this.state.person[1].name}
                    imageUrl={this.state.person[1].imageUrl}
                    secondaryText={this.state.person[1].secondaryText}/>
                <Member 
                    name={this.state.person[2].name}
                    imageUrl={this.state.person[2].imageUrl}
                    secondaryText={this.state.person[2].secondaryText}/>
            </div>
        );
    }
}

export default Members;

Here is what our app looks like after we add the 3 personas. We don’t want to have to hard code our Member tags so next we’ll work on making that part dynamic.

Back in our render method, we’ll create a membersList variable that will store an array of our Member tags. We’ll use the map function to return a Member tag for each person listed in our state. Then we’ll use the membersList in the render function’s return.

import * as React from 'react';

import Member from '../../statelessComponents/Member/Member';
import styles from './Members.module.scss';

class Members extends React.Component<any, any> {
   state = {
        person: [
            {
                name: "Jason Rivera",
                imageUrl: "",
                secondaryText: "SharePoint Architect"
            },
            {
                name: "Adele Vance",
                imageUrl: "",
                secondaryText: "Business Analyst"
            },
            {
                name: "John Doe",
                imageUrl: "",
                secondaryText: "Manager"
            }
        ]
    }

    public render(): React.ReactElement<any> {

        // create a member tag for each person in our state
        let membersList = (
            this.state.person.map(p =>{
                return (
                <Member 
                    name={p.name}
                    imageUrl={p.imageUrl}
                    secondaryText={p.secondaryText} />
                );
            })
        );

        return (
            <div className={styles.Members}>
                {membersList}
            </div>
        );
    }
}

export default Members;

I added some styles to clean it up a little and the current look, while not perfect, is as follows.

Conclusion

At this point, we have an app that is mostly comprised of stateless functional components. For the most part, they receive some data and define how that data is rendered. We have a couple of components that will manage state and feed that data to the functional components and for now, our data has been hard coded throughout.

In the next post, we’re going to switch gears from components and data and revisit the navigation.

Building an App with SPFx, React, and TypeScript Part 1: Stateless Functional Components

Introduction

This is the first of a series of posts where I’ll walk through the process of building an event management web part for SharePoint Online. I decided to do this to go over several concepts in React and how they’re implemented using TypeScript. I go over the main points of the solution and try to walk through how I went about it. The solution is available on github.

I’m going to skip over setting up the project. There are several resources that can walk you through that process. I wrote a series a while back on quickly setting up a project that you can refer to but keep in mind that some of the details may be outdated as the packages used were older versions.

Setup

To start, I created my Event Hub project, I did a little bit of organization. Inside the components directory, I created an EventHub folder and moved my related event hub files into it. I also created a “statelessComponents” directory to separate my stateless functional components.

Note: This folder setup is just how I decided to do it for this particular solution. Typically my components are in a “Containers” folder and my stateless functional components are in a “Components” folder. You can organize your files however you want.

The EventHub component that was created for me is a Component that manages state and not a Stateless Functional Component. We’ll briefly touch on the EventHub just for some initial setup but this post will focus on Stateless Functional Components as most of your app’s components should be made up of these types of components.

Creating a Stateless Functional Component

Inside my statelessComponents directory, I created another called “chrome” and inside of it, I created chrome.tsx. This will be a stateless functional component since, as of now, I don’t plan on managing state with it. It’s purpose is to define the apps layout and for now, it’ll simply be a top nav bar and a main content area.

Before I write anything in chrome.tsx, I’m going to install a a required package that we’ll need.

npm i --save @types/react-router-dom

Once that’s installed, we can start building our chrome. As I mentioned, it’s role is to define the app’s layout. We’ll keep it simple and set up some placeholders. The following code is really simple. It’s just a stateless functional component so there’s no state and you can see a wrapping div, and two elements inside. The first element is a div where the top nav will appear and the main will display the contents of any component that we wrap with this chrome component.

import * as React from 'react';

const chrome = (props:any) => (
    <div>
        <div>Here is where the top nav will be</div>
        <main>
            {props.children}
        </main>
    </div>
);

export default chrome;

If we decide to add more to the layout, like a footer, a left nav, or anything else, we can edit the chrome component. We’ll revisit this component later as we start build but right now, it’s enough to show a header and the contents of other components wrapped by it.

Using our Chrome

At this point, let’s go back to EventHub.tsx, which was created for us when we setup our SPFx web part. The following is the default code that appears in the class. This is where the web part’s UI is defined. We’re going to remove most of it and apply our own.

import * as React from 'react';
import styles from '../EventHub.module.scss';
import { IEventHubProps } from './IEventHubProps';
import { escape } from '@microsoft/sp-lodash-subset';

export default class EventHub extends React.Component<IEventHubProps, {}> {
  public render(): React.ReactElement<IEventHubProps> {
    return (
      <div className={ styles.eventHub }>
        <div className={ styles.container }>
          <div className={ styles.row }>
            <div className={ styles.column }>
              <span className={ styles.title }>Welcome to SharePoint!</span>
              <p className={ styles.subTitle }>Customize SharePoint experiences using Web Parts.</p>
              <p className={ styles.description }>{escape(this.props.description)}</p>
              <a href="https://aka.ms/spfx" className={ styles.button }>
                <span className={ styles.label }>Learn more</span>
              </a>
            </div>
          </div>
        </div>
      </div>
    );
  }
}

I’m going to edit this component by removing the JSX that is being returned and replacing it with some simple text wrapped by my chrome component. We need to import Chrome from our chrome component which you can see right before the class definition. Then you’ll see that I use Chrome in the return statement with some simple text. We’ll replace the text with a component later but this should be enough to have our layout produce a header and a welcome message underneath.

import * as React from 'react';
import styles from '../EventHub.module.scss';
import { IEventHubProps } from './IEventHubProps';
import { escape } from '@microsoft/sp-lodash-subset';

import Chrome from '../../statelessComponents/chrome/chrome'

export default class EventHub extends React.Component<IEventHubProps, {}> {
  public render(): React.ReactElement<IEventHubProps> {
    return (
      <Chrome>
        Welcome to the event hub
      </Chrome>
    );
  }
}

Running a gulp serve will open our workbench and we can drop our event hub web part to the page. Again, I assume you know the basics of creating and running an SPFx web part. The result should look like the following image. Nothing special just yet but we have Chrome.tsx defining a top nav that we’ll see regardless of what component we want to render and we can see the contents of another component rendered underneath the top nav section.

Adding Navigation

Let’s focus on the top nav bar which we’ll call the Menu Bar. I’m going to create a series of stateless functional components (SFC) to make it up. The components will be the MenuBar which will contain a logo, and navigation items.

Let’s start building these components in sort of a reverse order. Structurally, our menu will look something like:

  • MenuBar
    • Logo
    • Navigation Items
      • Navigation Item

We’ll begin with the Navigation Items. Under the stateless components directory, I created a Navigation Folder which will contain the MenuBar component, the Navigation Items, and the Navigation Item. With that said, the path to my Navigation Items is /statelessComponents/Navigation/NavigationItems/NavigationItems.tsx.

For now, I’ll hard code a few items and start with an unordered list.

import * as React from 'react';

import styles from '../NavigationItems/NavigationItems.module.scss';

const navigationItems = () => (
    <ul className={styles.NavigationItems}>
        <li>Home</li>
        <li>About</li>
        <li>Members</li>
    </ul>
);

export default navigationItems;

Let’s create a component for the logo. I’m going to use the office fabric’s icons for this one so we’ll need to import it. I’m importing an older version of the package because as of the time that I’m writing this, the latest version isn’t working with SPFx.

npm install --save office-ui-fabric-react@5.135.0

Once installed, we can create our logo component which will simply be a call to an Office Fabric icon. This component will be located under statelessComponents/Logo/Logo.tsx.

import * as React from 'react';

import { Icon } from 'office-ui-fabric-react';

const logo = () => (
    <div>
        <Icon iconName='ScheduleEventAction' className='ScheduleEventAction' />
    </div>
);

export default logo;

Next, we’ll bring the Logo and NavigationItems together inside the MenuBar component. This new component will be found under statelessComponents/Navigation/MenuBar/MenuBar.tsx.

import * as React from 'react';

import NavigationItems from '../NavigationItems/NavigationItems';
import Logo from '../../Logo/Logo';
import styles from '../MenuBar/MenuBar.module.scss';

const menuBar = () => {
    return (
        <header className={styles.MenuBar}>
            <Logo  />
            <nav>
                <NavigationItems /> 
            </nav>
        </header>
    );
}

export default menuBar;

Now we need to go back to our Chrome.tsx and introduce MenuBar.tsx so that we can start seeing something other than our placeholder text. Back in the Chrome.tsx, we now import MenuBar and replace our placeholder with the new element.

import * as React from 'react';
import MenuBar from '../Navigation/MenuBar/MenuBar';

const chrome = (props:any) => (
    <div>
        <MenuBar />
        <main>
            {props.children}
        </main>
    </div>
);

export default chrome;

At this point, we have a menu bar at the top of our web part with a logo, followed by a few links. Our navigation items component has a hard coded set of links. I want to pull them out into their own component (navigation item). This SFC will change later and if you are familiar with routing in react, the href should provide a hint around what those changes will be.

import * as React from 'react';

import styles from '../NavigationItem/NavigationItem.module.scss';

export interface NavigationItemProps {
    url: string,
    children: React.ReactNode
}

const navigationItem = (props:NavigationItemProps) => (
    <li className={styles.NavigationItem}>
        <a 
            href={'#' + props.url}
            >{props.children}</a>
        
    </li>
);

export default navigationItem;

The above code is defining our list item, applying a class, adds an anchor tag inside of it and assigns the url prop to the href . The title of the link is the content that this navigationItem component will wrap. We’ll see that shortly.

Now that we have an SFC for our individual items, we need to go back to our NavigationItems SFC to start using our single items. Back in NavigationItems.tsx, we will import NavigationItem and replace our links. (The names are similar but hopefully, it’s no too difficult to follow along).

import * as React from 'react';

import NavigationItem from '../NavigationItem/NavigationItem';
import styles from '../NavigationItems/NavigationItems.module.scss';


const navigationItems = () => (
    <ul className={styles.NavigationItems}>
        <NavigationItem url='/' >Home</NavigationItem>
        <NavigationItem url='/about'>About</NavigationItem>
        <NavigationItem url='/members'>Members</NavigationItem>
    </ul>
);

export default navigationItems;

After some style updates, we have a simple bar along the top of our web part with a logo made from an Office Fabric UI icon and a few links. Here are a few shots of what our project structure and web part look like.

Conclusion

We started with an SPFx solution and restructured the directories a little bit. I used the Component folder for our stateful components and created a “statelessComponents” folder for our stateless functional components. That’s not a common practice. I did that just for this demo because I’m using this solution to explain certain concepts outside of this blog series and naming the folders this way will probably help people unfamiliar with react remember what is in those folders.

We did make some changes to our EventHub component just so that we can see our new additions but this post focused mostly on the components that don’t maintain their own state.

In the next post, we’re going to focus on components that will handle state. These components will maintain the content and pass down the appropriate data as props. We already saw an example of that when we set up our navigation items to pass props down to the navigation item.

The Return of SharePoint Saturday Philly

Last night, we announced that June 22, 2019 will be the return of SharePoint Saturday Phillywith a twist. The innovations made in Office 365, over the years, with SharePoint as a core product make it nearly impossible to look at SharePoint in a bubble. We’ve decided that SharePoint Saturday Philly will focus on the Microsoft Cloud as a whole. What does that mean? We want to see our speakers talk about Azure, Dynamics, DevOps, the suite of Office 365 products, and of course, SharePoint.

There’s been some buzz lately about bringing this event back to our area so we got a small group together and we’re making it happen, but we will need help from Sponsors to ensure that we can make it happen. We’ve opened the call for speakers. Attendee registration is also open.

Seats are limited. Let’s make this a successful return!

For more information, please visit our SPSPhilly page on SPSEvents.org or contact the SPSPhilly team at contact@spsphilly.org

Read Names from a Person Field with PnPJS

If you’re starting out and you need to read names from a person field, it may not be clear how to go about this. In this example, I have a list that has an Organizer person field. In order for me to get the name(s) in that field for a given item, I will name the fields that I need in my select and then expand my Organizer field. If you don’t do this, all that get’s returned is an OrganizerId field.

sp.web.lists.getByTitle(this.props.listName)
   .items.getById(id)
   .select("Title", "Organizers/Title", "Event_x0020_Location/Address", "Members")
    .expand("Organizers/Title")
    .get().then((item: any) => {
      
      // array of meeting organizers
      const meetingOrganizers = item['Organizers'];

      console.log(meetingOrganizers[0].Title)

});

In the example, we’re naming a few fields that we want returned. Title, Organizer/Title, Event Location/Address, and Members. Then we expand Organizers/Title. Expanding Organizer/Title will include the names of the individuals as an array of objects. I used a console.log to display the first record in this example. Hope that saves you some time.

How to Extract Location Data with PnPJS

The SharePoint location field is a nice way of adding location information with the help of Bing Maps. It contains quite a bit of information about a given location. The field stores the data in JSON format so it’s simple to get the data that you need.

A location will be stored in the following format:

{
	"EntityType": "LocalBusiness",
	"LocationSource": "Bing",
	"LocationUri": "https://www.bingapis.com/api/v6/localbusinesses/YN873x128404500",
	"UniqueId": "https://www.bingapis.com/api/v6/localbusinesses/YN873x128404500",
	"DisplayName": "Microsoft",
	"Address": {
		"Street": "45 Liberty Boulevard",
		"City": "Malvern",
		"State": "PA",
		"CountryOrRegion": "US",
		"PostalCode": "19355"
	},
	"Coordinates": {
		"Latitude": 40.05588912963867,
		"Longitude": -75.52118682861328
	}
}

If you’re building a web part with React, and are using the pnpjs libraries, it’s pretty straight forward and here’s a snippet showing how you might do it.

import { sp } from '@pnp/sp';

...

sp.web.lists.getByTitle(this.props.listName)
     .items.getById(id).get().then((item: any) => {
         //parse the event location info
        const location = JSON.parse(item['Event_x0020_Location']);
                   
        console.log(location.Address.City);
        console.log(location.Address.State);

});

So once you have an item, you can assign it’s location field to a variable/const using JSON.Parse. Once you have that, you can access the data via properties. Address is available and has properties of it’s own. City, State, etc. That’s it. Pretty simple.

Telephone Links In SharePoint

I was asked to build a contact directory in SharePoint. I started with the People web part but the list included people who didn’t work for the company so I scrapped that.

On attempt #2, I created a site page and tried the text, and markdown web parts. The text web part has a Hyperlink but this doesn’t work because it expects a full link but all we want is something like <a href=”callto:5551233777″>555-123-3777</a> or
<a href=”tel:5551233777″>555-123-3777</a>

Next, I tried the markdown web part but this only half worked. In the web part, I typed:

[Call Now](callto:5551233777)
[Call Now](tel:5551233777)

In the browser, the above creates a link that will prompt you to choose an app to make that call. That same page on a mobile browser or the SharePoint app will produce the same content, but no link. Bummer.

What does work is the hyperlink field in a list, but callto isn’t valid. Instead, you need to use “tel”. Using Tel in a list produces a link that will work on a mobile device.