In Part 1 of this article, I walked through the various components that we’ll need to build to create a responsive calendar feed web part that mimics the out-of-the-box SharePoint events web part.
In this article, we’ll:
The final product will look like this:
If you haven’t done so yet, set up your SharePoint Framework development environment following Microsoft’s awesome instructions.
We’ll create a solution called react-calendar-feed-1. In future articles, we’ll take what we built in this article as the foundation for react-calendar-feed-2, and so on until we’re done with the solution, which we’ll call react-calendar-feed. Of course, you can skip all the steps and get the code for the final solution, if you’d like.
When you’re ready to create the solution, use the following steps:
md react-calendar-feed-1
cd react-calendar-feed-1
yo @microsoft/sharepoint
When prompted for the solution name, accept the default react-calendar-feed-1.
For the baseline package select SharePoint Online only (latest).
When asked Where do you want to place the files? accept the default Use the current folder.
When asked if you want to allow the tenant admin the choice of being able to deploy the solution to all sites immediately respond No.
When asked for the type of client-side component to create select WebPart.
For Web part name, use CalendarFeedSummary. Later, we’re planning on adding other web parts for searching events (but that’s another blog post).
For Web part description, enter Displays events from an external feed.
When asked for a framework select React.
What Yeoman is done creating the project for you, it’ll say Congratulations! Solution react-calendar-feed-1 is created. Run gulp serve to play with it!.
Since we’re not quite ready to play with the web part yet, let’s launch Visual Studio Code by typing:
code .
Once Visual Studio Code is launched, we’re ready to code!
If you open the CalendarFeedWebPart.ts file, you’ll notice that there are multiple exports : one for ICalendarFeedSummaryWebPartProps and one for CalendarFeedSummaryWebPart.
One practice that I’ve learned by reading the Office UI Fabric code is they keep the component code separate from the Prop and State interfaces in separate files, making each component file simpler and easier to read. This is a practice I tend to follow as well, so let’s create a separate file for the web part’s types:
View the code on Gist.
Back in CalendarFeedSummaryWebPart.ts, you’ll want to add an import to the interface we just moved out. At the top of the file, just below the last import line, type the following:
import { ICalendarFeedSummaryWebPartProps } from './CalendarFeedSummaryWebPart.types';
When the final solution will be completed, our web part will consume calendar feeds from various services. Those services will also be re-usable by other web parts.
We’ll start by create a single mock service that will return events in the format that we need. In future posts, we’ll add more types of services.
Our calendar service providers will return a bunch of events that will all have the same properties:
Why “if applicable”? Not all event providers are capable of returning all properties for events.
To make it easier to work with, we’ll create an ICalendarEvent that will expose all the above properties. Why an interface and not a class? Well, in Typescript, an interface is the easiest way to describe a type of thing without actually saying what the thing does or how it does things.
If our events needed to do things, like calculate their own duration (end date minus start date) or something of the sort, we’d need a class to implement the method; our ICalendarEvent interface is really a convenient way to describe that all events have a title, a start date, end date, etc.
To create the ICalendarEvent interface:
View the code on Gist.
Some may argue that the ICalendarEvent is really a model and it should really reside in a different folder where all models go, but I like the simplicity of the CalendarService folder holding everything it needs to deliver a calendar feed. If I ever wanted to move it out to its own module, I could do it very simply.
We’ll first create an interface that all calendar service providers will implement. Again, the interface will describe what the calendar service providers will look like. Later, we’ll create a calendar service provider class that will implement the interface.
But for now, let’s create the interface:
View the code on Gist.
As you’ll see, the ICalendarService interface says that all calendar service providers will need to implement a getEvents method that will return a promise of an array of ICalendarEvent. We return a promise because we’ll (usually) be retrieving events from calendar service providers asynchronously, and promises make it easier to do that.
Don’t worry, we’ll explain this better when we implement our first real calendar service provider.
You’ll notice that we create a index.ts in the root of the CalendarService folder and exported both the ICalendarService and the ICalendarEvent interfaces. Why? Just like index.html used to be the default web page for a site, index.ts is the default file for a folder in Typescript. If you don’t specify a file when using an import, it automatically looks for the default file.
But why would I create an index.ts file? Isn’t just an extra file that I’ll need to maintain? Yes, but it makes it easier to hide the complexities of the CalendarService to the rest of the application — they just need to know that they need an ICalendarService and an ICalendarEvent interface from the CalendarService folder, without needing to know where (in which specific file) the interfaces are implemented. When we start adding new service providers, or when we move stuff around, we won’t have to change our imports because we’ll always point to the default index.ts for the CalendarService.
Don’t worry, it’ll make sense very soon.
Now that we have an ICalendarEvent interface to represent events, and an ICalendarService to represent a service provider, let’s combine the two and return some sample events.
Instead of created events with hard-coded dates that will become obsolete as time goes by, we’ll create events with dates that are dynamically generated when the web part is displayed. To make our lives easier, we’ll use Moment.js to manipulate dates throughout this project. Moment.js makes it easy to manipulate dates and format them into human-readable formats.
From Visual Studio Code’s Integrated Terminal (CTRL-`) type
npm install moment
In the src | shared | services folder, create a new folder called MockCalendarService.
In the new folder, create a new file called MockCalendarService.ts, then create another file called index.ts.
Copy and paste the content from the files below into the respective files below.
export * from './MockCalendarService';
import* as moment from ‘moment’; import { ICalendarEvent } from ‘../ICalendarEvent’; import { ICalendarService } from ‘../ICalendarService’;
const today: Date = new Date(); const sampleEvents: ICalendarEvent[] = [ { “title”: “This event will be tomorrow”, “start”: moment().add(1, “d”).toDate(), “end”: moment().add(1, “d”).toDate(), “url”: “http://web.archive.org/web/20230917181131/https://www.contoso.com/news-events/events/1/”, “allDay”: true, “category”: “Meeting”, “location”: “Barrie, ON”, “description”: “This is a description” }, { “title”: “This event will be in one week”, “start”: moment().add(1, “w”).toDate(), “end”: moment().add(1, “w”).toDate(), “url”: “http://web.archive.org/web/20230917181131/https://www.contoso.com/news-events/events/2/”, “allDay”: true, “category”: “Meeting”, “location”: undefined, “description”: undefined }, { “title”: “This event will last two days”, “start”: moment().add(1, “w”).toDate(), “end”: moment().add(1, “w”).add(2, “d”).toDate(), “url”: “http://web.archive.org/web/20230917181131/https://www.contoso.com/news-events/events/2/”, “allDay”: true, “category”: “Meeting”, “location”: undefined, “description”: undefined }, { “title”: “This event will be in two weeks”, “start”: moment().add(2, “w”).toDate(), “end”: moment().add(2, “w”).toDate(), “url”: “http://web.archive.org/web/20230917181131/https://www.contoso.com/news-events/events/3/”, “allDay”: true, “category”: “Meeting”, “location”: undefined, “description”: undefined }, { “title”: “This event will be in one month”, “start”: moment().add(1, “M”).toDate(), “end”: moment().add(1, “M”).add(2, “d”).toDate(), “url”: “http://web.archive.org/web/20230917181131/https://www.contoso.com/news-events/events/4/”, “allDay”: true, “category”: “Meeting”, “location”: undefined, “description”: undefined }, { “title”: “This event will be in two months”, “start”: moment().add(2, “M”).toDate(), “end”: moment().add(2, “M”).toDate(), “url”: “http://web.archive.org/web/20230917181131/https://www.contoso.com/news-events/events/5/”, “allDay”: true, “category”: “Meeting”, “location”: undefined, “description”: undefined }, { “title”: “This event will be in 1 quarter”, “start”: moment().add(1, “Q”).toDate(), “end”: moment().add(1, “Q”).toDate(), “url”: “http://web.archive.org/web/20230917181131/https://www.contoso.com/news-events/events/6/”, “allDay”: true, “category”: undefined, “location”: undefined, “description”: undefined }, { “title”: “This event will be in 4 months”, “start”: moment().add(4, “M”).toDate(), “end”: moment().add(4, “M”).toDate(), “url”: “http://web.archive.org/web/20230917181131/https://www.contoso.com/news-events/events/7/”, “allDay”: true, “category”: undefined, “location”: undefined, “description”: undefined }, { “title”: “This event will be in 5 months”, “start”: moment().add(5, “M”).toDate(), “end”: moment().add(5, “M”).toDate(), “url”: “http://web.archive.org/web/20230917181131/https://www.contoso.com/news-events/events/8/”, “allDay”: true, “category”: undefined, “location”: undefined, “description”: undefined }, { “title”: “This event will be in 6 months”, “start”: moment().add(6, “M”).toDate(), “end”: moment().add(6, “M”).toDate(), “url”: “http://web.archive.org/web/20230917181131/https://www.contoso.com/news-events/events/9/”, “allDay”: true, “category”: undefined, “location”: undefined, “description”: undefined }, { “title”: “This event will be in 9 months”, “start”: moment().add(9, “M”).toDate(), “end”: moment().add(9, “M”).toDate(), “url”: “http://web.archive.org/web/20230917181131/https://www.contoso.com/news-events/events/10/”, “allDay”: true, “category”: undefined, “location”: undefined, “description”: undefined }, { “title”: “This event will be in 1 year”, “start”: moment().add(1, “y”).toDate(), “end”: moment().add(1, “y”).toDate(), “url”: “http://web.archive.org/web/20230917181131/https://www.contoso.com/news-events/events/11/”, “allDay”: true, “category”: “Partayyyy!”, “location”: undefined, “description”: undefined }, { “title”: “This event will be in 18 months”, “start”: moment().add(18, “M”).toDate(), “end”: moment().add(18, “M”).toDate(), “url”: “http://web.archive.org/web/20230917181131/https://www.contoso.com/news-events/events/12/”, “allDay”: true, “category”: “Meeting”, “location”: undefined, “description”: undefined } ];
export class MockCalendarService implements ICalendarService {
public getEvents = (): Promise<ICalendarEvent[]> => {
return new Promise<ICalendarEvent[]>((resolve: any) => {
setTimeout(() => {
resolve(sampleEvents);
}, 1000);
});
}
} ```
The MockCalendarService creates a series of events that range from tomorrow to 18 months from now. Some are only 1 day long, but some events last a few days.
The getEvents method in MockCalendarService simulates the delay of getting the events through an HTTP request and returns the list of pre-generated events. In a later article, we’ll actually get real events, but — for now — this should do to test our rendering.
Although our goal is to render calendar events that look exactly like what SharePoint does, we’ll begin by rendering a list of events as bullet points. This is to ensure that our code works, and to allow us to finish this article with something that works before we explore rendering.
public componentDidMount(): void {
private _loadEvents(): void {
export interface ICalendarFeedSummaryState {
import { ICalendarEvent } from "../../../shared/services/CalendarService";
export default class CalendarFeedSummary extends React.Component<ICalendarFeedSummaryProps, {}> {
export default class CalendarFeedSummary extends React.Component<ICalendarFeedSummaryProps, ICalendarFeedSummaryState> {
import { ICalendarFeedSummaryProps } from './ICalendarFeedSummaryProps';
with:
import { ICalendarFeedSummaryProps, ICalendarFeedSummaryState } from './CalendarFeedSummary.types';
constructor(props: ICalendarFeedSummaryProps) {
public render(): React.ReactElement<ICalendarFeedSummaryProps> {
); }
{ this.state.events.map(e=>{ return
The final code should look as follows:
View the code on Gist.
When you’re ready to test the web part, type:
gulp serve
and add the web part we created to the page. The events will render as a list:
Although it isn’t very exciting (yet), the web part we created creates a bunch of events, simulates retrieving them from an HTTP request and renders them in a list.
In our next article, we’ll render the events so that they look like SharePoint events.