Author

Hugo Bernier

Browsing

Introduction

Microsoft has an awesome web site called SharePoint Design. It provides design guidance on beautiful and fast sites, pages, and web parts with SharePoint in Office 365.

However, it does not tell you how to create those beautiful web parts.

This blog series is intended as a companion to the SharePoint Design site. It provides code samples and detailed how-to information for every design topic.

It should help you create web parts that look exactly like the ones on the SharePoint Design site.

So far, we have discussed the following topics:

In today's post, we'll continue our discussion about the web part layout patterns and discuss the compact layout.

What is the Compact layout?

Compact layout

According to the SharePoint Design site:

The compact layout is designed to show content in a smaller format and works the best in a one-third column. This layout can support a small image or icon and a few rows of text for a title, description, and/or metadata.

However, it seems that the compact layout is not only used in 1/3 columns. The SharePoint look book and the SharePoint Online Provisioning Service both make use of the compact layout with the Quick Links web part.

When to use the compact layout

Use the compact layout when the items in your web part have very little information. A title and an icon or a small thumbnail is pretty much all you'll be able to fit it.

How is it made?

The compact layout is simply a Grid layout, but where the individual items use a compact document card layout instead of the full document card.

To create your own, you would use an Office UI Fabric List control and render each item inside the list with a DocumentCard control where the type of the DocumentCard control would be set to DocumentCardType.compact.

Or, you can simply use the component I built to make my life easier.

How to create a web part with the compact layout

Note: The source code for this sample can be found in the WebPartLayouts sample on my repo.

  1. Create your own web part solution. For this sample, we'll assume that your web part is called CompactWebPart and that the component which renders the content of the web part is called Compact.

  2. Copy the content of the src\components\compactLayout from my sample code to your own solution. You may need to create a components and compactLayout folder under src to do so.

  3. In your web part, load the items you wish to display. For this example, we'll set them in your web part's state and hard-code them in your web part's constructor, but feel free to load any data you want to use. We'll want to have at least a title attribute and a thumbnail attribute, but -- as you'll see later -- you can really display any information you want when you render each item.

    constructor(props: ICompactProps) {
    super(props);
    
    // Sample data generated at https://mockaroo.com/
    this.state = {
      items: [{
        thumbnail: "https://robohash.org/nostrumquiiure.png?size=48x48&set=set1",
        title: "Aerified"
      }, {
        thumbnail: "https://robohash.org/minimafugitenim.png?size=48x48&set=set1",
        title: "Viva"
      }, {
        thumbnail: "https://robohash.org/nihilbeataeculpa.png?size=48x48&set=set1",
        title: "Overhold"
      }, {
        thumbnail: "https://robohash.org/essequiquo.png?size=48x48&set=set1",
        title: "Latlux"
      }, {
        thumbnail: "https://robohash.org/inipsumtotam.png?size=48x48&set=set1",
        title: "Biodex"
      }, {
        thumbnail: "https://robohash.org/utmodiet.png?size=48x48&set=set1",
        title: "Bitchip"
      }, {
        thumbnail: "https://robohash.org/undeenimvel.png?size=48x48&set=set1",
        title: "Rank"
      }, {
        thumbnail: "https://robohash.org/pariaturoditdolore.png?size=48x48&set=set1",
        title: "Opela"
      }, {
        thumbnail: "https://robohash.org/nullaullamincidunt.png?size=48x48&set=set1",
        title: "Rank"
      }, {
        thumbnail: "https://robohash.org/accusantiumnonvoluptatibus.png?size=48x48&set=set1",
        title: "Bitchip"
      }, {
        thumbnail: "https://robohash.org/culpaeossapiente.png?size=48x48&set=set1",
        title: "Sonsing"
      }, {
        thumbnail: "https://robohash.org/harumnihilvelit.png?size=48x48&set=set1",
        title: "Duobam"
      }, {
        thumbnail: "https://robohash.org/quianesciuntet.png?size=48x48&set=set1",
        title: "Prodder"
      }, {
        thumbnail: "https://robohash.org/aliquidipsamrem.png?size=48x48&set=set1",
        title: "Keylex"
      }, {
        thumbnail: "https://robohash.org/dignissimoseosaccusamus.png?size=48x48&set=set1",
        title: "Span"
      }, {
        thumbnail: "https://robohash.org/exomnisexcepturi.png?size=48x48&set=set1",
        title: "Stringtough"
      }, {
        thumbnail: "https://robohash.org/occaecatimolestiaererum.png?size=48x48&set=set1",
        title: "Prodder"
      }, {
        thumbnail: "https://robohash.org/consequaturinquis.png?size=48x48&set=set1",
        title: "Alpha"
      }, {
        thumbnail: "https://robohash.org/sapienteofficiisest.png?size=48x48&set=set1",
        title: "Job"
      }, {
        thumbnail: "https://robohash.org/similiquesuntiusto.png?size=48x48&set=set1",
        title: "Cookley"
      }, {
        thumbnail: "https://robohash.org/sitnequequi.png?size=48x48&set=set1",
        title: "Stronghold"
      }]
    };
    }
  4. In your web part's component, add an import for the compactLayout. You'll also need an import for the UI Fabric DocumentCard component:

    // Used to render document cards
    import {
    DocumentCard,
    DocumentCardPreview,
    DocumentCardDetails,
    DocumentCardTitle,
    IDocumentCardPreviewProps,
    DocumentCardType
    } from 'office-ui-fabric-react/lib/DocumentCard';
    import { ImageFit } from 'office-ui-fabric-react/lib/Image';
    import CompactLayout from '../../../components/compactLayout/CompactLayout';
  5. Add a _onRenderGridItem method:

    private _onRenderGridItem = (item: any, _index: number): JSX.Element => {
    const previewProps: IDocumentCardPreviewProps = {
      previewImages: [
        {
          previewImageSrc: item.thumbnail,
          imageFit: ImageFit.centerCover,
          height: 48,
          width: 48
        }
      ]
    };
    
    return <div
      data-is-focusable={true}
      data-is-focus-item={true}
      role="listitem"
      aria-label={item.title}
    >
      <DocumentCard
        type={DocumentCardType.compact}
        onClick={(ev: React.SyntheticEvent<HTMLElement>) => alert(ev)}
      >
        <DocumentCardPreview {...previewProps} />
        <DocumentCardDetails>
          <DocumentCardTitle
            title={item.title}
            shouldTruncate={true}
          />
        </DocumentCardDetails>
      </DocumentCard>
    </div>;
    }
    }
  6. Update your component's render method to render the compact layout as follows:

    public render(): React.ReactElement<ICompactProps> {
    return (
      <div className={styles.compact}>
        <CompactLayout
          items={this.state.items}
          onRenderGridItem={(item: any, index: number) => this._onRenderGridItem(item, index)}
        />
      </div>
    );
    }

That's really all there is to it! You web part will render something like this:
The compact layout

The great thing is that it is entirely up to you how you want to render each item. I chose to use the DocumentCard control, but you can replace any part of your _onRenderGridItem method to suit your needs.

For example, if you wanted to render a date instead of an thumbnail, you could use something like the DateBox component that I wrote for the React Calendar Feed sample.

The Datebox control

Adding pagination

When I first built the React Calendar Feed sample, the Events web part showed little Previous and Next buttons at the bottom of the web part when displaying in the compact mode.

It seems that the pagination has since been removed from the standard web part design. Nevertheless, I have included a sample control to show how you can add pagination to your compact web part.

Use it at your discretion.

To add pagination to your web part, follow these steps:

  1. Copy the content of the src\components\paging from my sample code to your own solution. You may need to create a paging folder under src\components to do so.
  2. Add a variable to store the current page number in your component's state, as follows:
    export interface ICompactState {
    items: any[];
    currentPage: number;
    }
  3. In your component's constructor, set the current page to 1, as follows:

    constructor(props: ICompactProps) {
    super(props);
    
    // Sample data generated at https://mockaroo.com/
    this.state = {
      currentPage: 1,
      items: [{
        thumbnail: "https://robohash.org/nostrumquiiure.png?size=48x48&set=set1",
        title: "Aerified"
      }, {
        thumbnail: "https://robohash.org/minimafugitenim.png?size=48x48&set=set1",
        title: "Viva"
      }, {
        thumbnail: "https://robohash.org/nihilbeataeculpa.png?size=48x48&set=set1",
        title: "Overhold"
      }, {
        thumbnail: "https://robohash.org/essequiquo.png?size=48x48&set=set1",
        title: "Latlux"
      }, {
        thumbnail: "https://robohash.org/inipsumtotam.png?size=48x48&set=set1",
        title: "Biodex"
      }, {
        thumbnail: "https://robohash.org/utmodiet.png?size=48x48&set=set1",
        title: "Bitchip"
      }, {
        thumbnail: "https://robohash.org/undeenimvel.png?size=48x48&set=set1",
        title: "Rank"
      }, {
        thumbnail: "https://robohash.org/pariaturoditdolore.png?size=48x48&set=set1",
        title: "Opela"
      }, {
        thumbnail: "https://robohash.org/nullaullamincidunt.png?size=48x48&set=set1",
        title: "Rank"
      }, {
        thumbnail: "https://robohash.org/accusantiumnonvoluptatibus.png?size=48x48&set=set1",
        title: "Bitchip"
      }, {
        thumbnail: "https://robohash.org/culpaeossapiente.png?size=48x48&set=set1",
        title: "Sonsing"
      }, {
        thumbnail: "https://robohash.org/harumnihilvelit.png?size=48x48&set=set1",
        title: "Duobam"
      }, {
        thumbnail: "https://robohash.org/quianesciuntet.png?size=48x48&set=set1",
        title: "Prodder"
      }, {
        thumbnail: "https://robohash.org/aliquidipsamrem.png?size=48x48&set=set1",
        title: "Keylex"
      }, {
        thumbnail: "https://robohash.org/dignissimoseosaccusamus.png?size=48x48&set=set1",
        title: "Span"
      }, {
        thumbnail: "https://robohash.org/exomnisexcepturi.png?size=48x48&set=set1",
        title: "Stringtough"
      }, {
        thumbnail: "https://robohash.org/occaecatimolestiaererum.png?size=48x48&set=set1",
        title: "Prodder"
      }, {
        thumbnail: "https://robohash.org/consequaturinquis.png?size=48x48&set=set1",
        title: "Alpha"
      }, {
        thumbnail: "https://robohash.org/sapienteofficiisest.png?size=48x48&set=set1",
        title: "Job"
      }, {
        thumbnail: "https://robohash.org/similiquesuntiusto.png?size=48x48&set=set1",
        title: "Cookley"
      }, {
        thumbnail: "https://robohash.org/sitnequequi.png?size=48x48&set=set1",
        title: "Stronghold"
      }]
    };
    }
  4. Add an import for the paging component at the top of your file:
    import { Paging } from '../../../components/paging';
  5. Change your render method to get a subset of items to show, as follows:

    public render(): React.ReactElement<ICompactProps> {
    let pagedItems: any[] = this.state.items;
    const totalItems: number = pagedItems.length;
    let showPages: boolean = false;
    const maxEvents: number = 5; // Use any page size you want
    const { currentPage } = this.state;
    
    if (true && totalItems > 0 && totalItems > maxEvents) {
      // calculate the page size
      const pageStartAt: number = maxEvents * (currentPage - 1);
      const pageEndAt: number = (maxEvents * currentPage);
    
      pagedItems = pagedItems.slice(pageStartAt, pageEndAt);
      showPages = true;
    }
    
    return (
      <div className={styles.compact}>
        <CompactLayout
          items={pagedItems}
          onRenderGridItem={(item: any, index: number) => this._onRenderGridItem(item, index)} />
    
        {showPages &&
          <Paging
            showPageNumber={true}
            currentPage={currentPage}
            itemsCountPerPage={maxEvents}
            totalItems={totalItems}
            onPageUpdate={this._onPageUpdate}
            nextButtonLabel={strings.NextLabel}
            previousButtonLabel={strings.PreviousLabel}
          />
        }
      </div>
    );
    }
  6. Finally, add a method to store the current page number in your state every time the page changes, as follows:
    private _onPageUpdate = (pageNumber: number): void => {
    this.setState({
      currentPage: pageNumber
    });
    }

Test your web part. You should now get a next and previous buttons at bottom of your web part.

Compact pagination

Conclusion

The compact layout is simply a grid control which renders compact document cards.

In our next post, we'll discuss the list layout!

Introduction

According to Microsoft documentation, you can use a base64-encoded image for your web part icons. However, base64-encoded SVG images -- as is shown in the documentation -- don't work as expected. And by the looks of it, this hasn't worked for a while.

Since the SPFx team has been working tirelessly to deliver more than 15 releases of SPFx since its general availability, I'm sure we can forgive them for letting something so minor fall through the cracks.

However, I really don't like how icons that were converted from PNG look slightly blurry. (I tend to obsess about little things like that sometimes).

As I was writing another post for my SharePoint Framework Design series, I decided to get to the bottom of the issue.

As it turns out, it was pretty easy to solve.

TL;DR

This is an interactive post: we'll convert your SVG so that you can use it as an SPFx web part icon.

If you don't care about the explanation, you can go straight to the solution

The Challenge

My challenge was that I wanted to use SVG images in my web part's manifest, but I didn't want to have a separate SVG asset. I wanted to define a base64-encoded SVG file, as is demonstrated in the SharePoint documentation.

I knew it didn't work (believe me, I've tried), so I wanted a solution that would not rely on any unsupported hacks.

Since I don't have access to change the SharePoint code, I needed a solution that would work without requiring any changes from SharePoint.

How SPFx web part icons work

According to the Microsoft documentation, for your SPFx web part icon you can use either an Office UI Fabric icon, an external icon image, or a base64-encoded image.

To change your icon to an Office UI Fabric icon, you simply open your web part's [YourWebPartName].manifest.json and change the "officeFabricIconFontName" node to the name of the icon you want from the UI Fabric site.

The default icon is Page. If you wanted to change your icon to the Sunny icon, you would simply change the following line:

"officeFabricIconFontName": "Page",

To this:

"officeFabricIconFontName": "Sunny",

Note that you have to re-build the web part (run gulp build) and reload your workbench (press [F5] on your keyboard) before you'll see the new icon. Trust me, I wasted a lot of time trying to figure out why my new icons weren't showing up.

If you want to set the icon to an external image, you'll want to find the URL of a publicly-accessible image and change your web part's manifest "officeFabricIconFontName" attribute from the following:

"officeFabricIconFontName": "Page",

For a "iconImageUrl" image URL, as follows:

 "iconImageUrl": "https://assets.contoso.com/weather.png",

You can also use a CDN, or use your SharePoint CDN to store your images.

If you don't want to use an external image, you'll want to use a base64-encoded image.

To do so, you'll have to first convert your image using a tool like this web site. Once you have your base64-encoded image, you'll change your web part's manifest from this:

"officeFabricIconFontName": "Page",

To this:

 "iconImageUrl": "data:...",

And you would put the entire base64-encoded string as the web part's URL. Because you see, the base64-encoded string is actually a valid URL.

It is just a special kind of URL, called a data URI.

About data URI

Base64-encoded images, whether they are SVG or PNG, JPG, or GIF, rely on data URI. Data URI allow you to embed a resource in a URI which would normally result in a separate HTTP request.

Most data URI consists of the data type of the item (e.g.: image/png), followed by a semicolon, and the data of that file.

The cool thing about data URI is that -- on most modern browsers -- you can pretty much use them anywhere you would normally use a link to a resource.

Parker

For example, to show the above image of Parker in HTML, you would embed an img element and point the src attribute to the URL of the image, as follows:

<img src="https://avatars0.githubusercontent.com/u/31443929?s=200&v=4" />

Using a data URI, you could also do this:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAABpUUlEQVR4nOxdBVgU2xc/S3d3d0iDtKgI2N3d+tBnd+uzu7D924mJGIgoIAooKiGIIp1KLsvS+f/mbrAxu+xiofL75JvZmTt3Ztd75vQ5Qi0tLdCJTnQCHwK/+gE60YmOjE4C6...

Which produces this image:

The first version of this image had a URL pointing to the original source of the image, while the second one contained the actual image!

To generate the super-long gibberish, I used this online base64-encoder and uploaded a copy of Parker's avatar, then copied that super-long string it generated into the src attribute of my img tag.

You have to base64-encode the image because you're trying to embed a binary file inside of HTML.

Except that SVG files are not binaries...

Using data URI without base64-encoding

SVG files are simply text markup files, like HTML.

During my research, I found an article entitled Probably don't base64 SVG which explains that when you base64-encode an image...

"It takes 4 characters per 3 bytes of data, plus potentially a bit of padding at the end."

Essentially, base64-encoding SVG takes more room than the original file! Files are essentially 133% bigger!

Fortunately, you can actually use your UTF-8 encoded SVG file inside a data URI as follows:

<img src="data:image/svg+xml; ... ">

Now, you need to make sure that you don't embed unsupported characters inside your src attribute. For example, if your SVG has a single quote (') and your src attribute uses single-quotes to define it, you'll want to escape the '.

Fortunately, Taylor Hunt wrote a great article explaining how to encode SVGs in data URI while optimizing them to help you with that.

Taylor's awesome article inspired someone to write a codepen to encode SVGs.

I forked Jakob-e's codepen to make my own which makes it easy to encode SVGs to work as a data URI icon for a web part's manifest.

Using a data URI SVG as your icon

To use your custom SVG as an icon, take the following steps:

  1. Design an icon so that it fits within a square, ideally 64x64, or at least 32x32.

  2. Make sure that your icon <svg> element sets the width and height attributes -- otherwise, your icon will be cropped and/or will not appear centered. To do so, simply change your SVG opening tag from:

    <svg>

    To this:

    <svg height="64" width="64">
  3. Consider optimizing your SVG by using SVGOMG or a similar tool.

  4. Paste your SVG in the box below to encode your SVG

VariableValue
Original SVG

  1. In your web part's [YourWebPartName].manifest.json, change the following line:

    "officeFabricIconFontName": "Page",

    To this:


    Don't forget the extra , at the end of the line.

  2. Rebuild your web part by running gulp build
  3. Refresh your workbench

Your new SVG icon should appear!

Conclusion

You still can't use base64-encoded icons as your web part icon -- at that's probably a good thing, because base64-encoded SVG files are bigger! -- but you can use a data URI image instead.

This article shows you how to convert your SVG so that it will produce beautiful web part icons.

Regardless of whether Microsoft ever fixes the issue with SVG icons for web parts, you should still consider optimizing your SVG before using it in your solutions.

I hope this helps?

Updates

  • September 1, 2019: This approach will also work for application extension.

Sources

Introduction

I recently wrote a post called My GitHub cheat sheet for PnP contributions -- an interactive cheat sheet which explains the GitHub commands that I use when I start a contribution. The post is interactive: you just tell it your GitHub username and what repository you want to contribute to, and it customizes the instructions for you.

I also wrote another post talking about how impressed I am with David Warner II's offer to help anyone with their first contribution.

After briefly chatting with David I realized that the biggest hurdle for people is that they just don't know where to get started. In my post, I recommend that you read the contribution guidelines for every repo, but I found that they are often hard to find in each repository.

I also say that most repositories want you to start from the dev branch, but as the Chris Kent pointed out in yesterday's PnP community call, some repositories prefer you use the master branch.

So, with David's help, we compiled a list of the most common PnP repositories to help you get started.

The list contains the following:

  • Repo: Name of the repository
  • What is it?: Description of the repository
  • Getting started: Links to the most likely resource if you want to get started contributing to that repository
  • Branch: The branch you should target when submitting your pull requests

Please note that the information in each of the repositories can change and that you should always refer to the repository for the latest information.

If you find that we forgot a repository, or that something is wrong, let us know in the comments of via Twitter and we'll get it updated!

Popular repositories

RepoWhat is it?Getting startedBranch
SharePoint/PnP-PowerShellSharePoint PnP PowerShell CmdLets https://aka.ms/sppnp-powershellContribution guidancedev
SharePoint/sp-dev-docsSharePoint Developer Documentation https://docs.microsoft.com/en-us/sharepoint/dev/Contribute to SharePoint developer documentationmaster
SharePoint/sp-dev-fx-controls-reactReusable React controls for SPFx solutions https://sharepoint.github.io/sp-dev-fx-controls-react/- Contribution guidelines
- Submitting a PR
dev
SharePoint/sp-dev-fx-extensionsCode samples and developer content targeted towards SharePoint Framework client-side extensions. http://dev.office.com/sharepointContribution guidanceCreate from:master
Submit to: dev
SharePoint/sp-dev-fx-library-componentsSample solutions from community around the SharePoint Framework library componentContribution guidanceCreate from: master
Submit to: dev
SharePoint/sp-dev-fx-property-controlsReusable SPFx property pane controls - Open source initiative https://sharepoint.github.io/sp-dev-fx-property-controls/- Contribution guidelines
- Submitting a PR
dev
SharePoint/sp-dev-fx-webpartsCode samples and developer content targeted towards SharePoint Framework client-side web parts. http://aka.ms/spfxContribution guidelinesCreate from: master
Submit to: dev
SharePoint/sp-dev-list-formattingSharePoint List Formatting Samples https://sharepoint.github.io/sp-dev-list-formatting/Contribution guidelinesmaster
SharePoint/sp-dev-modernizationAll modernization tooling and guidance http://aka.ms/sppnp-modernizeThe modernization repositorydev
SharePoint/sp-provisioning-serviceCode for the provisioning service hosted at http://provisioning.sharepointpnp.comContributingdev
SharePoint/sp-usage-docsSharePoint Documentation on usage and feature patterns for site owners and citizen developersAdding contentmaster
Sharepoint/PnP-sites-coreOffice 365 Dev PnP Core component (.NET) targeted for increasing developer productivity with CSOM based solutions.Contribution guidancedev
pnp/office365-cliManage Microsoft Office 365 and SharePoint Framework projects on any platform https://aka.ms/o365cli- Contribution guidelines
- Adding a new command
- Submitting a PR
dev
pnp/pnpjsSharePoint Patterns and Practices Reusable Client-side Libraries https://pnp.github.io/pnpjsContribution guidedev

Thanks

Thanks to David Warner II with putting together this list, and for always making yourself available to help people in this community.

This list wouldn't be possible without the hard work of all of those who contributed (and continue to contribute) to the above repositories. Thank you for your contributions!

Photo credit

Image by StockSnap from Pixabay

Updates

  • August 25, 2019: Thanks to Bert Jansen for providing us with details for the SharePoint/sp-dev-modernization repository.
  • August 23, 2019: Erwin van Hunen Tweeted to remind us about SharePoint/PnP-Sites-Core. It is so foundational to other components, I don't know how we missed it. Urgh! I hate to disappoint someone I hold in such high regard! Sorry!
  • August 23, 2019: Waldek Mastykarz rightly pointed out that we forgot the PnPjs repository.

Introduction

In my previous post I showed how you can use Flow to trigger a scheduled flow and retrieve an iCal feed over HTTP.

I also spent a bit of time explaining how to parse the text returned from the iCal feed into individual events and extract the information required to be able to import and synchronize the events in a SharePoint list. I mostly covered at a high-level and didn't give you detailed steps -- which is a good thing, because the article was already pretty long!

In today's post, we'll set up a SharePoint list and walk you through how to implement the flow, step-by-step.

Let's get going!

Setting up a SharePoint list

To synchronize an iCal feed to SharePoint, we need two things:

  • A feed
  • A SharePoint list

And we'll need a flow to combine the two.

For the feed, I'll use one of CalendarLabs.com's iCal feeds. Being Canadian, I'll use the Canadian Holidays feed, but feel free to use the US Holidays or any other that suits you. (Make sure that you get the URL from the Download button). I have to admit that I only learned about CalendarLabs.com when I was researching this post, but I'll definitely be recommending it to everyone!

There are all sorts of useful iCal feeds out there. For example, if your company uses People HR to for your HR software, you can get an iCal feed of employee vacations directly from People HR and synchronize with a list called Staff Calendar.

Let's assume you have found an iCal feed you want to sync and you tested it to make sure it returns something.

For the SharePoint list, we'll create one. Since the iCal feed I'm using is a list of Statutory Holidays, I'll call my new list Statutory Holidays (I know, right? How do I come up with this stuff?!)

To create the list, follow these steps:

  1. From a SharePoint site of your choice, go to Settings then Site contents
  2. From the Site contents page, select + New and pick App. We use App instead of List because we want to create an events list, not a regular list.
  3. From the Site contents > Your Apps page, pick Calendar
  4. From the Adding calendar dialog, in the Name field, enter Statutory Holidays (or whichever name you picked for your event list, I'm flexible!) then select Create.

Your list should automatically get created. Now we need to add one more field called UID to keep track of the unique identifiers for each event. The UIDs are provided by the iCal feed.

To add the column, follow these steps:

  1. From the list you just created, go to List settings
  2. In the Settings page, in the Columns section, select Create column.
  3. In the Settings > Create column page, type UID for the Column name.
  4. Un-check Add to default view, but leave everything else. We just want a simple text column to store the unique identifier, no need to get fancy.
  5. Select Ok to create the column.
  6. If you want, you can remove the columns that you won't use, but for the sake of brevity, I'll leave the existing fields as it.

To set up the Flow

Yesterday, I briefly touched how I build the flow to extract the data. Today, we'll do it step-by-step, starting with these steps:

  1. Log-in to https://flow.microsoft.com
  2. From the left navigation, select + Create
  3. From the Start from blank section, select Scheduled flow
  4. In the Build a scheduled flow dialog, pick a Flow name -- something like Sync Statutory Holidays from iCal Feed
  5. Under Run this flow, pick a Starting time that suits you, and select how often you want to repeat the flow. I picked 1 day under Repeat every.
  6. Select Create.
    Creating a Scheduled Flow

To retrieve the iCal feed

Once your flow is created, follow these steps:

  1. Select the + New step from the flow editor and type HTTP in the Choose an action window
  2. From the list of Actions, select HTTP
  3. Rename the new action you added to Get events from iCal
  4. In the HTTP action's details, set the Method to GET, and put your iCal feed's URL in the URI field.
  5. Leave everything else as is.
    HTTP Action

Try your flow by using the Test button in the upper right corner. Your Get events from iCal should return events. If it doesn't, check the URL.

To get the list of events

  1. Select the + New step and type Initialize in the Choose an action window.
  2. From the list of Actions, select Initialize variable
  3. Rename your new action to Get list of events
  4. In the initalize action's details, set the Name to Events, the Type to Array and the Value to:
    split(replace(body('Get_events_from_iCal'), '\\n', ''), 'BEGIN:VEVENT')

    Getting list of events

This will split the iCal feed into an array of events where BEGIN:VEVENT can be found.

To filter array elements that aren't events

  1. Select the + New step and type Filter in the Choose an action window
  2. From the list of Actions, select Filter array
  3. Rename the new action to Remove rows that are not events
  4. In the From field, select the Events variable using the Dynamic content
  5. Below the From field, select Edit in advanced mode and type:
    @not(startsWith(item(), 'BEGIN'))

Not events

This will keep only array items that don't start with BEGIN, thus removing all the iCal header information and leaving you with only events.

To create a loop to process all events

  1. Select the + New step button and type apply to each in the Choose an action window.
  2. From the list of Actions, select Apply to each
  3. Rename your action Loop through every event
  4. In the Select an output from previous steps field, select the Body of Remove rows that are not events in the Dynamic content picker.

This will create a loop for every event in the array.

Loop

To split every event into lines

  1. Within the Loop through every event loop, select Add an action
  2. In the Choose an action window, type compose
  3. From the list of Actions, select Compose
  4. Rename your new action Get all lines in event
  5. In the Input field, you'll want to use:
    split(item(), json('{"NL":"\n"}')?['NL'])

Splitting into lines

Now, all we need to do is extract the data from every relevant line

To get the Start Date

  1. Still within the loop, select Add an action and type Filter in the Choose an action window
  2. From the list of Actions, select Filter
  3. Rename the new action Find DTSTART
  4. In the Inputs field's Expression tab, type:
    @startsWith(item(), 'DTSTART')
  5. Add another action using Add an action and type Compose
  6. From the list of actions, select Compose
  7. Rename the action to Get DTSTART
  8. In the Inputs field, type the following expression:
    replace(first(body('Find_DTSTART')), 'DTSTART;VALUE=DATE:', '')

Getting Start Date

To get the End Date

Repeat the same steps as above except that you should call the Filter action Find DTEND and use the following expression:

@startsWith(item(), 'DTEND;VALUE=')

And in the Compose action, call it Get DTEND and use the following expression

replace(first(body('Find_DTEND')), 'DTEND;VALUE=DATE:', '')

Getting End Date

To get the Summary

You guessed it, repeat same as above, but name the Filter action Find SUMMARY and use the following expression:

@startsWith(item(), 'SUMMARY:')

And set your Compose action to Get SUMMARY and use this expression:

replace(first(body('Find_SUMMARY')), 'SUMMARY:', '')

Getting Summary

To get the Unique Identifier

One last time! Repeat same as above, but name the Filter action Find UID and use the following expression:

@startsWith(item(), 'UID:')

And set your Compose action to Get UID and use this expression:

replace(first(body('Find_UID')), 'UID:', '')

Getting UID

Now you have all the properties we need. You should test your flow to make sure everything works.

Everything we need

Note that if your iCal feed has different fields that you need, you may need to adjust your actions above. For example, some feeds will return DATE-TIME and DATE events, so you may want to add a little condition up there to deal with such events. To keep things simple, we won't do that here.

To verify if the event is already in SharePoint

  1. Still within the loop, select Add an action and type SharePoint in the Choose an action window
  2. From the list of Actions, select Get items
  3. In the Site Address field, type your site's URL
  4. From the List Name pick Statutory Holidays.
  5. Under Filter Query type UID eq ' then select the Output from Get UID in the Dynamic content window.
  6. Add ' to close the filter query
  7. Under Top Count, enter 1. We only care if there is already an event with a matching UID, so returning only 1 will do for us.

Getting SharePoint items

If you test your flow now, every Get Items action should return the following:

{
[]
}

Because there are no events to retrieve. Let's fix that.

To create a list item if there are no matches found

  1. Still within the loop, select Add an action and type Condition in the Choose an action window

  2. From the list of Actions, select Condition

  3. Rename the condition Any existing events found

  4. In the expression below type:

    length(body('Get_items')?['value'])
  5. Select by is greater than in the next field

  6. In the next field, type 0

  7. This will cause the condition to go to If yes when there is an existing event, and If no if there isn't an existing event. We'll only worry about If no for now, but you could always update the existing item in the If yes side if you wanted to. Just no today, ok?

  8. In the If no site, select Add an action

  9. In the Choose an action field, type Create item

  10. In the Actions list, select Create item from SharePoint

  11. In the Create item dialog, type your Site Address and pick Statutory Holidays from the List Name

  12. In the Title field, bind to the Output from Get SUMMARY

  13. We'll skip the Start Time and End Time for now. In the UID field, set it to the Output of the Get UID action.

  14. For the Start Time, we'll need to do some surgery because SharePoint expects the value to be formatted as yyyy-MM-ddThh:mm:ss. To do this, we'll use substring() to extract the year, month, and day from the event's date but using the following expression:

    concat(substring(outputs('Get_DTSTART'),0,4),'-',substring(outputs('Get_DTSTART'),4,2),'-',substring(body('Get_DTSTART'),6,2),'T00:00:00-00:00')

    Which essentially combines the first 4 characters of the Start Date with a -, followed by the next 2 characters of Start Date, followed by another -, and the last two characters of Start Date, followed by T00:00:00 to set the time to midnight. If your iCal returns DATE-TIME values, you'll also want to parse the time element instead of setting it to 00:00:00.
    The last part -00:00 is for the timezone. If you find that your events are coming in at the wrong time, you can adjust the time zone accordingly (e.g.: +01:00 or -01:00).

  15. Repeat the same formula for End Time, except that you should use GET_DTEND instead of GET_DTSTART.

SharePoint create items

Test your workflow, and you should have a whole bunch of new events! Then try again, and you should not get more events until the iCal feed adds a new event.

Conclusion

(Sigh of relief!) That was a long post!

This post showed you how to get an iCal feed in Flow and import events from that feed into a SharePoint list.

You can use a similar approach for other types of feeds.

There are a few more opportunities to improve the resiliency of this flow, and to deal with all-day events that span over two days when they should really last one day... but that'll have to be for another post.

I hope this helped?

Photo credit

Image by Free-Photos from Pixabay

Introduction

Let's say you have public iCal feed and you want to access the events from within SharePoint Online.

You could use the sample web part I created many months ago which allows you to display external event feeds from RSS, WordPress, Exchange, SharePoint and iCal. You'd get something that looks like it exists in SharePoint.

But what if you wanted to use the events from that iCal feed within SharePoint as a lookup for another list. Or what if that iCal feed contained statutory holidays and you wanted to skip scheduled Flows on those days?

In such situations, you need to actually import the data into SharePoint so that you can use it. My web part sample won't do!

Today, we'll explain how to use Flow to import events daily from an iCal feed into a SharePoint list.

The idea for this post came from an issue that Tejas kindly submitted. Thanks for the inspiration!

The problem with repetitive tasks

At first, I was very tempted to tweak my React Calendar Feed web part sample to display events and automatically add events to a SharePoint event list. After all, I was already parsing the iCal feeds in my code, so couldn't I just add the events that were not already in the list?

The problem with this idea is that it would require someone with sufficient permissions to add events to that event list to load that web part once a day -- or at least regularly enough to make sure the iCal events and the list were in sync.

Bad idea.

Any time you require someone to do a manual step regularly to keep a system going is not sustainable. It's like that countdown timer in the TV show Lost that required someone to enter a sequence of numbers or the world would end.

Lost countdown
The countdown clock from Lost -- I don't need that kind of responsibility!

It's what we called on a project I worked on "Death by a thousand cuts". It may not seem like a big deal to ask someone to do something every x days to keep a system going, but those little temporary solutions you take on eventually accumulate to a point where you need a person whose job is just to do those little things. It isn't a job that's very fulfilling for anyone.

Might as well use Homer Simpson's "stupid bird" to push a button every once in a while.

Homer Simpson's stupid bird
Homer's stupid bird

So, the lazy approach wouldn't work for me this time.

What could I use? A timer job on a server? Nah, I want a server-less solution.

How about a scheduled Azure web job? No, I want a no-code solution. Or at least low-code.

If only there was a way to schedule workflows to run regularly that wouldn't require any code...

Scheduled flows to the rescue!

Thankfully, Microsoft Flow allows you to create scheduled flows. We already discussed using schedule flows in a previous post, so I know with certainty that it will work.

Scheduled flows

I configured my flow to run every day. That's probably an overkill depending on how often your iCal feed gets updated, so feel free to adjust accordingly:

Schedule recurrence, every 1 day

Now here's the problem: Flow does not have a way to parse iCal feeds. I looked everywhere for a ready-to-use "import iCal" connector, but couldn't find any. As it turns out, I'm not the only one who wants this.

Note to Microsoft: if you accept open source contributions for connectors, let me know and I'll gladly submit an iCal connector.

But iCal feeds are really just text files with a very specific structure. Could we not just use the Flow HTTP connector and retrieve the feed as a simple string of text and parse it?

HTTP connector

Note: Unfortunately, HTTP is a Premium connector in Flow... but you get so much more with Flow Premium that it is worth it! Trust me!

This is how I configured my HTTP connector to retrieve my iCal feed:

HTTP call to iCal

Parsing the results to get a list of events

I configured an HTTP connector to do an HTTP GET with a public iCal feed. I used CalendarLabs.com's Canadian Holidays sample, but they have many other great sample calendar feeds.

And it worked. The response wasn't pretty, but it worked!

Here is an example of what I got. The full list is a lot longer, but you get the idea.

BEGIN:VCALENDAR
PRODID:fd29_Array_canada_country_holidays@calendarlabs.com
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:Canada Holidays
X-WR-TIMEZONE:America/New_York
BEGIN:VEVENT
DTSTART;VALUE=DATE:20180101
DTEND;VALUE=DATE:20180102
DTSTAMP:20111213T124028Z
UID:5c60f18d0973d@calendarlabs.com
CREATED:20111213T123901Z
DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. 
 Like us on Facebook: http://fb.com/calendarlabs to get updates.
LAST-MODIFIED:20111213T123901Z
LOCATION:Canada
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:New Year's Day
TRANSP:TRANSPARENT
END:VEVENT
BEGIN:VEVENT
DTSTART;VALUE=DATE:20180212
DTEND;VALUE=DATE:20180213
DTSTAMP:20111213T124028Z
UID:5c60f18d09784@calendarlabs.com
CREATED:20111213T123901Z
DESCRIPTION:Visit https://calendarlabs.com/holidays/canada/family-day.php to know more about Family Day (BC). 
 Like us on Facebook: http://fb.com/calendarlabs to get updates.
LAST-MODIFIED:20111213T123901Z
LOCATION:Canada
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:Family Day (BC)
TRANSP:TRANSPARENT
END:VEVENT

As you can see, iCal feeds use lines as a delimiter for each field. Every new event start with BEGIN:VEVENT on a new line.

So I used the Initialize variable to create an array variable which would contain every event in the feed. I called the variable Events.

Initialize variable

For the initial value of the variable, I used the split() function, and I specified that it should create a new array element every time it found a BEGIN:VEVENT.

Some iCal feeds that I tested also had extra newline characters, so I took the opportunity to remove those while I was parsing the feed, by using the replace() function to replace all \\n with nothing ('') as follows:

split(replace(body('Get_events_from_iCal'), '\\n', ''), 'BEGIN:VEVENT')

Splitting the events

The problem is that the split() function just blindly creates array elements wherever it finds the delimiter you specify. It doesn't care what comes before or after the delimiter.

For example, my sample feed contains 8 lines before the first event, meaning that this text:

BEGIN:VCALENDAR
PRODID:fd29_Array_canada_country_holidays@calendarlabs.com
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:Canada Holidays
X-WR-TIMEZONE:America/New_York
BEGIN:VEVENT
DTSTART;VALUE=DATE:20180101
DTEND;VALUE=DATE:20180102
DTSTAMP:20111213T124028Z
UID:5c60f18d0973d@calendarlabs.com
CREATED:20111213T123901Z
DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. 
 Like us on Facebook: http://fb.com/calendarlabs to get updates.
LAST-MODIFIED:20111213T123901Z
LOCATION:Canada
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:New Year's Day
TRANSP:TRANSPARENT
END:VEVENT
BEGIN:VEVENT
DTSTART;VALUE=DATE:20180212
DTEND;VALUE=DATE:20180213
DTSTAMP:20111213T124028Z
UID:5c60f18d09784@calendarlabs.com
CREATED:20111213T123901Z
DESCRIPTION:Visit https://calendarlabs.com/holidays/canada/family-day.php to know more about Family Day (BC). 
 Like us on Facebook: http://fb.com/calendarlabs to get updates.
LAST-MODIFIED:20111213T123901Z
LOCATION:Canada
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:Family Day (BC)
TRANSP:TRANSPARENT
END:VEVENT

When the text is split where BEGIN:VEVENT is found, I get the following array:

[ "BEGIN:VCALENDAR\nPRODID:fd29_Array_canada_country_holidays@calendarlabs.com\nVERSION:2.0\nCALSCALE:GREGORIAN\nMETHOD:PUBLISH\nX-WR-CALNAME:Canada Holidays\nX-WR-TIMEZONE:America/New_York\n",

"\nDTSTART;VALUE=DATE:20180101\nDTEND;VALUE=DATE:20180102\nDTSTAMP:20111213T124028Z\nUID:5c60f18d0973d@calendarlabs.com\nCREATED:20111213T123901Z\nDESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. \n Like us on Facebook: http://fb.com/calendarlabs to get updates.\nLAST-MODIFIED:20111213T123901Z\nLOCATION:Canada\nSEQUENCE:0\nSTATUS:CONFIRMED\nSUMMARY:New Year's Day\nTRANSP:TRANSPARENT\nEND:VEVENT\n",

"\nDTSTART;VALUE=DATE:20180212\nDTEND;VALUE=DATE:20180213\nDTSTAMP:20111213T124028Z\nUID:5c60f18d09784@calendarlabs.com\nCREATED:20111213T123901Z\nDESCRIPTION:Visit https://calendarlabs.com/holidays/canada/family-day.php to know more about Family Day (BC). \n Like us on Facebook: http://fb.com/calendarlabs to get updates.\nLAST-MODIFIED:20111213T123901Z\nLOCATION:Canada\nSEQUENCE:0\nSTATUS:CONFIRMED\nSUMMARY:Family Day (BC)\nTRANSP:TRANSPARENT\nEND:VEVENT\n",
...
]

Notice that the first array element is different than the others. It contains information about the calendar feed, which we don't care about for our needs.

To remove anything that isn't an event, I simply used the Filter action and kept only array elements that do not start with BEGIN:

Filtering array where the item doesn't start with BEGIN

(I could have filtered for events that start with DTSTART, but I didn't want to have to deal with the funny \n character at the start of every element)

This is the array the filter action returned:

[
"\nDTSTART;VALUE=DATE:20180101\nDTEND;VALUE=DATE:20180102\nDTSTAMP:20111213T124028Z\nUID:5c60f18d0973d@calendarlabs.com\nCREATED:20111213T123901Z\nDESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. \n Like us on Facebook: http://fb.com/calendarlabs to get updates.\nLAST-MODIFIED:20111213T123901Z\nLOCATION:Canada\nSEQUENCE:0\nSTATUS:CONFIRMED\nSUMMARY:New Year's Day\nTRANSP:TRANSPARENT\nEND:VEVENT\n",

"\nDTSTART;VALUE=DATE:20180212\nDTEND;VALUE=DATE:20180213\nDTSTAMP:20111213T124028Z\nUID:5c60f18d09784@calendarlabs.com\nCREATED:20111213T123901Z\nDESCRIPTION:Visit https://calendarlabs.com/holidays/canada/family-day.php to know more about Family Day (BC). \n Like us on Facebook: http://fb.com/calendarlabs to get updates.\nLAST-MODIFIED:20111213T123901Z\nLOCATION:Canada\nSEQUENCE:0\nSTATUS:CONFIRMED\nSUMMARY:Family Day (BC)\nTRANSP:TRANSPARENT\nEND:VEVENT\n",

"\nDTSTART;VALUE=DATE:20180214\nDTEND;VALUE=DATE:20180215\nDTSTAMP:20111213T124028Z\nUID:5c60f18d097c3@calendarlabs.com\nCREATED:20111213T123901Z\nDESCRIPTION:Visit https://calendarlabs.com/holidays/us/valentines-day.php to know more about Valentine's Day. \n Like us on Facebook: http://fb.com/calendarlabs to get updates.\nLAST-MODIFIED:20111213T123901Z\nLOCATION:Canada\nSEQUENCE:0\nSTATUS:CONFIRMED\nSUMMARY:Valentine's Day\nTRANSP:TRANSPARENT\nEND:VEVENT\n",
...
]

Now all I needed to do was to loop through every event and parse the values...

Processing each event

So far, I have a scheduled flow which retrieves an iCal feed, splits the text into an array of events, and filters out things that aren't events.

To process every item in the array of events, I just used the Apply for each action:

Apply for each

When it asked me what I wanted to loop through, I specified the Body of the filter action from before.
Looping through events

Now that I have the flow looping through every event, I need to split the event into individual lines, using the split() function, like before.

Except that this time, instead of storing the array of lines in a variable, I use the Compose action to temporarily build my array.

Compose

The only problem is, I found it very difficult to write a split() function to divide by the newline character. Luckily, someone who is way smarter than I am (tre4B) came up with a solution.

tre4B's solution is to temporarily define a JSON object which defines the newline character as a JSON element, and use that JSON element, as follows:

json('{"NL":"\n"}')?['NL']

I must admit, it's almost like voodoo to me, but it works.

So, to split every event into individual lines, I used:

json('{"NL":"\n"}')?['NL']

If your feed uses both a newline and a carriage return, you would use this instead:

split(item(), json('{"NL":"\r\n"}')?['NL'])

Splitting by newline

Now, every event returns an array of lines that look like this:

[
  "",
  "DTSTART;VALUE=DATE:20180101",
  "DTEND;VALUE=DATE:20180102",
  "DTSTAMP:20111213T124028Z",
  "UID:5c60f18d0973d@calendarlabs.com",
  "CREATED:20111213T123901Z",
  "DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. ",
  " Like us on Facebook: http://fb.com/calendarlabs to get updates.",
  "LAST-MODIFIED:20111213T123901Z",
  "LOCATION:Canada",
  "SEQUENCE:0",
  "STATUS:CONFIRMED",
  "SUMMARY:New Year's Day",
  "TRANSP:TRANSPARENT",
  "END:VEVENT",
  ""
]

Once I have every line as an array element, I can just use the same filter() technique I used before to find each line for the Start Date, End Date, Summary and anything else I need.

For example, to find the Start Date, I find the line which starts with DTSTART:

Filtering for DTSTART

And to get the date value, I use the replace() function to remove everything before the date, which looks like this:

replace(first(body('Find_DTSTART')), 'DTSTART;VALUE=DATE:', '')

All that's left is a string that represents the date. I won't bother converting the string to a date because SharePoint will expect a very specific date format later anyway.

I just repeat the same thing with DTEND for the End Date and SUMMARY for the event title.

Also, I'll retrieve every event's UID which is a unique identifier that I'll be able to use later to verify if the event has already been created in SharePoint.

Once completed, my event parsing looks like this:

My event parsing loop

It looks like it's a lot of work, but thanks to Flow's new clipboard functionality, it was able to copy and paste the Find DTSTART and Get DTSTART actions for the DTEND, SUMMARY and UID.

Easy!

To be continued

Sorry if this post was long (and probably boring). We had a lot of stuff to cover, and we're not finished yet!

So far, we have created a scheduled workflow which reads an iCal feed and parses each event to retrieve their individual attributes.

I should point out that my approach isn't the only way to do this, I'm sure. This is the way I did it. For example, instead of using replace(), I could have used the substring() function to extract the parts of the strings I wanted, but I prefer replace() because it makes it easier to read what the function is doing. Feel free to use whichever approach you like.

Tomorrow, we'll use the same technique we used before to see if every event exists in a SharePoint list. If it doesn't, we'll create it.

I'll also share the step by step instructions tomorrow.

I hope you'll come back tomorrow for the second part!

Photo credit

Image by Karolina Grabowska from Pixabay

Introduction

I use mind maps to understand things. I do this a lot. They're a great way to summarize things in a simple picture.

My goal is to take major SharePoint/Office 365 announcements, videos, and other relevant pieces of information which may require too much time for busy people to read and watch, and to boil it down to the essential.

This one highlights what's new in August 2019 with SharePoint pages and news authoring.

Just click on the mind map to see a larger image.

August 2019 Updates to SharePoint pages and news authoring mind map

Conclusion

Let me know if this was useful for you. Also, if I missed something, let me know.

Sources

Introduction

Sometimes, you just want to create a link to someone's Delve profile in SharePoint Online.

Thanks to an article from The Baretta, we know how to formulate the link.

However, since I started doing interactive blog posts, I thought I'd create one which automatically generates the URL for you.

To create a link to a Delve profile

To create a link to someone's Delve profile:

  1. Take your tenant name, and add -my.sharepoint.com/PersonImmersive.aspx?accountname=i%3A0%23%2Ef%7Cmembership%7C to the end of it.
  2. Append the person's email address at the end of what you got in step 1.

Generate a link now

Enter the your tenant name (the part before .sharepoint.com and without https://) and the email address of the person for whom you want to create a profile below, and the URL will automatically be created for you. Click Copy to clipboard to send it to your clipboard.

Don't worry, we don't store any information you enter.

VariableValue
SharePoint Online tenant name
User's email address

Your URL:

https://mytenant-my.sharepoint.com/PersonImmersive.aspx?accountname=i%3A0%23%2Ef%7Cmembership%7Csomeuser@mytenant.com


Conclusion

That's all there is to it. I really just wanted to create another interactive blog post, because they are fun to create!

Thanks to The Baretta for posting this information in the first place.

Photo credit

Delve screenshot from Microsoft

Introduction

I have to admit, I still consider myself a GitHub newbie.

Most of my clients use TFS or Azure DevOps and my previous experiences with GitHub were unpleasant.

But since I started contributing to the Office 365 Developer Community, I had no choice but to become familiar with GitHub.

The majority of the repositories under https://github.com/SharePoint and https://github.com/officedev -- for example -- have contributing guidelines, but they assume that you have a basic understanding of GitHub -- or at least, a better understanding of GitHub than I had when I started contributing.

Because I still hesitate with GitHub commands once in a while, I use a cheat sheet when I start a new PnP contribution. I copy and paste the GitHub commands from the cheat sheet and substitute placeholders with the values I want. It is really an amalgamation of GitHub commands from the various contributing guideline documents I have found useful into (I hope) a coherent set of instructions. I try to use the GitHub browser interface wherever I can, and GitHub commands where it is easier.

I have written this post to answer all the questions I had when I started. It is really a note for myself, but I hope it can be useful for someone else who wants to get started. While this article is written specifically for PnP contributions, they also apply to most open source contributions on GitHub.

I am not a GitHub expert. There may be better ways to do what is described in this post, but these are the instructions that have worked well for me in the past. If you have any suggestions on how to improve the instructions, please submit a comment below. I'll even buy you a coffee when you're in town!

This article focuses on the GitHub commands. If you need help with your first contribution, David Warner II has kindly volunteered to help anyone with their first PnP contribution. He's a true master on the topic.

Note

I'm trying something new today: interactive, personalized instructions for you. Yes, you!

Instead of putting placeholders in the instructions (like [your_repo_name]) that you have to change as you follow the instructions, the placeholders are connected to a form at the top of each section.

When you change the values of the placeholders, the instructions in this post change automatically!

To get customized instructions, just replace the placeholder values with your own values.

Don't worry, we don't save any values you entered.

Let me know if you like this in the comments below. If you do, I may do more interactive posts in the future. It's kinda fun!

Overview

If you know me, you know that I love processes.

The diagram below represents the contribution process that we'll discuss today.

graph LR
AA[Fork repository]-->A
A[Clone repository]-->B[Create branch]
B-->C[Contribute]
C-->D[Push changes]
D-->E[Submit pull request]
E-->|Repeat|B

It consists of the following steps:

  1. Fork repository
  2. Clone repository
  3. Create a branch
  4. Contribute
  5. Push changes
  6. Submit pull request
  7. Repeat (go back to Create branch)

Step 1: Fork repository

Before you can contribute to a repository, you need to Fork it. Forking a repository creates a copy of it from the original owner's account to your own.

Once you have created your own fork, you can freely change the code in your own copy of the repository without worrying about affecting the original repository, or upstream repository. However, GitHub remembers what the upstream repository is, which will make it easy for you to submit your contributions when you're done but also to synchronize changes that have been made to the upstream repository since you forked it.

Forking only happens within GitHub; it doesn't affect the code on your machine (that is, until you clone it, but that'll happen later).

How to fork a repository

  1. In your browser go to http://github.com and find the repository you want to contribute to. For example, the sp-dev-fx-webparts is where you would submit SharePoint web part samples.
  2. In the upper right corner, select the Fork button
    The Fork button in GitHub
  3. If you haven't already logged in to GitHub, you'll be prompted to do so. If you haven't already created a GitHub account, you'll be able to create one. If you have already logged in, it will automatically begin the forking process. You get a cute little animation showing that it is "copying" the repository, and you end up in your own copy of the repository.
    The Forking has begun
  4. You'll know that you're in a fork because the owner will have changed to you, and it should say "forked from ..."
    hugoabernier/sp-dev-fx-webparts forked from SharePoint/sp-dev-fx-webparts

The repository name should now be [your_github_username]/[repo_name], for example, if you forked SharePoint/sp-dev-fx-webparts and your username is hugoabernier, your forked repository will be called hugoabernier/sp-dev-fx-webparts.


Enter your own variables

Take a second to enter the original repository URL and your GitHub username below, and the rest of this post will automatically change to match what you should see:

VariableValue
Original Repository (Upstream)
Your GitHub username

You have created your own fork! This is what is called an origin repository. I know, I know, the terms are confusing, but here is a table that may help break down the differences:

CharacteristicsUpstreamOrigin
What is itThe original repositoryYour forked instance of a repository
Where is ithttps://github.com/SharePoint/sp-dev-fx-webpartshttps://github.com/[your_github_username]/sp-dev-fx-webparts
Who owns itSharePoint (Not you!)[your_github_username] (You)
What changes should you makeCreate issues, submit a pull requestWhatever you want
What is the impact of your changesAffects the original repositoryAffects only your fork
Who can see your changesEveryoneEveryone who looks at your repository, unless you made it private

Once you have forked a repository, clicking on Fork again will do nothing; It simply redirects you to your own fork.

Step 2: Clone repository

Once you created your origin repository, it is time to clone it. Cloning a repository creates a copy of your fork to your local machine, so you can work on it.

The cloned repository, which will reside on your local hard-drive, will become your local repository; The repository that is on GitHub will be what's called your remote repository.

You can make any changes to the local files on your hard-drive, it will only affect your local repository. It won't affect your remote repository (until later, when we push the changes).

To clone your repository

  1. From your computer, launch whatever tool you like to run Git commands. Some people like Git Bash, but I prefer Cmder or the Node.js command prompt.
  2. Make sure that your command prompt is in the directory where you'll want to create your local repositories. I like to use c:\github. You can do so by typing:
    cd \github

    or, if using Git Bash:

    cd /c/github
  3. The repository you will clone will be created a directory within your current directory. To clone the repository, just type git clone followed by the URL of the repository you forked in the previous section. Note that the URL should end with .git:
    git clone https://github.com/[your_github_username]/sp-dev-fx-webparts.git
    

    It should create a directory with the same name as your repo, then should download all the files locally to that directory. It doesn't take very long, but it really depends on your internet connection. This is what it should look like:
    Git Clone in action

  4. Once your local repo is created, change to the directory that was just created by typing cd followed by the repo name and [Enter]:
    cd sp-dev-fx-webparts
    
  5. To link your local repo with the original upstream repo, you'll type git remote add upstream followed by the original upstream repo URL, as follows:
    git remote add upstream https://github.com/SharePoint/sp-dev-fx-webparts.git
    

    Linking to the upstream repo will make your life easier later when you want to make more contributions.

  6. Before you start making changes, you should make sure that you have the latest version from the original upstream repository. Of course, since you just forked the repo, you should already up to date, but if make more contributions later, you'll want to make sure you're always making your new contributions against the latest code. To do so, simply type the following:
    git fetch upstream

Now you'll have a local repository with a remote that is connected to the upstream repo that we can create a branch from.

Step 3: Create a branch

In GitHub, repositories usually have multiple branches. In GitHub, a branch is a way to keep track of change by grouping them into a feature set. The master branch is usually the default branch, where all approved code usually ends up. You normally shouldn't make changes to the master branch directly, that's what pull requests are for.

When you want to make changes in a repo, you should create your own branch to help keep track of the changes you're making. For example, if you want to add a cool feature to a repo, you would create a branch called cool-feature. Meanwhile, someone else may create their own branch called coolest-feature, which may later become the basis for someone else's most-coolest-feature branch.

Eventually, when those branches are submitted via a pull request (assuming they get approved), they'll end up being merged back to the master branch.

Branches in GitHub

Most PnP repositories usually have a dev branch, which is meant to be your starting point for your changes.

Take a moment to look in your repo for any contribution guidelines to make make sure that the starting branch is the dev branch.

To help you, David Warner II and I have compiled a list of popular PnP repositories that will tell you which branch you should use.

Once you have confirmed what branch you should start from, you should create own branch from the starting branch, and give it a name that will describe what you'll be doing in that branch. For example coolerest-feature, or hugo-patch-1. Try to avoid spaces and funny characters.


Enter your own variables

Enter the name of the branch you want to create, and we'll update the instructions for you:

VariableValue
Start branch name (default is dev)
Branch name

To create your branch

To create your branch, follow these steps:

  1. To create a branch, we'll start by calling git pull upstream, which will update your local repository with the latest changes from the upstream repository. We'll also specify which branch to start from, and what to call the new branch by typing the following:
    git pull upstream dev:coolerest-feature
    
  2. Now we'll let your forked origin repo know about the new branch you've created by typing git push origin followed by your new branch name, as follows:
    git push origin coolerest-feature
    
  3. Finally, we'll switch to the new branch you've created by calling git checkout, followed by your new branch name. Type the following:
    git checkout coolerest-feature
    

If you're using Cmder, you should see that your prompt has changed to indicate that you're now in your new branch:
New branch

Note: If you need instructions to configure Cmder to display your repo and branch, read my earlier post

Now you're reading to contribute!

Step 4: Contribute

Now that you have your own branch, you can make the changes you need. Please make sure you follow the Microsoft Open Source code of conduct.

If you aren't sure about the code of conduct, you can also check out the Code of Conduct FAQ.

Once you're done making your changes, you'll want to push your contributions.

Step 5: Push your changes

As you make changes to files in your local branch, your changes will be tracked locally. Changing the files in your local folder does not affect the local repository until you commit your changes.

Once you have committed your changes to the local repository, you can push your changes to your remote repository (the one on GitHub.com).

You can do so by following these steps:

  1. From the local branch folder, type:
    git add .
  2. Commit your changes by typing git commit -v -a -m followed by a comment indicating what your changes were. For example, if you wanted to say "Initial commit", you would type the following:
    git commit -v -a -m "Initial commit"

Now your changes are committed with a comment. Time to submit a pull request!

Step 6: Submit a pull request

In GitHub, a pull request is really simply a request for someone else to review the work that you've done and merge your changes in. When you create a pull request, you need to select two branches on GitHub, the branch that you've made your changes on, and the branch where you would want to merge your changes into.

To do so, follow these steps:

  1. Push your changes to your origin repository (the forked repository you created), you'll want to type git push origin followed by your branch name, as follows:
    git push origin coolerest-feature
    
  2. Once done, use your browser to your forked repository (https://github.com/[your_github_username]/sp-dev-fx-webparts), you should see that your changes have already been reflected to GitHub.
  3. From your forked repository, click on Pull requests in the navigation, then click on New pull request. Optionally, you can visit https://github.com/hugoabernier/sp-dev-fx-webparts/pull/new/coolerest-feature.
    New pull request
  4. You'll be prompted to confirm the branches you want to merge, with an arrow going from one branch to another. Make sure that the arrow is pointing from your branch on your forked repo to the branch on the remote repo. If you follow all the steps above, you should also see Able to merge.
    New pull request
  5. Provide a descriptive title for your pull request. For example, New coolerest feature
  6. Most PnP repositories have a pull request template. Please be courteous and follow the instructions in the template. Follow the prompts and answer as much as possible. If there are sections that say > _(DELETE THIS PARAGRAPH AFTER READING)_, delete them.
  7. When you have filled the template, click Create pull request.

After you've completed your pull request, you'll see that its status is marked as Open

New open pull request

All you have to do now is to wait for your pull request to be merged with the master branch.

It can take a few days, sometimes weeks before your pull request is approved. Please be patient; Most reviewers are volunteers and have a day-to-day job.

While you're waiting, you can start a new contribution!

Step 7: Repeat

If you want to continue making contributions, you simply create a new branch from the original base branch. For example, if you were created the second update to your coolerest-feature, you could call your next branch coolerest-feature-2.


Enter your own variable

Enter the name of the next branch you want to create, and we'll update the instructions for you:

VariableValue
Next branch name

To create your next branch, follow these steps:

  1. Calling git pull upstream with your next branch name by typing the following:
    git pull upstream dev:coolerest-feature-2
    
  2. Push your new branch by typing git push origin followed by your next branch name, as follows:
    git push origin coolerest-feature-2
    
  3. Finally, switch to your new branch by calling git checkout, followed by your next branch name. Type the following:
    git checkout coolerest-feature-2
    

Once your next branch is created, continue contributing as you did before (contribute, push your changes, submit a pull request).

Deleting your branch

Once your pull request has been approved and merged to the master, you can delete your branch. Do not delete your branch before it has been approved -- just in case you need to make a change to your pull request before it has been approved.

Trust me on this one.

Conclusion

I know, this was a long post. However, I hope that it will be useful for someone who wants to get started with making contributions to the PnP community.

For more information

There are many other resources available out there. Here are some that you should definitely check out:

Updates

  • August 23, 2019: Added For more information section, because there are many great references out there that people should know about. Also, added link to the latest list of popular PnP repositories

Photo credits

Branch image by GitHub.
Note image by Pexels from Pixabay

Introduction

Do you find a spelling mistake in the SharePoint Documentation but you don't know how to fix it?

Do you have a cool SPFx web part or extension sample that you think other SharePoint developers would appreciate?

Do you want to add to the PnP reusable controls, PnP property controls, or write your own command for the PnP Powershell or Office365-CLI?

But you never got around to it because you just didn't know where to start?

At the 2019 SharePoint Conference, the always entertaining Vesa Juvonen presented a session about how to start contributing to the PnP community. (I can't find the actual session title or a video of it, but if anyone has a link to it, please let me know).

The whole point of the session was very clear: the SharePoint Development community (or PnP) is open for everyone to contribute.

However, it can be very overwhelming to get started.

Luckily, the community is here to help!

Together, as a community, we can achieve much more than alone.

It is scary for everyone

I'm sure if you asked any of the existing PnP contributors, they'll tell you the same thing: the first contribution is always scary.

Why is it scary? Gene Zelazny who taught me everything about public speaking says that being nervous about speaking in public is a good thing: it means that you respect for the audience.

I think that being scared of making your first contribution to the PnP community is a good thing; it means that you respect the members of the community and you don't want to introduce something that will break a solution, introduce bugs, or lower the quality bar.

That's a good thing!

It doesn't matter how much (or how little) experience you have with SharePoint. Newbies feel that maybe they have nothing new to add, or that it isn't their place to contribute, while more senior developers probably experience impostor syndrome.

If you're worried that you'll make a mistake and make a fool of yourself, don't be. I've written many love letters about the PnP community (here and here), but the fact is: the PnP community is filled with awesome people who will help you. If you make a mistake, they may either fix your mistake for you, or reject your submission, make suggestions to fix it, and kindly encourage you to re-submit.

David Warner II is one of those amazing PnP contributors who goes even further: he'll help everyone with their first contribution!

He recently tweeted this:

If you want to contribute & add your name to the contributors' list, but not sure how I’m offering my time to help! DM me and I will personally walk you thru your 1st contribution in docs! #SharePoint #SPFx #SPC19 #OfficeDev

-- David Warner II via Twitter

If you don't know who David Warner II, he's a prolific Microsoft Office Development MVP who is deeply focused on developing & branding -- two of my favourite topics.

During the day, David is a Managing Consultant at Catapult Systems, but after dark (or whatever spare time he has), he turns into a masked PnP contributor who has contributed to pretty much every PnP repo there is.

Somewhere in between, he also finds the time to write summaries for every PnP community call, complete with screenshots and links.

David Warner II
Come on, how could you not trust this guy?!

I have reached out to David before to seek his advice, and he could not have been more friendly or patient.

So, if you're still afraid to contribute but you want to do so, reach out to him via Twitter.

Paying it forward

The PnP community is so awesome that I feel it is also my responsibility to pay it forward and help.

I'm not a GitHub expert and I still get confused with pull, pushes, branches, commits and pull requests, so I'm not the right person to help you with making your first contribution to GitHub.

However, I'm pretty comfortable with SPFx web parts and extensions; If you have an idea for an SPFx web part that you'd like to build and you don't know where to start, reach out to me and I'll help you start your project and build it -- as long as you promise to share it with the rest of the community as your first contribution.

If the solution doesn't work, you can even blame me 🙂

Conclusion

The Office Dev PnP community is an awesome community that encourages sharing. It welcomes and celebrates newcomers just like you and me.
It can be overwhelming to get started, but the always awesome David Warner II has offered to help anyone get started with a new contribution.

Together, as a community, we can achieve much more than alone.

Welcome to the PnP community. I look forward to your first contribution!

Photo Credit

Photo credit by Esi Grünhagen from Pixabay

Introduction

The SharePoint Design is a beautiful web site that provides design guidance on beautiful and fast sites, pages, and web parts with SharePoint in Office 365.

Unfortunately, the SharePoint Design site does not tell you how to create the beautiful web parts they show you.

This series is intended as a companion to the SharePoint Design site, providing you with code samples and detailed how-to information for every design topic. It should help you create web parts that look exactly like the ones on the SharePoint Design site.

In our last post we discussed the filmstrip layout.

In today's post, we'll continue our discussion about the web part layout patterns and discuss the carousel layout.

What is the carousel

The carousel is a standard web part layout that presents your web part content, showing items one at a time.

A demo of the carousel layout

The carousel is best suited when you want to showcase the content instead of the metadata about the content. For example, if you want to show a large preview of your documents or a large picture.

By default, the carousel will expand your images to fit the whole width of your web part and maintain the aspect ratio of your images, with a minimal amount of metadata below the image (for example, the file name and location). It also shows how many items there are in the carousel and which "slide" you're currently viewing.

As your cursor approaches the carousel, two buttons appear, providing the users with the options to go to the next and/or the previous slide. The buttons are positioned halfway down the carousel, one button on each side of the image.

On touch-enabled devices, such as phones and tablets, users can also swipe left of right to indicate whether they like the image to move to the next/previous slide.

After the user has viewed all images, the carousel restarts with the first images, potentially keeping your users entertained for hours and hours.

The problem with carousels

If you mention carousels to most designers, they'll often groan and sigh heavily. That's because the carousel quickly became a trend that everybody wanted on their web sites without really understanding the impact on their users.

If you ask me, there were worse trends out there (like when suddenly everyone decided that you needed to enter your email address and then confirm your email, presumably to fool someone who was trying to enter a fake email address to give your the right email address the second time...? )

I already discussed the issues with carousels when I covered the filmstrip, but here is a brief summary:

  • Their design and frequent movement make people think that they are an ad, causing banner blindness.
  • Moving elements can hurt accessibility, especially for users with motor skill issues
  • People with cognitive difficulties can have difficulty reading all the text before the carousel moves to the next slide
  • Because they see only one slide at a time, users often miss content

The SharePoint carousel: the kinder, gentler carousel

Fortunately, the SharePoint carousel is better designed and less evil.

For one, it does not automatically scroll. It puts users in control of the navigation.

It also supports keyboard navigation and gestures, making it slightly easier for those with accessibility requirements to use the carousel.

It is designed to keep the amount of information on each slide simple (instead of overloading users with too much information)

How to use a carousel properly

Here are some tips for a better carousel design (backed by science!!!!):

  • Keep your slides to fewer than 5: It can be frustrating to swipe through too many items in a carousel. Also your users may not be able to keep a mental list of more than 5 items and may not recognize when they're seeing the same items over and over again. Keeping your items to fewer than 5 the number helps users discovering content, and makes it easy for them to find content again later.
  • Show how many items there are and which slide is currently displayed: It helps users understand that there are more items and helps them feel "in control".
  • Try to show items that are related: Because users only get to see one item at a time, stick to items that are related so that users can easily predict what the other items (the ones they can't see) are.

And probably the most important: make each slide a giant button. It's Fitts's law!

Fitts's law

In 1954 -- long before there were carousels and web sites -- Paul Morris Fitts was researching the human motor system. I'll spare you the boring math, but what he found is this:

  • The greater the distance between your starting point and your target, the harder it is to hit a target
  • The smaller the target is, the harder it is to hit
  • The faster you move from your starting point to your target, the harder it is to hit.

Nowadays, Fitts's law is used in user experience and user interface design. It is the reason why the menu on your Mac computer is located at the top of the screen without a top border (essentially making the menu a button of infinite size), and why Windows 8 tried to use the edges of your screen for the start menu, the share menu, and the context menus.

For example, here is how the distance between two buttons affect how easy it is to hit the target:
Further distance is hard to hit

And here is how size affects how easy it is to hit a target:

The reason why the SharePoint Framework web part layouts (the grid, the filmstrip, the carousel and the compact layout) mostly use large rectangular targets is not just because it looks cool, it is also because it makes it easier for users to hit the targets, regardless of where they started from on the page.

That's why you need to make sure that your entire carousel slide is a giant button that will lead to whatever item you're currently displaying.

Unfortunately, I still think that the next and previous buttons (and the little dots on the filmstrip layout) are a little too small, but since the layout controls support keyboard, and touch gestures, I'll stop complaining now.

For more information about Fitts's law, check out this cool interactive visualization

How carousel control is implemented

As is the case with the filmstrip control, the out-of-the-box carousel layout uses Ken Wheeler's Slick slider -- not just to provide the previous/next functionality, but to control how each slide is resized to fit within the viewable area.

The control I created for this sample aims to mimic the exact functionality of the out-of-the-box SharePoint carousel layout. It also uses the Slick slider. (Hey, if it's good enough for the SharePoint team, it's good enough for me). It the one I wrote for the React Calendar Feed sample in June 2018.

However, it is worth noting that Piotr Siatka recently submitted a carousel control to the Reusable React controls for SharePoint Framework solutions and I can't wait for it to become available in an upcoming version of the PnP controls. He actually built it without any third-party libraries and it looks really cool. I encourage you to check out his awesome work.

Piotr's awesome carousel control
Piotr's carousel control, coming soon to a PnP library near you

When it will become available, Piotr's control will give you the ability to control most aspects of carousel appearance and behavior.

My carousel layout control is designed to make it easy to create a web part with that mimics the carousel layout. It doesn't give you control over how the arrows look, where they are located, or anything else that deviates from the carousel layout standard.

Feel free to use whichever control is most suitable for you.

To create a carousel layout web part

To create your own web part using the carousel layout, follow these steps:

  1. Copy the folder src/components/carouselLayout from my sample code to your own web part project.
  2. In your src\webparts\[YourWebPartName]\components\[YourWebPartName].tsx, add an import for the CarouselLayout and the CarouselSlide controls.
    import { CarouselLayout, ICarouselItem } from '../../../components/carouselLayout';
  3. In your web part's component, retrieve the items you wish to display and store them where you can retrieve them later. For example, it your web part's state. For this example, I'll store sample items in the constructor:

    constructor(props: ICarouselProps) {
    super(props);
    
    this.state = {
      items: [{
        imageSrc: "https://lorempixel.com/744/418/technics/1/",
        title: "Adventures in SPFx",
        location: "SharePoint",
      }, {
        imageSrc: "https://lorempixel.com/744/418/technics/2",
        title: "The Wild, Untold Story of SharePoint!",
        location: "SharePoint",
      }, {
        imageSrc: "https://lorempixel.com/744/418/technics/4",
        title: "Not Your Grandpa's SharePoint",
        location: "SharePoint",
      }, {
        thumbnail: "https://lorempixel.com/744/418/technics/5/",
        title: "Get with the Flow",
        location: "Flow",
      }]
    };
    }

    This is pretty much the same thing we've done in all web part layouts samples so far, except that this time the items we pass must follow the ISlideItem interface and provide a title, a imageSrc, and a location (that's all the carousel has room to display).

  4. In the onRender method, add a CarouselLayout object and pass it the items you wish to display. You'll also need to provide a pagingTemplate string, which is template to control how you want to render the "1 of 5" items label. In your pagingTemplate, use a 0 for the current item placeholder, and 1 for the number of items placeholder.

    public render(): React.ReactElement<ICarouselProps> {
    
    return (
      <div className={styles.carousel}
    
      >
        <CarouselLayout
          pagingTemplate={'{0} of {1}'}
          ariaLabel={'Use right and left arrow keys to navigate between images in the carousel. Use up and down arrow keys to access the edit and remove buttons for any image.'}
          items={this.state.items}
          onSlideClick={(currentSlide) => { alert(`You clicked on slide ${currentSlide+1}`); }}
        >
        </CarouselLayout>
      </div>
    );
    }

    You should also provide an ariaLabel for accessibility's sake, and you must pass an event handler to handle when a user clicks on a slide.

If all goes well, you'll render the following carousel:

The end result

Conclusion

I'm not crazy about the fact that the carousel layout control I used in this sample isn't as flexible as the previous controls, but that's because all we can display is an image, a title and a location.

On the other hand, it makes it pretty easy to implement your own carousel layout web part.

The code for this sample can be found in my WebPartLayouts solution on my repo.

We're almost done with the layouts! Next stop: the compact layout.

Until then, let me know if you have any questions, comments, or concerns!

Note: There are a lot of screenshots in this post. Here's why: there is nothing that I dislike more than having to download/install something to find out exactly what's inside. Yes, there are a lot of screenshots, but you'll know exactly what to expect if you decide to download my PowerPoint template.

Introduction

As the self-proclaimed World's Laziest Developer, I always look for shortcuts and ways to avoid doing work.

When I start a SharePoint project where I have to design an Information Architecture, I like to use low fidelity wireframes instead of spending countless hours creating SharePoint sites.

I'm not a designer or a user experience specialist, but I'll take the time to do paper napkin drawings, whiteboard drawings, and any other available tools to help work through how my sites are going to be structured and laid out.

I'll even use tools like Balsamiq, UXPin, Adobe XD, and Visio, but I like to spend as little time as possible building my low-fidelity wireframes. Also, I find that few people have experience with those tools (even though they are awesome).

I do it because it is faster convey to my stakeholders what I'm planning on doing and getting feedback with a minimum effort. And I'm all about minimum efforts.

Since PowerPoint now has sketchy shapes, I decided to create a PowerPoint template that contains a sketchy design for SharePoint pages and a few of the commonly used web parts.

My template is not intended to replace the SharePoint Toolkit, but it requires Adobe XD and most client workstations I use do not have Adobe XD. If you can use Adobe XD and the SharePoint Toolkit, do so. If you can't feel free to use my PowerPoint template.

If you want to use it, you can download it and use it as you wish.

Note: This template will only work in the desktop version of PowerPoint. And it may require the Office Insider version of PowerPoint.

A few rules, though:

  • If you add more page designs and web parts that you think other people would enjoy, share it with me and I'll add it to the template
  • Don't go selling this template
  • Share it with others (#SharingIsCaring)

The components

Here are the elements you can use for now:

The Communication Site master

Communication site master

There is also a read-only version of the communication site if you want to show what the experience looks like for visitors:

Communication site read-only

Guided layouts

If you choose a slide master, you can pick a layout to help you place your web parts on the page.

1 column guide
1 column guide

2 column guide
2 column guide

3 column guide
3 column guide

Simply place your web parts so that they fit within the gray area. Once you're done moving the web parts, select the No guide version, which hides all the guides.

Hero web part

Hero web part

(I had a lot of fun drawing the placeholder pictures. Very therapeutic.)

News web part

News web part

Events web part

Events

Documents web part

Documents web part

People web part

People web part

Generic grid layout web part

If you want to write your web part with the grid layout and would like to mock them up in your wireframes, you can use the Generic Grid Layout Web Part. It comes in 1/3 column, 2/3 columns, and full-width variations.

Generic Grid Layout Web Part -- Full width
Generic Grid Layout Web Part -- Full width

Generic Grid Layout Web Part -- 2/3 columns
Generic Grid Layout Web Part -- 2/3 columns

Generic Grid Layout Web Part -- 1/3 column
Generic Grid Layout Web Part -- 1/3 column

Generic filmstrip layout web part

If you want to write your web part with the filmstrip layout and would like to mock them up in your wireframes, you can use the Generic Filmstrip Layout Web Part. It comes in 1/3 column, 2/3 columns, and full-width variations.

Generic Filmstrip Layout Web Part -- Full width
Generic Filmstrip Layout Web Part -- Full width

Generic Filmstrip Layout Web Part -- 2/3 columns
Generic Filmstrip Layout Web Part -- 2/3 columns

Generic Filmstrip Layout Web Part -- 1/3 column
Generic Filmstrip Layout Web Part -- 1/3 column

Generic carousel layout web part

If you want to design a custom web part that uses the carousel layout, use this template. Like the other generic web parts, it comes in 3 flavours: Full-Width, 2/3 columns and 1/3 column.

Generic Carousel Web Part -- Full Width
Generic Carousel Web Part -- Full Width

Generic Carousel Web Part -- 2/3 columns
Generic Carousel Web Part -- 2/3 columns

Generic Carousel Web Part -- 1/3 column
Generic Carousel Web Part -- 1/3 column

Generic list layout web part

I couldn't do the other generic layouts and omit the list layout! This one is also available in Full Width, 2/3 columns and 1/3 column.

Generic List Web Part -- Full Width
Generic List Web Part -- Full Width

Generic List Web Part -- 2/3 columns
Generic List Web Part -- 2/3 columns

Generic List Web Part -- 1/3 column
Generic List Web Part -- 1/3 column

Color palettes

Although many believe that low-fidelity wireframes should use grayscale and contrast instead of colors, sometimes I like to include colors. It really depends on my mood (and the audience).

Whether you're in the "no-colors" camp or in the "with colors" camp, every slide includes the SharePoint color palettes on the right of the page -- outside of the viewable area.

SharePoint color palettes

When you're editing your pages, you can pick the Eyedropper tool to pick the colors you need from the palette.
Eyedropper tool

Don't worry: because they are outside of the viewable area, the color palettes won't show up while you're presenting or when you print.

Color palettes won't print

If you need to find out more about what each color is, there is a section at the end of the PowerPoint template that explains the SharePoint colors.

SharePoint Colors Slide

Other stuff

There is also some token lorem ipsum placeholder text in case you need it, but I try to avoid it.

Also, there are sticky notes to help annotate your wireframes.

Sticky notes and wireframes

What else would you like to see? Let me know in the comments.

Using the template

Here are simple tips to use the template:

  • The template uses an 11"x17" page template. It may seem big, but it prevents you from having to deal with 4pt fonts. Don't worry, it presents well on a screen, and prints great posters for your team project room walls.
  • It uses the new sketchy line styles. It may not work in the web version of PowerPoint or if you don't have the Office Insider edition of PowerPoint. Let me know if you need a non-sketchy version and I'll try to oblige. If you get an error opening it in the web browser, try opening it on your desktop
    Error message with PowerPoint
    If you get this message, it is most likely because I used sketchy lines
  • To emphasize on the "work in progress" look, I used the Segoe Print font because I found that it was available on most workstations that I use. If you don't have this font, feel free to use any font you like. Ink Draft and Segoe Marker work well too. Just resist the urge to use Comic sans !
  • For now, every slide uses the communication site layout (and the read-only variation). I'll add the team site layout if I get requests to do so
  • The title of the slide is the site title
    Site title is the slide title
  • Every site element is grouped to make it easier to move them around. Feel free to un-group them and edit them as you need
    Grouping within the slide
  • I used some theme colors, but feel free to make the designs monochromatic if you want
  • If you need to edit the navigation, feel free to copy the navigation elements (called Site Navigation ) from the master slide and paste the customized navigation on your slide. It already has a white background to hide the navigation from the master slide
    Site Navigation

To edit site navigation

To edit the site navigation, follow these steps:

  1. From your PowerPoint slide, go to View then select Slide Master in the Master Views group
    Slide Master from the View ribbon
  2. PowerPoint should automatically take you to the master slide called Communication site. Select the site navigation by clicking on the site icon and click Copy.
    Copy site navigation
  3. From the Slide Master menu, select Close Master View to go back to your slide
    Close Master View
  4. Paste the navigation on top of the existing navigation. The white background behind the site navigation you just copied should hide the existing one from the master slide.
  5. Edit the navigation and site icon as you wish

To create your own wireframe

Although I have a sample wireframe in my template, you can create your own using these simple steps:

  1. In PowerPoint, insert a new slide in your presentation by going to New Slide and select the layout you want
    New Slide Menu
    You can pick from the Communication Site -- Edit Mode to show what an author would see, or Communication Site -- Read-only Mode to show what a visitor would see.
  2. Try to start with one of the guides. You can choose from 1 column guide, 2 column guide or 3 column guide.
  3. From the Web Part Templates section, find the web parts you want from the other slides in the PowerPoint template, copy them and paste them onto your new slide.
  4. Make sure to place the web parts within the gray areas on the guides.
  5. Edit the web part elements (like web part title and content). It may help to ungroup them first.
  6. Once you're done placing your web parts, go to Layout and pick the No guide version of whatever master you chose. It will remove the background guides, but will not affect where you placed your web parts.
    Layouts

Tips when preparing low fidelity wireframes

As Page Laubheimer from the Nielsen Norman Group puts it:

Sharing low fidelity user-interface prototypes with stakeholders is a great way to transfer knowledge and get buy-in early.

Tell your audience that this is work in progress. It may be obvious to you, but I have had clients get upset that their site was going to look "all wobbly" (and others who asked if the site was going to only be "in French" because I used lorem ipsum). Explain that you used this style of wireframe to help focus on the content and structure, not the look.

Explain to your audience that this is a great opportunity to share knowledge: for them to transfer their knowledge to you by getting their feedback early.

Don't worry about the look and feel, worry about the content/structure and function. Resist the urge to put lorem ipsum and try to put some text that is as real as you can make it for now.

Another way to use these wireframes is to print them and get your audience to mark them with their notes. You'd be amazed by what information you can get from watching people circle, underline, and annotate paper wireframes.

Conclusion

My PowerPoint template is available for you to use.

I'll continue adding web parts and designs. If you need anything else added to it, let me know in the comments.

Maybe I'll create a higher-fidelity version of these if people like them. I'd also love to create a version that you can print out and cut so that you can layout page designs on paper.

Let me know what you think?

Updates

  • August 5, 2019: Thank you Vesa Juvonen for pointing out the broken link. Moved files to GitHub repo to make it easier for those of you who wish to contribute.
  • August 3, 2019: Added more web parts (List, carousel, filmstrip) and explanation why I have so many damned screenshots. Also added introduction how to use the guides and section explaining color palettes.
  • August 2, 2019: Added more web parts (generic grid 1/3, 2/3, and full width) and guide layouts

Introduction

The SharePoint Design is a beautiful web site that provides design guidance on beautiful and fast sites, pages, and web parts with SharePoint in Office 365.

Unfortunately, the SharePoint Design site does not tell you how to create the beautiful web parts they show you.

This series is intended as a companion to the SharePoint Design site, providing you with code samples and detailed how-to information for every design topic. It should help you create web parts that look exactly like the ones on the SharePoint Design site.

In our last post we discussed layout patterns and showed how to create a Grid Layout web part.

In today's post, we'll continue our discussion about the web part layout patterns.

The Filmstrip layout

Like the grid layout, the filmstrip displays cards to display content. You can use any other rectangular content though. For example, you can use images of kittens, or event cards (as I did with my Calendar Feed web part).

Calendar feed web part
Calendar feed web part switches between filmstrip and compact mode

However, unlike the grid layout, the filmstrip displays items on a single row. As the screen resizes, the filmstrip layout resizes the items to keep the same number of items displayed at once. If there are more items than can be shown at once, the filmstrip will change to a carousel and break items into "pages".

Filmstrip layout resizing items as the screen resizes
The filmstrip layout resizes items as the page resizes

If you're a web designer, the word carousel may make you shudder. In fact, if you search online, you'll find over fifty-four million search results explaining why carousels are bad.

Banner blindness

You see, most people have issues with the giant photo carousels on web sites. For most users, the giant photos trigger our banner blindness because we automatically assume it is an advertisement, and we ignore it.

Since the carousel in the filmstrip layout does not show a giant image, but a series of smaller images (usually a preview of documents), this isn't something we have to worry about (although we'll have to have another conversation when we discuss the Carousel layout).

Mobile issues

Another issue is that giant carousels are notoriously bad on mobile devices.

One of the issues with carousels on mobile devices is that because of limited screen real-estate, it shows only one item at a time. This forces users to use sequential access: to view items in the order that is dictated by the control, not by the user's choice.

Research shows that -- with sequential access -- users will stop looking at items after the third or fourth item in a carousel.

Another issue with carousels that people have is with discoverability. If someone is in a hurry and they quickly glance at a typical carousel, they may not know that there are more items to see. Most carousels will use a teeny tiny little dot (which are often a bad design choice) to indicate that there are more items.

Fortunately, the mobile version of the filmstrip control uses bigger dots that are as big as the main text with a lot of colour contrast to help.

Sample mobile carousel
Mobile filmstrip. This isn't so bad?

If you plan on using the filmstrip layout, follow these guidelines:

  • Limit your items to less than 5: Otherwise, people may not see the other items.
  • Prioritize your items: Keep in mind that, depending on the screen size, some users may only see one item at a time. Make sure that you place the most important items first.
  • Make sure items are related: Because people may not see all items in your filmstrip, make sure that the items you show on the filmstrip are related so that users can predict/easily guess what items are hidden. In other words, don't put apples and oranges on the filmstrip.

Accessibility

For me, the biggest concern about most carousels has to do with accessibility.

You see, most evil carousels break a lot of accessibility guidelines:

  • The dots at the bottom are often too small to see and/or click
  • The dots often have a poor colour contrast
  • The slides often change automatically, without giving users a chance to read the content or slow it down
  • The left/right arrows often rely on the use of a mouse, with no keyboard alternatives
  • Left/right arrows often have poor colour contrasts
  • Carousels often do not provide alternative text

Luckily for us, the SharePoint filmstrip (and the carousel layout) doesn't have many of the typical carousel accessibility issues:

  • The slides do not change automatically
  • Users can navigate through the slides using the and keys on their keyboard and use TAB to cycle through the slides.
  • The left/right navigation arrows use a high contrast so that users with colour blindness and/or various forms of visual impairment can see them
    Left/right arrows have high contrast
  • The left/right navigation arrows aren't too small (but they could be bigger)
  • The filmstrip provides alternative text for the entire filmstrip (e.g.: "Highlighted content web part, showing Most recent Documents., Use right and left arrow keys to navigate between cards in the film strip."), as well as alternative text for every item in the filmstrip.
  • The bullets at the bottom of the filmstrip are still pretty small, but they have high contrast (21:1 on the theme I tested) exceeding the minimum required contrast (4.5:1)

It still isn't perfect, but it isn't as bad as most carousel controls out there.

How the filmstrip layout is implemented

Behind the scenes, the out-of-the-box SharePoint filmstrip and carousel layouts use Ken Wheeler's awesome Slick carousel.

Slick carousel
The slick carousel as a filmstrip

They simply made it more "Fabric UI" and fixed some accessibility issues.

Unlike the grid layout, which calculates the optimal card size as you resize the page, the filmstrip layout uses breakpoints to use pre-determined settings depending on the dimensions of the filmstrip control.

How to use the filmstrip layout

Unfortunately -- as was the case with the grid layout -- there aren't any ready-to-use controls to create a filmstrip layout web part.

I had to create my own filmstrip layout when I created the Calendar Feed web part for the SharePoint/sp-dev-fx-webparts repo.

React Calendar Feed Web Part with filmstrip
Custom filmstrip control in the calendar web part

And since I'm the world's laziest developer and I hate to write things more than once, I had created the filmstrip component as a re-usable component. (Ok, I had called it CarouselContainer back then, but it was really a filmstrip layout control).

For the rest of this post, we'll use my filmstrip layout control. If you don't like the one I created, feel free to use your own.

As we did for the grid layout, I'll show you how to insert a copy of my control into your own web parts -- that way, you can customize it to suit your own needs.

(Don't worry, I'll show you how to move the controls into a component library in a later post, so you won't have to always copy and paste my code into your solutions)

To create your own filmstrip layout web part, follow these steps:

  1. Create a web part solution with the Yeoman generator. For this sample, I'll call my web part filmstrip
  2. Copy the src\components\filmstripLayout folder from my project to yours
  3. In your project's terminal, install the slick-carousel and react-slick dependencies by using the following command:
    npm i slick-carousel react-slick
  4. In your web part's component, located at src\webparts\[YourWebPartName]\components\[YourWebPartName].tsx, add an import for the filmstripLayout control:
    import { FilmstripLayout } from '../../../components/filmstripLayout';
  5. In your code, load the items you wish to display. You can load an array of any items you wish, but for my sample, I used items with thumbnail, title, name, profileImageSrc, location, and activity props because I want to use the DocumentCard control to render my list items. For my sample, I initialized the state with a static list of items in my component's constructor:

    constructor(props: IFilmstripProps) {
    super(props);
    
    this.state = {
      items: [{
        thumbnail: "https://lorempixel.com/400/200/technics/1/",
        title: "Adventures in SPFx",
        name: "Perry Losselyong",
        profileImageSrc: "https://robohash.org/blanditiisadlabore.png?size=50x50&set=set1",
        location: "SharePoint",
        activity: "3/13/2019"
      }, {
        thumbnail: "https://lorempixel.com/400/200/technics/2",
        title: "The Wild, Untold Story of SharePoint!",
        name: "Ebonee Gallyhaock",
        profileImageSrc: "https://robohash.org/delectusetcorporis.bmp?size=50x50&set=set1",
        location: "SharePoint",
        activity: "6/29/2019"
      }, {
        thumbnail: "https://lorempixel.com/400/200/technics/3",
        title: "Low Code Solutions: PowerApps",
        name: "Seward Keith",
        profileImageSrc: "https://robohash.org/asperioresautquasi.jpg?size=50x50&set=set1",
        location: "PowerApps",
        activity: "12/31/2018"
      }, {
        thumbnail: "https://lorempixel.com/400/200/technics/4",
        title: "Not Your Grandpa's SharePoint",
        name: "Sharona Selkirk",
        profileImageSrc: "https://robohash.org/velnammolestiae.png?size=50x50&set=set1",
        location: "SharePoint",
        activity: "11/20/2018"
      }, {
        thumbnail: "https://lorempixel.com/400/200/technics/5/",
        title: "Get with the Flow",
        name: "Boyce Batstone",
        profileImageSrc: "https://robohash.org/nulladistinctiomollitia.jpg?size=50x50&set=set1",
        location: "Flow",
        activity: "5/26/2019"
      }]
    };
    }
  6. In your render method, add a filmstripLayout component. You should set the ariaLabel prop, but it is optional.

    public render(): React.ReactElement<IFilmstripProps> {
    return (
      <div className={styles.filmstrip}>
        <FilmstripLayout 
                ariaLabel={"Sample filmstrip layout web part, showing sample items., Use right and left arrow keys to navigate between cards in the film strip."}
    
        >
    
        </FilmstripLayout>
      </div>
    );
    }
  7. To render elements within your filmstrip layout, you simply add rectangular elements as children of the FilmstripLayout component. In my sample, I'll use a DocumentCard to render each item. To do so, you'll need to add the following imports:
    // Used to render document cards
    import {
    DocumentCard,
    DocumentCardActivity,
    DocumentCardPreview,
    DocumentCardDetails,
    DocumentCardTitle,
    IDocumentCardPreviewProps,
    DocumentCardLocation,
    DocumentCardType
    } from 'office-ui-fabric-react/lib/DocumentCard';
    import { ImageFit } from 'office-ui-fabric-react/lib/Image';
  8. Then, loop through your items and render a DocumentCard in your render method:

    public render(): React.ReactElement<IFilmstripProps> {
    return (
      <div className={styles.filmstrip}>
        <FilmstripLayout
        ariaLabel={"Sample filmstrip layout web part, showing sample items., Use right and left arrow keys to navigate between cards in the film strip."}
        >
          {this.state.items.map((item: any, _index: number) => {
            const previewProps: IDocumentCardPreviewProps = {
              previewImages: [
                {
                  previewImageSrc: item.thumbnail,
                  imageFit: ImageFit.cover,
                  height: 130
                }
              ]
            };
    
            return <div
              className={styles.documentTile}
              data-is-focusable={true}
              role="listitem"
              aria-label={item.title}
            >
              <DocumentCard
                type={DocumentCardType.normal}
                onClick={(ev: React.SyntheticEvent<HTMLElement>) => alert("You clicked on an item")}
              >
                <DocumentCardPreview {...previewProps} />
                <DocumentCardLocation location={item.location} />
                <DocumentCardDetails>
                  <DocumentCardTitle
                    title={item.title}
                    shouldTruncate={true}
                  />
                  <DocumentCardActivity
                    activity={item.activity}
                    people={[{ name: item.name, profileImageSrc: item.profileImageSrc }]}
                  />
                </DocumentCardDetails>
              </DocumentCard>
            </div>;
          })}
        </FilmstripLayout>
      </div>
    );
    }

If I didn't forget anything, you should see a web part that looks like this:

Custom filmstrip in action

Conclusion

The filmstrip layout is a great way to show more items than available screen, but keep in mind that there are accessibility/usability issues with it.

If you want to build a filmstrip layout web part, you can use my code sample to get started.

In our next post, we'll continue exploring the web part layout design patterns.

Photo Credits

Introduction

Sometimes, when working on a SPFx project, I just want to define a CSS class in my .scss file but I don't want the SASS pre-processor to append random strings to my class names.

For example, let's say I wanted to customize the DocumentCard elements within my SPFx web part to add a border. If I write my SCSS like this:

.myWebPart .ms-DocumentCard {
    border: 2px solid red;
}

It won't work.

That's because when building my solution, the SASS pre-processor will append random strings to my class names. So, my .myWebPart and .ms-DocumentCard CSS classes might become .myWebPart-223 and .ms-DocumentCard-242.

The problem is, I don't want my CSS classes to change from .ms-DocumentCard to .ms-DocumentCard-242 because the .ms-DocumentCard CSS class comes from another component (in this case, Microsoft's Fabric UI DocumentCard).

Luckily, there's a way around it. Every time I need to remember how to do it though, I find myself having to re-open old projects.

Using the :global pseudo selector

To prevent the SASS pre-processor from appending random strings to my CSS class name, just use the :global pseudo selector.

For example:

:global(.ms-DocumentCard) {
    border: 2px solid red;
}

You should be careful, though: global CSS changes apply, well, globally. This means that if you use global(.ms-DocumentCard) in your CSS, every single element with a CSS class of .ms-DocumentCard on the entire page will be affected -- not just the ones in your web part.

If you want to override styles within your web part, use a CSS selector that is a bit more restrictive; something like this:

.yourWebPart {
    :global(.ms-DocumentCard) {
        border: 2px solid red;
    }
}

If you need to define a whole bunch of CSS classes that you don't want to be renamed, you can define a global block, as follows:

:global {
  .ms-DocumentCard {
    border: 2px solid red;

    .ms-DocumentCard--compact {
      .ms-DocumentCardPreview {
        -ms-flex-negative: 0;
        flex-shrink: 0;
        width: 144px;
      }
    }

    .ms-DocumentCardPreview-icon img {
      width: 32px;
      height: 32px;
    }
  }

  .ms-DocumentCard:not(.ms-DocumentCard--compact) {
    ...
  }
}

More information

When you create a SPFx solution, the Yeoman generator creates a [YourWebPartName].module.scss file automatically for you.

You may have asked yourself why the file isn't just called [YourWebPartName].scss instead of [YourWebPartName].module.scss. Well, as it turns out, the .module part of the file name is what instructs the pre-processor to make every CSS class names unique.

If you changed your .scss file to [YourWebPartName].scss, the pre-processor would stop renaming the CSS class names, but you'd risk getting more issues; instead of being scoped to your web part, the CSS classes would be globally applied to the page.

Instead, it is better to continue using [YourWebPartName].module.scss and use the :global pseudo selector.

By the way, if you want to define a local CSS class name within a global block, simply use the :local pseudo selector. It works exactly the opposite of the :global pseudo selector.

For example:

:global {
  .ms-DocumentCard {
    border: 2px solid red;

        :local(.myDocument) {
            border: 2px solid green;
        }
    }
}

Conclusion

SCSS rocks, but sometimes it can be annoying how the CSS class names are automatically renamed to make them unique.

To prevent renaming a class name, use :global() or :global { } in your SCSS.

Whatever you do, resist the urge to make all your CSS classes global.

I hope it helps?

Photo Credit

Image by Christoph Meinersmann from Pixabay

Introduction

The SharePoint Design is a beautiful web site that provides design guidance on beautiful and fast sites, pages, and web parts with SharePoint in Office 365.

Unfortunately, the SharePoint Design site does not tell you how to create the beautiful web parts they show you.

This series is intended as a companion to the SharePoint Design site, providing you with code samples and detailed how-to information for every design topic. It should help you create web parts that look exactly like the ones on the SharePoint Design site.

In today's post, we'll begin discussing the web part layout patterns.

SharePoint web part layouts

If you look at the various web parts that are available out-of-the-box on SharePoint, you'll find that there are many different layouts for web parts.

In fact, there are 5 commonly-used layouts:

  • Grid
  • Filmstrip
  • List
  • Carousel
  • Compact

Five common web part layouts
5 common web part layouts

Each layout is best suited for different uses.

How to decide which layout to use

When planning your web part design, you can pick the best layout by considering the following criteria:

  • How "visual" is the content: does the content consist of a picture or a document preview (more visual), or is it mostly text (less visual)?
  • How much metadata to display: Do you need to simply provide a title, author, date and maybe a category (like most out-of-the-box web parts do) or do you need to provide more metadata?
  • Number of items to display: Do you want to display only a few items, or do you need to display many items at once.

To help the decision process, I use the following matrix which places the standard layouts against a grid of how visual and how much metadata you wish to display. The size of each circle helps to compare how many items you want to display -- the bigger the circle, the more items you can show.

Web part layout decision matrix

You should also consider how much space you'll have on a page and the size of the page.

"But I don't have control over the content and size of the page!"

...I can already hear some of you say. You're absolutely right! That's why you should consider giving your authors a few layout options so that they can pick the layout that is best suited for their page designs.

Luckily, you don't have to write multiple versions of the same code to offer multiple layouts; Most of the layouts re-use the same/similar components.

For example:

  • The Grid layout and the Filmstrip layout are essentially the same, except that the Grid wraps content over multiple rows, where the Filmstrip keeps items on a single row.
  • The Filmstrip turns into a Carousel when there are more items to show that what can be displayed on a single screen.
  • The Grid turns into a Compact layout when the screen is too small

The easiest way to demonstrate this is probably with code!

Grid layout

The grid layout presents content in rectangular areas in rows and columns from left to right and top to bottom. The grid layout will attempt to fit as many columns as possible and resize the grid items, or individual content elements within the grid, to fit the entire width of the grid.

When the grid resizes, it re-flows the grid items by keeping the same number of columns but making each column narrower. When the columns become too narrow, the grid will remove one column and resize the remaining columns to fit within the grid.

When there is only room for 1 column within the grid (e.g.: when viewed on a mobile device or if the web part is located in a one-third column), the grid layout will change to a compact layout.

Grid layout re-flowing contnet
The grid layout re-flows content in rows and columns

Creating a grid layout web part

Unfortunately, there isn't a readily-usable grid layout component that you can use in your web parts. The Office UI Fabric GitHub repo has a component called TileList which gives you a grid layout, but it is still in the experiments package which contains "not production-ready components and should never be used in [production code]". I'm not sure if Microsoft ever plans on moving the TileList component to the main Office UI Fabric component, but if they do, I'll update this article and code sample accordingly.

Instead, I created a GridList component that uses the Office UI Fabric List component and made a few modifications to make it look like a grid layout. I used the code from the List of 5000 grid items sample on the UI Fabric web site as the base for my code.

You can find the code in the src/components/GridList folder from my repository.

The control reacts the same way as the out-of-the-box grid layout. It even handles compact layouts:

Custom GridList control in action

Using the GridList component in your web part

Here's how to call the GridList component in your web part:

  1. If you haven't done so already, copy the content of the src/components/GridList folder from the sample code to your own project's src/components/GridList.
  2. In your src/webparts/[YourWebPartName]/components/[[YourWebPartName].tsx, retrieve the items you wish to display. To simplify this sample, I used a hard-coded list of items. I am too lazy (and unimaginative) to come up with my own sample data, so I used Mockaroo.com to generate a JSON structure that contains the items I wanted. I saved the items to my component's state in the constructor, as follows:
export default class GridLayout extends React.Component<IGridLayoutProps, IGridLayoutState> {
  constructor(props: IGridLayoutProps) {
    super(props);

    this.state = {
      items: [{
  thumbnail: "https://pixabay.com/get/57e9dd474952a414f1dc8460825668204022dfe05555754d742e7bd6/hot-air-balloons-1984308_640.jpg",
  title: "Adventures in SPFx",
  name: "Perry Losselyong",
  profileImageSrc: "https://robohash.org/blanditiisadlabore.png?size=50x50&set=set1",
  location: "SharePoint",
  activity: "3/13/2019"
}, {
  thumbnail: "https://pixabay.com/get/55e8d5474a52ad14f1dc8460825668204022dfe05555754d742d79d0/autumn-3804001_640.jpg",
  title: "The Wild, Untold Story of SharePoint!",
  name: "Ebonee Gallyhaock",
  profileImageSrc: "https://robohash.org/delectusetcorporis.bmp?size=50x50&set=set1",
  location: "SharePoint",
  activity: "6/29/2019"
}, {
  thumbnail: "https://pixabay.com/get/57e8dd454c50ac14f1dc8460825668204022dfe05555754d742c72d7/log-cabin-1886620_640.jpg",
  title: "Low Code Solutions: PowerApps",
  name: "Seward Keith",
  profileImageSrc: "https://robohash.org/asperioresautquasi.jpg?size=50x50&set=set1",
  location: "PowerApps",
  activity: "12/31/2018"
}, {
  thumbnail: "https://pixabay.com/get/55e3d445495aa514f1dc8460825668204022dfe05555754d742b7dd5/portrait-3316389_640.jpg",
  title: "Not Your Grandpa's SharePoint",
  name: "Sharona Selkirk",
  profileImageSrc: "https://robohash.org/velnammolestiae.png?size=50x50&set=set1",
  location: "SharePoint",
  activity: "11/20/2018"
}, {
  thumbnail: "https://pixabay.com/get/57e6dd474352ae14f1dc8460825668204022dfe05555754d742a7ed1/faucet-1684902_640.jpg",
  title: "Get with the Flow",
  name: "Boyce Batstone",
  profileImageSrc: "https://robohash.org/nulladistinctiomollitia.jpg?size=50x50&set=set1",
  location: "Flow",
  activity: "5/26/2019"
}]
    };
  }
  1. Define your component's props and state interfaces so that you have what you need to retrieve and store the items you wish to display (my props in this sample does not require anything so I left it empty):
export interface IGridLayoutProps {
// Add your own props here
}

export interface IGridLayoutState {
  items: IGridItem[];
}
  1. Define an interface to hold the properties for the items you wish to display. For my sample, I used the following interface:
export interface IGridItem {
  thumbnail: string;
  title: string;
  name: string;
  profileImageSrc: string;
  location: string;
  activity: string;
}
  1. In your src/webparts/[YourWebPartName]/components/[[YourWebPartName].tsx, onRender method, insert a GridList element, as follows:
public render(): React.ReactElement<IGridLayoutProps> {
    return (
      <div className={styles.gridLayout}>
        <GridList
          items={this.state.items}
          onRenderGridItem={(item: any, finalSize: ISize, isCompact: boolean) => this.onRenderGridItem(item, finalSize, isCompact)}
        />
      </div>
    );
  }
  1. Make sure to add an import for the GridList at the top of your file:
import { GridList } from '../../../components/gridList';

Note that your GridList element uses a method called onRenderGridItem which isn't defined yet. We'll do this next.

Using the DocumentCard component to render grid items

Typically, grids use cards to showcase content, but you can also use any rectangular content you wish to use.

In our sample code, we'll use Fabric UI DocumentCard components to render our content.

Our DocumentCard elements will contain two sub-elements:

  • DocumentCardPreview: which will contain a preview image of the item
  • DocumentCardDetails: which will contain further details

DocumentCard elements

The DocumentCardDetails itself contains more elements:

  • DocumentCardTitle: The title of the element
  • DocumentCardLocation: The "location" of the element. Often used to indicate an item's category or sub-title.
  • DocumentCardActivity: Used to indicate the item's latest activities, such as date last modified and last modified by

DocumentCardDetails elements

The DocumentCardActivity defines the following props:

  • activity: Describes the activity that has taken place, such as "Created Feb 23, 2020".
  • people: One or more people who are involved in this activity. Each person consists of the following two props:
    • name: The person's name you wish to display
    • profileImageSrc: The URL for the person's profile picture.

You can define more properties, but that's what I used for this sample. If you want to see the full list of properties, look at the IDocumentCardActivityPerson interface, and the IDocumentCardActivityProps interface on the Fabric UI documentation.

When the GridList wants to render a grid item, it will call your onRenderGridItem handler with three parameters:

  • item: The item it wants to render
  • finalSize: Size of the item to render
  • isCompact: Returns true if the grid is rendering in a compact mode.

If you return a DocumentCard element within every grid item, you'll do something like this:

private onRenderGridItem = (item: any, finalSize: ISize, isCompact: boolean): JSX.Element => {
    const previewProps: IDocumentCardPreviewProps = {
      previewImages: [
        {
          previewImageSrc: item.thumbnail,
          imageFit: ImageFit.cover,
          height: 130
        }
      ]
    };

    return <div
      className={styles.documentTile}
      data-is-focusable={true}
      role="listitem"
      aria-label={item.title}
    >
      <DocumentCard
        onClick={(ev: React.SyntheticEvent<HTMLElement>) => alert(ev)} >
        <DocumentCardPreview {...previewProps} />
        <DocumentCardLocation location={item.location} />
        <DocumentCardDetails>
          <DocumentCardTitle
            title={item.title}
            shouldTruncate={true}
          />
          <DocumentCardActivity
            activity={item.activity}
            people={[{ name: item.name, profileImageSrc: item.profileImageSrc }]}
          />
        </DocumentCardDetails>
      </DocumentCard>
    </div>;
  }

Make sure to add the following imports otherwise, your code will be sad:

// Used to render document cards
import {
  DocumentCard,
  DocumentCardActivity,
  DocumentCardPreview,
  DocumentCardDetails,
  DocumentCardTitle,
  IDocumentCardPreviewProps,
  DocumentCardLocation,
  DocumentCardType
} from 'office-ui-fabric-react/lib/DocumentCard';
import { ImageFit } from 'office-ui-fabric-react/lib/Image';

Note that in my code, I simply display an alert when someone clicks on a grid item. In your real code, you'll want to replace onClick={(ev: React.SyntheticEvent<HTMLElement>) => alert(ev)} with your own onClick handler.

Notice that, in our previous code, we don't really use the isCompact parameter. It is passed to tell us whether we should render the grid using a compact layout or not.

Luckily, the DocumentCard component has a built-in compact layout that you can use by simply passing type={DocumentCardType.compact} if the DocumentCard should be compact. The default type for the DocumentCard is DocumentCardType.normal.

To handle the compact rendering, I'll simply change the DocumentCard's type depending on whether we're rendering in compact mode or not:

   <DocumentCard
        type={isCompact ? DocumentCardType.compact : DocumentCardType.normal}
        onClick={(ev: React.SyntheticEvent<HTMLElement>) => alert(ev)}

      >

Finally, the out-of-the-box grid layouts tend to remove unnecessary information when rendering in compact mode (otherwise, there would just be too much stuff). The DocumentCardLocation is often omitted in compact mode. To mimic this behaviour, I only render the DocumentCardLocation if the layout isn't compact, using the following code:

{!isCompact && <DocumentCardLocation location={item.location} />}

Which makes the final code for your onRenderGridItem as follows:

private onRenderGridItem = (item: any, finalSize: ISize, isCompact: boolean): JSX.Element => {
    const previewProps: IDocumentCardPreviewProps = {
      previewImages: [
        {
          previewImageSrc: item.thumbnail,
          imageFit: ImageFit.cover,
          height: 130
        }
      ]
    };

    return <div
      className={styles.documentTile}
      data-is-focusable={true}
      role="listitem"
      aria-label={item.title}
    >
      <DocumentCard
        type={isCompact ? DocumentCardType.compact : DocumentCardType.normal}
        onClick={(ev: React.SyntheticEvent<HTMLElement>) => alert(ev)}

      >
        <DocumentCardPreview {...previewProps} />
        {!isCompact && <DocumentCardLocation location={item.location} />}
        <DocumentCardDetails>
          <DocumentCardTitle
            title={item.title}
            shouldTruncate={true}
          />
          <DocumentCardActivity
            activity={item.activity}
            people={[{ name: item.name, profileImageSrc: item.profileImageSrc }]}
          />
        </DocumentCardDetails>
      </DocumentCard>
    </div>;
  }

If all goes well, you get something like this:
Custom GridList control

Putting the finishing touches

If you replace the sample data from above with real data, your web part should be barely distinguishable from the out-of-the-box web parts.

For example: in the screen shot below, the top web part is the out-of-the-box Highlighted content web part, while the bottom one is my own, using (almost) the exact same code as show in this post.

Bottom part is custom

The only differences are:

  • My web part needs a title (that's easy to fix, just follow the instructions in my previous post)
  • In my onRenderGridItem, I added an iconSrc parameter to my previewProps to point to the document's icon, as follows:
const previewProps: IDocumentCardPreviewProps = {
      previewImages: [
        {
          previewImageSrc: item.thumbnail,
          imageFit: ImageFit.cover,
          height: 130,
          iconSrc: item.iconSrc
        }
      ]
    };

And, of course, I used an API call to retrieve the latest documents instead of returning hard-coded data.

Conclusion

There are many standardized web part layouts you can chose from. We discussed how to use the Grid layout in today's post. The code for today's post can be found in my GitHub repo.

In our next posts, we'll continue to discuss the standard web part layouts.

I hope this helps?

Update

  • July 29, 2019: Thanks everyone for the kind comments. I have submitted my grid layout control to the @pnp/spfx-controls-react library. Hopefully, the will find it as useful as you did!

Photo credits

I was building a sample SPFx Timeline web part and needed a timeline to test it with.

To celebrate the release of the SPFx Framework v1.9, I created this timeline which highlights the history the SharePoint Framework since it first became available.

This is not official documentation. Any errors are mine and mine alone. If I have made any mistakes, please leave a comment below and I'll make sure to fix them.

The timeline below looks much better in full screen.

I hope you'll enjoy!

Open full screen

Updates

  • Aug 14: Added reference to SPFx 1.9.1!
  • July 26: Added entry to show that SPFx 1.9 package was pulled from NPM.

Introduction

I love the SharePoint Design web site. It is a beautiful web site that provides design guidance on how to create beautiful and fast sites, pages, and web parts with SharePoint in Office 365.

However, the site does not provide you with enough code samples to tell you how to create the beautiful web parts they show you.

This series is intended as a companion to the SharePoint Design site. It provides you with code samples and detailed how-to information for every design topic.

It should help you create web parts that look exactly like the ones on the SharePoint Design site.

Today's post is the third on property panes. In part I, we covered the various types of property panes. In part II we showed how to create non-reactive web parts and how to add a loading indicator.

Today, we'll look at out-of-the-box web parts and we'll discuss how to replicate some of their property panes in your own web parts.

If you want to download the code samples used in this article, download the solution from the GitHub repo.

Create a choice group with images

One of the most frequently asked questions I see with property panes is about the property pane choice groups with images.

The out-of-the-box Highlighted content, Hero, Image gallery are examples of how the choice group property field is used.

Highlighted content choice groupo
Highlighted content web part with a choice group

Hero choice group
Hero web part choice group

You should use the property pane choice group when you have a small number of choices -- I recommend less than 7, no more than 9 choices -- where the user may not be immediately able to understand the differences between the choices.

For example, the difference between the Brick, Grid and Carousel layout in the Image gallery web part may not be immediately obvious to everyone. Adding images to represent each choice makes it easier to understand:

Image gallery choice group
Image gallery web part choice group

As it turns out, it's not very complicated to do.

Let's reproduce the Image gallery layout setting for our demo. To do so, follow these steps:

  1. In your [YourWebPartName]WebPart.ts file, create a property to store the new setting you want. We'll use layout. To make things easier to read, I'll just use possible values of Brick, Grid, or Carousel, but feel free to use an integer value, an enum, or any other data type you want:
    export interface IChoiceGroupWebPartProps {
    layout: 'Brick'|'Grid'|'Carousel';
    }
  2. At the top of the file, there should already an import statement for the @microsoft/sp-property-pane. To it, add an import for PropertyPaneChoiceGroup, which is the type of field we'll need to use:
    import {
    IPropertyPaneConfiguration,
    PropertyPaneChoiceGroup
    } from '@microsoft/sp-property-pane';
  3. You'll need an image or an icon for every choice you want to show in your choice group. I like to use SVG files with dimensions of 32px by 32px. I also like to store my web part images in a folder called assets in the [YourWebPartName] folder. For this sample, we'll use brick.svg, grid.svg and carousel.svg. Feel free to store the images wherever you like.
    The images within the web part
  4. The easiest way to include the image in your bundle is to use a require statement with the path to the file. Because I'll need to use each image twice (for each choice's imageSrc and selectedImageSrc), we'll define a variable for each image we'll need at the top of the getPropertyPaneConfiguration:
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    const layoutBrick: string = require('./assets/brick.svg');
    const layoutGrid: string = require('./assets/grid.svg');
    const layoutCarousel: string = require('./assets/carousel.svg');
    ...
  5. Add a propertyPaneChoiceGroup if your property pane's groupFields, making sure to pass the same image for the imageSrc and selectedImageSrc. Of course, you can have a different image for the selected and un-selected images if you'd like, but I don't usually. You getPropertyPaneConfiguration method will look as follows:
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    const layoutBrick: string = require('./assets/brick.svg');
    const layoutGrid: string = require('./assets/grid.svg');
    const layoutCarousel: string = require('./assets/carousel.svg');
    return {
      pages: [
        {
          header: {
            description: null
          },
          groups: [
            {
              //groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneChoiceGroup('layout', {
                  label: "Layout", // don't forget to localize your test in a real-world solution
                  options: [
                    {
                      key: 'Brick',
                      text: 'Brick',
                      selectedImageSrc: layoutBrick,
                      imageSrc: layoutBrick,
                    },
                    {
                      key: 'Grid',
                      text: 'Grid',
                      selectedImageSrc: layoutGrid,
                      imageSrc: layoutGrid,
                    },
                    {
                      key: 'Carousel',
                      text: 'Carousel',
                      selectedImageSrc: layoutCarousel,
                      imageSrc: layoutCarousel,
                    }
                  ]
                }),
              ]
            }
          ]
        }
      ]
    };
    }

    Note that in the above code, I did not localize the text to make the code easier to read. Please consider localizing all your text.

  6. I recommend that you define a default layout value in your [YourWebPartName]WebPart.manifest.json's preconfiguredEntries:
    "properties": {
      "layout": "Brick"
    }

    Remember that your changes to the manifest.json file will not take effect until you re-build your web part and re-add it to your workbench.

If you run gulp serve on your web part, you should see the following property pane:
Choice Group property pane

Create a choice group with Fabric icons

If you're lucky enough to find Office UI Fabric icons that suit your needs, it is even easier to create a choice group with icons.

To do so, follow these steps:

  1. Add a property to store your new setting. We'll use shape and we'll allow Circle, Square, or Triangle:
    export interface IChoiceGroupWebPartProps {
    layout: 'Brick'|'Grid'|'Carousel';
    // ADDED: For icon choice group
    shape: 'Circle'|'Square'|'Triangle';
    // END: added
    }
  2. Add a PropertyPaneChoicegroup control in your getPropertyPaneConfiguration method, making sure to pass a iconProps value instead of an imageSrc and selectedImageSrc. The iconProps should contain a single property officeFabricIconFontName, which should be set to the name of the icon you wish to use:
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    const layoutBrick: string = require('./assets/brick.svg');
    const layoutGrid: string = require('./assets/grid.svg');
    const layoutCarousel: string = require('./assets/carousel.svg');
    return {
      pages: [
        {
          header: {
            description: null
          },
          groups: [
            {
              //groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneChoiceGroup('layout', {
                  label: "Layout", // don't forget to localize your test in a real-world solution
                  options: [
                    {
                      key: 'Brick',
                      text: 'Brick',
                      selectedImageSrc: layoutBrick,
                      imageSrc: layoutBrick,
                    },
                    {
                      key: 'Grid',
                      text: 'Grid',
                      selectedImageSrc: layoutGrid,
                      imageSrc: layoutGrid,
                    },
                    {
                      key: 'Carousel',
                      text: 'Carousel',
                      selectedImageSrc: layoutCarousel,
                      imageSrc: layoutCarousel,
                    }
                  ]
                }),
                // ADDED: For icon-based choice group
                PropertyPaneChoiceGroup('shape', {
                  label: "Shape", // don't forget to localize your test in a real-world solution
                  options: [
                    {
                      key: 'Circle',
                      text: 'Circle',
                      iconProps: {
                        officeFabricIconFontName: 'CircleShapeSolid'
                      }
                    },
                    {
                      key: 'Square',
                      text: 'Square',
                      iconProps: {
                        officeFabricIconFontName: 'SquareShapeSolid'
                      }
                    },
                    {
                      key: 'Triangle',
                      text: 'Triangle',
                      iconProps: {
                        officeFabricIconFontName: 'TriangleShapeSolid'
                      }
                    }
                  ]
                })
                // END: Added
              ]
            }
          ]
        }
      ]
    };
    }
  3. As before, I recommend that you define a default shape value in your [YourWebPartName]WebPart.manifest.json's preconfiguredEntries:
    "properties": {
      "shape": "Square"
    }

Running gulp serve will give you the following:
Property pane with layout and shape choice groups

Conditional field in property panes

Sometimes, you want to show settings that are dependent on each other. If a user selects one choice, you may want to show a dependent setting, but if they select another choice, you may want to hide that setting.

The out-of-the-box Yammer web part does this by hiding or showing the Number of conversations to show setting depending on the conversation source the user selects.

Yammer web part shows and hides fields based on selection

This is what I'll call conditional fields for the purpose of this conversation.

Making fields conditional can help improve the user experience by removing choices that do not apply, therefore reducing the complexity of choice (remember Hick's law).

However, there are some guidelines you should follow:

  • Make sure that the user easily understands the reason why a field is shown or hidden. For example, the Yammer web part lets you see plainly that the Number of conversations is impacted by the conversation source.
  • Make sure that fields that depend on each other are logically grouped/visible together. For example, do not place one field on a property pane page (or step) and the field that depends on it on another page.
  • If you find it difficult to convey the relationship between two fields, you may wish to enable and disable the dependent field instead of hiding it. For bonus points, you should add some text (or, at least, some form of tooltip) that explains why the field is disabled.
  • If you want some options to be available some times, and not available some other times, consider disabling the option instead of hiding it. If the option will never be available because of a specific condition (e.g.: user doesn't have sufficient permissions, the wrong source selected, etc.), consider hiding the field instead.

To demonstrate this concept, we'll recreate the Select conversation source, Search for a sourcem and Number of conversations to show fields from the Yammer web part. To do so, follow these step steps:

  1. In your [YourWebPartName]WebPart.ts, add properties to store the conversation source, the search criteria, and the number of conversations:
    export interface IConditionalFieldWebPartProps {
    conversationSource: 'Group'|'User'|'Topic'|'Home';
    searchCriteria: string;
    numberOfConversations: number;
    }
  2. In the getPropertyPaneConfiguration, add the following code to render the property pane fields. Again, code is not localized to make it easier to read, please localize text in a real-world scenario:
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: "Select conversation source from groups, topics, users, or home."
          },
          groups: [
            {
              groupFields: [
                PropertyPaneDropdown('conversationSource',{
                  label: "Select conversation source",
                  selectedKey: this.properties.conversationSource,
                  options: [
                    {
                      key: "Group",
                      text: "Group"
                    },
                    {
                      key: "User",
                      text: "User"
                    },
                    {
                      key: "Topic",
                      text: "Topic"
                    },
                    {
                      key: "Home",
                      text: "Home"
                    },
                  ]
                }),
                PropertyPaneTextField('searchCriteria', {
                  label: "Search for a source",
                  placeholder: "Type to search"
                }),
                PropertyPaneDropdown('numberOfConversations',{
                  label: "Number of conversations to show",
                  selectedKey: this.properties.conversationSource,
                  options: [
                    {
                      key: 4,
                      text: "Small - 4 conversations"
                    },
                    {
                      key: 8,
                      text: "Medium - 8 conversations"
                    },
                    {
                      key: 12,
                      text: "Large - 12 conversations"
                    }
                  ]
                })
              ]
            }
          ]
        }
      ]
    };
    }
  3. To Make the Search for a source field hide if the conversation source field is set to Home, add this.properties.conversationSource !== "Home" && in front of the line that begins with PropertyPaneDropdown('numberOfConversations'. Doing so says "Only execute the next line if the conversation source is not equal to Home". The new getPropertyPaneConfiguration method should look like this:

    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: "Select conversation source from groups, topics, users, or home."
          },
          groups: [
            {
              groupFields: [
                PropertyPaneDropdown('conversationSource',{
                  label: "Select conversation source",
                  selectedKey: this.properties.conversationSource,
                  options: [
                    {
                      key: "Group",
                      text: "Group"
                    },
                    {
                      key: "User",
                      text: "User"
                    },
                    {
                      key: "Topic",
                      text: "Topic"
                    },
                    {
                      key: "Home",
                      text: "Home"
                    },
    
                  ]
                }),
                // ADDED: conditional rendering for field
                this.properties.conversationSource !== "Home" && PropertyPaneTextField('searchCriteria', {
                  label: "Search for a source",
                  placeholder: "Type to search"
                }),
                PropertyPaneDropdown('numberOfConversations',{
                  label: "Number of conversations to show",
                  selectedKey: this.properties.conversationSource,
                  options: [
                    {
                      key: 4,
                      text: "Small - 4 conversations"
                    },
                    {
                      key: 8,
                      text: "Medium - 8 conversations"
                    },
                    {
                      key: 12,
                      text: "Large - 12 conversations"
                    }
                  ]
                })
              ]
            }
          ]
        }
      ]
    };
    }
  4. No necessary for this example, but if you want the mimic the Yammer web part and enable/disable the Number of conversations field on the selection, add a isDisabled attribute to the PropertyPaneDropDown field, as follows:

    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: "Select conversation source from groups, topics, users, or home."
          },
          groups: [
            {
              groupFields: [
                PropertyPaneDropdown('conversationSource',{
                  label: "Select conversation source",
                  selectedKey: this.properties.conversationSource,
                  options: [
                    {
                      key: "Group",
                      text: "Group"
                    },
                    {
                      key: "User",
                      text: "User"
                    },
                    {
                      key: "Topic",
                      text: "Topic"
                    },
                    {
                      key: "Home",
                      text: "Home"
                    },
    
                  ]
                }),
                this.properties.conversationSource !== "Home" && PropertyPaneTextField('searchCriteria', {
                  label: "Search for a source",
                  placeholder: "Type to search"
                }),
                PropertyPaneDropdown('numberOfConversations',{
                //ADDED: To enable/disable field based on selection
                  disabled: this.properties.conversationSource !== "Home",
                  label: "Number of conversations to show",
                  selectedKey: this.properties.conversationSource,
                  options: [
                    {
                      key: 4,
                      text: "Small - 4 conversations"
                    },
                    {
                      key: 8,
                      text: "Medium - 8 conversations"
                    },
                    {
                      key: 12,
                      text: "Large - 12 conversations"
                    }
                  ]
                })
              ]
            }
          ]
        }
      ]
    };
    }
  5. Set default property values in your [YourWebPartName]WebPart.manifest.json, under preconfiguredEntries:
    "properties": {
      "conversationSource": "Group",
      "searchCriteria": "",
      "numberOfConversations": 8
    }

Running a new gulp serve should give you a web part pane that behaves like the Yammer web part:

The logic for enabling/disabling and hiding/showing property pane fields in Yammer is more complicated than what we cover in this example, but I hope it'll do for now.

In my opinion, the Yammer Number of conversations should probably tell you why it is disabled. If it is never supposed to be available from any other sources than Home, it should probably be hidden instead of disabled -- but that's my personal preference, not a hard rule.

Note: Don't worry, we'll cover how to create a drop-down box with icons in a later post.

Creating conditional property pane groups

The out-of-the-box Quick chart web part allows users to choose between a Column chart or a Pie chart. If the user selects a Column chart, the property pane will show a Data property group and a Layout property group; If they choose Pie chart, the property pane will hide the Layout property group and only show the Data property group.

Quick chart with conditional property groups

By hiding or showing only the property pane groups that are relevant to the user's selected chart type, the Quick chart web part helps the user quickly configure the web part without presenting unnecessary settings.

Imagine if -- instead of hiding the Layout property pane group -- we disabled the property group entirely. The screen would be needlessly cluttered.

A while ago, I created a Chartinator web part sample for the SharePoint sp-dev-fx-webparts samples repository, which extends on the Quick chart web part and allows users to configure 9 different types of charts. Depending on which chart type the user selects, multiple property pane groups appear and disappear to help users make sense of the options available to them. If we didn't hide some sections, the property pane would be much too complicated to use.

The Chartinator web part

For this code sample, we'll emulate the Quick chart property pane and allow users to select one of two types of charts. Depending on the selection, we'll hide or show the Layout property pane group. The Data property pane group will always be available.

To keep this sample simple, we won't add any property fields in either property pane group -- we'll just put some text to illustrate the concept. If you really want to see how to implement the full Quick chart property pane, check out the Chartinator sample.

To mimic the Quick chart web part property pane, follow these steps:

  1. As before, add a property to your web part to store the Chart type selection:
    export interface IConditionalGroupWebPartProps {
    chartType: "Column" | "Pie";
    }
  2. Change the getPropertyPaneConfiguration method to create an IPropertyPaneConfiguration variable to store the property pane configuration (instead of simply returning it) so that we can manipulate the content of the property pane configuration.

    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    const chartDescription: string = this.properties.chartType === "Column" ?
      "Use a column chart to show data changes over time or comparisons among items. Categories are typically shown along the horizontal axis and values along the vertical axis."
      : "Use a pie chart to show percentages of a whole. Best when used with fewer than seven categories.";
    
      const configuration: IPropertyPaneConfiguration = {
      pages: [
        {
          header: {
            description: "Select a chart type and then select a data source. You can enter up to 12 data points, or show up to 50 data points if you use a SharePoint list on this site as the data source."
          },
          displayGroupsAsAccordion: true,
          groups: [
            {
              groupName: "Chart type",
              groupFields: [
                PropertyPaneChoiceGroup('chartType', {
                  options: [
                    {
                      key: 'Column',
                      text: 'Column chart',
                      iconProps: {
                        officeFabricIconFontName: 'BarChart4'
                      }
                    },
                    {
                      key: 'Pie',
                      text: 'Pie chart',
                      iconProps: {
                        officeFabricIconFontName: 'PieDouble'
                      }
                    }
                  ]
                }),
                PropertyPaneLabel('chartType', {
                  text: chartDescription
                }),
    
              ]
            },
            {
              groupName: "Data",
              isCollapsed: false,
              groupFields: [
                PropertyPaneLabel('data', {
                  text: "This is some sample text for the data property group."
                }),
              ]
            }
          ]
        }
      ]
    };
    
    return configuration;
    }
  3. At the end of the same method, add some conditional logic to insert a new element in the groups array using push:

    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    const chartDescription: string = this.properties.chartType === "Column" ?
      "Use a column chart to show data changes over time or comparisons among items. Categories are typically shown along the horizontal axis and values along the vertical axis."
      : "Use a pie chart to show percentages of a whole. Best when used with fewer than seven categories.";
    
      const configuration: IPropertyPaneConfiguration = {
      pages: [
        {
          header: {
            description: "Select a chart type and then select a data source. You can enter up to 12 data points, or show up to 50 data points if you use a SharePoint list on this site as the data source."
          },
          displayGroupsAsAccordion: true,
          groups: [
            {
              groupName: "Chart type",
              groupFields: [
                PropertyPaneChoiceGroup('chartType', {
                  options: [
                    {
                      key: 'Column',
                      text: 'Column chart',
                      iconProps: {
                        officeFabricIconFontName: 'BarChart4'
                      }
                    },
                    {
                      key: 'Pie',
                      text: 'Pie chart',
                      iconProps: {
                        officeFabricIconFontName: 'PieDouble'
                      }
                    }
                  ]
                }),
                PropertyPaneLabel('chartType', {
                  text: chartDescription
                }),
    
              ]
            },
            {
              groupName: "Data",
              isCollapsed: false,
              groupFields: [
                PropertyPaneLabel('data', {
                  text: "This is some sample text for the data property group."
                }),
              ]
            }
          ]
        }
      ]
    };
    // ADDED: To insert a conditional group
    // If the selected type is not Column, we don't need to make any further changes
    if (this.properties.chartType !== "Column") {
      return configuration;
    }
    
    // Get the list of property groups
    const { groups } = configuration.pages[0];
    
    // Insert a property pane
    groups.push({
      groupName: "Layout",
      isCollapsed: false,
      groupFields: [
        PropertyPaneLabel('layout', {
          text: "This is some sample text for the layout property group."
        }),
      ]
    });
    
    // END: added
    return configuration;
    }
  4. Make sure to define some default properties in your web part's manifest.json file:
    "properties": {
      "chartType": "Column"
    }

On your next gulp serve, you'll have a web part property pane hides/shows conditional property groups just like the Quick chart web part:

Sample quick chart web part with conditional groups

At the risk of repeating myself: the sample does not localize the text to help keep the code easy to read. It is killing me to do this. Please localize your text in a real solution.

Conclusion

In today's post, we discussed how to create image choice groups, create conditional property pane fields and conditional property pane groups.

All the code samples for this post can be found in my sample solution from the GitHub repo.

Every time I created a web part for this series so far, I wanted to change the body of the web part to make it look better (instead of using the default styles rendered by the Yeoman generator). It took everything in me not to tinker with the web part body and create something that would be worthy of the SharePoint Design series.

In our next post, we'll focus on the web part body.

Finally!!!

Introduction

The SharePoint Design is a beautiful web site that provides design guidance on beautiful and fast sites, pages, and web parts with SharePoint in Office 365.

Unfortunately, the SharePoint Design site does not tell you how to create the beautiful web parts they show you.

This series is intended as a companion to the SharePoint Design site, providing you with code samples and detailed how-to information for every design topic. It should help you create web parts that look exactly like the ones on the SharePoint Design site.

Today's post continues on my previous post. It discusses various aspects of property panes that you may find useful.

Like the previous post, this is a companion to the Designing SharePoint web part page.

If you want to download the code samples used in this article, download the solution from the GitHub repo.

Reactive and non-reactive web parts

By default, when you change settings in a property pane, the changes are immediately reflected within the web part on the page; the web part is considered reactive.

Reactive web parts are recommended because it follows the WYSIWYG (what you see if what you get) user experience principles for authoring.

However, sometimes you may not want every change a user makes in the property pane to immediately be reflected in the web part.

For example, if your web part has to load data or call an API every when your user changes settings, you may want to make your web part non-reactive.

When you make your web part non-reactive, your property pane will add an Apply button at the bottom of your property pane. Any changes users make in your property pane will not take effect until they select Apply.

To make your web part non-reactive, all you need to do define a disableReactivePropertyChanges function in your web part, and return true. To do so, follow these steps:

  1. In your [YourWebPartName]WebPart.ts file, find the getPropertyPaneConfiguration function.
  2. Add the following code before or after (it's really up to you):
    protected get disableReactivePropertyChanges(): boolean {
    return true;
    }

That's it!

When you run your web part, the property pane will display the Apply button we discussed.

Non-reactive property pane

Showing a property pane loading indicator

You may have noticed that most web part property panes do not show a loading indicator. That's because we always want web parts to appear fast and responsive.

A loading indicator
This is what we mean by 'Loading indicator'

However, sometimes you need to make asynchronous requests to load some data for your property pane, which could take a long time. That's when you should display a loading indicator to the end-user, so they don't think your web part has crashed or has become unresponsive.

How long before a user thinks the system has crashed? According to the Doherty Threshold law of user experience, you should provide feedback within 400 milliseconds in order to keep your user's attention and increase their trust in the system.

If your async requests take longer than 400 milliseconds, you should display a loading indicator.

When I want to display a loading indicator in the property pane, I follow these steps:

  1. First, you'll need a variable to store whether you should display the loading indicator or not (otherwise, your loading indicator will always display!). To do so, add a boolean variable to you [YourWebPartName]WebPart class:

    export default class LoadingIndicatorWebPart extends BaseClientSideWebPart<ILoadingIndicatorWebPartProps> {
    
    // ADDED: To store whether we should display the loading indicator or not
    private showLoadingIndicator: boolean = true;
    // END: added
    ...

    In my code, I always initially set it to true because I usually only load my asynchronous data once -- the first time a user displays the property pane.

  2. Add an onPropertyPaneConfigurationStart method to your web part class if you don't already have one. It will get called when preparing the property pane to display. This is where I like to make my asynchronous calls:
    // ADDED: To display the loading indicator when preparing the property pane
    protected async onPropertyPaneConfigurationStart(): Promise<void> {
    // Call your service
    // Remember that this method gets called *every time* before a user
    // displays the property pane. You should probably verify that your
    // data isn't already loaded before calling your async method again
    this.loadedPlantList = await this.getPlantNames();
    }
    // END: Added

    Note that in my code, I use an await statement to wait until my async call is complete. If you prefer, you could use this.getPlantNames().then(...), but await calls are quickly becoming the preferred way to handle async calls in React.

  3. Once your data is loaded, set your showLoadingIndicator variable to false (line 10):

    protected async onPropertyPaneConfigurationStart(): Promise<void> {
    // Call your service
    // Remember that this method gets called *every time* before a user
    // displays the property pane. You should probably verify that your
    // data isn't already loaded before calling your async method again
    this.loadedPlantList = await this.getPlantNames();
    
    //ADDED: To stop displaying the loading indicator
    // When done loading, set the loading indicator to false and refresh
    this.showLoadingIndicator = false;
    //END: Added
    }
  4. To make sure that your property pane updates when you turn the loading indicator on or off, make sure to call refresh on the property pane -- otherwise it won't take effect (line 12):

    protected async onPropertyPaneConfigurationStart(): Promise<void> {
    // Call your service
    // Remember that this method gets called *every time* before a user
    // displays the property pane. You should probably verify that your
    // data isn't already loaded before calling your async method again
    this.loadedPlantList = await this.getPlantNames();
    
    // When done loading, set the loading indicator to false and refresh
    this.showLoadingIndicator = false;
    
    //ADDED: To force the property pane to refresh
    this.context.propertyPane.refresh();
    //END: Addd
    }
  5. Finally, in your getPropertyPaneConfiguration, set the property pane's showLoadingIndicator prop to the value of your showLoadingIndicator variable (line 4):
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      // ADDED: To display a loading indicator
      showLoadingIndicator: this.showLoadingIndicator,
      // END: Added
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneDropdown('description', {
                  label: strings.DescriptionFieldLabel,
                  options: this.loadedPlantList
                })
              ]
            }
          ]
        }
      ]
    };
    }

That's all you should need to do (in theory). In my code sample, I call a fake service to get a list of plant names and load it in a property pane drop-down box. This is what the final product looks like (note that I exaggerated the delay for demo purposes):

Property pane with loading indicator

Delaying the loading indicator

As we discussed, we want to avoid showing the loading indicator if we can. We especially want to flash (i.e.: briefly show) a loading indicator every time we load the property pane.

But you also want to keep in mind the Doherty Threshold. Delay a response for longer than 400 milliseconds and people will start clicking around.

Luckily, you can delay the loading indicator and do both!

Let's pretend that you make a call to a service which always returns with results within 300 milliseconds -- except when everyone at work is watching YouTube, then it can take a lot longer. You may want to avoid showing a loading indicator as long as your async calls take less than 300 milliseconds, but start display an indicator if the calls take longer.

All you need to do is add a loadingIndicatorDelayTime value to your property pane configuration. The system will wait for whatever delay time you specified before displaying the loading indicator. If you turn off the loading indicator before the delay has elapsed (i.e.: when your async call returns within normal conditions), the loading indicator will never show. However, if you don't turn off the indicator before the delay period, the loading indicator will automatically start showing.

To do so, change your code as follows:

  1. In your web part's getPropertyPaneConfiguration method, add the loadingIndicatorDelayTime (line 5):
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      showLoadingIndicator: this.showLoadingIndicator,
      // ADDED: To delay the loading indicator
      loadingIndicatorDelayTime: 300,
      // END: Added
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneDropdown('description', {
                  label: strings.DescriptionFieldLabel,
                  options: this.loadedPlantList
                })
              ]
            }
          ]
        }
      ]
    };
    }

Two things to note:

  • If you don't specify a loadingIndicatorDelayTime value, and your showLoadingIndicator is set to true, SharePoint will wait 500 milliseconds by default.
  • The loadingIndicatorDelayTime also seems to delay displaying the property pane until either the delay has elapsed or your showLoadingIndicator is set to false.

Conclusion

Today we expanded a little on how to create professional property panes by making them reactive (or not) and by adding a loading indicator when necessary.

There is a lot more to cover on property panes, but we'll covert it in our next post.

I hope this helps?

Introduction

In Part I of this series, we discussed why you should adopt an acquisition model that re-uses what you already have, buys what you can't re-use, and builds what you can't buy.

We briefly discussed that one of the reasons why you shouldn't just buy something by just looking at the price tag because of the total cost of ownership (TCO), but we didn't go in details.

This article explains why you should consider TCO when looking at buying a new software.

What is TCO?

The term TCO was originally coined in the late 1980s by Gartner research to describe the cost of owning and deploying personal computers.

Their findings showed that each PC costs an enterprise nearly $10,000 per year.

Originally, it caused quite a stir in the technology community and among CFOs who scrutinized their methodology and -- eventually -- accepted it as a standard way to evaluate total costs.

Simply put, TCO consists of the costs, direct and indirect, incurred throughout the life-cycle of an asset, including acquisition, deployment, operation, support and retirement.

Some sources say that the amount on the price tag represents less than 10 percent of the total cost spent on IT assets over its lifetime.

In other words, the price tag of software is like the visible part of an iceberg, while the hidden costs are just like the submerged parts.

Iceberg showing partially submerged portion
Total cost of ownership and icebergs have a lot in common

Like other tools, TCO does not solve all problems. For example, TCO does not assess risk or help align technology investments with your strategic goals. Nevertheless, TCO is an important tool for the analysis of IT costs and for the management of those costs in an IT organization.

Cost areas

When calculating your software's total cost of ownership, consider two areas:

  • Obvious costs
  • Hidden costs

Obvious costs

Those are the costs that everyone who was involved in planning and vendor selection is familiar with, such as:

  • Capital expenses:  License fees and/or subscription fees. If buying an on-prem solution, you may also need to purchase new hardware.
  • Operating expenses: Services, support & maintenance fees to keep the equipment running. Consider additional license costs each year (or the price increase at the end of each term is purchasing a subscription) and the number of years until a major upgrade.

Hidden costs

These are the costs people usually forget about when planning to purchase new software.

The hidden costs are less obvious cost that are easy to overlook, but they can be very large.

Large enough to matter.

When calculating your TCO, consider the following "hidden" costs:

  • Acquisition costs: the costs of identifying, selecting, ordering, receiving, inventorying, or paying for something. Will you have to go through an RFP process to purchase your software? Consider the cost of preparing the RFP.
  • Installation costs: Year one install & setup costs.
  • Upgrade, enhancement, or initial setup costs: Most software may require some initial set-up, even if you don’t intend to customize them or integrate with other systems.
  • Reconfiguration costs
  • Customization & Integration: Keep in mind that the more you customize your software, the more you will have to maintain and update when major upgrades occur.
  • Data migration: When deploying a new system, it's often necessary to migrate your data from your existing system. The cost of this migration depends on the amount and format of the data. You may first need to convert the data to another format, consolidate it with other sources, or “scrub” it for duplicate, obsolete, or otherwise bad entries. When moving to the cloud, you also need to consider that some SaaS providers may charge egress costs which are often proportional to the amount of data transferred.
  • Operating costs: for example, human (operator) labour.
  • Change management costs:  for example, costs of user orientation, user training, workflow/process change design and implementation. Training users is critical to get the most out of your new software. It may involve sending employees to training centers, bringing trainers on-site, participating in webinars, or creating custom courses and documentation. Keep in mind that you will also need to train new employees as they join your organization.
  • Infrastructure support costs:  for example, costs brought by the acquisition for heating/cooling, lighting,  or IT support.
  • Environmental impact costs: for example, costs of waste disposal/clean up, or pollution control, or the costs of environmental impact compliance reporting.
  • Security costs:
    • Physical security: If buying an on-prem solution, you may need to increase security measures in your building, including new locks, secure entry doors, closed-circuit television, and security guard services.
    • Information security: for example, security software applications or systems, offsite data backup, disaster recovery services, etc.
  • Financing costs: for example, loan interest and loan origination fees.
  • Disposal / Decommission costs: Some jurisdictions may require you to pay disposal fees. You may also need to shred or demagnetize your electronic storage media.

this is not a comprehensive list by any means. It is just a list of costs I've tallied up over the years. You may have to consider other areas specific to your own situation.

How does the saying go? "Your mileage may vary".

Conclusion

When purchasing new software, you have to consider more than just the price tag.

Thankfully, more and more software companies (and research companies) have created online TCO calculators to help you identify those hidden costs.

I've found a couple so far:

Did I miss any obvious costs? Did you find any TCO calculators out there? Let me know in the comments.

Photo Credit

Piggy Bank Image by 3D Animation Production Company from Pixabay

Iceberg Image created by: Ralph A. Clevenger
Credit:© Ralph A. Clevenger/CORBIS
Copyright:© Corbis. All Rights Reserved

Introduction

The SharePoint Design is a beautiful web site that provides design guidance on beautiful and fast sites, pages, and web parts with SharePoint in Office 365.

Unfortunately, the SharePoint Design site does not tell you how to create the beautiful web parts they show you.

This series is intended as a companion to the SharePoint Design site, providing you with code samples and detailed how-to information for every design topic. It should help you create web parts that look exactly like the ones on the SharePoint Design site.

Today's post is a companion to the Designing SharePoint web part page.

What is a property pane?

The property pane is a panel that appears when users select Edit on a web part. It allows users to enable and disable features, select a layout, connect to another web part, or set other options.

Web Part with a property pane highlighted
The property pane

When visible, the property pane is 320 pixels, which makes it the perfect size to appear full-screen on a mobile device. On a tablet or desktop device, the page content area shrinks by 320 pixels and the content re-flows responsively to make room for the property pane.

Property pane in edit mode
The property pane in action

When to use a property pane

You should use a property pane when you need to offer options to your users to configure the web part.

You should not use a property pane to edit the web part's content.

Directness is a user interface design principle which says that your users should be able to achieve their goals through a minimal set of actions. It also says that users should be able to manipulate the objects they're working on in the most direct manner.

In other words, directness says that, whenever possible, you should allow users to edit the content of a web part where the content actually resides -- in the web part's content area.

That's why the out-of-the-box Text and Hyperlink web parts make you edit content directly within the web part.

Directness in text web part
Text web part in action

Directness in hyperlink web part
Hyperlink web part in action

In contrast, the Image web part opens a pane to insert and edit an image, because there isn't a natural way to create an image directly from the web part's content area.

Image web part uses a property pane
Image web part's use of the property pane

In some cases, you may wish to use the property pane to provide additional options for the selected content of the web part. For example, the Text web part uses the property pane to provide users with additional formatting options that would otherwise clutter the text editing toolbar. Users edit the content directly within the web part, but can also use additional formatting options in the property pane.

Text web part using the property pane to format text
The text web part uses the property pane to provide additional formatting options

The best way to leverage property panes it to use it to provide options to your page authors that you don't want other users (i.e.: readers) to see. Because SharePoint automatically hides or shows the Edit depending on the user's permissions, you don't have to build your own sophisticated security logic to hide or show options within your web part.

If in doubt about whether you should show an option within the web part body or within the property pane, ask yourself whether you want all users to be able to control that option. For example, if you want to allow all users to refresh a feed via a Refresh button, or if you want users to control whether they want their personal feed to be sorted in ascending or descending order, give the users the option to do that within the web part's body.

On the other hand, if you only want page authors to control options that will affect all users, you should probably place that option within the property pane.

Take a look at the various out-of-the-box web parts to see how they present their various options for inspiration.

Types of property panes

When designing a property pane, you can use one of three property pane types:

Single pane

Used for simple web parts with a few properties to configure. As the name implies, there is only one single pane without any grouping.

Single pane

Accordion pane

Contains a group or groups of properties with many options, and where the groups result in a long scrolling list of options.

For example, you might have three groups named Properties, Appearance, and Layout, each with many options.

Accordion pane

Steps pane

Group properties in multiple steps or pages. Used when web configuration needs to follow a precise order of steps, or when choices in the first step affect the options that display in next steps.

The property pane will display a Back and Next buttons at the bottom of the pane, along with an indication of how many steps there are, and which step is currently displayed.

Steps pane

For more information on each property pane type refer to the SharePoint Design site.

Create a single pane property pane

To create a single pane property pane, follow these steps:

  • Do nothing

That's because it is what the SPFx Yeoman generator automatically produces for you when you create a web part.

Let's take a second to explore the default code to understand it better.

When you create a web part using the Yeoman generator, this is the standard getPropertyPaneConfiguration method that you get in your [YourWebPartName]WebPart class:

 protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneTextField('description', {
                  label: strings.DescriptionFieldLabel
                })
              ]
            }
          ]
        }
      ]
    };
  }

If you aren't familiar with SPFx, Typescript, or React, it may not be obvious what each part of the code does. The biggest reason is that the SharePoint team is being really nice to us and provides us with a template that encourages you to localize all your web part resources (i.e.: the text in your web parts). That's why you see strings.PropertyPaneDescription as the description for the property pane. It is a great habit to follow, and I encourage you to keep localizing all your resources -- even if your web part is only ever going to be in one language. It makes it easier to change the text (like when fixing spelling mistakes) without having to touch the code.

To make things easier, let's replace the code and see what happens. Replace your existing getPropertyPaneConfiguration code with the following code:

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: "This is the pane description"
          },
          groups: [
            {
              groupName: "This is the group name",
              groupFields: [
                PropertyPaneTextField('description', {
                  label: "This is the field label"
                })
              ]
            }
          ]
        }
      ]
    };
  }

When test your web part (using gulp serve), your property pane will look like this:

Property pane describing each component

If we use our browser's developer toolbar to explore the various elements, we can see the individual components:

Highlights each section of a property pane

You may have noticed that the property pane's title isn't configured anywhere. That's because it uses the web part's title, which is configured in the [YourWebPartName].manifest.json under preconfiguredEntries > title > default. If you change the title, remember that the settings won't take effect until you re-bundle the web part (by running gulp serve again, or gulp build) and re-add the web part to your page.

Property pane page description

I encourage you to use the page description to provide your users with instructions on what you expect them to do. For example, this is what the out-of-the-box Image property pane page description looks like:

Change your image and image options. Turn on or off the display of text over your image, add a link, and add or modify alternative text.

The File viewer web part gives you different directions based on what type of file you select. This is what it says if you select a PowerPoint presentation:

Select the slide you want people to see first.

And this is what you get if you selected a Word document:

Select the page you want people to see first.

If you didn't select a file, however, this is what you'll see:

Add a file to view on your page. You can select from a variety of file types including Excel, Word, PowerPoint, Visio, PDFs, 3D models, and more. You can also connect to a source to dynamically view files by selecting the ellipses (…) and Connect to source.

Last example: this is what the Highlighted content web part page description says:

Select the content you want to highlight, and choose layout options.

Use your property pane page description to provide users with useful guidance. If you don't have anything useful to say, you can simply omit the page description by removing the line in your code:

 protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          // REMOVED: to remove the description
          // header: {
          //   description: "This is the pane description" //strings.PropertyPaneDescription
          // },
          groups: [
            {
              groupName: "This is the group name", //strings.BasicGroupName,
              groupFields: [
                PropertyPaneTextField('description', {
                  label: "This is the field label" //strings.DescriptionFieldLabel
                })
              ]
            }
          ]
        }
      ]
    };
  }

Which gets you this:

Property Pane without Description
Property pane without a description

For the record, though, I encourage you to provide some helpful text instead of removing the description.

Property pane group

Your property pane can have more that one group. However, when you use the single pane property pane when you have few options to present, otherwise you should use the accordion or steps property pane.

You may have noticed that some of the out-of-the-box web parts have no property pane groups. Or, rather, they have a single property pane group with no group header.

Image web part property pane
The image web part property pane has no group header

Text web part property pane
The text web part property pane has no group header either

To reproduce the same look, all you need to do is omit a group header in your code:

 protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          // REMOVED: To hide the property pane page description
          // header: {
          //   description: "This is the pane description"
          // },
          groups: [
            {
              //REMOVED: For single pane property pane
              //groupName: "This is the group name",
              groupFields: [
                PropertyPaneTextField('description', {
                  label: "This is the field label"
                })
              ]
            }
          ]
        }
      ]
    };
  }

Which produces the following web part property pane:

Single pane, no header, no group header

Create an accordion pane property pane

Sometimes you have too many options to present to your users at one time.

Take the out-of-the-box Highlighted content web part, for example. Imagine if showed all available options without any grouping. It would look like this:

Highlighted content web part with no grouping
Too many options at once!

Now take the same web part, but group the options logically. You get something like this:

Highlighted content web part with grouping
Ah! Much easier to understand!

It makes sense to group your choices into smaller, collapsible sections known as accordion panes.

Why should you do this? It's the law!

Hick’s Law, to be precise. Also known as the Hick-Hyman Law, it is named after a British and an American psychologist team of William Edmund Hick and Ray Hyman. In 1952, they found a relationship between the number of stimuli present and an individual's reaction time to any given stimulus.

Specifically, they found that the more stimuli to choose from, the longer it takes the user to make a decision.

(That's what I like to call the Cheesecake Factory Law, because of how long it takes to decide what to eat when at the Cheesecake Factory. Have you seen how long their menu is?!)

When you bombard your users with too many choices at once, they have to take time to interpret and decide, giving them work they don't want.

If you have a lot of choices to offer your users, consider grouping them into smaller choices. For example, instead of giving you 7 configuration choices to pick from, the Highlighted content web part gives you three simple choices:

  • Content
  • Filter and sort
  • Layout

Within each of those three, you have two or three smaller choices to make. Making it easier for users to make sense of the choices that are available to them.

Note that the rule only applies to choices that the user is unfamiliar with. For example, if you want to list the months of the year, don't group them into smaller choices. Otherwise, you'll actually increase the time it takes for a user to find the item they want.

For this sample, we have created 6 properties (demoProperty1 through demoProperty6) which we'll group into three categories (Group 1, Group 2 and Group 3). Feel free to use your own categories and properties.

If you want to create an accordion pane property pane, follow these steps:

  1. In your [YourWebPartName]WebPart.ts file, find the getPropertyPaneConfiguration function and create as many groups as you need, with fields within those groups.
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: "This web part demonstrates how to use an accordion property pane"
          },
          groups: [
            {
              groupName: "Group 1",
              groupFields: [
                PropertyPaneTextField('demoProperty1', {
                  label: "Property 1"
                }),
                PropertyPaneTextField('demoProperty2', {
                  label: "Property 2"
                })
              ]
            },
            // ADDED: Group 2 and 3 for accordion support
            {
              groupName: "Group 2",
              groupFields: [
                PropertyPaneTextField('demoProperty3', {
                  label: "Property 3"
                }),
                PropertyPaneTextField('demoProperty4', {
                  label: "Property 4"
                })
              ]
            },
            {
              groupName: "Group 3",
              groupFields: [
                PropertyPaneTextField('demoProperty5', {
                  label: "Property 5"
                }),
                PropertyPaneTextField('demoProperty6', {
                  label: "Property 6"
                })
              ]
            }
            // END added
          ]
        }
      ]
    };
    }

    The groups and groupFields nodes expect arrays of items, which means that we can have many groups and many fields within the groups.

  2. To make the groups appear as accordions, add a displayGroupsAsAccordion property and set it to true, as follows (see line 9):
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: "This web part demonstrates how to use an accordion property pane"
          },
          // ADDED: to turn groups into accordions
          displayGroupsAsAccordion: true,
          // END added
          groups: [
            {
              groupName: "Group 1",
              groupFields: [
                PropertyPaneTextField('demoProperty1', {
                  label: "Property 1"
                }),
                PropertyPaneTextField('demoProperty2', {
                  label: "Property 2"
                })
              ]
            },
            {
              groupName: "Group 2",
              groupFields: [
                PropertyPaneTextField('demoProperty3', {
                  label: "Property 3"
                }),
                PropertyPaneTextField('demoProperty4', {
                  label: "Property 4"
                })
              ]
            },
            {
              groupName: "Group 3",
              groupFields: [
                PropertyPaneTextField('demoProperty5', {
                  label: "Property 5"
                }),
                PropertyPaneTextField('demoProperty6', {
                  label: "Property 6"
                })
              ]
            }
          ]
        }
      ]
    };
    }
  3. If you wish to make the groups initially appear as expanded or collapsed, you can specify the isCollapsed property at the group level and set it to true or false, as desired:
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: "This web part demonstrates how to use an accordion property pane"
          },
          displayGroupsAsAccordion: true,
          groups: [
            {
              groupName: "Group 1",
              // ADDED: to collapse group initially
              isCollapsed: true,
              // END added
              groupFields: [
                PropertyPaneTextField('demoProperty1', {
                  label: "Property 1"
                }),
                PropertyPaneTextField('demoProperty2', {
                  label: "Property 2"
                })
              ]
            },
            {
              groupName: "Group 2",
              // ADDED: to collapse group initially
              isCollapsed: true,
              // END added
              groupFields: [
                PropertyPaneTextField('demoProperty3', {
                  label: "Property 3"
                }),
                PropertyPaneTextField('demoProperty4', {
                  label: "Property 4"
                })
              ]
            },
            {
              groupName: "Group 3",
              // ADDED: to collapse group initially
              isCollapsed: true,
              // END added
              groupFields: [
                PropertyPaneTextField('demoProperty5', {
                  label: "Property 5"
                }),
                PropertyPaneTextField('demoProperty6', {
                  label: "Property 6"
                })
              ]
            }
          ]
        }
      ]
    };
    }
  4. If you run gulp serve, you should get an accordion pane property pane:
    A custom accordion pane

Note: To keep the code easier to read, we did not localize our text resources. Please don't do this at home.

Create a steps pane property pane

As we previously discussed, if you have a lot of configuration options in your web part, you should use an accordion pane and group your configuration options to make it easier for your users. Accordion panes work well when you want to allow your users to configure your web part in no particular order.

Sometimes, you need your users to configure your web part by following a specific sequence.

That's when you should use steps pane property panes.

To prepare this sample, we created demoProperty1 through demoProperty10. We'll show these options across 3 pages, divided into 5 groups. Feel free to use your own properties.

Just like the groups and groupField property, the pages property expects an array. You can define more than one page and they will be displayed in the same order that you defined them in the code.

To create our sample steps pane property pane, follow these steps:

  1. In your [YourWebPartName]WebPart.ts file, find the getPropertyPaneConfiguration function and create as many pages as you need, with groups and group fields within those pages:

    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: "This is the first page."
          },
          displayGroupsAsAccordion: false,
          groups: [
            {
              groupName: "Group 1",
              groupFields: [
                PropertyPaneTextField('demoProperty1', {
                  label: "Property 1"
                }),
                PropertyPaneTextField('demoProperty2', {
                  label: "Property 2"
                })
              ]
            },
            {
              groupName: "Group 2",
              groupFields: [
                PropertyPaneTextField('demoProperty3', {
                  label: "Property 3"
                }),
                PropertyPaneTextField('demoProperty4', {
                  label: "Property 4"
                })
              ]
            }
          ]
        },
        // ADDED: To add steps
        {
          header: {
            description: "This is the second page."
          },
          displayGroupsAsAccordion: false,
          groups: [
            {
              groupName: "Group 3",
              groupFields: [
                PropertyPaneTextField('demoProperty5', {
                  label: "Property 5"
                }),
                PropertyPaneTextField('demoProperty6', {
                  label: "Property 6"
                })
              ]
            },
            {
              groupName: "Group 4",
              groupFields: [
                PropertyPaneTextField('demoProperty7', {
                  label: "Property 7"
                }),
                PropertyPaneTextField('demoProperty8', {
                  label: "Property 8"
                })
              ]
            }
          ]
        },
        {
          header: {
            description: "This is the third and final page."
          },
          displayGroupsAsAccordion: false,
          groups: [
            {
              groupName: "Group 5",
              groupFields: [
                PropertyPaneTextField('demoProperty9', {
                  label: "Property 9"
                }),
                PropertyPaneTextField('demoProperty10', {
                  label: "Property 10"
                })
              ]
            }
          ]
        }
        //END added
      ]
    };
    }
  2. By default, the web part will start the property pane on the first page. If you'd like to override this (for example, if you want to bring attention to a specific page to encourage a user to solve an issue), specify the currentPage property as follows (see line 4):

    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      // ADDED: To change the default current page
      currentPage: 3,
      // END added
      pages: [
        {
          header: {
            description: "This is the first page."
          },
          displayGroupsAsAccordion: false,
          groups: [
            {
              groupName: "Group 1",
              groupFields: [
                PropertyPaneTextField('demoProperty1', {
                  label: "Property 1"
                }),
                PropertyPaneTextField('demoProperty2', {
                  label: "Property 2"
                })
              ]
            },
            {
              groupName: "Group 2",
              groupFields: [
                PropertyPaneTextField('demoProperty3', {
                  label: "Property 3"
                }),
                PropertyPaneTextField('demoProperty4', {
                  label: "Property 4"
                })
              ]
            }
          ]
        },
        // ADDED: To add steps
        {
          header: {
            description: "This is the second page."
          },
          displayGroupsAsAccordion: false,
          groups: [
            {
              groupName: "Group 3",
              groupFields: [
                PropertyPaneTextField('demoProperty5', {
                  label: "Property 5"
                }),
                PropertyPaneTextField('demoProperty6', {
                  label: "Property 6"
                })
              ]
            },
            {
              groupName: "Group 4",
              groupFields: [
                PropertyPaneTextField('demoProperty7', {
                  label: "Property 7"
                }),
                PropertyPaneTextField('demoProperty8', {
                  label: "Property 8"
                })
              ]
            }
          ]
        },
        {
          header: {
            description: "This is the third and final page."
          },
          displayGroupsAsAccordion: false,
          groups: [
            {
              groupName: "Group 5",
              groupFields: [
                PropertyPaneTextField('demoProperty9', {
                  label: "Property 9"
                }),
                PropertyPaneTextField('demoProperty10', {
                  label: "Property 10"
                })
              ]
            }
          ]
        }
        //END added
      ]
    };
    }

Try to avoid skipping directly to another page without a valid reason, otherwise, your users may be a bit confused. If you choose to skip to a page other than page 1, help the users understand why you did it -- perhaps with a little message at the top of the page.

Once completed, you should get a property pane with three pages (I kept the default page to page 1, in case you're wondering:
A steps pane property pane

Conclusion

That's probably enough for one day.

As you hopefully noticed, creating property panes isn't very difficult. Most of the work is already done for you.

The code for this post is available on the SharePoint Framework Web Part Design Series repo.

In our next post, we'll continue discussing property panes with some advanced options, such as adding images to property pane headers, showing loading indicators, and making responsive/non-responsive property pane web parts. We'll also discuss what the text within these property panes should look like.

Then we'll discuss more exciting stuff, like creating conditional property pane groups and fancy property pane fields.

I hope this helps?

Introduction

A tale, inspired by a true story:

Once upon a time, there was an IT guy who had been tasked to buy a piece of software. He wasn't asked to do research or to investigate whether his company had the required infrastructure (or staff) to run the software. He was told to buy it and not to ask any questions.

The boss had already made up his mind. He wanted this software because the adverts in the magazine told him it would solve all his problems. He had already talked to the salespeople over a game of golf and a lavish dinner with copious amounts of alcohol.

The salespeople -- really nice and friendly folks -- had assured the boss that the software would not need any configuration or installation, and it wouldn't affect any of his systems or cause any downtime.

When the IT guy started asking questions about whether the software the boss wanted was the right choice, the boss pretty much told him that if he didn't want to take care of it, the boss would find someone who would. From what he had seen from the marketing videos on YouTube, the boss was pretty sure that even he could do it himself.

Eventually, the IT guy gave in and installed the software.

But not before discovering that the software required a whole bunch of new servers and needed to run on an operating system that no one in the IT department had any experience managing. So they hired a new IT guy that knows that particular operating system.

Meanwhile, the database administrators found out that the only database platform that was supported by this new software was not the database platform the company had standardized on. They bought more servers and hired a new DBA that was familiar with that database platform.

When it came time to customize the software to meet the company's needs, they found out that none of the application development team had the time or the skills required to do such customization. Luckily, one of the top contractors in that programming language happened to be available and could start immediately -- at a premium rate.

One day, as the exasperated IT guy was eating a sandwich and staring blankly at the lunchroom wall, the Webmaster guy -- who usually works on another floor, but was in the neighbourhood for a meeting -- walked in.

- "Whoa, you look like you have had a rough few months!" said the webmaster. It was meant as a joke, but it was also true.

The IT guy and the webmaster had known each other for a long time. They worked together when the IT department was just a handful of guys. Back then, the webmaster was just running the company's web site, but he had since started managing the company's intranet and portal.

- "Agh! I just found out that we're going to have to start a nightly export of our user data because the [expletives deleted] isn't compatible with our Active Directory. Of course!", was the IT guy's response. "And our first migration to the new system is going to take a lot longer than we expected and we'll need to ask the accounting department to stop working for two whole weeks while we migrate the system. And they're unhappy because it is the end of the fiscal quarter."

The webmaster asked cautiously: "And... what exactly does this new software do?"

The IT guy explained what the new software did.

- "Uh, you mean like what our current portal platform has available out of the box?" asked the webmaster. "That's what [redacted]'s team has been using for about two years now. The boss even sent an email congratulating the team for doing such a good job. I even demoed it to the boss!".

The rest of the tale isn't appropriate for this blog. But there was a lot of cursing and yelling. Let's just say that they lived miserably forever after, having to maintain that software that never truly worked the way it was intended.

Sadly, this kind of scenario happens more often than you'd think. You may have experienced this yourself where you work.

This post will explain some of the tips and tricks to use when buying new software that will help you make an educated decision.

Software acquisition model

I often hear people talk about "Buy vs Build" when discussing their software acquisition model.

In reality, you should always consider Re-use, Buy, and Build.

st=>start: Start
d1=>condition: Can I re-use?
d2=>condition: Can I buy?
e1=>end: Re-use
e2=>end: Buy
e3=>end: Build
st->d1(no)->d2(no)->e3
d1(yes)->e1
d2(yes)->e2

Re-use

Do you already own a piece of software that will meet your needs? It may be an unused part of something you already bought, or it may be an internal application that another department has already developed.

You should also consider open-source solutions as part of the re-use decision. Can you re-use open-source software that already exists out there, for free, to meet your needs?

Is there an add-on feature available for of the software you already own that would meet your needs? Even if it would cost you a little more to enable that feature?

If the answer is yes (or mostly yes), you should explore the possibility of re-using what's already available before buying.

Don't compromise, but don't miss what's already right in front of you either.

I recently went through this process with a client that uses Office 365 -- with SharePoint, Flow, and PowerApps at their disposal. They wanted to buy a piece of software because it called a third-party API and made it possible to trigger data workflows from the results of the API... which is something that they could already do with Flow.

They just didn't know that feature was available.

Before considering to buy a new piece of software, it is a good idea to take an inventory of what you already have.

Buy

If you can't re-use -- or doing so would deliver a less-than-optimal solution -- by all means buy something!

However, before you pull out your credit card, make sure to do a proper gap analysis. Compare what your existing software (if any) actually offers against what you really need it to do. Then use that same gap analysis criteria to compare with the software you want to purchase.

When considering buying, keep in mind the Total Cost of Ownership of that comes with every piece of software.

Don't know what Total Cost of Ownership means? Check this blog for an upcoming post on the subject.

After considering your gap analysis and total cost of ownership, if you can't find software that meets your needs (within the budget you've been given), consider building something.

Whatever you do, resist the urge to skip the buying option and go straight to building.

Build

Don't listen to developers (like me) who'll tell you "Oh, that'll take me a couple of weeks to build". Because it never does.

This may sound weird coming from someone who considers himself a developer at heart, but it is true.

They're not lying to you on purpose to protect their jobs. And it isn't a reflection of their skills. They really do mean well.

But most organizations suck at building software projects.

Don't take my word for it: The Standish Group is an organization that publishes a yearly Chaos Report. The report describes the state of the software development industry and seeks to identify the scope of software project failures, the major factors that cause software projects to fail, and the key ingredients that can reduce project failure. I highly recommend that you buy your own copy -- it is worth it. And I am not affiliated with The Standish Group in any way.

Last time I bought the report, a staggering 31.1% of software projects will be cancelled before they ever get completed. 52.7% of projects will cost 189% of their original estimates.

Back in 1995, the Standish Group estimated that American companies and government agencies spent $81 billion for cancelled software projects. They paid an additional $59 billion for software projects that were being completed but exceeded their original time estimates.

The average number of software projects that are completed on-time and on-budget is only 16.2% -- a number that goes a low as 9% in larger companies.

And once completed, those "successful" projects will only deliver approximately %42 of the originally-proposed features and functions.

It doesn't need to be an all-or-nothing situation

Nowadays, software is so much more open and versatile than it was many years ago. Yet, we still deal with software acquisitions as a giant monolith that cannot integrate with anything.

The ideal solution for your needs may very well be a hybrid solution: using the software you already own (re-use), adding a component or an app that meets most of your needs (buy), and making minor customizations to meet your exact needs.

Although this post isn't about Office 365, I often see organizations running Office 365, SharePoint Online and Dynamics 365, but they fail to fully recognize the capabilities available at their disposal.

Take a look at the various Office add-ins and Dynamics 365 AppSource for solutions that you can buy that will handle most of your needs. And, with Flow and PowerApps, you can easily configure your solutions to do exactly what you want. There are countless connectors available that may allow you to build a low-code or no-code solution that can adapt as your company's needs evolve.

The same applies to other products -- not just Office 365. Understand what you have so that you can fully leverage it before you look at buying or building something new.

Conclusion

Impulse buying is something that may be suitable for a pack of gum while you're waiting to pack at the grocery store, but it should never be an option when it comes to enterprise applications.

Make an educated decision, and follow an acquisition model that will help you find the ideal solution for your organization's needs.

I hope this helps?

Sources

Image Credit

Image by Arek Socha from Pixabay

Introduction

The SharePoint Design is a beautiful web site that provides design guidance on beautiful and fast sites, pages, and web parts with SharePoint in Office 365.

Unfortunately, the SharePoint Design site does not tell you how to create the beautiful web parts they show you.

This series is intended as a companion to the SharePoint Design site, providing you with code samples and detailed how-to information for every design topic. It should help you create web parts that look exactly like the ones on the SharePoint Design site.

Today's post is a companion to the Titles and descriptions for SharePoint web parts

What is a Web Part Title?

A Web Part title is a heading that appears above a web part to visually separate the web part from other content and to provide information to users about the purpose of a web part.

Source: Microsoft

Most often, web parts provide a default title that page authors can override (or remove) to customize the web parts to suit their needs.

What is a Web Part Description

The Web Part description is often smaller text within the web part that describes the web part content. For example, the caption element in the Image web part.

The caption in the image web part is an example of a web part description

Just like the title, page authors can override or omit the web part description to suit their needs.

It is important to not confuse description with alternative text. The description is optional, but it is always visible.

The alternative text is something that you should always provide for visual content (such as images, charts, etc.). We'll discuss alternative text in a later post.

Why use titles and descriptions?

Understanding

Back in my McKinsey & Company days, a wise person told me:

When you leave it up to your audience to make an assumption, you lose control over that assumption

Seems kind of obvious, but what they meant was that if you present information to users and expect them to make a conclusion from it, they may make the wrong conclusion.

Don't assume your users are stupid, but don't assume that by just putting a web part on the page that they will understand the purpose of the web part is.

Titles and descriptions are a consistent way to indicate to your users the purpose of your web part.

Accessibility

When I tell people about accessibility requirements in software design, most people's initial reaction is often something like:

"We don't have any blind people [who work here], so we don't need to worry about accessibility"

Unfortunately, accessibility is more than about blindness. It is estimated that more than 10% of the population live with a disability.

Accessibility is actually broken down into 4 categories:

  • Visual: This can include blind (or non-sighted) users, but also includes users with low-vision, users with obstructed vision, or age-related visual impairment. This includes color blindness, which affects 1 in 12 men (or about 8%) and 1 in 200 women.
  • Auditory: This includes hearing-impaired users.
  • Motor: People with motor impairments includes people with Repetitive Stress Injuries, Cerebral palsy, Parkinson’s and Muscular dystrophy.
  • Cognitive: which relates to the ease processing of information. It includes people with autism, Down's syndrome, Dyslexia, or global developmental delay. Consider stroke victims and concussion victims as well.

Accessibility may be a permanent or temporary condition. People often (wrongly) assume that web accessibility is only for people with permanent or long-term disabilities, but accessibility benefits people with or without disabilities. For example, it may impact the following people:

  • People who are not fluent in English.
  • People who do not have or are unable to use a keyboard or mouse.
  • People with temporary disabilities due to accident or illness.
  • Older people.
  • New users.

Plus, in some countries, you are now legally obligated to provide accessible resources to your employees.

Lucky for you, the use of titles and descriptions in web parts help alleviate some of the accessibility issues by providing headings, plenty of white space on the page, visually grouping information together, and even tackling colour contrast requirements on the page -- to name only a few benefits.

When should you use titles

Let's pretend for a second that we are not talking about SharePoint, but Microsoft Word instead. You can add things in a Word document that will make the body of the document. Things like text, images, hyperlinks, page breaks, etc. When you insert such elements in a Word document, you want them to blend in together and print nicely.

If your web part is intended to blend in with other content within your SharePoint page, you probably don't need a title. For example, the following out-of-the-box web parts do not need a title:

  • Text
  • Image
  • File viewer
  • Link
  • Embed
  • Divider
  • Markdown

On the other hand, if your web part provides a new set of information that should be visually distinctive from the rest of your page content, it should probably provide a title.

For example, if you add the Events web part or the Document library web part in a page, you probably don't want to dump a list of events or documents in between two paragraphs without providing context to your users.

This decision tree may help. You can tell that it was written by a consultant because one of the answers is "it depends".

st=>start: Does my web part need a title?
e=>end: No title needed
cond=>condition: Does your web part 
need to blend in?
cond2=>condition: Does it need to visually
group information?
io=>parallel: No title needed
para=>end: Title is recommended
st->cond
e3=>end: It depends :-)
cond(yes)->e
cond(no)->cond2(yes)->para
cond2(no)->e3

When should you use descriptions

Unlike the web part title, the description is not intended to visually distinguish the web part. It is purely intended to help understand the purpose or context of a web part. I recommend providing the option for a description in your web parts if the body of your web part mostly consists of images or graphics. For example, a chart web part, or an image.

In my Comparer web part, which allows users to compare two images side-by-side, I allow editors to provide a description for each image.

How to add a web part title to your web part

If you want to see the sample web part I'm building in this post, visit the GitHub repo and open the WebPartTitles solution. The web part is called EditableTitle

I chose the web part title as the first in this series because it is probably the easiest one to implement. That's because the PnP Reusable Controls library provides a perfect Web Part Title component.

Web Part Title Control in Action

Here is how to add the web part title in your SPFx web part solution using React:

  1. Using your Node.js Command Prompt (or whatever terminal you wish to use), make sure that you're in your web part solution's root directory. (It should be the same folder where your solution's package.json is located).
  2. Type the following command to add the PnP Reusable Controls library to your project:
    npm install @pnp/spfx-controls-react

    > Note: most instructions tell you to use npm install @pnp/spfx-controls-react --save --save-exact, but the --save parameter is considered obsolete now. Feel free to use what you feel most comfortable with.

  3. In your web part class (the one that extends BaseClientSideWebPart), add a property to store your web part's title. If your web part is called EditableTitle, you would open the EditableTitleWebPart.ts file, and look for the IEditableTitleWebPartProps interface (usually located at the top of the file. Add the following code:
    export interface IEditableTitleWebPartProps {
    description: string;
    // BEGIN Add to support web part title
    title: string;
    // END Add
    }
  4. In your render method, your web part will need to pass the web part title, the displayMode (to determine whether the web part is in Edit Mode or Read Mode), and a function to handle changes to the title. All these things get passed to the component that is responsible for rendering the body of your web part. Your new render method will look like this:

    public render(): void {
    const element: React.ReactElement<ieditabletitleprops> = React.createElement(
      EditableTitle,
      {
        description: this.properties.description,
        // BEGIN: Add to support web part title
        // Don't forget that you need to add a comma at the end of the previous line
        title: this.properties.title,
        displayMode: this.displayMode,
        updateTitle: (value: string) => {
          this.properties.title = value;
        }
        //END: Add
      }
    );
    
    ReactDom.render(element, this.domElement);
    }

    If you get a nasty error message when you add the code, don't worry. We just haven't defined a title, displayMode, and updateTitle property for your component yet.

    Error message in code
    Don't worry about this error message, we'll fix it soon

  5. Open your component's I[YourWebPartName]Prop interface (located under src\webparts\[YourWebPartName]\components\I[YourWebPartName]Props.ts). Since my web part is called editableTitle, I'll open src\webparts\editableTitle\components\IEditableTitleProps.ts and add an import for the DisplayMode at the top of your file:
    import { DisplayMode } from '@microsoft/sp-core-library';
  6. Add the properties your component will need to support the web part title in your I[YourWebPartName]Props interface:
    export interface IEditableTitleProps {
    description: string;
    //BEGIN: Add support for web part title
    title: string;
    displayMode: DisplayMode;
    updateTitle: (value: string) => void;
    //END: Add
    }
  7. Open the web part's component TSX file (located under src\webparts\[YourWebPartName]\components\[YourWebPartName].tsx) and add an import for the WebPartTitle control:
    import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
  8. In your [YourWebPartName]'s render method, add the WebPartTitle control. You should add it immediately after the first opening <div>:
    public render(): React.ReactElement<ieditabletitleprops> {
    return (
      <div classname="{styles.editableTitle}">
        {/* BEGIN: Add to support web part title */}
        <webparttitle displaymode="{this.props.displayMode}" title="{this.props.title}" updateproperty="{this.props.updateTitle}">
        {/* END: Add */}
        <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>
      </webparttitle></div>
    );
    }

    Note: some people prefer to add the web part title within the second div -- the one with the container CSS class. I'll be removing that div in future code samples, but feel free to do as you please.

  9. Run gulp serve to test your web part and try editing your web part's title

The Web Part Title in action

Adding a default title

To make things easier for your users, you should provide a default title for your web part. Try to use a title that users won't immediately have to change (like Insert Title Here). For example, if your web part retrieves a list of recently added documents, try using Recent documents. If your web part displays events, try Upcoming events or Events.

Since the web part title is stored in a web part property (we called it title in our sample above), the easiest way to add a default title is to use your web part manifest's preconfiguredEntries to provide a default title.

To do so:

  1. Open the [YourWebPartName]WebPart.manifest.json file (located under the src\webparts\[YourWebPartName] folder)
  2. Locate the preconfiguredEntries JSON node
  3. Under the "properties" node, add the default web part title as follows:
    "properties": {
      "description": "EditableTitle",
      "title": "My Default Web Part Title"
    }
  4. Run gulp serve again. If your solution was already running, you need to stop the existing gulp serve and start a new one, otherwise, the updated manifest won't be reflected in your web part.
  5. If your web part was already running, remove it from the workbench, refresh the page, and re-add the web part. You should see your updated web part, now with a default title:

My Default Web Part Title

Adding a placeholder title

If your web part title is optional, or you prefer to start with a blank title, you should add a placeholder.

For example, the People web part uses People profiles for the placeholder title. It shows the title placeholder when your page is in Edit mode, but if you don't specify a title and save your page to view it in Read mode, the title will disappear.

People web part in edit mode shows placeholder
People web part in edit mode shows placeholder

People web part in reading mode hides placeholder
People web part in reading mode hides placeholder

To implement this functionality, follow the same steps you did to add the web part title, but don't provide a default title. Then:

  1. In the [YourWebPartName].tsx file, find your WebPartTitle component in the render method.
  2. Add the placeholder prop with the placeholder title you want to use.
    <webparttitle displaymode="{this.props.displayMode}" title="{this.props.title}" updateproperty="{this.props.updateTitle}" placeholder="{"Web" part="" placeholder"}="">

Note: please consider localizing your placeholder text. I'll explain localizing in a later post.

If you want to see the sample web part I'm building in this post, visit the GitHub repo and open the WebPartTitles solution. The web part is called PlaceholderTitle

How to add a description

Unfortunately, there isn't a WebPartDescription component that you can use to easily add a description section to your web part.

Fortunately, we can cheat and use the WebPartTitle component and apply some CSS magic to get the same results.

Here is how:

  1. If you haven't done so already, add the PnP Reusable Controls library to your project by typing the following command:

    npm install @pnp/spfx-controls-react
  2. In your web part class (located under src\webparts\[YourWebPartName]\[YourWebPartName]ts. Add a property to store your description. Since the default web part already has a description prop, I used webPartDescription, but in real life, I'd use the description prop for that purpose.

    export interface IEditableTitleWebPartProps {
    description: string;
    // BEGIN Add to support web part description
    webPartDescription: string;
    // END Add
    }
  3. In your render method, your web part will need to pass the web part description, the displayMode (to determine whether the web part is in Edit Mode or Read Mode), and a function to handle changes to the description. All these things get passed to the component that is responsible for rendering the body of your web part. Your new render method will look like this:

    public render(): void {
    const element: React.ReactElement<ieditabletitleprops> = React.createElement(
      EditableTitle,
      {
        description: this.properties.description,
        // BEGIN: Add to support web part description
        // Don't forget that you need to add a comma at the end of the previous line
        webPartDescription: this.properties.webPartDescription,
        displayMode: this.displayMode,
        updateDescription: (value: string) => {
          this.properties.webPartDescription = value;
        }
        //END: Add
      }
    );
    
    ReactDom.render(element, this.domElement);
    }
  4. Open your component's I[YourWebPartName]Prop interface (located under src\webparts\[YourWebPartName]\components\I[YourWebPartName]Props.ts). Since my web part is called webPartDescription, I'll open src\webparts\webPartDescription\components\IWebPartDescriptionProps.ts.

  5. If you haven't done so already, add an import for the DisplayMode at the top of your file:

    import { DisplayMode } from '@microsoft/sp-core-library';
  6. Add the properties your component will need to support the web part description in your I[YourWebPartName]Props interface:

    export interface IWebPartDescriptionProps {
    description: string;
    //BEGIN: Add support for web part description
    webPartDescription: string;
    displayMode: DisplayMode;
    updateDescription: (value: string) => void;
    //END: Add
    }
  7. Open the web part's component TSX file (located under src\webparts\[YourWebPartName]\components\[YourWebPartName].tsx).

  8. If you haven't done so already, add an import for the WebPartTitle control:

    import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
  9. In your [YourWebPartName]'s render method, add the WebPartTitle control where you would like the description to appear. We also add a placeholder (Add a description to override the default Web Part Title placeholder that comes with the WebParttitle component). In my sample, I'll add it below the container div:

    public render(): React.ReactElement<iwebpartdescriptionprops> {
    return (
      <div classname="{" styles.webpartdescription="" }="">
        <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>
         {/* BEGIN: Add to support web part description */}
         <webparttitle displaymode="{this.props.displayMode}" title="{this.props.webPartDescription}" updateproperty="{this.props.updateDescription}" placeholder="{"Add" a="" description"}="">
        {/* END: Add */}
      </webparttitle></div>
    );
    }
  10. To change the CSS for the web part description, we'll add some CSS. Open the [YourWebPartName].module.scss (located under src\webparts\[YourWebPartName]\components\[YourWebPartName].module.scss) and add the following CSS just above the .container class:

    .descriptionElement textarea {
    font-size: 14px;
    font-weight: 400;
    line-height: 1.6em;
    overflow-x: hidden;
    text-overflow: ellipsis;
    color: $ms-color-neutralPrimary;
    }
    
    .descriptionElement__NoMargin textarea  {
    margin-bottom: 0;
    }
    
    .descriptionElement__centerAlign textarea {
    text-align: center;
    }

    We're defining three CSS classes here: descriptionElement to render a smaller font, descriptionElement__NoMargin to remove the bottom margin, and descriptionElement_centerAlign to align the description at the center of the web part. You can use all three CSS styles, or only descriptionElement as it suits you.

  11. Go back to your [YourWebPartName].tsx.

  12. Add a reference to the css() function at the top of the file.

    import { css } from "@uifabric/utilities/lib/css";

    The css() function allows you to combine multiple CSS classes. To do so, I added. If you want to read more about the css() function, read my post.

  13. Add the CSS prop to the WebPartTitle you added in step 9. Use whatever combination of the three CSS classes we defined earlier as you desire (I use all three here):

         {/* BEGIN: Add to support web part description */}
         <webparttitle displaymode="{this.props.displayMode}" title="{this.props.webPartDescription}" updateproperty="{this.props.updateDescription}" placeholder="{"Add" a="" description"}="" classname="{css(styles.descriptionElement," styles.descriptionelement__nomargin,="" styles.descriptionelement__centeralign)="" }="">
        {/* END: Add */}
  14. Run gulp serve and test your web part.

You should get something that looks like this:

Web part description

Note: I really dislike the default shadowy border that comes with every Yeoman-generated SPFx web part because it isn't found anywhere else in the out-of-the-box SharePoint web parts. I always remove it from my projects. Although this is outside of the scope of this post, if you want to remove it, simply comment out the box-shadow line in the .container CSS class of your [YourWebPartName].module.scss. You'll get the following result:
Web parts without borders

If you want to see the code for this web part, visit the GitHub repo and open the WebPartTitles solution. The web part is called WebPartDescription. I know, I didn't think the web part name through when I built it.

UI text guidelines for Titles and descriptions

I've mostly copied this from the UI text guidelines for SharePoint web parts, but I include it here (and will continue to include the relevant sections throughout this series) because it is important to use consistent User Interface (UI) text within your web parts.

Capitalization

Use sentence casing (the first letter of the first word is capitalized, the rest all lowercase) for all titles.

Always capitalize:

  • The first word of a web part title or description
  • The word following a colon in a title. For example, "Step 1: Begin by entering your account information."
  • Proper nouns, such as the names of people, cities, and so on.

Punctuation

Don't use periods in titles. For other punctuation types, follow the basic rules of punctuation.

Voice and tone

If you want your users to fall in love with your web parts, make sure to use the right tone in your UI text. It will help build a strong, lasting relationship with your users.

Try to keep your words crisp and clear, warm and relaxed, and approachable.

Follow these simple tips:

  • Use a casual, conversational tone in the UI.
  • Use contractions. For example, use "can't" instead of "cannot".
  • Read your UI text out loud to test the tone. Does it sound like everyday language?
  • Use simple words.
  • Remove technical details if they're not relevant to the user experience.
  • Use "Please" only if you are inconveniencing the user. Avoid overuse.
  • Use "Sorry" only in error messages in SharePoint that result in serious problems for the customer.

Pronouns

Avoid pronouns if you can.

If you must use pronouns, follow these rules:

  • Use second person ("you" or "your") when you're presenting something that belongs to the user. For example, "Your drafts" or "Your images".
  • Use the first person ("me" or "my") for UI in which the user instructs the service to do something. For example, "Alert me when someone responds to my post."
  • Use "they" or "their" as a singular possessive modifier to avoid awkward "he/she" or "his/her" constructs. Ideally, rewrite the sentence as plural if possible.
  • Avoid using "them"; instead, use words like "someone" or "people". For example, "Enter a user name and domain to give someone permission to use this PC."

If you want to sound really cold and snobby, use third person references. Instead of saying "Users can change the layout", use a phrase like "You can change the layout".

Hint text

Hint text, or ghost text, is the text element you display in a UI element to help the user interact with the UI.

In the case of titles and descriptions, your hint text is your placeholder attribute. It should give information about what the user should enter.

You should try to use hint text sparingly, and only if it helps the user. Because titles and descriptions are not usually visible on a web part if they are empty, this is an instance where you should definitely use hint text.

Conclusion

This post described how to add titles and descriptions to your web parts in accordance with the SharePoint Design Principles.

In future posts, I'll explain the other design areas... but since the other design areas are a bit more complicated, I'll break each down into a few smaller posts.

I hope this helps?

Thanks

This post wouldn't have been possible without the amazing work from the SharePoint team. The technical writers don't often get the glory, so we should especially thank Linda Caputo and David Chestnut for their contributions.

More Information

I could have gone on for days about some design topics in this article, but I'll let the actual experts do the talking:

Update

  • July 12, 2019: Added the UI text section because it was driving me nuts that I had omitted it in the first place.
  • July 17, 2019: Fixed some embarassing spelling mistakes. I thought I was on a roll and writing well, but it turns out my spell checker was simply turned off.

Introduction

Sometimes you just need to figure out what version of the SPFx Yeoman generator is installed on someone's machine.

I got tired of having to look it up all the time, but I can never find the command (probably because it is too obvious for most people to write it down?).

So here is a note for myself, but I hope it helps somebody else too one day.

Getting the version number

To find the version of your SPFx Yeoman generator, follow these steps:

  1. Launch a Node.js command prompt command or whatever terminal you use
  2. Type the following command:
    npm ls -g --depth=0 @microsoft/generator-sharepoint
  3. Wait...
  4. The response should look a little like this:
    @microsoft/generator-sharepoint@1.8.2

The command isn't specific to the SPFx Yeoman generator. It can be used for any NPM package. Here is what it really does:

  • npm indicates a Node Package Manager command.
  • ls means to list packages
  • -g means that you want to list the global packages. If you don't use -g, you'll only list the packages installed in the current solution (assuming that you're currently in a folder that contains a solution)
  • --depth=0 means that you only want the top-level modules. In other words, you don't want to list all modules that includes the package you're looking for.
  • @microsoft/generator-sharepoint is the actual package you want to list. You can actually put whatever package you want here. For example, npm ls -g --depth=0 yo would tell you what version of the Yeoman generator is globally installed, and npm ls --depth=0 office-ui-fabric-react would tell you what version of Office UI Fabric React is currently installed in your current solution.

How to check if Yeoman has an update for you

As Stefan Bauer pointed out, if you don't want to know which version of the SPFx Yeoman generator you have installed, but you want to see if there is an update, you can follow these steps:

  1. Launch a Node.js command prompt command or whatever terminal you use
  2. Type the following command:
    yo
  3. Yeoman will greet you. If you have an update available, Yeoman should tell you right away (see how my Office generator has an update in the screen shot below)
    Office (Heart) Update Available!
  4. Select Update your generators
  5. Yeoman will prompt you to select the generators you want to upgrade. Use the spacebar to toggle which generators you want to update, then press Enter
    Selected generator-office
  6. Yeoman will do its thing, then will tell you I've just updated your generators. Remember, you can update a specific generator with npm by running npm install -g generator-______. Good to know Yeoman, good to know.
    Upgraded!

Conclusion

This article shows you how you can use a standard NPM command to query what version of the SPFx Yeoman generator is installed on a workstation.

You can use the same command for any NPM package, but in my particular case, I just wanted to remember how to diagnose the version of the SPFx Yeoman generator.

There may be an easier/faster way to do this. If you know a different way, please share with the rest of the class.

I hope it helps?

Update

  • Thanks to Stefan Bauer (https://n8d.at/) for confirming that there aren't any faster ways to do this, and for also suggesting that I explain how the command works. The section about using yo to update your generators was also his idea. Stefan is someone that I respect immensely and I truly appreciated his feedback!

Introduction

Anyone who has worked with me on a SharePoint project knows that I firmly believe that a good custom web part must be indistinguishable from the out-of-the-box SharePoint web parts. They need to look and behave like they were written by whichever awesome team at Microsoft is responsible for writing those things.

Someone at Microsoft must feel the same way as I do because they took the take to create a nice SharePoint Design web site dedicated to the SharePoint design principles; not just for web parts, but for sites and pages as well.

If you haven't visited it yet, I encourage you to do so now. Go ahead, I'll wait.

I like that Microsoft is documenting their design principles for web part look and feel, but they do not explain how to achieve the awesome look they show.

That's why I've created this series of posts on how to build SharePoint web parts, using SPFX, that follow the Microsoft design principles. Each post is intended as a companion to their respective section in the SharePoint Design site.

At the end of this series, you'll be able to build beautiful web parts that will conform to Microsoft's design principles and that will be indistinguishable from the out-of-the-box web parts.

In other words, the perfect web part.

Why should I follow Microsoft's design principles for SharePoint web parts?

Luke Wroblewski, a Product Director at Google once wrote:

“Getting in the way of a speeding freight train usually doesn’t end well. It takes a lot of effort to shift the course of something with that much momentum. Rather than forcing people to divert their attention from their primary task, come to where they are.”

Every web part with custom styles, fonts, and designs that are hosted within a page competes for your user's attention -- in a bad way. You're asking your users to learn a new user interface with every custom-layout web part you create.

Instead of focusing on your content, your users have to struggle just to make sense of your user interface.

In UX (user experience) circles, that's a concept called Cognitive overload.

Cognitive overload is often caused by overstimulation. If you want a good example of overstimulation, go visit LingsCars.com and notice how you'll have to struggle to take in all the information on that page.

A good example of overstimulation
LingsCars.com

In user experience, it is often said that:

The best user experience is the one the user doesn't notice

In SharePoint, your users are already familiar with the web part user interface and layout. They know where to look for the "Show all items" option, or what happens when they click on the pencil icon.

In this awesome article on cognitive load, they say that a way to reduce cognitive overload is to Follow time-proof conventions:

Don't reinvent the wheel. Users don't wanna take another driving lesson.
Dana Kachan

Following the SharePoint design principles for web parts is to follow an existing convention established in SharePoint and Office 365.

Building trust

Many years ago, I was working with a brilliant developer. Well, he did all the work while I attended meetings and demoed all his hard work, pretty much.

One day, he figured out a problem to a very difficult issue. I can't remember what it was, but it was one that most people we talked to said that it couldn't be solved.

When he demoed it to me, I was impressed, and I told him so. But then I pointed out that he was using the wrong font and colours, and that there was a spelling mistake on his screen.

(I'm talking comic sans with italic and ugly green fonts. Yuck!!!)

He was shocked. He had just solved an impossible problem and I was complaining about a minor user interface issue?!!?

I explained to him that when we'll demo his code to the client -- who has no appreciation for how complicated the issue was and how amazing the solution was -- all he will see is the ugly fonts, colours, and spelling mistakes. Instead of seeing a professional-looking solution, he'll see something that looks amateurish. It will break -- or at least chip away at -- the trust he has in us.

A more recent example of this is when Game of Thrones was in its last season, people got really upset about a coffee cup that was visible in one of the scenes.

Thar be mermaids!

Everybody knows that Game of Thrones was not really filmed in a fantasy time where dragons existed, right?

So why did people get upset?

Because the coffee cup that was carelessly forgotten in a shot chipped away at people's trust and respect for what was otherwise a beautifully produced show. It happened in the last season of the show when people were starting to criticize the writing and the rushed pace of the final episodes, and many people couldn't overlook it.

When you design your own look and feel within SharePoint, you're also chipping away at your user's trust.

Who should read this series?

If you're a designer who's anti-Microsoft and says "SharePoint looks like crap" and "I can do a better job myself", you're absolutely right. You don't need to read this series of blog posts.

If you're a developer who is new or somewhat experienced with creating SPFx web parts, but typically doesn't pay attention to how your web parts look -- as long as they work, you may find this series of posts useful.

Next article

Join me tomorrow for the first real article in the series: Web Part Titles.

Introduction

In my last two posts, I covered how to use the SharePoint Get items action in Flow and how to tell if the SharePoint Get items action returned items (by counting them).

I really wanted to provide a real-life sample how one would use the two concepts together in Flow.

Since we just had a national holiday and I completely forgot about it (got ready to go to work and everything), I thought I'd create a sample flow that automatically runs on a schedule and prompts users to do something, except when today's date is a holiday.

The workflow logic looks a little like this:

st=>start: Start (every n days)
e=>end: End
op2=>operation: Get today's date
op3=>operation: Find statutory holidays
with today's date
op4=>operation: Today is not a holiday
sub1=>subroutine: (Do something)
cond=>condition: Is today a holiday?
(Did you find any items)
io=>operation: Today is a holiday
(Do nothing)
st->op2->op3->cond
cond(yes)->io->e
cond(no)->op4->sub1->e

Let's get started by creating the environment we need for this workflow.

Creating a list of statutory holidays

In this example, we'll create a SharePoint list which will contain an entry for every statutory holiday. We'll use a list because it allows our HR folks to maintain it without needing a special app. We can also show the list on our SharePoint site so all employees can see what days are statutory holidays.

You can also use your own database, or an API, or even a static Excel spreadsheet if you want, but I wanted to use a SharePoint list to show how to use the SharePoint Get items action in Flow.

To create the list, follow these steps:

  1. From a SharePoint site, go to Site contents via the local navigation or the Settings option.
  2. In the Site contents, select + New then choose List from the drop-down menu.
  3. In the Create list pane, enter Statutory Holidays as the list's Name. Check or uncheck Show in site navigation depending if you want your users to see the list or not.
  4. Select Create to create the list
  5. In your newly created list, select + Add column then select Date from the drop-down list.
  6. In the Create a column pane, enter Date for the column Name. Set Include time to No and select Require that this column contains information to Yes under More options. This list doesn't make much sense if you don't require a date for each stat holiday.
  7. Select Save to create the column.

If you need to support statutory holidays for multiple states/provinces/countries, feel free to add more columns to your list to support your needs. I wanted to keep this list as simple as possible.

Why didn't I use a calendar list? I didn't want to add the extra columns that come with a calendar list. If you really want a calendar view, just add it as a custom view for your list.

Use your list's Quick edit to enter your statutory holidays. I use this site to get the list of statutory holidays for every year.

When you're done, you should have a list that looks like this:
Statutory Holidays, Canadian Style

Now let's create a scheduled flow that uses the list!

Creating a scheduled flow

  1. From https://flow.microsoft.com, go to My flows to view your list of flows.
  2. From the + New menu, select Scheduled--from blank
  3. In the Build a scheduled flow window, give your flow a descriptive Flow name. I named mine Prompt managers to approve timesheets.
  4. Under Run this flow, select the schedule that suits your needs. I want mine to go once a week on Mondays, so I selected 1 week under Repeat every, then selected M under On these days and unselected every other day.
    file.
  5. Select Create to create your workflow.

Your workflow will be created and open in the workflow editor. I renamed the Recurrence action to Every Monday because I always want my workflows to be easy to understand without having to expand every action.

Unfortunately, Flow won't let you save until you add another action.

Funny, cause that's exactly what we'll do next!

Connecting to the Statutory Holiday SharePoint list

Before we can access the Statutory Holidays list in SharePoint, we need to add a connection to SharePoint by following these steps:

  1. From within your flow editor, select +New step at the bottom of the flow.
  2. In the Choose an action prompt, type Get items in the Search connectors and actions. Search is case insensitive.
  3. Select the Get items action with a SharePoint logo from the list of Actions that appears. If the search query returns too many actions and you can't find the SharePoint Get items, you can filter out all other connectors by clicking on SharePoint just below the search bar.
  4. As soon as you select Get items, the Choose an action box will transform into the Get items box.
  5. If you haven't created a connection to SharePoint yet, you'll be prompted to Sign in to create a connection to SharePoint. Click Sign in to sign in with the account that you wish to use to access SharePoint.

    The account you use here specifies who will access SharePoint. Make sure that you use an account that can see the site and the list where you want to get items from. It is a good idea to use a service account that isn't using your own credentials to connect.

  6. Once connected, enter URL to the site that contains your list under Site address. If you experience problems typing or pasting the URL, try selecting Enter a custom value from the drop-down; it will turn the drop-down box into a text box.
  7. If the site URL you entered is valid and the credentials you supplied are correct, you should be able to pick the Statutory Holidays list from the List Name drop down.

Adding a filter to retrieve today's statutory holidays

If you ran the flow now, it would retrieve every statutory holiday in the list.

We want SharePoint to return only statutory holidays on the days the flow runs. To do this, we'll add an ODATA filter by following these steps:

  1. In the new Get items action you just created, select Show advanced options
  2. In the Filter Query field, enter Date eq datetime''.
  3. Place your cursor between the two single quotes you just typed and select Add dynamic content
  4. Select the Expression tab
  5. Scroll to the Date and time category and select See more, then select formateDateTime(timestamp, format) to insert it in the expression field.
  6. Making sure your cursor is between the two parentheses of the formatDateTime function, find the utcNow() function in the Date and time category.
  7. After utcNow() but before the last ), type ', 'yyyy-MM-ddT00:00:00') and select OK to insert the expression.
  8. In the Top Count field, enter 1 -- we only need to know if there is a statutory holiday or not, so we don't need to return more than one.
  9. In the Limit Columns by View, select All Items. This will ensure that we only return the Title and Date columns, instead of returning every single column in the list.

    If you want to test your flow, save it and use Test in the upper right corner. You can add a temporary list item in your statutory holidays list with today's date to see that SharePoint returned something.
    Test worked

If everything goes well, your flow is now able to retrieve statutory holidays from the SharePoint list every time your flow runs.

Now let's add logic to detect whether something was returned or not...

But before we do, let's rename the Get items action to Retrieve statutory holidays for today's date to make it easier to read. Hey, my blog, my naming conventions 🙂

Count how many statutory holidays were returned for today's date

As I explained in my previous post, I like using variables to make my flows easier to debug and easier to understand. We'll store the number of items returned in a variable called Number of statutory holidays.

Since this is the first time we set the variable, we'll use Initalize variable using the following steps:

  1. In the flow editor, select +New step
  2. From the Choose an action box, type variable in the search box.
  3. From the list of suggested actions, select Initialize variable.
    4.An Initialize variable box will replace the Choose an action box. Give your variable a descriptive Name. For example: Number of statutory holidays.
  4. In the Type field, select Integer -- because we'll be storing the number of items returned.
  5. We'll write the expression to calculate the number of items returned the Value field. If the dynamic content pane doesn't show, select Add dynamic content, the select Expression.
  6. Look for the length(collection) function in the Collection category and select it to insert it in the expression box. The length function is specifically designed to calculate how long a collection of items is -- and that's what the Get items action returns: a collection of items.
  7. Make sure your cursor is positioned between the two parantheses () in the length function. Select the Dynamic content tab and look for the value dynamic content for the Retrieve statutory holidays for today action.
  8. Flow will automatically insert body('Retrieve_statutory_holidays_for_today''s_date')?['value'] inside your length() function. The final expression should be:
    length(body('Retrieve_statutory_holidays_for_today''s_date')?['value'])
  9. Select OK to insert the value.

Save and test your flow. Mine returned 1 item:
1 item returned

Testing if any items were returned

Now that you have a variable that contains the number of statutory holidays, you can use it anywhere you want.

Let's create a conditional branch to do something if today is not a statutory holiday:

  1. In the flow editor, select +New step
  2. From the Choose an action box, select Control then Condition.
  3. A Condition box will replace the Choose an action box. Give your condition a descriptive name. For example: Is today a statutory holiday.
  4. If the Choose a value box, use Add dynamic content to select the variable you created earlier.
  5. In the next field, select is greater than
  6. In the next field (Choose a value) enter 0.
    Is today a stat holiday?

Save and test your flow. If everything worked well, the Expression value from your condition should return true if SharePoint found items, and false if nothing was found. My test returned true.

We have a stat holiday!

That's it! Now you can insert actions under If no to do something when today isn't a statutory holiday.

You could even add something under If yes to delay the flow until next day, but that's another post.

Conclusion

You can use Scheduled flows to run every n days and easily query a SharePoint list containing statutory holidays to skip running when the current date is a statutory holiday.

Note that in today's sample, I didn't deal with timezones by setting the start time of my workflow so that it is later than midnight in UTC time. If you run your workflow across multiple timezones, you should keep this into consideration.

I hope this helps you create workflows that know when to take it easy.

Because everyone deserves a vacation once in a while!

Photo credits

Image by Free-Photos from Pixabay

Introduction

Im my previous post, I explained how to use the SharePoint Get items action in Flow. As the name implies, it retrieves items from a SharePoint list.

Sometimes you need to know if your Get items action returned any items. For example, if you wanted to update an existing item or create a new item in none was found.

In this post, I'll show you how to count how many items were returned by SharePoint and how to test if any items were found.

And don't worry, this post won't be as long as the last one.

Counting results from SharePoint Get items

For the purpose of this example, we'll assume that you already created a flow with a SharePoint Get items action. If you haven't done so yet, take a look at my previous post.

Sample flow

When I have to use fancy formulas in many places within my flow, I like to define a variable. That way, I can just refer to the variable instead of re-entering the formula in many places.

You should always strive to make your flows easy to read so that if someone else has to maintain it (or if you have to come back to it later), it will be easy to understand what the flow does. Make sure to give your actions a descriptive name (not Get items like in my example, use something like Get existing responses from current user, for example). Using variables is another way to make your flows easier to use.

When using variables in flow, you use a different action to define a variable the first time (Initialize variable) than you would to set the variable or change its value (Set variable, Increment variable, and Decrement variable for example).

Since this is the first time we set the variable, we'll use Initalize variable using the following steps:

  1. In the flow editor, select +New step
  2. From the Choose an action box, type variable in the search box.
  3. From the list of suggested actions, select Initialize variable.
    Initialize variable
    4.An Initialize variable box will replace the Choose an action box. Give your variable a descriptive Name. For example: Number of existing items.
  4. In the Type field, select Integer -- because we'll be storing the number of items returned.
  5. We'll write the expression to calculate the number of items returned the Value field. If the dynamic content pane doesn't show, select Add dynamic content, the select Expression.
    Using an expression
  6. Look for the length(collection) function in the Collection category and select it to insert it in the expression box. The length function is specifically designed to calculate how long a collection of items is -- and that's what the Get items action returns: a collection of items.
    Length function
  7. Make sure your cursor is positioned between the two parantheses () in the length function. Select the Dynamic content tab and look for the value dynamic content for the Get items action (or whatever your SharePoint Get items action is called).
    Inserting body of get items
  8. Flow will automatically insert body('Get_items')?['value'] inside your length() function. The final expression should be:
    length(body('Get_items')?['value'])
  9. Select OK to insert the value.
    Formula inserted

Save and test your flow. Mine returned 1 item:
1 item returned

Testing if any items were returned

Now that you have a variable that contains the number of items, you can use it anywhere you want.

For example, if you wanted your flow to do something if any items were returned, and something else if nothing was returned you would follow these steps:

  1. In the flow editor, select +New step
  2. From the Choose an action box, select Control then Condition.
    Condition
  3. A Condition box will replace the Choose an action box. Give your condition a descriptive name. For example: Are there any existing items.
    Adding a condition
  4. If the Choose a value box, use Add dynamic content to select the variable you created earlier.
  5. In the next field, select is greater than
  6. In the next field (Choose a value) enter 0
    Condition for more than 0 items

Save and test your flow. If everything worked well, the Expression value from your condition should return true if SharePoint found items, and false if nothing was found.
We found items

Of course, you would want to add actions to your If yes and If no paths, but that's for another post.

Conclusion

The key to testing if the SharePoint Get items action returned items it to understand that Get items returns a collection of items. Using the length() function against the return value of your Get items action will tell you the length of your collection of items.

I could have avoided using a variable and just entered the length(body('Get_items')?['value']) formula directly in the condition, but I wouldn't be able to tell how many items were returned when I was testing the flow. This sample was an easy one, and I really didn't need to evaluate how many items were returned more than once -- so I really didn't need a variable -- but in more complicated flows, you'll find it a lot easier to define variables and use the variables throughout instead of copying the same formula every time.

I hope this helps?

Introduction

I love Flow (and Logic Apps)!

Favourite thing to do with Flow is doing demos and workshops!

When I meet a new customer who tells me they have a business problem, I love to put together a quick proof of concept how to solve their business problem using a no-code solution, involving SharePoint, PowerApps, and Flow -- right there, in front of them, while projecting. No safety nets, PowerPoint or scripted demos.

The SharePoint connector is by far my most frequently-used connector, because it allows me to quickly query, create, and update content in SharePoint as part of my solutions.

Even if I have used this connector many times, I sometimes get demo blindness and I forget how to use it when I'm in the middle of a demo.

This article explains how to use the SharePoint connector and the GetItems action to retrieve items from a SharePoint list.

Hopefully, next time I forget how to use it in the middle of a demo, this article will show up in the search results.

NOTE: This article focuses on Microsoft Flow, but you can use the connector in Logic Apps in (almost) the same way, and PowerApps with these instructions.

Creating a Test Flow

For this article, we'll assume you want to connect to SharePoint to get one or more items in a list from within an existing Flow.

If you already have an existing flow you can use to follow along, go ahead and skip to the next section.

If you don't have an existing flow, let's create a Flow that you can manually trigger by following these steps:

  1. From https://flow.microsoft.com, navigate to My Flows.
  2. Select New then Instant -- from blank to create a flow that we'll be able to trigger at any time to test. Feel free to use any other type of flow here.
    New | Instant -- from blank
  3. In the Build an instant flow dialog, enter a Flow name and select From Microsoft Flow when prompted Choose how to trigger this flow and select Create.
    Build an instant flow  dialog

Your new flow will be created. Note that you need to insert at least one step before you can save or test it.

Good thing that's what we're doing next!

Adding a SharePoint connection

Before we can access SharePoint items, we need to add a connection to SharePoint by following these steps:

  1. From within your flow editor, select +New step at the bottom of the flow. You can also click on the + button that appears between two existing flow steps.
    New step
  2. In the Choose an action prompt, type Get items in the Search connectors and actions. Search is case insensitive.
    Get items
  3. Select the Get items action with a SharePoint logo from the list of Actions that appears. If the search query returns too many actions and you can't find the SharePoint Get items, you can filter out all other connectors by clicking on SharePoint just below the search bar.
  4. As soon as you select Get items, the Choose an action box will transform into the Get items box.
  5. If you haven't created a connection to SharePoint yet, you'll be prompted to Sign in to create a connection to SharePoint. Click Sign in to sign in with the account that you wish to use to access SharePoint. If your instance of SharePoint is on-prem, you can check Connect via on-premises data gateway -- but that's for another post.
    Sign in to SharePoint

    The account you use here specifies who will access SharePoint. Make sure that you use an account that can see the site and the list where you want to get items from. It is a good idea to use a service account that isn't using your own credentials to connect.

  6. Once connected, enter URL to the site that contains your list under Site address. If you experience problems typing or pasting the URL, try selecting Enter a custom value from the drop-down; it will turn the drop-down box into a text box.
    file
  7. If the site URL you entered is valid and the credentials you supplied are correct, you should be able to pick the list you want to use from the List Name drop down. In my example, I only have one list called Parking Lot Passes.
    Selecting a list name

    If you get a GUID in your List Name instead of a friendly name, make sure that the connection you're using has permissions to access the list. You can change the connection by selecting the elipsis (***) at the top of the Get items box and using the My connections section to change or add a new connection.
    My connections

Let's test it!

  1. Select Test from the top toolbar. If it is disabled, you may need to Save it first.
    Test
  2. From the Test Flow pane, select I'll perform the trigger action and select Save & Test.
    Test Flow
  3. The Run Flow dialog will prompt you to confirm the conneciton information. Make sure everything is correct and select Continue
    Run Flow
  4. Once you confirmed the connection, you'll get prompted again. It usually only happens the first time you create or change a connection. Select Run flow.
    Run flow -- again
  5. If everything went well, you should see Your flow run successfully started. Select See flow run activity to see if everything went well.
    Your flow run successfully started.
  6. You'll get a Run history for your flow, usually sorted by newest at the top. Select the top (and most likely only) one by clicking on the start time.
    Run history
  7. From your flow history page, you should see green checkmarks next to every step in your workflow. If you get a red x, check your connection information.
    Success
  8. Click on the Get items action to see what SharePoint returned. You want to see a 200 Status code, and a Body that returns value items.
    Get Items results

You now have a connection to your SharePoint list. Now let's add a filter to get only the items you want from the list.

Building an ODATA filter

For this article, I'll use a list I had created for a Park Pass request application. It has a Text column called Make, a Person column called RequestedBy, and Date and Time columns called From and To. You can use whatever list you want in your workflow.

Pro Tip: when creating a column that has a space (or any other funny characters) in the name, create the column without the space first, then rename it with a space. That way, you'll avoid funny column names like Requestedx20By. In my example the Requested By, was created as RequestedBy (no spaces) first, then renamed it to Requested By.

The Get Items action allows you to specify an ODATA filter query to filter returned items from a list. To specify a filter, select Show advanced options from the Get items action.

Get Items Filter

If you aren't familiar with ODATA filters, you can read the article about using ODATA query operations in SharePoint REST requests, or read below to find how I build my ODATA filters.

In most cases, you can write your query as [columnname] [operator] [value]. Where [operator] is one of the following keywords:

  • Lt: Less than
  • Le: Less than or equal to
  • Gt: Greater than
  • Ge: Greater than or equal to
  • Eq: Equal to
  • Ne: Not equal to

For example, to retrieve all cars where the Make is Canyon Arrow, you would write:

Make eq 'Canyon Arrow`

If you wanted to retrieve any Make but the Canyon Arrow, you would write:

Make ne 'Canyon Arrow`

If you can't figure out why your ODATA filter doesn't work, here is a mostly foolproof way to build your ODATA filter:

  1. Using your browser, navigate to: https://[yourtenant].sharepoint.com/sites/[yoursite]/_api/lists/getbytitle('[Your list title']). For example, my if my tenant is ashbay16, my site is TestPowerApps and my list is titled Parking Lot Passes, my URL would be:
    https://**ashbay16**.sharepoint.com/sites/**TestPowerApps**/_api/lists/getbytitle('**Parking%20Lot%20Passes**').
  2. If you entered the right URL, you should see information about the list in XML format.
  3. Add /fields at the end of the URL you created in step 1 to get all the fields names in your list. For example, my URL is now https://ashbay16.sharepoint.com/sites/TestPowerApps/_api/lists/getbytitle('Parking%20Lot%20Passes')/fields
  4. You may want to use an XML editor (like Visual Studio Code) to view the XML results from the previous step. Find the field you want to filter on by searching the XML file for the field title.
  5. While you're looking at the XML definition for the field, take a look at the d:Filterable node to see if it is filterable (it should be true). Also, take a look at the d:TypeAsString node to see what type of field you're dealing with. Finally, look at d:EntityPropertyName -- that'll be how you refer to that field in your filter. For example, to filter by Approval Status, you would use OData__ModerationStatus. Note that the column names are case sensitive.
  6. Look at the ODATA query syntax chart below to see what possible filter you can use to build your filter. I've grayed out the parts that don't apply below:
    ODATA filter query
  7. Depending on your field's d:TypeAsString, you can use the following queries:
    • Text: lt, le, gt, ge, eq and ne, plus startswith() and substringof(). For example, substringof('S', Model) will return all entries where the Model column contains the letter S. Note that the field name is the second parameter with substringof and startswith.
    • Number: lt, le, gt, ge, eq or ne
    • DateTime: day(), month(), year(), hour(), minute(), second(). You can also use datetime to compare a date. For example SubmittedDate gt datetime'2019-06-14T00:00:00' to get items where the SubmittedDate column is greater than June 14, 2019.
    • User: Use lt, le, gt, ge, eq and ne, plus startswith() and substringof() to evaluate against the user's display name, or specify the user's attribute. For example: RequestedBy/EMail eq 'hugo.bernier@contoso.com' to find items where the email address of the RequestedBy user is hugo.bernier@contoso.com.
  8. Test your filter in your browser by replacing /fields with /items?$filter=[yourfilter]. For example, if my filter is Make eq 'Canyon Arrow', my URL would be https://ashbay16.sharepoint.com/sites/TestPowerApps/_api/lists/getbytitle('Parking%20Lot%20Passes')/items?$filter=Make%20eq%20%27Canyon Arrow%27. Notice that when I type spaces and single quotes, the browser will url encode the values for me -- meaning Canyon Arrow becomes Canyon%20Arrow.

Once you have built your ODATA filter, add the filter to your Get Items action.

Specifying the ODATA filter

Now that you have your ODATA filter, go back to your flow and:

  1. Select Show advanced options on your Get items action.
  2. In the Filter Query enter your ODATA filter. For example, I entered Make eq 'Canyon Arrow'
    Static filter
  3. Save and test again.

Your results should now be filtered!

Specifying a dynamic ODATA filter

Flow allows you to enter dynamic values pretty much anywhere. Let's say we wanted to filter items that were previously submitted by the user who triggered the workflow.

  1. In your Get items action, make sure that the advanced options are showing. If not, select Show advanced options.
  2. In the Filter Query field, enter SubmittedBy/EMail eq ''
  3. Position your cursor between the two single quotes and select Add dynamic content to show the list of possible dynamic values you can use.
  4. From the list of dynamic content, find User Email from the Manually trigger a flow category
    Adding dynamic content.
  5. Save and test your workflow.

Note, you could also have simply used SubmittedBy eq '[User name]' from the dynamic content list, but I specifically wanted to compare by email in this example.

Your results should contain only items that were submitted by you (since you triggered the workflow).

Dealing with results from Get Items

The SharePoint Get Items action always returns an array of items -- whether it found 1 record, zero records, or a whole bunch of records.

If you insert a new action that uses the results from Get Items, Flow will automatically wrap the action in a loop, iterating through each record that was returns from Get Items.

For example, let's pretend we wanted to email the person who submitted every SharePoint list item where the To column contains a date that is earlier than today (or utcNow() in Flow). You would first set your query as follows:

  1. In the Get items action, enter To lt datetime'' in the Filter Query field.
  2. Place your cursor between the two single quotes. This time, instead of using Dynamic content, select the Expression tab.
  3. In the list of possible expressions, select utcNow() from the Date and time category. Between the parentheses, type 'yyyy-MM-ddTHH:mm:ssZ'. Your final expression should be utcNow('yyyy-MM-ddTHH:mm:ssZ'). Click Update to insert your expression.
    Entering an expression
  4. Immediately below the Get items action, select +New step and insert a Send an email action from the Office 365 Outlook group.
    Send an email
  5. In the newly inserted Send an email action, select the To field and use Add dynamic content to select Requested by Email (or whatever field you want) from your Get items action.
  6. You'll notice that as soon as you select dynamic content from the Get items action, Flow converts your Send an email into an Apply to each loop.
    Apply to each loop.
  7. Finish writing your test email and test your workflow. (Be careful that you don't send emails to a whole bunch of people, they might not appreciate it!).

Caution: Throttling

When using the SharePoint Get items action, your Flow may get throttled (i.e.: slowed down) if you exceed more than 600 calls within 60 seconds. You may want to keep that in mind when designing your flow.

Conclusion

In this long post (too long!), I explained how to use the SharePoint Get items action within Flow.

There is still a lot to cover (for example, how to detect if any items were returned, how to reduce chances of throttling by specifying the columns to return and the number of items to return, etc.), but I hope that you'll be able to get started using Get Items.

Have fun!

Introduction

I think that custom SharePoint web parts should always look like they belong to SharePoint; I want my users to be unable to tell my custom web parts from the ones that come out-of-the-box (O.O.B.) with SharePoint. I think that it helps users by presenting a common interface that they are already familiar with.

Sometimes, I'll even go as far as examine the HTML, CSS and even analyze the network traffic an O.O.B. SharePoint web part produces to make sure mine behave consistently.

When testing a SharePoint web part on the SPFx workbench, analyzing network traffic using your web browser's developer tools can get pretty overwhelming; there are so many telemetry calls every few seconds that it becomes impossible to figure out what calls are real API calls, and which ones are telemetry calls.

For example, I added a test web part on my workbench page and refreshed the page, then took a screenshot of the number of telemetry calls the page made (non-telemetry calls are greyed out):

That's a lot of telemetry

I left the page running while I wrote this introduction, and took another screenshot of the network traffic the page logged; this is what it looks like now:

More telemetry calls

The page is filled with telemetry calls! Every little tick in the timeline pane above the list is a telemetry call.

Fortunately, you can temporarily disable telemetry while you're debugging your web parts on the SPFx workbench!

What is telemetry

SPFx uses telemetry to collect measurements (such as performance and usage) and automatically sends that data at regular intervals automatically. It normally does not affect performance and can be quite useful to the Engineering team to detect (and resolve) potential issues.

When using your browser's developer tools, on the Network tab, you can see a bunch of harmless Ajax calls to a link that looks like: https://spoprod-a.akamaihd.net/files/sp-client-prod_2019-06-21.008/3.vendors~sp-client-telemetry-aria_d335ca85ff1f5f8fcce5.js

DisableTelemetry=true

If you want to debug a web part on your SPFx workbench (https://localhost:5432/workbench or https://yourtenant.sharepoint.com/_layouts/15/workbench.aspx ), you can simply append ?disableTelemetry=true to the your query string to temporarily disable telemetry calls.

Notice how there are no more telemetry calls on my page's network traffic below? No more regular dots in the timeline means no more regular Ajax calls.

No more telemetry

That's all!

Conclusion

Telemetry can be super useful, but sometimes it can be annoying -- especially when you're trying to analyze a page's network traffic.

The disableTelemetry=true query string parameter only seems to work on SPFx workbench pages, but that's fine with me.

I hope it helps you?

Image Credit

Image by swooshed from Pixabay

Introduction

Sometimes, you need to create a view in a SharePoint list where the items are sorted using a custom sort order. For example, if you had a list of items that needs to be sorted by Rating where the possible choices are High, Medium, and Low, your list items would appear in the following order: High, Low, and Medium because SharePoint will want to sort your Rating values alphabetically.

Sorting a choice column alphabetically doesn't always work

Poor SharePoint, it doesn't know that you want a custom sort order!

First instinct is to change the possible choices so that the list gets sorted in the right order. For example, renaming Low to 1 - Low, Medium to 2 - Medium, and High to 3 - High. It will force SharePoint to sort by category in the right order.

But sometimes you don't want to change the values in your metadata just so that it sorts properly.

This post will explain a quick method I use to sort list items in a view using a custom sort order.

It is so easy, it is almost embarassing.

Solution

All you need to do is simply add a calculated column where you assign a numerical equivalent to the column you wish to sort on.

Here are the steps:

  1. From your list, select Add column
    Add column
  2. When prompted to choose a column type, select More...
    Select More...
  3. In the Create Column page, select a Column name that suits you. I usually call it [Choice column] Sort, where the [Choice column] is the name of the choice column you want to sort on. For example, to sort by Rating, I would call the column Rating Sort. Feel free to use whatever name you like.
  4. For The type of information in this column is:, select Calculated
  5. Let's skip the formula for a second, we'll get back to it. For The data type returned from this formula is:, select Number and select 0 for the Number of decimal places
    Select Number and Zero decimal places
  6. For the Formula, use a whatever logic you want to assign a numerical value to the item. For example, to sort Ranking, I would assign 1 to Low, 2 to Medium, and 3 to High by using the following formula:
    =IF(Rating="Low",1,If([Rating] = "Medium", 2, 3))
  7. Select OK to create your column.
  8. Test that your column is returning the right values:
    Does my formula work?
  9. If you're getting the right numerical values, change your view to sort by the column you just created by going to Settings | List settings and selecting the view you want to change under Views.
  10. In the Edit View page, make sure to de-select the column you created (we don't actually want to show it!)
    De-selecting your sorting column
  11. Scroll to the Sort section and select your new column as the sort order.
    Sort by Ranking Sort
  12. Select OK to save your view.
  13. Test your view. It should short your items sorted using your sort logic.
    Sorted!

Conclusion

I told you it was simple!

All you have to do is add a calculated column that calculates the sort value you want, and sort by that column!

I hope this helped?

More Information

The example in this post uses the IF function.

The IF syntax is as follows:

IF(logical_test,value_if_true,value_if_false)

So, if you want to return 1 if the [Rating] column is equal to Low, otherwise return 2 you would write:

IF([Rating] = "Low", 1, 2)

You can nest IF statements too. For example, if the value of [Rating] isn't Low, it could be Medium or High. To return the appropriate value, we nest a second IF in the first IF's value_if_false parameter, as follows:

IF([Rating] = "Low", 1, IF([Rating] = "Medium", 2, 3))

However, it'll suck if you have a lot of choices to pick from. For example, if you wanted to sort by month, you would have to do something ugly like this:

IF([Month] = "January",1 , IF([Month] = "February", 2, IF([Month] = "March", 3, IF([Month] = "April", 4, IF([Month] = "May", 5, IF([Month] = "June", 6, IF([Month] = "July", 7, IF([Month] = "August", 8, IF([Month] = "September", 9, IF([Month] = "October", 10, IF([Month] = 11, 12)))))))))))

But that's ugly!

You don't have to use IF to calculate your numerical equivalent. For example, I like to use FIND to find the value in a long string.

The FIND syntax is as follows:

FIND(find_text,within_text,start_num)

Where:

Find_text is the text you want to find.

Within_text is the text containing the text you want to find.

Start_num is the character at which to start the search. If you omit it, default is 1.

Since FIND returns the location in the string where it found the value you're searching for, you just need to make sure your possible values are in order (and unique). For example, this is how I would sort by month:

FIND([Month], "January February March April May June July August September October November December")

Which would return 1 for January, 9 for February, 18 for March, etc.

Sure, the numbers aren't sequential, but we don't care, as long as they get bigger with each value!

Examples of common formulas in SharePoint Lists
The IF function
The FIND function

Introduction

A while ago, I wrote an SPFx Application Customizer that allows you to insert custom CSS on your SharePoint modern pages and posted about it.

The solution is now a sample in the SharePoint SharePoint Framework Extensions Samples & Tutorial Materials repo.

I received lots of feedback, comments and questions about the article so I decided to write an updated article to answer the most frequently asked questions.

I have since updated the solution to SPFx 1.8 and created a simple automated deployment script to (hopefully) reduce issues.

This blog post walks you through how to deploy your custom CSS using the deployment scripts and use the application extender to inject custom CSS on SharePoint.

This blog post focuses on deploying the pre-packaged version of the application extender. The source code is available for anyone who wishes to create their own version of the solution; if you want to build your own version, I encourage you to read the README.MD in the solution's repository.

You'll need to follow 3 steps:

graph LR
A[Prepare CSS] ==>B(Deploy solution)
B ==> C[Activate Application Customizer]

Prepare your CSS

Fair warning: you should customize your SharePoint CSS as a last resort. If this was a feature that Microsoft wanted to support, they would have built it in already. Do not call Microsoft (or me, for that matter) to complain that your custom CSS broke your SharePoint pages.

First things first, you should prepare your custom CSS by following these steps:

  1. Using your favorite file editor, create a custom CSS file that meets your needs. For example, this CSS will change the feedback button's background color to orange.
.sitePage-uservoice-button {
  background-color: orange;
 }
  1. Upload the CSS file as custom.css to your Styles Library of the root site collection (i.e.: https://<your-tenant>.sharepoint.com/Style%20Library/Forms/AllItems.aspx).

    For example, the CSS provided above will make the feedback button appear as follows:
    Orange Feedback

If you need help finding what CSS classes you should change, read the last update where I provide some steps.

Deploy the solution

Deploying the solution adds the application extension to your app catalog. Once the application extension is in your app catalog, you can add it to your sites -- either one site at a time, or all sites at once.

There are two ways to deploy the solution:

  • Manually
  • Using PowerShell

Before you begin either, download the pre-packaged solution.

You will also need to know your tenant's app catalog. The app catalog is where you can deploy custom and third-party solutions for your SharePoint tenant.

Finding your app catalog URL

If you don't know where your App Catalog is, you can find out using the PnP PowerShell Cmdlets and running the following commands from your PowerShell console:

  1. Connect to your tenant (make sure to replace yourtenant with your own tenant name):
    connect-pnponline https://yourtenant.sharepoint.com -UseWebLogin

    Adding -UseWebLogin will use the Office 365 browser-based login window, which is crucial if your tenant uses two-factor authentication. You can omit it if you'd like.

  2. Get your tenant's app catalog URL:
    Get-PnPTenantAppCatalogUrl

The URL it returns is the URL to your app catalog site collection. If it returns nothing, your tenant doesn't have an app catalog configured. Follow these steps from Microsoft to configure your tenant's app catalog.
App Catalog site collection home page
You can get to your app catalog by selecting Apps for SharePoint in the site navigation (or selecting Distribute apps for SharePoint on the home page.

Your app catalog should look a little like this (except that it may be empty):
Sample app catalog

Manual Deployment

  1. Upload the react-application-injectcss.sppkg you downloaded from the pre-packaged solution or drag and drop the file onto the library.
    Drag unt drop
  2. In the Do you trust react-application-inject-client-side-solution? dialog, select whether you want to Make this solution available to all sites in the organization or not, the select Deploy. As long as you trust me, of course.
    Do you trust me?

NOTE: as per Henry Radke's comments on my previous post, make sure to use https:// not http:// when you upload the solution to your app catalog. Thanks Henry for the tip!

Automated deployment

  1. If you have not done so already, install the PnP PowerShell Cmdlets
  2. Download the DeployApplicationCustomizer.ps1 PowerShell script from the GitHub repo. You should save it in the same folder as the react-application-injectcss.sppkg you downloaded from the pre-packaged solution.
  3. Edit the DeployApplicationCustomizer.ps1 file and change line 1 to point to your tenant. You should be replacing <your-tenant> for your own tenant name.
  4. From a PowerShell console, run the DeployApplicationCustomizer.ps1 script. The script will make the extension available to all sites.
  5. You will be prompted to enter your credentials.
  6. After providing your credentials, the extension will be deployed

Activate Application Customizer

If you selected Make this solution available to all sites in the organization or used the automated deployment option above, the extension should already be activated for all sites.

NOTE: It can take up to 20 minutes for the application customizer extension to activate on all sites. Be patient.

Verifying a tenant-wide extension

If you wish, you can verify that the extension is activated tenant-wide using the following steps:

  1. From your App Catalog site collection, select Site contents from the site navigation
  2. In the Site contents, select Tenant Wide Extensions
  3. From the Tenant Wide Extensions list, you should see one entry called InjectCSS
    InjectCSS

Activating extension for a site

If you wish to activate the application customizer extension on a site-by-site basis, you'll need to use the following steps:

  1. Download the EnableApplicationCustomizer.ps1 from the GitHub repo.
  2. On line 3, change <your-tenant> to your actual tenant.
  3. On the same line, change <your-site> to the site URL for the site where you want to activate the extension.
  4. The PowerShell script assumes that you uploaded the CSS as custom.css to your Styles Library of the root site collection (i.e.: https://<your-tenant>.sharepoint.com/Style%20Library/Forms/AllItems.aspx). If you chose a different name or path, make sure to change line 1 to match the location of your custom CSS.
  5. Run the PowerShell script.
  6. You will be prompted to enter credentials.
  7. Once you provide your credentials, the application customizer extension will be activated on your site.

Conclusion

My InjectCSS application customizer extension allows you to inject custom CSS on your SharePoint tenant.

Remember to use this feature responsibly; Microsoft may change the page structure and CSS classes at any time, which may break your customizations.

Thank you to all who sent me emails, tweets, GitHub issues and left feedback on my previous posts. I am always happy to help (but be prepared to get a lecture about how you should only make CSS changes as a last resort).

I would love to see what customizations you have done. Leave some comments!

I hope this helps?

Introduction

Every once in a while, I write a blog post as a note to myself about something that I couldn't find easily with the hope that next time I (or someone else in need) look for it, it'll be easy to find.

This is the case with this one.

Yesterday, I was helping to set up a new Office 365 tenant for a company that has their head offices in Toronto.

We created some sites and used the SharePoint Online Provisioning Service but found it annoying that the time zone for every site was UTC -8. We wanted UTC -5, or Eastern Standard Time as our default time zone.

It's almost as if the company that wrote this product was based in Seattle, or something.

But when it came time to set the default time zone, I couldn't remember where the setting was. I've done it so many times before, but it was as if all my past with all the previous versions of SharePoint got mixed together. Throw in the additional confusion by considering the differences between the on-premises and online versions, and I just could not remember where it was.

This post is to make sure I don't have to look it up anymore.

Finding the setting

My brain is too small to remember things that I can look up or easily deduce. The location of the setting for the default time zone for new SharePoint sites is not something easily deduced.

I clicked around and couldn't find the setting. It must've been a case of demo blindness, a condition that often occurs in professional consultants during demos where they can't see something that it plainly in front of them.

I eventually searched it online and found an article that Mark D. Anderson, someone I respect immensely, wrote in 2018.

The setting is in the SharePoint Admin Center (located at https://**yourtenant**-admin.sharepoint.com/), under Settings and Site Creation (site creation is the last setting on that page):

SharePoint Admin Settings

In the Site Creation pane, you'll find Default time zone:
Site Creation pane in SharePoint Admin Settings.

Select the time zone you want and select Save.

Note that this setting will not overwrite the time zone setting for existing sites. It will only affect new sites created after you apply the setting.

Conclusion

The setting is in the SharePoint Admin Center, under Settings and Site Creation.

Thanks Mark for saving my demo 🙂

Introduction

I love the Office 365 Dev Community.

My career has been dedicated to the IT industry, and I have always been passionate about technology. I've spent a great deal of time sharing that passion with others through my consulting work practice, mentoring, blogging, and various speaking engagements.

Recently, I received a very kind message from someone I worked with many years ago, thanking me for sharing my thoughts and experiences through my blog posts.

When we first met, he was just getting started in IT. He didn't know a lot about programming, but he was smart, driven and humble, and he used those gifts to drive himself to always find a solution. He was tenacious in his pursuits, he had the hallmarks of success.

While we no longer work together, I have continued to check in on him from time to time. I very pleased to see that he is a very successful SharePoint guru who works with some of the most brilliant people I have ever met.

He deserves all the praise for his success and I am happy to have played a small role in that by giving him an opportunity to be engaged, empowered, and to expand his skills.

He did the rest.

The Office 365 Dev Community is one of those online communities that also engages and empowers their members, and gives them the opportunity to expand their skills.

I have already written about the SharePoint Developer Community in my post titled Open Source Contributors are People Too! and talked about how people tend to take the great work the Office 365 Dev Community does for granted.

This blog post explains why I think the Office 365 Dev Community is awesome and why you should consider becoming part of it.

My SharePoint Journey

This blog post isn't about me.

But in order to explain the impact the Office 365 Dev Community has, I need to start with my experience with SharePoint.

Site Server

Many years ago, I had just finished an e-commerce implementation for one of the largest telecommunications providers in Canada using Microsoft Site Server.

During the project, the team and I had to install (and re-install) MS Site Server many times. It was back when installing a server product required us to perform ritual sacrifices to appease the installation Gods or nothing would work.

Site Server came with a sample site called the Knowledge Management site that seemed more appropriate for intranet implementations.

At the same time, Microsoft had a Web Part framework that really consisted of XML, XSLT, and VBScript. It was just a framework on its own that didn't connect with anything else.

By the end of the project, we successfully implemented a cool Knowledge Management instance of Site Server and used web parts in our solution. It was really cool.

Tahoe

My next opportunity came with McKinsey & Company, as a Senior Associate. I was hired in the e-Business Building practice with the mandate of helping the Firm advise their clients about e-commerce technologies, best practices, and their ongoing e-commerce initiatives.

McKinsey was famous (and still is) for their PDNet -- a Lotus Notes-based knowledge-management platform that allows some of their greatest minds to share their research with other consultants within the Firm.

As part of an internal initiative, my team and I were tasked to research Knowledge Management (KM) best practices to see what new technologies, if any, could be used to help improve KM within the Firm.

It was an audacious goal. We had 6 weeks, a small team, and an office in Singapore to prepare a presentation on our findings.

Since we were all e-Business Builders, we thought that we should build a prototype instead of a boring presentation. After all, I had just built a cool KM solution using Site Server.

When we started building the solution, we found out that Microsoft was separating the "Knowledge Management" template from Site Server into a new product called Tahoe; The "Commerce" component of Site Server would become Commerce Server, at least that's how our Microsoft rep had explained it to us.

We received access to an early preview of the product and engaged with two talented MS Consulting (MCS) developers, associates from the Singapore office to get started.

Here we were, two Toronto guys, a few Singapore team members, a Californian (who still thinks that SharePoint is a fad), and a genius webmaster from Helsinki who worked insane hours to build a working prototype of something that would change our lives and our careers immeasurably.

If you don't know the limits of something, there are no limits

Tahoe Server was awesome. It was also the first Microsoft product that came with (almost) the entire library of source code -- because it was all built using VBScript and ASP pages.

I read the entire source code, because I could. And I'm a geek.

We quickly found the limitations of Tahoe Server and since we had access to the source code, we could overcome these challenges and make it do what we wanted it to do.

So, we added the ability for self-service site creation. We indexed Active Directory users to create user profiles with skills matrices (to make it easy to find experts on a given topic). While many of those features became available in SharePoint 2003, we had the opportunity to build our own in Tahoe.

We created our own replication engine to replicate our Tahoe servers between the United States, Australia and Germany, thus making sure that every user would have a fast experience using our portal regardless of where they were in the world.

We even got a little cheeky and added the ability to access the portal with a mobile phone using SMS and/or WAP. Because we could.

When we demoed the prototype in Budapest in front of the entire e-Business Building community, they didn't believe that it was a fully working product. They thought we had people behind the curtains helping us create a fake demo.

Given the success of the demo, we received approval to implement the solution on a larger scale.

Over the next few years, we built more and more functionality for our portal. Tahoe was released as Sharepoint Portal Server 2001. Microsoft flew people from Redmond to Singapore twice to see what we were building. We moved the team to Helsinki, then Munich where our focus was operationalizing our portal.

The first time I went to demo our portal to the team in Munich, I took the first flight out from Helsinki so that I would get there in time for an early morning meeting.

When I got in my taxi at the Flughafen München, I gave the address to the Munich office in my broken German. I had the wrong address and the wrong phone number.

I tried calling people from the Helsinki office to get the proper address, but it was too early and the office hadn't opened yet.

Then, I remembered that I was coming to demo the portal and we had implemented functionality that would allow us to use text messages to query SharePoint using simple messages like "WHO person's name", "WHAT document name", and "WHERE office name". So, with taxi driver waiting and growing impatient, I nervously texted "Where Munich" to our mobile portal.

The response came back within seconds with the right address for the Munich office. I triumphantly gave the address to the driver who immediately said:

"Ah! McKinsey!"

Moving on

Once we were done, the team parted ways. One of the MCS guys moved to MS Corp to build their own version of the portal (eKM/ICE). I moved back to Toronto to build a similar portal for Microsoft Canada.

I continued to build SharePoint portals for three of the top management consulting firms globally and had the opportunity to travel all over the world to do what I loved.

My next challenge was to help architect and implement a large SharePoint service offering for a provincial government -- with over 75,000 users and had the opportunity to implement some of the first and largest Office 365 implementations in Canada.

Shortly thereafter, Microsoft approached me to become a Microsoft Virtual Technology Specialist for SharePoint. In this role, Microsoft would introduce me to their clients as a pretend Microsoft employee to conduct workshops with their clients, where we would build a solution to their problems within less than a day live, and without safety nets.

None of that work would really give me the satisfaction that I really wanted, which was to contribute to the greater SharePoint community.

Until SPFx came about.

SPFx and the Office 365 Dev Community

I was working on a large Office 365/SharePoint project implementing a student portal for a large college. We had specific needs mobile, responsive, and accessibility, that weren't available to us when we started. We chose to integrate the Office UI Fabric by hand into our web parts to give the site a consistent look and feel.

As we built the portal, we discovered that a SharePoint page loaded with dozens of custom provider-hosted SharePoint web parts was insanely slow.

When I found out about this new upcoming SharePoint Framework thingy, it had the promise to resolve many of our challenges and requirements.

Faster web parts, responsive, mobile, accessible, and with built-in support for Office UI Fabric?!?!

Everything I wanted!

Hungry to know more about the upcoming SPFx, my team and I started attending the SharePoint Development Community calls as often as we could.

I was always a Microsoft/.NET guy. I had always dismissed React, Node.js and GitHub as not as good because Microsoft wasn't doing it. Now Microsoft was telling me that the new way to build SharePoint web parts would be to use React, Node.js and Typescript? And in order to find out more about SPFx, I had to use GitHub?!

What the heck, Microsoft?

I had a lot of learning to do.

Fortunately, every Office 365 Dev Community call started with 15-20 minute from Vesa and/or Patrick telling us that we could submit issues and questions in GitHub. Everyone was invited to demo cool stuff they had done. Most calls ended with Q&As that gave us the opportunity to ask questions.

When we asked questions, our questions were given the consideration they deserved. No one was ever ridiculed for their questions. Not from the people hosting the calls, not from the guest presenters, and not from the other attendees.

There were even some calls where we, the attendees, were asked about our opinion!

It was the first time in over 20 years of working closely with Microsoft that I felt that Microsoft was actually listening.

Demoing my first SPFx web part

When SPFx was officially released and supported on SharePoint Online, our portal team had been learning SPFx and eagerly anticipated the opportunity to rebuild most of our web parts to SPFx.

Our project was Agile/SCRUM and we delivered our code in two-week sprints. We dedicated entire sprints to converting our web parts to SPFx, forcing everyone to jump in the deep end.

We quickly found what worked and what didn't. Some SPFx web parts never saw the light of the day, and some others were easily converted. We even rebuilt one web part that took a few months to build over a weekend!

When we demoed what we had done to our Microsoft reps, they asked us to do the same demo to other colleges and universities.

Armed with demoable web parts, I accepted to take Vesa and Patrick's invitation to contribute to the SPDev calls seriously. I sent an insanely long email to them (I wasn't actively using Twitter) and offered to demo some of the web parts we had done.

That was one year ago today.

I didn't expect to hear back from them. And I didn't, for about two weeks. It turns out that cool people don't use email.

Eventually Vesa sent me a response and I was scheduled for an upcoming call. My demo went okay, however the response from the attendees was amazing! I had people reaching out to me to find out how I had done this or that. Some people wanted me to share my code.

What value could I add to the community, except for some cool web parts? I thought that I didn't have anything worth blogging or tweeting about.

Contributing to the community

When I said that to a friend of mine, someone who I respect immensely (except, maybe, for the fact that he insists on sprinkling Salt & Vinegar seasoning on his popcorn), he explained that while I may not feel like I have anything of value to share with others, there are others who are just starting with SharePoint and SPFx who may benefit from my sharing what I have learned thus far, with them.

With this in mind, I wrote an SPFx sample to help others with some of the problems I had experienced when I started learning SPFx, and submitted it to the Samples repo. It was nothing amazing or earth-shattering, it was just a simple example of how to solve a small problem.

My sample got accepted.

Then I demoed it on a call.

And I found that, as my friend with the nasty pop-corn habit had predicted, other people did benefit from my contribution.

I built more samples based on solutions to challenges I had encountered when I started with SPFx and submitted them. They were always graciously accepted. Every time I demoed one of them, the community was always insanely supportive and kind.

Gaining confidence in the community, I wrote some PnP reusable controls that I wished were available, fixed some mistakes in the documentation, and added a very small command to the Office365-CLI. (In fact, my RichText control was just released today!)

No matter how small my contributions were, they were always welcome.

Any time I wasn't sure how to contribute, there was someone who was willing to help. If I made a mistake, nobody made me feel small or insignificant.

Everyone was always gracious and supportive.

Most of my career now has been spent being passionate about SharePoint, and I had finally found a place where other people who are just as passionate congregate and help each other.

Engage, Empower and Expand

That "kid" I hired over ten years ago became an expert because of his hard work. He took the opportunity to get engaged in our company and did great. He was empowered to make a difference and he did, beyond expectations. He was able to expand his skills and responsibilities beyond what (I suspect) he even believed he could, to become the best version of himself.

The Office 365 Development Community gives each of us the ability to do the same.

The various bi-weekly and monthly calls, videos and presentations engage us.

We are all empowered to do demos, create samples, contribute to the many repos out there. You don't have to be an MVP, to know someone at Microsoft, or to be an employee of a big company to participate.

We can all expand the capabilities of SharePoint/O365, and supporting tools by submitting feedback, creating new components, command-lines, tools, web parts, and more.

We have the opportunity to make SharePoint the best platform by collaborating together!

Welcome to the Office 365 Dev Community

If you would like to find out more what's new in the SharePoint development space, you should visit the SharePoint Developer Community (SharePoint PnP) resources. You'll find videos, blog posts, and social media resources that will help.

If you'd like to meet other people who are passionate about SharePoint development, consider attending one of the various community calls.

If you want to see what other people in the community have done, or if you want to contribute, visit the list of open-source projects.

Every single one of us in the Office 365 Dev Community started as a newbie at some point. As long as you do the same, everyone will treat you kindly and with respect.

Every one of us has something important to say for someone else. We all have different backgrounds, experiences, and industries that make us unique. As long as you don't act like you know better than everyone else, and you don't try to sell anything, people will listen.

As long as you genuinely want to share with the rest of the community, not for the purpose of self-promoting, people will appreciate your contributions.

We've all had project deadlines and demanding customers, and we understand the pressures that you're under when you ask for help. We're also under the same pressure, but someone will surely try to help you if you need help.

Welcome to the Office 365 Dev Community. I think you'll like it here.

Conclusion

My SharePoint journey has been a long one. At times, I felt more like a crazy person on a soap-box telling those who didn't want to listen to how awesome SharePoint is.

Maybe I am such a crazy person, but the Office 365 Dev Community is filled with other crazy people who feel the same way.

To all of you in the Office 365 Dev Community, thank you for being awesome and supportive.

I'm proud to be one of you.

Introduction

When migrating from SharePoint on-premises to SharePoint Online/Office 365, you may find that some users have a checked-out file called spcommon[1].png. If you ask users about it, they'll have no idea what you're talking about.

As it turns out, this isn't a bug. It is possible for users to check-out this file without knowing they did it.

But for this issue to occur, you need a "perfect storm" to happen: a series of things that occur that are seamingly unrelated that results in the issue we're discussing today.

What is spcommon[1].png

spcommon.png is an image that SharePoint uses to render things like checkboxes, arrows, gears, and pretty much any icon that you see on a SharePoint page.

If you download the file, it looks like a bunch of icons in one bigger image:
SPCOMMON.PNG

Those types of images are called sprites; they usually consist of many images grouped together as a single image. That image is bigger to download than individual images, but since most browsers try to avoid re-downloading a file it has already downloaded (something known as caching -- pronounced cashing) -- making the entire page load faster.

SharePoint uses the giant sprite and hides irrelevant parts of the image (by setting a background-url and background-position CSS styles).

On a typical SharePoint page, the spcommon.png image may be shown dozens of times.

So why do I get a spcommon[1].png file that gets checked out by users?

Perfect Storm

This issue will typically happen in document libraries that either Require check-out before editing files or that have a mandatory property.

I've seen this issue in SharePoint 2013 and SharePoint 2016 migrations, but it could potentially happen in SharePoint 2019 as well.

As you already know, users can drag and drop documents unto a document library using their browsers, and SharePoint will try to upload the document.

If you try drag and drop a document to a library that requires checking in or one that has mandatory properties, the newly uploaded document will be checked out until the user provides values for the mandatory properties and checks the document in.

Most browsers also allow you to select an image from a web page and drag and drop it. (Try it now with an image on this page!).

If you try to drag and drop an image onto a document library page, it will try to upload that image into the document library. If that library has mandatory properties, the image will be uploaded but checked-out.

Here is where it gets crazy: if a user tries to click on the checkmark next to a document and accidentally drags the mouse instead -- even for a few pixels -- the browser will think that the user meant to upload the image to the document library.

The checkmark

And the image used to display the checkbox is -- you guessed it --spcommon.png.

It often happens too quickly for users to notice, but here is what happens if you slow it down and take a screen shot of a user dragging the checkmark icon in a document library:

Dragging the checkmark

And since most browsers will try to uniquely name dragged files, the spcommon.png file is automatically renamed to spcommon[1].png.

Conclusion

The issue with a mysterious checked-out spcommon[1].png file in a document library requires a lot of factors to happen.

Fortunately, It seems that newer versions/patches of SharePoint prevent this issue from happening by giving users an error message saying "Folders and invalid files can't be dragged to upload", meaning that you're less likely to find this issue in future migrations.

Issue prevented?

If you see this error when migrating files to SharePoint Online, you can safely ignore it.

This article was written in collaboration with Eric Skaggs

Introduction

In today's SharePoint Dev Ecosystem (PnP) Bi-Weekly Call, Eric Skaggs asked a question I've heard many times before:

Do you have an example that shows how to go from a Javascript SPFx web part to a SPFx React web part?

(I'm paraphrasing)

I had been looking for an opportunity to write such as article, so I told Eric to DM me on Twitter to see if he had an example of a web part he'd like to convert.

As it turns out, Eric has such a web part: his GitHub Badge WebPart is a great example of a Javascript-only SPFx web part.

Eric's Javascript Only SPFx Web Part

Eric's SPFx web part works great, and there is really no need to convert it to React. We'll convert it to React simply to demonstrate the process and to highlight some of the design differences between a Javascript only web part and a React web part.

It is also important to point out that every developer has their own coding styles and preferred approaches that do not affect the final product. In this article, I'll try to point out where I applied my own coding style.

This article is written as if you're following along and converting the application yourself. Feel free to skip to the end to get the code and compare Eric's Javascript-only SPFx web part with my React version of the same web part.

Starting from scratch

The SPFx framework is constantly improving. In fact, the framework went from 1.8 to 1.8.1 two days ago!

Because of this, I like to make sure that I create a new solution every time I start converting a web part (I do this a lot, as it turns out).

Start by making sure that your environment is configured to create SPFx solutions. If you haven't done so yet, follow these steps to get you started.

If you were already set up, make sure to update your version of the Yeoman generator to the latest. To do so, use the following command from your Node.js command prompt:

npm install -g @microsoft/generator-sharepoint

Once this is completed, I followed the instructions from the SharePoint Framework documentation except, you know, with React GitHub Badge as the solution name:

  1. Create a new project directory in your favourite location.

    md react-github-badge

    Your web part solution does not need to start with react-. I just named it that way because it is the naming convention in the SP-Dev-Fx-WebParts repository

  2. Go to the new folder you created:

    md react-github-badge
  3. Create the React GitHub Badget web part by running the Yeoman SharePoint Generator.

    yo @microsoft/sharepoint
  4. When prompted:

    • Accept the default react-github-badge as your solution name, and then select Enter.
    • Select SharePoint Online only (latest), and select Enter.
    • Select Use the current folder for where to place the files.
    • Select N to allow the solution to be deployed to all sites immediately.
    • Select N on the question if solution contains unique permissions.
    • Select WebPart as the client-side component type to be created.
  5. The next set of prompts ask for specific information about your web part:

    • Enter GitHub Badge as your web part name, and then select Enter.
    • Enter Displays information from GitHub for a specified user as your web part description, and then select Enter.
    • For framework you would like to use, select React, and then select Enter.

The Yeoman generator in action

You'll know it has completed when you see the following message:

Success

Once it has completed, run:

gulp serve

to test your web part. (It should work). You should see something like this:
I created an SPFx solution and all I got was this lousy web part

It isn't pretty, but it's a start.

Fixing potential vulnerabilities

If you paid attention as the Yeoman generator created the solution, you may have noticed a nasty message like this one:

added 1759 packages from 1071 contributors and audited 565045 packages in 63.66s
found 1957 vulnerabilities (1806 low, 36 moderate, 115 high)
  run npm audit fix to fix them, or npm audit for details

Unfortunately, that's the nature of building solutions with open-source components.

I don't like it, so I typically run the following command to fix as many issues as possible:

npm audit fix

Once completed, you should see less scary exploits:
After npm audit fix

Run your web part again and make sure it still works:

gulp serve

Separating Web Part and Component

Eric's no-framework web part has all the code for retrieving a user's GitHub profile, managing web part properties, and rendering in the GitHubBadgeWebPart.ts. That's how it is done with "no-framework" web parts.

In SPFx React solutions, the web part will be broken into smaller components:

  • GitHubBadgeWebPart.ts: The Web Part, which is responsible for storing and retrieving web part properties, displaying the property pane, and calling components to render the web part.
  • GitHubBadge.tsx: The main component, which renders the content of the web part.

We're going to take Eric's code and move the content to GitHubBadge.tsx, and leave the web part code in GitHubBadgeWebPart.ts.

Adding a web part property to store the GitHub user name

  1. Using your favourite code editor, open GitHubBadgeWebPart.ts (located under src\webparts\gitHubBadge and find the following code that was generated by Yeoman:

    export interface IGitHubBadgeWebPartProps {
    description: string;
    }
  2. Since we don't need a description property for our web part, let's rename it to gitHubUserName. The code should look like this:

    export interface IGitHubBadgeWebPartProps {
    gitHubUserName: string;
    }

    If you use Visual Studio Code, simply place your cursor over description and hit F2. Type in gitHubUserName and hit Enter
    Using F2 to rename variables

  3. If you do not use Visual Studio Code, you should look for a line that says:

    description: this.properties.description

    and replace it for:

    description: this.properties.gitHubUserName
  4. Find the code inside the getPropertyPaneConfiguration function that looks like this:

    groupFields: [
     PropertyPaneTextField('description', {
         label: strings.DescriptionFieldLabel
     })
    ]

    and rename the description property to gitHubUserName. The code should look as follows:

    groupFields: [
    PropertyPaneTextField('gitHubUserName', {
        label: strings.DescriptionFieldLabel
    })
    ]
  5. Finally, let's rename the localized label for the DescriptionFieldLabel to GitHubUserNameFieldLabel by using the F2 method. Doing so will also rename the localized variable in src\webparts\gitHubBadge\loc\mystrings.d.ts. If you don't use Visual Studio Code, make sure to rename the DescriptionFieldLabel to GitHubUserNameFieldLabel.
    As a general rule, I always name the localized variable for all my properties as [PropertyName]FieldLabel. So, GitHubUserName becomes GitHubUserNameFieldLabel. Feel free to use your own naming convention.

  6. We'll also need to change the localized text! Go to src\webparts\gitHubBadge\loc\en-us.js and find the line that looks like this:

    "DescriptionFieldLabel": "Description Field"

    And change it to:

    "GitHubUserNameFieldLabel": "GitHub user name"

As always, run gulp serve to test your changes and make sure your web part didn't self-destruct.

If you click on your web part's edit button, you should see the following:
Web part property

So far, so good.

Your web part's manifest contains a section for pre-configured properties in case you want to provide default properties when users add the new web part to their page. Let's go make Eric's username the default GitHub username:

  1. Open src\webparts\gitHubBadge\GitHubBadgeWebPart.manifest.json and find the properties in the preconfiguredEntries section. Replace the following line:
    "description": "GitHub Badge"

    to this:

    "description": "skaggej"
  2. JSON files don't support comments, and Visual Studio Code will kindly remind you of that by showing the file in red. For bonus points, find all the comments (starting with \\) in that file and remove them. Visual Studio Code will reward you with a nice green file name instead.

When you change the manifest, you won't notice the difference until you stop and restart gulp serve, remove then re-add the web part to your page. I wasted a lot of time trying to debug this issue before I learned this the hard way.

Disabling reactive property changes

By default, SPFx web parts apply property changes as soon as you make them.

In this example, we don't want the web part to retrieve the GitHub user's profile until we're done entering the name.

We can do this by adding an Apply button to the property pane. To do so, open the Web Part's code at src\webparts\gitHubBadge\GitHubBadgeWebPart.ts and add the following code just above the getPropertyPaneConfiguration() function:

protected get disableReactivePropertyChanges(): boolean {
    return true;
}

When you refresh your web part, you'll get a nice Apply button at the bottom of your property pane.

Adding GitHubUserName property to the GitHubBadge props

Now that we've renamed the web part's Description property to GitHubUserName, we need to do the same to the property that gets passed into the GitHubPage component.

Typically, your React component will define a I[ComponentName]Props to store properties, and a I[ComponentName]State to store the component's state.

By default, the Yeoman generator will have created the IGitHubBadgeProps interface for you, which should be placed in the src\webparts\gitHubBadge\components\IGitHubBadgeProps.ts file.

Because I learned React from reading the Office UI Fabric code, and they already have awesome Coding Style, React and TypeScript guidelines, I tend to follow their standards.

In Office UI Fabric, they often group all types related to a component in a file called [ComponentName].types.ts.

You don't have to do this, but I prefer to do the same by storing both my I[ComponentName]Props and I[ComponentName]State interfaces in the same file called [ComponentName].types.ts.

In this case, I'll just rename the IGitHubBadgeProps.ts to GitHubBadge.types.ts by selecting the file in the Visual Studio Code explorer pane and hitting F2 then typing GitHubBadge.types.ts followed by Enter.

We'll add the IGitHubBadgetState interface later.

For now, though, let's open the newly renamed GitHubBadge.types.ts file and find the following line in the IGitHubBadgeProps interface:

  description: string;

and rename the description property to gitHubUserName by using the trusty F2 rename shortcut.

If all goes well, you'll notice that both src\webparts\gitHubBadge\GitHubBadgeWebPart.ts and src\webparts\gitHubBadge\components\GitHubBadge.tsx will update where they refer to the description property to point to the new gitHubUserName property.

Rendering static HTML

So far, I haven't used any of Eric's code.

That's about to change.

  1. Open:
  2. Find the following code in the render function and select it:
    <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.gitHubUserName)}</p>
            <a href="https://aka.ms/spfx" className={ styles.button }>
            <span className={ styles.label }>Learn more</span>
            </a>
        </div>
    </div>
    </div>
  3. Notice that the line that looks like:
    <div className={ styles.gitHubBadge }>

    and the last </div> isn't included in the selected code.

  4. Copy the following code from Eric's sample over the selected code:
    <div class="${ styles.container }">
          <div class="${ styles.row }">
            <div class="${ styles.column }">
              <div id="gitHubUserProfilePic"></div>
              <div id="gitHubUserName" class="${ styles.title }">${this.properties.gitHubUserName}</div>
              <div id="login" class="${ styles.label }"></div>
              <div id="id" class="${ styles.label }"></div>
              <div id="node_id" class="${ styles.label }"></div>
              <div id="avatar_url" class="${ styles.label }"></div>
              <div id="gravatar_id" class="${ styles.label }"></div>
              <div id="url" class="${ styles.label }"></div>
              <div id="html_url" class="${ styles.label }"></div>
              <div id="followers_url" class="${ styles.label }"></div>
              <div id="following_url" class="${ styles.label }"></div>
              <div id="gists_url" class="${ styles.label }"></div>
              <div id="starred_url" class="${ styles.label }"></div>
              <div id="subscriptions_url" class="${ styles.label }"></div>
              <div id="organizations_url" class="${ styles.label }"></div>
              <div id="repos_url" class="${ styles.label }"></div>
              <div id="events_url" class="${ styles.label }"></div>
              <div id="received_events_url" class="${ styles.label }"></div>
              <div id="type" class="${ styles.label }"></div>
              <div id="site_admin" class="${ styles.label }"></div>
              <div id="name" class="${ styles.label }"></div>
              <div id="company" class="${ styles.label }"></div>
              <div id="blog" class="${ styles.label }"></div>
              <div id="location" class="${ styles.label }"></div>
              <div id="email" class="${ styles.label }"></div>
              <div id="hireable" class="${ styles.label }"></div>
              <div id="bio" class="${ styles.label }"></div>
              <div id="public_repos" class="${ styles.label }"></div>
              <div id="public_gists" class="${ styles.label }"></div>
              <div id="followers" class="${ styles.label }"></div>
              <div id="following" class="${ styles.label }"></div>
              <div id="created_at" class="${ styles.label }"></div>
              <div id="updated_at" class="${ styles.label }"></div>
              <div id="notfound" class="${styles.label}"></div>
            </div>
          </div>
        </div>

    You will get some errors. Don't panic.

  5. React doesn't like it when you use the word class to define the CSS class name. It is a reserved word. Instead, you must use className. Luckily, you can replace all instances of the word class by using a trick I've described in my multi-cursor editing in Visual Studio Code article. Select the first instance of the word class= (including the = sign) and hit CTRL-SHIFT-L, then type className= instead, followed by the ESC key to stop multi-cursor editing. This should replace all instances of class to className.
  6. The keyword properties is also unique to the WebPart-derived classes. In React, the properties for a component are called props. Find the line that looks like this:
    <div id="gitHubUserName" className="${ styles.title }">${this.properties.gitHubUserName}</div>

    and replace it for this:

    <div id="gitHubUserName" className="${ styles.title }">{this.props.gitHubUserName}</div>

In Eric's code, he defines an ID for all the elements he wants to populate with data and dynamically inserts the text once he has retrieved it by calling every element by ID. It is a really efficient way to dynamically update web part content.

Although you technical can refer to HTML elements by IDs in React, it is rarely encouraged. One of the reasons for this is that if you add the web part twice on the same page, you'll get conflicts.

Instead, we'll later bind each element to the component's state, then populate the state when we receive the data from GitHub.

For now, let's just get rid of all those ID on every element.

Thankfully, you can do this by using multi-cursor editing!

  1. From the code, select the first instance of id=". Make sure to include the = and the double quotes ".
  2. Just like you did before hit CTRL-SHIFT-L to select all instances of the currently selected text. You should see that all instances of id=" got selected.
  3. Now hold SHIFT and CTRL and press the RIGHT arrow key. It should automatically select the word to the right of id=". (SHIFT means to extend the selection, while CTRL-RIGHT selects the next word).
  4. Hold SHIFT again (you can let go of CTRL) and hit the RIGHT arrow again. That should select the last double-quotes (") to the right of the text you've already selected.
  5. Hit BACKSPACE to delete the text you have selected. If you want, hit BACKSPACE once more to remove the extra space that is left after every <div.

Isn't multi-cursor editing cool?

TSX files in React TypeScript projects make it easy to combine HTML with React. To insert dynamic text as an HTML attribute, you just need to use { }. You don't even need the quotes around the attribute. This means that every instance of className="${ in Eric's former Javascript code can be simply replaced by className={.

You can do so by using multi-cursor editing again:

  1. In the code, find the first instance of "${ and select it.
  2. Hit CTRL-SHIFT-L to select all instances.
  3. Hit DEL to delete the selected text.
  4. Type { instead and hit ESC to stop multi-cursor editing.
  5. Go to the end of the className attribute, and select }"
  6. Hit CTRL-SHIFT-L to select all instances of }".
  7. Hit DEL and type } instead, followed by ESC.

The final render function should look like this:

    public render(): React.ReactElement<IGitHubBadgeProps> {
    return (
      <div className={ styles.gitHubBadge }>
        <div className={ styles.container }>
          <div className={ styles.row }>
            <div className={ styles.column }>
              <div></div>
              <div className={ styles.title }>{this.props.gitHubUserName}</div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={ styles.label }></div>
              <div className={styles.label}></div>
            </div>
          </div>
        </div>
      </div>
    );
  }

Finally, open replace the content of the src\webparts\gitHubBadge\components\GitHubBadge.module.scss with Eric's original SCSS:

@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';

.gitHubBadge {
  .container {
    max-width: 700px;
    margin: 0px auto;
    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
  }

  .row {
    @include ms-Grid-row;
    @include ms-fontColor-white;
    background-color: $ms-color-themeDark;
    padding: 20px;
  }

  .column {
    @include ms-Grid-col;
    @include ms-lg10;
    @include ms-xl8;
    @include ms-xlPush2;
    @include ms-lgPush1;
  }

  .title {
    @include ms-font-xl;
    @include ms-fontColor-white;
  }

  .subTitle {
    @include ms-font-l;
    @include ms-fontColor-white;
  }

  .description {
    @include ms-font-l;
    @include ms-fontColor-white;
  }

  .button {
    // Our button
    text-decoration: none;
    height: 32px;

    // Primary Button
    min-width: 80px;
    background-color: $ms-color-themePrimary;
    border-color: $ms-color-themePrimary;
    color: $ms-color-white;

    // Basic Button
    outline: transparent;
    position: relative;
    font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
    -webkit-font-smoothing: antialiased;
    font-size: $ms-font-size-m;
    font-weight: $ms-font-weight-regular;
    border-width: 0;
    text-align: center;
    cursor: pointer;
    display: inline-block;
    padding: 0 16px;

    .label {
      font-weight: $ms-font-weight-semibold;
      font-size: $ms-font-size-m;
      height: 32px;
      line-height: 32px;
      margin: 0 4px;
      vertical-align: top;
      display: inline-block;
    }
  }
}

You deserve a reward! Run gulp serve again from your Node.js command prompt and refresh your web part. You should see something that looks like this:
Static HTML Preview

We're getting there!

Let's retrieve the data next!

Creating an IGitHubServices interface

Eric's example keeps things simple by putting the code to retrieve the user's GitHub profile in the web part class.

However, React solutions benefit from breaking things into smaller components with a clear division of responsibilities.

For example, the code that calls the GitHub API to retrieve the GitHub user's profile can be separated from the code that is responsible for rendering the profile information.

This is done because React makes it easy to create individualized components that do specific things. By keeping the code that retrieves the data separate from the code that renders the data, we could re-use the GitHub profile component in different ways.

It also makes it easier to create unit tests and mock services without having to change your GitHub profile component.

It doesn't make the React code better than the no-framework code. It's just a different approach.

Since the purpose of this example is to demonstrate converting a no-framework web part to a React web part, I'll show you the extra steps of creating a separate IGitHubService interface, with a mock service and a real service.

  1. First, let's create a new folder called services under the src folder. It should be at the same level as the webparts folder.

You may find other examples that place their services under the web part folder for the web part that calls it, but I like to design my services so that they can be used by more than one web part -- hence placing it at the same level as the webparts folder. Feel free to place it where you prefer.

  1. Under the src\services folder, create another folder called GitHubServices.

  2. In the src\services\GitHubServices folder, create a new file called GitHubServices.types.ts

  3. Let's create an interface that represents the returned data from GitHub APIs. From your browser, visit https://api.github.com/users/skaggej. It will return the JSON for Eric's profile.

  4. Copy the content of the JSON. It should look like this:

    {
    "login": "skaggej",
    "id": 1846656,
    "node_id": "MDQ6VXNlcjE4NDY2NTY=",
    "avatar_url": "https://avatars1.githubusercontent.com/u/1846656?v=4",
    "gravatar_id": "",
    "url": "https://api.github.com/users/skaggej",
    "html_url": "https://github.com/skaggej",
    "followers_url": "https://api.github.com/users/skaggej/followers",
    "following_url": "https://api.github.com/users/skaggej/following{/other_user}",
    "gists_url": "https://api.github.com/users/skaggej/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/skaggej/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/skaggej/subscriptions",
    "organizations_url": "https://api.github.com/users/skaggej/orgs",
    "repos_url": "https://api.github.com/users/skaggej/repos",
    "events_url": "https://api.github.com/users/skaggej/events{/privacy}",
    "received_events_url": "https://api.github.com/users/skaggej/received_events",
    "type": "User",
    "site_admin": false,
    "name": "Eric Skaggs",
    "company": "http://www.catapultsystems.com",
    "blog": "http://www.ericskaggs.net",
    "location": "Phoenix, AZ",
    "email": null,
    "hireable": null,
    "bio": "Fuse Solution Architect at Catapult Systems",
    "public_repos": 29,
    "public_gists": 3,
    "followers": 8,
    "following": 33,
    "created_at": "2012-06-13T14:01:52Z",
    "updated_at": "2019-04-09T00:18:35Z"
    }

    (don't worry too much if there are minor differences)

  5. If you don't have the awesome JSON to TS extension for Visual Studio, go install it now. It will allow us to convert the JSON you just copied into a TypeScript interface. (Did I mention I'm the world's laziest developer?)

  6. Place your cursor in the GitHubServices.types.ts file. We're about to insert some code in there.

  7. With JSON to TS installed, hit F1 to launch the Visual Studio Code command line and start typing JSON to TS. You should see **JSON to TS: Convert from clipboard. Select it and be patient. The extension will insert a TypeScript Interface called RootObject` that contains a property for every attribute returned by the GitHub API. The code should look like this:

    interface RootObject {
    login: string;
    id: number;
    node_id: string;
    avatar_url: string;
    gravatar_id: string;
    url: string;
    html_url: string;
    followers_url: string;
    following_url: string;
    gists_url: string;
    starred_url: string;
    subscriptions_url: string;
    organizations_url: string;
    repos_url: string;
    events_url: string;
    received_events_url: string;
    type: string;
    site_admin: boolean;
    name: string;
    company: string;
    blog: string;
    location: string;
    email?: any;
    hireable?: any;
    bio: string;
    public_repos: number;
    public_gists: number;
    followers: number;
    following: number;
    created_at: string;
    updated_at: string;
    }
  8. Rename the RootObject to IGitHubUserProfile and export the interface so that it can be accessed in other files. The code should look like this:

    export interface IGitHubUserProfile {
    login: string;
    id: number;
    node_id: string;
    avatar_url: string;
    gravatar_id: string;
    url: string;
    html_url: string;
    followers_url: string;
    following_url: string;
    gists_url: string;
    starred_url: string;
    subscriptions_url: string;
    organizations_url: string;
    repos_url: string;
    events_url: string;
    received_events_url: string;
    type: string;
    site_admin: boolean;
    name: string;
    company: string;
    blog: string;
    location: string;
    email?: any;
    hireable?: any;
    bio: string;
    public_repos: number;
    public_gists: number;
    followers: number;
    following: number;
    created_at: string;
    updated_at: string;
    }

    We use the convention I[Something] to indicate that this is an interface, not a class. That way, we can take any TypeScript object that implements the properties defined in the interface (login, id, node_id, avatar_url, etc.) -- regardless of how it was created. That way, we'll be able to convert the JSON we retrieved from the GitHub API to the IGitHubUserProfile interface and pass it around.

  9. We'll add an IGitHubService interface to the same file. The interface will implement one method called getUserProfile that receives an alias string and returns an asynchronous promise of an IGitHubUserProfile. Just paste the following code below the IGitHubUserProfile:

    export interface IGitHubService {
    getUserProfile(alias: string): Promise<IGitHubUserProfile>;
    }

    Again, we use an interface so that we can later have a MockGitHubService and a (real) GitHubService that both implement the IGitHubService. Our component won't care whether it is using a real service or a mock service, because it will expect an object that implements IGitHubService.

For your reference, the entire content of GitHubServices.types.ts should look like this:

export interface IGitHubUserProfile {
  login: string;
  id: number;
  node_id: string;
  avatar_url: string;
  gravatar_id: string;
  url: string;
  html_url: string;
  followers_url: string;
  following_url: string;
  gists_url: string;
  starred_url: string;
  subscriptions_url: string;
  organizations_url: string;
  repos_url: string;
  events_url: string;
  received_events_url: string;
  type: string;
  site_admin: boolean;
  name: string;
  company: string;
  blog: string;
  location: string;
  email?: any;
  hireable?: any;
  bio: string;
  public_repos: number;
  public_gists: number;
  followers: number;
  following: number;
  created_at: string;
  updated_at: string;
}

export interface IGitHubService {
  getUserProfile(alias: string): Promise<IGitHubUserProfile>;
}

Now let's create the mock service!

Creating MockGitHubService

We'll use a mock service to return test data so that we don't have to worry about getting blocked by GitHub for calling the API too many times when we're testing the look and feel of the web part. The mock service will be interchangeable with the real service at a later time.

  1. In the src\services\GitHubServices folder, create a new file called MockGitHubService
  2. In the now empty file, paste the following code:
    import { IGitHubService, IGitHubUserProfile } from "./GitHubServices.types";
    export class MockGitHubService implements IGitHubService {
    public getUserProfile(alias: string): Promise<IGitHubUserProfile> {
    // This space for rent
    }
    }

The import { IGitHubService, IGitHubUserProfile } from "./GitHubServices.types"; line tells the TypeScript transpiler that those two interfaces are located in another file in the same folder.

The export class MockGitHubService implements IGitHubService says that this class (MockGitHubService) will do (or implements) everything the IGitHubService interface does, and that it should be available outside of this file by other files (or exported).

Let's add some code to return sample data after simulating some delays.

  1. In the code, replace the line that says // This space for rent with the following code:
    return new Promise<IGitHubUserProfile>((resolve) => {
      // pretend we're getting the data from the GitHub API by adding a delay
      setTimeout(() => {
        const fakeProfile: IGitHubUserProfile = {
          login: "skaggej",
          id: 1846656,
          node_id: "MDQ6VXNlcjE4NDY2NTY=",
          avatar_url: "https://avatars1.githubusercontent.com/u/1846656?v=4",
          gravatar_id: "",
          url: "https://api.github.com/users/skaggej",
          html_url: "https://github.com/skaggej",
          followers_url: "https://api.github.com/users/skaggej/followers",
          following_url: "https://api.github.com/users/skaggej/following{/other_user}",
          gists_url: "https://api.github.com/users/skaggej/gists{/gist_id}",
          starred_url: "https://api.github.com/users/skaggej/starred{/owner}{/repo}",
          subscriptions_url: "https://api.github.com/users/skaggej/subscriptions",
          organizations_url: "https://api.github.com/users/skaggej/orgs",
          repos_url: "https://api.github.com/users/skaggej/repos",
          events_url: "https://api.github.com/users/skaggej/events{/privacy}",
          received_events_url: "https://api.github.com/users/skaggej/received_events",
          type: "User",
          site_admin: false,
          name: "Eric Skaggs",
          company: "http://www.catapultsystems.com",
          blog: "http://www.ericskaggs.net",
          location: "Phoenix, AZ",
          email: null,
          hireable: null,
          bio: "Fuse Solution Architect at Catapult Systems",
          public_repos: 29,
          public_gists: 3,
          followers: 8,
          following: 33,
          created_at: "2012-06-13T14:01:52Z",
          updated_at: "2019-04-09T00:18:35Z"
        };
        resolve(fakeProfile);
      }, 500);
    });

The entire content of the MockGitHubService.ts file should be as follows:

import { IGitHubService, IGitHubUserProfile } from "./GitHubServices.types";

export class MockGitHubService implements IGitHubService {
  public getUserProfile(alias: string): Promise<IGitHubUserProfile> {

    return new Promise<IGitHubUserProfile>((resolve) => {
      // pretend we're getting the data from the GitHub API by adding a delay
      setTimeout(() => {
        const fakeProfile: IGitHubUserProfile = {
          login: "skaggej",
          id: 1846656,
          node_id: "MDQ6VXNlcjE4NDY2NTY=",
          avatar_url: "https://avatars1.githubusercontent.com/u/1846656?v=4",
          gravatar_id: "",
          url: "https://api.github.com/users/skaggej",
          html_url: "https://github.com/skaggej",
          followers_url: "https://api.github.com/users/skaggej/followers",
          following_url: "https://api.github.com/users/skaggej/following{/other_user}",
          gists_url: "https://api.github.com/users/skaggej/gists{/gist_id}",
          starred_url: "https://api.github.com/users/skaggej/starred{/owner}{/repo}",
          subscriptions_url: "https://api.github.com/users/skaggej/subscriptions",
          organizations_url: "https://api.github.com/users/skaggej/orgs",
          repos_url: "https://api.github.com/users/skaggej/repos",
          events_url: "https://api.github.com/users/skaggej/events{/privacy}",
          received_events_url: "https://api.github.com/users/skaggej/received_events",
          type: "User",
          site_admin: false,
          name: "Eric Skaggs",
          company: "http://www.catapultsystems.com",
          blog: "http://www.ericskaggs.net",
          location: "Phoenix, AZ",
          email: null,
          hireable: null,
          bio: "Fuse Solution Architect at Catapult Systems",
          public_repos: 29,
          public_gists: 3,
          followers: 8,
          following: 33,
          created_at: "2012-06-13T14:01:52Z",
          updated_at: "2019-04-09T00:18:35Z"
        };
        resolve(fakeProfile);
      }, 500);
    });
  }
}

Adding an index.ts file to GitHubServices

Now that we've defined some exports in our GitHubServices, we'll want to make it easy for the component to use them.

The problem is that if we want to import our IGitHubService, IGitHubUserProfile and MockGitHubService in our GitHubBadge component, we'll have to import each item from the files that contain them, like this:

import { IGitHubService, IGitHubUserProfile } from '../../../services/GitHubServices/GitHubServices.types';
import { MockGitHubService } from '../../../services/GitHubServices/MockGitHubService';

However, if we ever decide to move the various elements of the GitHubServices to different files, we'll have to update all the import statements in all the components that use the services.

But why should the components know about the internal structure of the GitHubServices? Wouldn't it be better to abstract all that stuff from the components?

Luckily, we can use index.ts to do just that!

  1. In the src\services\GitHubServices folder, add a new file called index.ts.
  2. In the index.ts file, export all the things that you want the components to have access to, as follows:
    export * from './GitHubServices.types';
    export * from './MockGitHubService';

Now we can just add the following line in our src\webparts\gitHubBadge\components\GitHubBadge.tsx file to import everything we need:

import { IGitHubService, IGitHubUserProfile, MockGitHubService } from '../../../services/GitHubServices';

Adding some state to GitHubBadge

State is a funny concept in React.

It allows us to temporarily capture the information we need to support the different "ways" we want our component can be in (I'm really trying hard not to use the word state here).

For example, our GitHubBadge component potentially has 5 states:

  • Not configured
  • Loading
  • Loaded with data
  • Error because the user was Not found
  • Error while calling GitHub API (network, throttling, etc.)

This is something that's represented as follows:

Title: GitHubBadge States
Not configured->Loading: When user sets web part properties
Loading->Loaded: Normal scenario
Loading-->Not found: Invalid user name
Loading-->Error: Exception with GitHub API

I promised Eric I'd keep this simple, so I'll ignore the Not configured and we'll combine the Not found and Error states for now. (I can't promise I won't come back to this in a later article though).

To represent these states in out GitHubBadge component, we'll use the following variables:

  • isLoading: a boolean that will be set to true when the web part loads.
  • userProfile: a IGitHubUserProfile variable that can be set to undefined (if there is no data to show)
  • errorMessage: a string containing an error message that can also be set to undefined if nothing went wrong.

If isLoading is false, it will mean that the service call is complete. If errorMessage contains a message, it means there was an error. Otherwise, if userProfile contains data, it means that we received our data and that we want to show it.

Let's start implementing this by creating an IGitHubBadgeState interface in our src\webparts\gitHubBadge\components\GitHubBadge.types.ts file:

  1. Open the GitHubBadge.types.ts file
  2. Add the following code below the IGitHubBadgeProps interface:
    export interface IGitHubBadgeState {
    isLoading: boolean;
    userProfile?: IGitHubUserProfile;
    errorMessage?: string;
    }
  3. Make sure to add the following line at the top of the file:
    import { IGitHubUserProfile } from "../../../services/GitHubServices";

The ? at the end of the variable names means that the variables can be nullable.

Now let's use the state in our component!

Adding state to the GitHubBadge component

Now we're finally getting somewhere!

Let's start by telling the GitHubBadge that is has a state:

  1. Open the src\webparts\gitHubBadge\components\GitHubBadge.tsx file

  2. At the top of the file, replace the following line:

    import { IGitHubBadgeProps } from './GitHubBadge.types';

    for this:

    import { IGitHubBadgeProps, IGitHubBadgeState } from './GitHubBadge.types';
  3. Replace the following line:

    export default class GitHubBadge extends React.Component<IGitHubBadgeProps, {}> {

    with this:

    export default class GitHubBadge extends React.Component<IGitHubBadgeProps, IGitHubBadgeState> {

    This tells the GitHubBadge class that it should use IGitHubBadgeProps for its properties, and IGitHubBadgeState for its state.

  4. Add a constructor to define a default state by adding the following code below the code you just changed, and above the public render() method:

    constructor(props:IGitHubBadgeProps) {
    super(props);
    
    this.state = {
      isLoading: true
    };
    }

    Note that the constructor is the only time you can change the state directly by using this.state = . Everywhere else, you'll only be able to use this.setState().

  5. For now, let's some conditional rendering logic in the render method so that when the web part is loading (i.e.: isLoading equals true), we'll write "Loading..." in the top of the web part. Replace the following line (just below <div className={ styles.column }>:

<div></div>

with this:

<div>{ this.state.isLoading && "Loading..." }</div>

If you try to use gulp serve now, you'll notice that the web part always displays "Loading..." because the isLoading state variable is set to true at in the constructor and we never change that.

Loading

But we'll fix that right now...

Loading and displaying mock data

React applications typically try to be responsive (as in "fast") by avoiding any delays in rendering the components.

It is better to render a "Loading..." web part and immediately change it to show the data that's you just retrieved than not rendering anything until the data has returned.

To achieve this, we'll call the getUserProfile method from the MockGitHubService after the GitHubBadge is mounted. Once the MockGitHubService returns data, we'll call this.setState() and set isLoading to false and populate the userProfile state variable with whatever data we received.

Calling this.setState will automatically trigger any elements that are bound to state variables on the component to re-render.

To the code!

  1. In src\webparts\gitHubBadge\components\GitHubBadge.tsx, add a method called componentDidMount above the render method, as follows:

    public componentDidMount(): void {
    
    }

    (It will still work if you put the code after the render method, I suggest where to put it in the code so that your code looks like mine once completed).

  2. Add the following code inside the componentDidMount function:

    // Create an instance of the GitHub service
    const service: IGitHubService = new MockGitHubService();
    
    // Call the GitHub service
    // In real-life, we would only call it when we're sure that there is a username
    service.getUserProfile(this.props.gitHubUserName).then((results: IGitHubUserProfile)=>{
      // Set the userProfile with the results we got and isLoading to false, because we're done
      // loading. It'll make things redraw magically.
      this.setState({
        userProfile: results,
        isLoading: false
      });
    });
  3. Replace the entire render function with the following code. Don't worry, I'll explain shortly:

    public render(): React.ReactElement<IGitHubBadgeProps> {
    const { userProfile, isLoading, errorMessage } = this.state;
    
    return (
      <div className={ styles.gitHubBadge }>
        <div className={ styles.container }>
          <div className={ styles.row }>
          { isLoading &&
            <div className={ styles.column }>
              <div>Loading...</div>
            </div>
          }
          { !isLoading && userProfile &&
            <div className={ styles.column }>
              <div><img src={userProfile.avatar_url} alt="GitHub User Profile Picture" /></div>
              <div className={ styles.title }>{this.props.gitHubUserName}</div>
              <div className={ styles.label }>{ userProfile.login}</div>
              <div className={ styles.label }>{userProfile.id}</div>
              <div className={ styles.label }>{userProfile.node_id}</div>
              <div className={ styles.label }>{userProfile.avatar_url}</div>
              <div className={ styles.label }>{userProfile.gravatar_id}</div>
              <div className={ styles.label }>{userProfile.url}</div>
              <div className={ styles.label }>{userProfile.html_url}</div>
              <div className={ styles.label }>{userProfile.followers_url}</div>
              <div className={ styles.label }>{userProfile.following_url}</div>
              <div className={ styles.label }>{userProfile.gists_url}</div>
              <div className={ styles.label }>{userProfile.starred_url}</div>
              <div className={ styles.label }>{userProfile.subscriptions_url}</div>
              <div className={ styles.label }>{userProfile.organizations_url}</div>
              <div className={ styles.label }>{userProfile.repos_url}</div>
              <div className={ styles.label }>{userProfile.events_url}</div>
              <div className={ styles.label }>{userProfile.received_events_url}</div>
              <div className={ styles.label }>{userProfile.type}</div>
              <div className={ styles.label }>{userProfile.site_admin}</div>
              <div className={ styles.label }>{userProfile.name}</div>
              <div className={ styles.label }>{userProfile.name}</div>
              <div className={ styles.label }>{userProfile.company}</div>
              <div className={ styles.label }>{userProfile.location}</div>
              <div className={ styles.label }>{userProfile.email}</div>
              <div className={ styles.label }>{userProfile.hireable}</div>
              <div className={ styles.label }>{userProfile.bio}</div>
              <div className={ styles.label }>{userProfile.public_repos}</div>
              <div className={ styles.label }>{userProfile.public_gists}</div>
              <div className={ styles.label }>{userProfile.followers}</div>
              <div className={ styles.label }>{userProfile.following}</div>
              <div className={ styles.label }>{userProfile.created_at}</div>
              <div className={ styles.label }>{userProfile.updated_at}</div>
            </div>
          }
          { !isLoading && errorMessage &&
            <div className={ styles.column }>
    <div className={styles.label}>WARNING - error when calling URL https://api.github.com/users/{this.props.gitHubUserName}. Error = {errorMessage}</div>
            </div>
          }
          </div>
        </div>
      </div>
    );
    }

Now save your code and treat yourself to a gulp serve. Refresh your web part and you should see the web part say Loading... for half a second, then load Eric's profile.

"But that was a lot of weird code you just introduced!", you'll say. I know! Let me walk you through it.

The first line in the render function:

const { userProfile, isLoading, errorMessage } = this.state;

defines a "shortcut" to the state variables isLoading, userProfile, and errorMessage. That way, in the rest of the code, we don't have to say this.state.userProfile, we can simply use userProfile.

The first section:

{ isLoading &&
            <div className={ styles.column }>
              <div>Loading...</div>
            </div>
          }

says: "if isLoading is true, render the HTML between { and }".

Similarly, this line:

{ !isLoading && userProfile &&

says "If isLoading is not true, and there is a userProfile render the HTML between the {}.

Guess what:

{ !isLoading && errorMessage &&

does? It only renders the HTML between the {} if the web part is done loading and there is an error message.

Everywhere else in that function uses {} to bind to a state or prop. For example:

<div><img src={userProfile.avatar_url} alt="GitHub User Profile Picture" /></div>

Renders an image that binds the src attribute to the avatar_url attribute of the userProfile state variable, while:

<div className={ styles.label }>{userProfile.login}</div>

Insert the value of the login attribute of the userProfile state variable inside the <div/> element.

Everywhere else in the render function works the same way.

We're almost done! We just need to retrieve the real data by passing the HTTP context and implementing the GitHubService to use it to call the real GitHub API.

Passing HTTP Context

In order to make HTTP requests, the component needs to use the HttpClient object exposed by the web part.

That means that the GitHubBadge component needs to add an HttpClient variable to its IGitHubBadgeProps interface.

Let's do this:

  1. Open src\webparts\gitHubBadge\components\GitHubBadge.types.ts
  2. At the top, import HttpClient from '@microsoft/sp-http', as follows:
    import { HttpClient } from '@microsoft/sp-http';
  3. Add a prop variable that will store the HttpClient to the IGitHubBadgeProps. The GitHubBadge.types.ts file will look as follows:
    
    import { IGitHubUserProfile } from "../../../services/GitHubServices";
    import { HttpClient } from '@microsoft/sp-http';

export interface IGitHubBadgeProps {
gitHubUserName: string;
httpClient: HttpClient;
}

export interface IGitHubBadgeState {
isLoading: boolean;
userProfile?: IGitHubUserProfile;
errorMessage?: string;
}

4. In the src\webparts\gitHubBadge\GitHubBadgeWebPart.ts web part, pass the new prop to the GitHubBadge component by changing the render as follows:
```TypeScript
public render(): void {
    const element: React.ReactElement<IGitHubBadgeProps> = React.createElement(
      GitHubBadge,
      {
        gitHubUserName: this.properties.gitHubUserName,
        httpClient: this.context.httpClient
      }
    );

    ReactDom.render(element, this.domElement);
  }</code></pre>
<blockquote>
<p>You may have seen some samples that pass the entire web part context to  the components (including some of my samples).  Waldek has a <a href="https://blog.mastykarz.nl/dont-pass-web-part-context-react-components/">great article</a> that explains why you shouldn't. In this example, we choose to pass the <code>HttpClient</code> object from the web part's <code>context</code> instead of passing the entire <code>context</code>.
As a general rule, always listen to Waldek :-)</p>
</blockquote>
<h2>Creating the GitHubService</h2>
<p>The moment of truth!</p>
<ol>
<li>Go to the <code>src\services\GitHubServices</code> folder and add a file called <code>GitHubService.ts</code></li>
<li>At the top of the file, add the following imports:
<pre><code class="language-TypeScript">import { IGitHubService, IGitHubUserProfile } from "./GitHubServices.types";
import { HttpClient, HttpClientResponse } from '@microsoft/sp-http';</code></pre></li>
<li>Create the <code>GitHubService</code> class that implements the <code>IGitHubService</code> interface:
<pre><code class="language-TypeScript">
export class GitHubService implements IGitHubService {</code></pre></li>
</ol>
<p>}</p>
<pre><code>4. Add a <code>private</code> variable of type <code>HttpClient</code> that will be used to store the HTTP client object passed into the GitHubService:
```TypeScript
export class GitHubService implements IGitHubService {
  private _httpClient: HttpClient;

}
  1. Add a constructor that receives the HttpClient object and stores it to the private variable:
    constructor(httpClient: HttpClient) {
    this._httpClient = httpClient;
    }
  2. Finally, implement the getUserProfile method that calls the GitHub API using the HttpClient

    public getUserProfile(alias: string): Promise<IGitHubUserProfile> {
    const gitHubUrl: string = "https://api.github.com/users/"+alias;
    
    // call the GitHub API
    return this._httpClient.get(gitHubUrl,
      HttpClient.configurations.v1, {}).then((response: HttpClientResponse) => response.json())
      .then((profile: IGitHubUserProfile) => {
        return profile;
      });
    }

    The code almost seems magical: it calls the API and converts the received JSON to an IGitHubUserProfile interface automatically.

The final GitHubService code looks like this:

import { IGitHubService, IGitHubUserProfile } from "./GitHubServices.types";
import { HttpClient, HttpClientResponse } from '@microsoft/sp-http';

export class GitHubService implements IGitHubService {
  private _httpClient: HttpClient;

  constructor(httpClient: HttpClient) {
    this._httpClient = httpClient;
  }

  public getUserProfile(alias: string): Promise<IGitHubUserProfile> {
    const gitHubUrl: string = "https://api.github.com/users/"+alias;

    // call the GitHub API
    return this._httpClient.get(gitHubUrl,
      HttpClient.configurations.v1, {}).then((response: HttpClientResponse) => response.json())
      .then((profile: IGitHubUserProfile) => {
        return profile;
      });
  }
}

To allow the GitHubBadge component to access the GitHubService, we need to add GitHubService to the src\services\GitHubServices\index.ts, making the entire index.ts as follows:

export * from './GitHubServices.types';
export * from './MockGitHubService';
export * from './GitHubService';

Note that unlike the MockGitHubService, the GitHubService needs the HttpClient to work. Because the getUserProfile function is defined in the IGitHubService interface, we can't change the function to pass the HttpClient when we need it.

However, we can change the constructor of the GitHubService to accept the HttpClient object we need.

Calling the GitHubService

To change the code in the GitHubBadge component to use the GitHubService, we simply need to change the componentDidMount by following these steps:

  1. Open the src\webparts\gitHubBadge\components\GitHubBadge.tsx file
  2. Change the import statement at the top to include GitHubService:
    import { IGitHubService, IGitHubUserProfile, MockGitHubService, GitHubService } from '../../../services/GitHubServices';
  3. In the componentDidMount function, comment out this line:
    const service: IGitHubService = new MockGitHubService();

    and add the following line just below:

    const service: IGitHubService = new GitHubService(this.props.httpClient);

Nothing else needs to change.

Run gulp serve and try the web part. The data will be really coming from GitHub.

However, if you try to change the user name property and click Apply, you won't see any changes unless you refresh the page.

We can fix that.

Responding to changing props

While React is happy to automatically redraw the components when their state changes, our component only changes the state once it receives the data from the GitHubService.

And the GitHubService is only called once after the component is mounted.

To call the GitHubService when the component is mounted and when the gitHubUserName prop changes, we need to move some code around.

To do so:

  1. In the src\webparts\gitHubBadge\components\GitHubBadge.tsx file, add a private function called getUserProfile that calls the web service:

    private getUserProfile()  {
    // Create an instance of the GitHub service
    //const service: IGitHubService = new MockGitHubService();
    const service: IGitHubService = new GitHubService(this.props.httpClient);
    
    // Call the GitHub service
    // In real-life, we would only call it when we're sure that there is a username
    service.getUserProfile(this.props.gitHubUserName).then((results: IGitHubUserProfile)=>{
      // Set the userProfile with the results we got and isLoading to false, because we're done
      // loading. It'll make things redraw magically.
      this.setState({
        userProfile: results,
        isLoading: false
      });
    });
    }
  2. Change the componentDidMount function to call the getUserProfile private function:
    public componentDidMount(): void {
    this.getUserProfile();
    }
  3. Add a componentDidUpdate that will compare if the previous props are different than the current props and will call the private getUserProfile function if it is different:
    public componentDidUpdate(prevProps: IGitHubBadgeProps, prevState: IGitHubBadgeState): void {
    if (prevProps.gitHubUserName !== this.props.gitHubUserName) {
      this.getUserProfile();
    }
    }

componentDidUpdate gets triggered any time the component's state or props change. In this case, we use it to compare gitHubUserName and react accordingly.

Try your changes now using gulp serve and see it all work when you update the GitHub user name.

The final result looks like this:
file

Conclusion

In this article, we took the GitHub Badge WebPart that Eric Skaggs wrote using only Javascript (no framework!) and converted it to a React web part that does the same thing.

We took a few detours on the way to convey some different concepts in React, but the result is mostly the same.

There are still a few things the sample web part should do:

  • Add error handler
  • Add a "Loading..." spinner
  • Add a placeholder when the web part isn't configured
  • Cache the results to avoid getting throttled by GitHub for making too many calls.
    ...but this article is already long enough.

You can find the entire solution on my GitHub repo.

Thanks again to Eric for writing such an awesome sample web part. I hope that I did your sample justice with the React version.

Introduction

Recently, Microsoft announced a plan to rename Office UI Fabric to Microsoft UI Fabric. The Fabric React Component that have become ubiquitous on Microsoft web applications will be updated to give the Fabric controls that new cool Fluent style -- eventually aligning the desktop and web app look and feel to give users a consistent experience.

To help understand how the controls will change with the Fluent style, the UI Fabric team created a Preview web site. On it, there is a component that allows you to compare how the controls will appear before and after the Fluent style update by dragging a slider left to right. Although it isn't a standard UI Fabric component, the comparer component looks and feels like it belongs to UI Fabric.

Fluent Comparer Control

At that time, I was putting together a demo for a client that needed to compare two images on their SharePoint site. I thought I'd re-create the comparer component into an SPFx web part.

As I was building the web part, I decided to add the ability for users to pick the Before and After images using a file picker like the one available in SharePoint.

The SharePoint File Picker

The out-of-the-box file picker allows users to pick files from their recent files, web search, OneDrive, the current site they're on, an uploaded file, or a hyperlink. Perfect for my needs!

Being the World's Laziest Developer, I looked for a standard control withint the SPFx libraries, Fabric UI, the PnP Reusable React controls for SPFx solutions, and the PnP Reusable SPFx property pane controls .

But I found nothing.

What I lack in being lazy, I make up in being stubborn. So I decided to write my own File Picker control.

The web part came out OK, but I was pretty happy with the File Picker.

Web Part in Action

This article will describe some of the techniques and approaches I used to reverse engineer the out-of-the-box File Picker to create my own.

If all you need is to see the code, you can find the React Comparer web part on the SharePoint SP-Dev-Fx-WebParts Sample Repository.

Design Criteria

Before I set out to build the File Picker, I defined some rules that I would have to stick to no matter what:

  1. To the best of my ability, the File Picker had to look and feel exactly like the out-of-the-box File Picker. That includes copying some of the weird inconsistencies in the out-of-the-box component -- I'll get to those later.
  2. Even though I only cared about picking images, the File Picker must be designed to eventually support picking documents as well as images. I wouldn't be spending any time testing the picker using documents, but I would spend every effort to make sure I would be able to add full support for documents later.
  3. The control should be easy to re-use in other solutions. Eventually, it should be easy to add it to the PnP Reusable SPFx property pane controls -- as long as anybody else shows interest in it.
  4. Using the File Picker control should not require setting custom permissions or prompt the user to enter their credentials -- or any other weird behavior (see #1).
  5. Should be designed to support mobile browsing in the future, but no testing on mobile device.
  6. The File Picker control should be easy to extend without disrupting the user experience. For example, I would like to add a Camera tab to allow users to insert an image from their camera, but I'd want the tab to look like the feature came out-of-the-box.

With all the above rules established (why do I do this to myself, again?), I started coding!

What kind of control?

When I first started trying to figure out how I'd go about doing this control, I thought I'd do a regular control that I'd launch when clicking on the property pane button. I thought I'd use the @microsoft/sp-webpart-base's PropertyPaneButton and the onClick handler to display a UI Fabric Panel control.

The problem with this approach is that I would need to track whether the panel was opened or not to determine when to render the panel. Since I had no control over the state of the PropertyPaneButton control, I would have to store that information in the web part properties -- something that I didn't want to do.

But if I created a custom property pane control, I could render the button to open the dialog and add the state of the dialog to the property pane control's state.

Luckily, there is a great training module called SharePoint Framework training package - Working with the Web Part Property Pane that teaches you how to use and develop your own property pane controls. The PnP
Reusable SPFx Property Pane Controls source code is also a great place to look for examples to get started.

Ultimately, I mimicked the code from PnP (after all, I'm planning on submitting the control to PnP!) and created the property control.

Taking inspiration from existing controls

Since my goal was to reproduce the exact same look and I feel, I started rebuilding the same component structure by using the out-of-the-box file picker and examining the HTML it produced.

Using your browser's Developer Toolbox (F12 on Chrome and Edge) Elements tab, you can navigate through the page elements and determine what kinds of controls are used.

Using the Dev Toolbox

For example, the navigation which allows users to select whether to pick a file from OneDrive, the Site, upload, etc. has the ms-Nav-group CSS class.

Navigation.

A quick search for ms-Nav-group in the UI Fabric source code pointed to the UI Fabric Nav component.

By comparing the samples, I was able to find the sample that matched the look of the out-of-the-box control the closest and started with that.

When to split components

Since React makes it easy to create controls that are made up of many components, it can be difficult to figure out when a group of HTML elements are from a single control, and when they aremade of individual controls.

Luckily, the majority of controls usually consist of the control's .tsx file, and a .scss file that contains all the CSS classes needed to render that control.

When React renders a control in a web page, it adds the control's CSS to the page. To prevent one's control's CSS from interfering with another control's CSS, React uniquely names each CSS class by appending a unique suffix to each class name.

In the example below, the nav CSS class is rendered as nav_b427d4d7. If you look closely at the parent elements of the nav_b427d4d7 element, you'll find there is an element with a class focusTrapZone_b427d4d7 -- with the same _b427d4d7 suffix.

file

Elements with the same class name suffix are generally rendered from the same React control. By navigating up and down the HTML elements, I was able to determine which items were rendered together.

Copying styles

While I was reverse engineering the controls I would need, I was also able to extract the styles I needed for each element by looking at the Developer Toolbar's Styles pane in the Elements tab:

Styles

I simply gave my HTML elements the same CSS class names that I would see in the Developer Toolbar (minus the unique suffix) and used the same styles.

With a bit of tweaking, I was able to make the control look exactly the same.

Listening In

Once I got the HTML to look the way I wanted, I had to figure out where SharePoint got the results.

Once again, the browser's Developer Toolbars came to the rescue. By loading the out-of-the-box file picker and using the Network tab, I was able to see what REST calls SharePoint makes to retrieve documents.

Using the Network Tab

After filtering through many XHR calls, I was able to find which calls returned the data I was looking for.

I used the @pnp/sp library to re-create the calls in my components and return the same data that the SharePoint file picker control returned.

Discrepancies

By analysing the file picker, I noticed some discrepancies how each tab was rendered. For example, the OneDrive tab and the Site tab may look very similar, but a closer look reveals minor differences between the two.

For example, the Site tab shows folders within the site with no file count:
Site Tab

Meanwhile, the OneDrive tab shows a file count within each file and offers users the ability to select which view they wish to use (in the uppoer right corner):
OneDrive Tab

The HTML behind each tab is also very different.

I chose to keep the same discrepancies between tabs, even if I was tempted to clean up and optimize the code. I suspect the each tab was created by a different team members (or teams?) within Microsoft, which explains the lack of consistency.

Conclusion

The value proposition of SPFx is that it gives third-party developers (like me) the same set of tools that first-party developers (like Microsoft) can use.

By using the browser's developer tools and a bit of elbow grease, you can reproduce the look and feel of SharePoint components and make them do whatever you want them to do.

If you want to find out more on how I wrote the File Picker control, take a look at the code.

I hope this helps?

Introduction

I know, I know, there are already tons of SharePoint migrations to Office 365 "Best Practices" articles out there. My evil twin even wrote an article on how to fail a migration recently, which received much more attention than it deserved.

I have done many large scale migrations and given many presentations on the topic, but I didn't want this article to be a "things Hugo says you should do" article. There are many other authors who are much more insightful and eloquent.

This article is a compilation of the best "best practices" articles I could find. I have included the list of articles at the end.

By all means, do not trust that my conclusions are accurate or unbiased. Feel free to read all the referenced articles and make your own conclusions -- and then comment below!

Overview

Migration Best Practices

Approach

I searched for SharePoint to Office 365 Migration Best Practices on Google and Bing and compiled all results.

I did not include articles that were behind a paywall, or those that required you to provide an email to get access to the article/download anything.

I also excluded any articles that were purely technical (e.g.: the ones that show you how to use a migration tool).

After carefully reading each article, I create a table listing each article with every "best practice" idea discussed in the article.

When looking for best practice criteria, I looked for articles that focused on the following outcomes:

  • Increased user adoption of Office 365
  • Increased user satisfaction
  • No loss of productivity/increased productivity
  • No loss of data
  • Accuracy of migrated data
  • Reduced risks

I tabulated the best practices that appeared in 3 or more articles.

I focused on the top 10 best practices but ended up with 11 topics. I chose to separate Clean Files and No "as-is" because -- while they are arguably similar -- most authors discussed both points separately.

Analysis

The list below provides links to each of the articles reviewed. I chose to use a legend (instead of writing each best practice in the header) because I wanted the table to be easy to see at a glance.

Legend

The following letters correspond to a best practice idea. We'll discuss each idea below.

A
Incremental
B
Inform Users
C
Analyze/Audit
D
Plan
E
No "As-Is"
F
Accurate Estimates
G
Clean-up
H
Train Users
I
Information Architecture
J
Not just an IT initiative
K
Test
L
Champions
ArticleAuthorABCDEFGHIJKL
5 common mistakes when migrating to Office 365lightningtools83
5 Key Steps for Migrating SharePoint to the CloudSteve Marsh
5 Mistakes To Avoid When Migrating from SharePoint to Office 365Maggie Swearingen
Adoption and SharePoint migrations – it’s a thing!Shafina Hassam
Anatomy of a SharePoint Migration [INFOGRAPHIC]Joanne Klein
Best Practices for a Smooth Office 365 MigrationMichael Pichna
Best way to implement SharePoint in a large organizationGregory Zelfond
How to Migrate Documents to SharePoint and Office 365: Step-by-Step InstructionsGregory Zelfond
Migrating SharePoint to Office 365 – Best (Recommended) PracticesVeenus Maximiuk
Migrating to SharePoint Online? Get this ULTIMATE checklist ready!Arut Selvan
Moving file shares to SharePoint on-premises to SharePoint Online isn’t a simple lift and shiftTervela
Recommended Best Practices for Migrating SharePoint to Office 365Veenus Maximiuk
SharePoint Migration InfographicGTconsult A Team
Top 5 Mistakes to Avoid When Migrating to Office 365David Davidov

Findings

Incremental

Many authors agree that you should migrate your content incrementally, or in batches, rather than migrating everything at once.

For example, you should consider migrating content on a team-by-team basis. Doing so gives you the opportunity to properly analyze and audit the data to be migrated, clean up the data and re-organize it as you migrate, and spend quality time with the users to inform them and train them.

It also gives you the opportunity to test each migration and -- more importantly -- to get your content owners to verify (and possibly sign-off) on the migration.

Getting sign-off from content owners is a great way to help speed up the decommissioning of the old data (which is a re-occurring best practice, but didn't make the top list).

Inform Users

The topic of "Informing Users" and "Communicating" was one of the most common best practices. Most experts agree that you should inform your users in advance about the exciting new platform they'll soon be using, describe the benefits of the new platform, and inform them of timing.

Migrating to Office 365 impacts users. Your users may make up their minds to "boycott" the new platform before they even see what's coming.

This is often due to fear of the unknown from your users.

Take the time to explain to your users what's coming, how they will benefit from this migration, and when it will happen.

You can't expect users to drop everything they're doing for your migration. Try migrating the accounting department during fiscal year end and you'll see just how much push back you can get from your users.

Analyze/Audit

The # 1 best practice seems obvious, but it is often forgotten.

Before migrating, you should perform an in-depth analysis of the content you'll be migrating. Don't just count how many files there are!

Consider the following :

  • Custom solutions
  • Business processes that will be affected
  • Security/permissions
  • Deprecated features
  • File path length (see this article to understand why you should care)
  • Metadata
  • How much space will be needed to migrate

Not all content needs to be migrated (see Clean Up, below), but every piece of content should be accounted for. While most of your files may be migrated, some may be archived or skipped because they are duplicate. Keeping an audit of all your content, along with their migration status will help ensure that all your content is accounted for.

Plan

A successful migration begins with creating a migration plan.

This is what I typically put in my migration plans:

- Objectives and Goals
    * Business-Related Goals
    * Migration-Related Goals
- Migration Strategies
    * Strategy 1
        * Tools
        * Implications
    * Strategy 2
        * Tools
        * Implications
    * ...
    * Strategy n
        * Tools
        * Implications
- Migration Environment
- Migration Guidelines
- Migration Process
    * Preparation
    * Migration Step 1
    * Migration Step 2
    * ...
    * Migration Step n
    * Migration Execution & Validation
    * Decommissioning of Replaced Resources
    * Rollback Plan

Your migration plan should include going to each team in your organization and identify their processes, the custom solutions they use, the files they need and plan how and where you will migrate everything.

No "As-Is"

Let me be clear: none of the best bet articles I listed above explicitly say "No As-Is".

Most experts agree, however, that doing a simple "lift and shift" is a bad idea.

Don't simply copy the documents over "as-is". Take the time to re-evaluate the Information Architecture to take advantage of the capabilities that SharePoint Online offers with Office 365.

Consider moving the sub-sites within sub-sites to a flatter site structure.

Consider using hubs to group sites together.

Consider distributing sub-folders to different document libraries.

Look for long file URLs and make sure that they'll fit within the URL length limits once migrated.

Just make sure that you don't accept the status quo.

Accurate Estimates

Failed migrations often underestimate the effort involved in migrating content to Office 365.

To paraphrase George W. Bush, "don't misunderestimate the effort involved".

As Joanne Klein explains in her article, don't underestimate the time it takes to plan the migration.

You also need to estimate the time you will need to migrate the files.

What's the best way to estimate the migration effort? Perform test migrations with real files.

You can actually test the migration in your new Office 365 tenant. If you're concerned about "tainting" your production environment, just migrate a site to a temporary destination in your production environment (e.g.: HR to HRTest) and delete the site after your tests are complete.

Once you have performed a test migration you can extrapolate the time it took to perform the test (including the time it took to do the analysis and planning) to get a better estimate.

Clean-up

As we discussed above (see No "As-Is"), you should not simply lift and shift your files.

Take this opportunity to clean up files as you go. Remove duplicates, remove old files and other files that have no business being moved to the new environment.

If your business folks have a "let's keep files forever" retention policy, now is a great opportunity to encourage them to adopt an adequate retention policy.

While you're at it, look for documents that have the words "Final" or "Draft" in them. Then look for documents with the same name -- but without "Final" and "Draft" in them (they often have dates or version numbers in the file names as well). They usually indicate users who don't quite understand how versioning works in SharePoint.

That's also a sign that you should train your users.

Train Users

With the number of training resources available out there, there are no excuses for not training your users.

The only valid reason for not training your users is if you want your migration to fail.

Information Architecture

Take the time to create an Information Architecture for your migrated content.

If you don't take the time to develop an Information Architecture that will help your users find the content they need, they'll blame SharePoint for not being able to find documents and you'll get poor user adoption.

If you need help with developing an Information Architecture, I've written a few articles on the subject -- but there are plenty of resources out there.

Not just an IT initiative

“Be prepared to answer the question "how much of my time/my staff's time will you need?". Someone will ask.

Migrating SharePoint to Office 365 is more than just moving files and server resources. You need to change how your users will work too.

Don't approach your migration as an IT-only initiative. It should not be.

Get executive buy-in, involve your stakeholders, involve your users.

The IT team can lead the migration initiative, but you need to involve other departments as well.

Consult the teams as you move their content and make sure that you listen to their pain points. Find a way to alleviate their pain points in Office 365.

As we said before, don't forget that other departments also have jobs to do. They aren't waiting at your beck and call. Be considerate of their time.

Be prepared to answer the question "how much of my time/my staff's time will you need?". Someone will ask.

Test

Most experts will encourage you to test your migration.

By "testing", we don't mean counting how many files are in the source and -- after migration -- counting how many files are in the destination to see if it matches.

Before the real migration, perform a test migration with real documents to your real production environment and verifying that everything that was supposed to migrate did migrate.

After migration, get your content owners to test finding and opening documents.

Verify that you didn't lose metadata when you migrated.

Verify that permissions are accurate and that users who shouldn't see files won't see those files.

Verify that those files with the longest URLs can be opened on a user's workstation without any issues.

Champions

Along the same lines as Not just an IT initiative and Train Users, consider finding users who are willing to be your first test subjects.

They'll become your champions later.

Don't let some executive name their favourite people who don't have any real experience using the old SharePoint site. Find actual users who are suffering so much that they're willing to spend extra time testing the new platform, finding all warts and bugs -- just for the possibility that the new platform will make their lives better.

Find those users who aren't afraid to complain loudly, but who are willing to listen.

Find those who are influential among their peers. They're the ones that will encourage other users to adopt your new platform.

Working with champions usually consists of three phases:

  • Engage: Get the champions involved in the decision process. Show them the possibilities. Try giving them choices ("would you prefer A or B?") and avoid asking questions that require them to be Office 365 experts to answer ("what content types do you need?").
  • Empower: Give your champions permissions to manage their own site. Give them room to make mistakes and to recover from them.
  • Extend: Encourage your champions to extend their use of SharePoint Online. Encourage them to create new document libraries, to invite other users, and to push the boundaries of how they'll use what's at their disposal. Remember: users don't always tell you what they need, they often tell you what they think you want to hear -- letting your champions extend how they use their new site may reveal requirements that were never discussed.

If you find that your users tell you that they don't have time for you, that they're too busy, it is probably because you didn't try to find champions first.

Once your champions find how awesome Office 365 is, they'll tell other users. Those users who were too busy and didn't have time for you will soon be begging to work with you because they want to have all the cool stuff the champions got.

Conclusion

Every expert has their own perspective on what best practices exist when it comes to migrating SharePoint to Office 365, but there are common ideas that they all seem to agree with.

I would love to make this list even more comprehensive. If you have found an article that you believe should be included in this article, please let me know in the comments. I'll keep growing the list and updating the research.

Introduction

A few days ago, I was showing a co-worker on how to localize a web part using SPFx. I had a series of words to copy and move into a JSON structure.

I selected the whole text and inserted a " in front of every word, a " after every word, and a , at the end of every line in about 5 keystrokes.

"HOW DID YOU DO THAT?!" my co-worker asked, inappropriately too loud for a quiet office setting.

I could have produced a rabbit from the computer and he wouldn't have been more impressed.

He had never used multi-cursor editing before -- or, apparently, seen anyone use it.

Then I remembered another time, a few months ago, when I had shown the same feature to a friend of mine. Someone that I have looked up to and respected for over 15 years, who has more to teach me than I could ever teach him. He had said, "you should blog about this!".

I had completely forgotten about it.

This article will demonstrate how to use multi-cursor editing. I don't think it is particularly earth-shattering, but I do hope that someone else will learn ways to save some keystrokes.

Multiple Cursors in Visual Studio Code for Windows

Multiple cursors is a feature that is available out-of-the-box within Visual Studio Code. (It is also available in Visual Studio, but some of the shortcut keys are different).

You use multiple cursors by creating multiple cursors in your editing window (selecting all instances of text you wish to edit), and editing your text.

Once you have multiple cursors in place, you can move them just like you would a single cursor, by using the arrow keys.

To go back to single-cursor editing, just hit ESCAPE.

It takes a while to get used to it, but once you get the hang of it, it can save you quite a bit of time.

CTRL+ALT+ ↑ / ↓: Select next/previous line

If you have a bunch of text in consecutive lines, you can simply start on a line and add cursors on the lines before or after by using CTRL-ALT-UP ARROW or CTRL-ALT-DOWN ARROW.

CTRL-ALT-DOWN ARROW and CTRL-ALT-UP ARROW to extend cursors by one line

ALT-CLICK: Create cursors

If you want to insert multiple cursors throughout a document that aren't on consecutive lines, you can simply hold ALT and click on each line.

ALT-CLICK to insert cursors

CTRL-U: Undo last cursor operation

Picture this: you carefully selected over one hundred lines by alt-clicking and -- as you get ready to click on the last line -- you click on the wrong line. You may think that alt-clicking again will deselect the line, but you'd be wrong. And don't try to let go of the ALT key to de-select the wrong line because you'll lose your entire selection!

Simply hit CTRL-U to under your last cursor operation. You can continue hitting CTRL-U to undo more cursor operations.

CTRL-U to undo the last cursor operation

CTRL-SHIFT-L: Select current match

You can insert cursors in every instance of the selected text by clicking CTRL-SHIFT-L. It saves you from having to manually find every instance of a word and Alt-click on every word. Fast!

CTRL-SHIFT-L

CTRL-F2: Select current word

To select all instances of the current word hit CTRL-F2.

CTRL-F2 selects all instances of the currently selected word

SHIFT-ALT-→ / ←: Expand/shrink selection

If you select a word and want to include the quotes (or brackets, or anything that surrounds a word), you can use SHIFT-ALT-RIGHT ARROW to expand your selection. For example, if your cursor is in the middle of every word, hitting SHIFT-ALT-RIGHT ARROW will select the entire words. Hitting SHIFT-ALT-RIGHT ARROW again will select the quotes around each word, and it will continue extending the selection every time you hit SHIFT-ALT-RIGHT ARROW. To shrink your selection, using SHIFT-ALT-LEFT ARROW.

SHIFT-ALT-RIGHT ARROW to expand selection, and SHIFT-ALT-LEFT ARROW to shrink selection

Rectangular Selections

You can use rectangular selections to edit ... well, rectangular areas of text.

SHIFT-ALT-Drag: Create rectangular selection

If you hold SHIFT and ALT while dragging your mouse, it will create a rectangular selection area, regardless whether there is text under the selection or not.

SHIFT-ALT-Drag to select rectangular areas

SHIFT-ALT-CTRL-Arrows: Create rectangular selection (keyboard-only)

You can also select a rectangular area from your current cursor position by using your arrow keys while holding SHIFT-ALT-CTRL.

CTRL-ALT-SHIFT-Arrows will allow you to use the keyboard to create rectangular selections

You can also use SHIFT-ALT-CTRL-PG UP and SHIFT-ALT-CTRL-PG DOWN to extend your rectangular selection by an entire page.

Other shortcuts to use with multi-cursor

These shortcut key combinations are not unique to multi-cursor editing, but -- when used with multi-cursor editing -- they can be quite useful.

CTRL-L: Select entire line

You can select the entire line where your cursor(s) sit by hitting CTRL-L.

CTRL-L to select a line

CTRL-→ / ←: Select to word boundary

Holding CTRL while using the left and right arrow will move the cursor to the next word boundary. A word boundary is anything that's not an alpha-numeric character, like space, quote, hyphen, etc. If you hold SHIFT while doing CTRL-LEFT or CTRL-RIGHT, it will select from your current cursor position to the next word boundary.

CTRL-LEFT and CTRL-RIGHT arrows will select to word boundary

Conclusion

Editing multiple with cursors in Visual Studio Code allows you to increase productivity by reducing repetitive steps and keystrokes.

I hope that you'll enjoy using multiple cursors in the future!

Introduction

A few years ago, my son fell in love with a song on the radio. He kept on asking us to play the song for him.

The problem is: like me, he is on the Autism Spectrum. He isn't always good at communicating.

He's incredibly sweet and incredibly caring, but he wasn't a very good singer.

He didn't know what the song was called. He couldn't sing it for us. He just called it the "nehbie decktur" song.

When we asked what song he meant, he would just repeat the same sound over -- in a sing-songy way.

"Nehbie decktur"

If you've ever tried to Google something when you don't have the right keywords, you know how hard it is to find information.

Wrong search query

I searched and searched everywhere for that damned song.

I tried to get him to hum the song in one of the various tune-matching search engines. No luck.

Until one day, the song came on the radio and my son heard it.

That song was Rude by Magic!.

The part where they sing "Marry that girl", in his mind, sounded like "Nehbie decktur".

To this day, we still call that song Nehbie decktur.

The Problem

When we design Information Architecture (IA), we often assume that people find content in one way.

If they know exactly what they're looking for, they'll search for content.

But if a user doesn't know what keywords to use in their search query, it can be very difficult to find the content they need.

In this article, we'll describe how we find content in different ways and how to design your Information Architecture to accommodate the various ways users find content.

3 Ways We Find Content

When looking for content, we use one of 3 approaches:

  • Browsing
  • Searching
  • Subscribing

We'll describe each approach below.

Browsing

Browsing allows users to find content through discovery.

Let's use an example: Nancy the New Employee recently started at Contoso Inc.

She isn't familiar with the organization structure, the department names, or even the company's lingo. She can't search because she doesn't know what keywords to use.

Browsing isn't only for new employees. It allows users to find content in an area of expertise that they aren't familiar with.

For example, your company may reimburse eligible daycare expenses for employees. Your Human Resource professionals, who are subject of matter experts on all things HR, may call this Childcare support plan.

However, your employees probably look terms like daycare, babysitting, or pre-school. Unless your HR folks used those keywords in their content, your users won't find what they need.

But if you have a SharePoint site that lists all benefits in one place (maybe grouped by life events, like getting married, having children, retiring, etc.), it will allow your employees to find the content they need without being experts in Human Resources.

To support browsing, make sure that users can access every piece of content by following links.

There shouldn't be any secret stash of content anywhere that can only be accessed through search.

How to design for browsing

Searching

When users know exactly what they're looking for, they use search. It is the fastest and most direct way to get to the content they need.

Microsoft's own Overview of search in SharePoint Online is a good place to get started with search best practices, including:

There are a few other things to consider:

Content consumers don't always share the same domain of expertise as creators

In our previous example our consumer, Nancy the New Employee, isn't a Human Resource professional. She doesn't know that the proper keyword should be childcare.

Unfortunately, the HR professionals (i.e.: the creators of content) use the proper terms in their documentation.

We can't expect the content creators to start tagging every single document with every single keyword that users might be thinking of.

One way to design for Search that satisfies the needs of both consumers and creators of content is to use managed metadata, particularly synonyms.

For example, to create synonyms for childcare, you would follow these steps:

  1. Go to your SharePoint tenant admin site (i.e.: https://**yourtenantname**-admin.sharepoint.com)
  2. From the SharePoint Admin Center, find term store in the left navigation.
  3. You can create your own term set (I created one called Human Resources) or use an existing one. Select the term set you wish to use and select Create term from the context menu.
    Create term
  4. The new term will be created in the navigation tree, just type the keyword your content creators will use. For example: childcare.
    New keyword
  5. In the right pane, while your new term is selected, find the Other labels field in the General tab and enter all the terms your consumers will use. You can type more than one keyword, just use the Enter key to create a new line. In our example, I typed daycare and babysitting.
    Other labels
  6. Hit Save to save your changes.

When users look for childcare or babysitting, they will find documents that also contain the word daycare.

Sometimes your consumers will use newer terms than your creators used. During the first H1N1 flu outbreak started making the news. My client's SharePoint servers started getting queries for H1N1 with no results found. However, the company already had detailed documentation on flu prevention and preventing outbreaks.

By monitoring search queries (the No Result Queries by Day and No Result Queries by Month, in your Admin center's Search > Search Reports are a good place to start), we were able to notice the increased number of searches for H1N1. After a bit of research, we found that all we had to do was to create synonyms for flu, influenza, "orthomyxovirus and H1N1. Just like that, users were able to find existing content for H1N1** they could not find the day before.

Consider search in your custom SPFx web parts

If you create custom SPFx web parts that use custom properties to store displayable content, your users may want to find the page containing your web part.

For example, I created a web part sample that allows users to create sequence diagrams and flowcharts using simple text. One of the web part sample, the Sequence diagram, exposes the text of the diagram as searchable by SharePoint.

Sequence Diagram in Action

In order to make the content of the web part searchable, I exposed the accessibleTitle and the accessibleText properties as isSearchablePlainText. (My web part creates an accessible equivalent of the diagram for users who use screen readers).

To do so, simply add a get propertiesMetadata method in your web part class which returns a list of property names and isSearchablePlainText: true.

  protected get propertiesMetadata(): IWebPartPropertiesMetadata {
    return {
      'accessibleText': { isSearchablePlainText: true },
      'accessibleTitle': { isSearchablePlainText: true }
    };
  }

If your property contains HTML text, you can also use isHtmlString: true in the same fashion.

Subscribing

The last way people find content is when the system finds content that the user has indicated they are interested in -- either explicitly or implicitly -- and returns that content.

This can be done explicitly by users creating Alerts or by Following a site.

But it can also be done by SharePoint recommending content on the SharePoint Home, SharePoint News showing aggregated news posts from sites the user belongs to or even Delve analyzing the user's interests via emails, tasks, etc.

The important point here is that if you design your information to be "subscribable", your users will find that information when they need it.

For example, if your company has offices in Toronto, Helsinki and Melbourne, you could create a single News site and post office news from all three offices on that site. But when someone posts news about the Helsinki office that is really intended for folks from the Helsinki office ("Freshly baked korvapuusti at reception!"), do we really want everyone in the company to get that news post?

Note: that's probably not a good use of SharePoint news, but korvapuusti (a yummy cinnamon-bun-type-thing with pearl sugar on top) and coffee is worth broadcasting!

Until audience targeting is available in News (which was announced recently, but then went missing from the announcement), you could create 3 sites (one for each office), and post news in each respective site.

Only users who follow the Helsinki office will see the news about the cinnamon "ear buns" promoted to them, while the other two offices will be blissfully ignorant.

Heikki, if you're reading this, I could use some korvapuusti right about now. Oh and some ruispala, kiitos.

Another way to create "subscribable" content is to break it into smaller elements.

For example, maybe I want to have a list of items listed somewhere that people would want to know right away if something new is added.

You could simply use the rich-text editing feature on a modern page and list all the items, but it will require users to actively subscribe to the page in order to get notified. If the page has other content on it, they'll get notified every single time any part of the page changes.

The other option is to create that list of items in a Custom list. Your page can use the List web part to embed the list. Now your users can subscribe to that list to get notified.
List web part

Conclusion

I have barely scratched the surface of how to design your Information Architecture for browsing, searching, and subscribing, but I hope that I managed to convey that people don't all find content the same way, and that taking some extra time to consider how people find content will help you design a better SharePoint.

As for me, I suddenly have a craving for cinnamon buns. I have to go make a batch for tomorrow morning! (Sugar-free, of course)

I hope this helps?

Introduction

I have known this couple for many years. They are both great friends and got the opportunity to work on engagement with both (at separate times -- I'm not getting involved in a husband/wife at work situation!).

A few years ago, they learned that their son was diagnosed with Autism Spectrum Disorder (ASD).

It was hard news for both, but the mother took the news especially hard.

In her mind, it was as if her son had been sentenced to never achieve any form of independence, to never fit in socially, and to never be able to have a job. Her son would never marry, have 2.4 kids, and buy that white picket fence.

When she shared her fears with her husband, he told her: "You know that Hugo is Autistic, right?" (I'm sure he said it with a lot more sensitivity than I did, but I'm the one telling the story). "He's successful at his job. He travelled the world for his work! He got married. He has kids."

I'm sure that it didn't take her pain away, but -- at that moment -- it helped her to know that someone she knew, someone she had worked with, who was ok at his job, who had a weird sense of humour also shared an ASD diagnosis.

I read her blog today. She chronicles her journey as a parent of a child with ASD, and her own son's journey. She writes beautifully poignant stories.

I asked her why she didn't share her blog.

"I write for me. It's therapeutic", she replied.

I encouraged her to share her blog, telling her that other parents of children with ASD would benefit from reading her blog.

Then it occurred to me that I was being hypocritical. I encouraged her to share her story, but I have never discussed my own ASD.

I don't like talking about it (unless I use it as an excuse to act like a jerk, then I blame my autism). But just like my friend's blog can help others, I hope that my writing about being a professional IT consultant with ASD will help someone else.

I don't rock myself back and forth, insist on pancake Tuesdays, have the ability to instantly count toothpicks on the floor (246), or many other things people think is associated with autism.

I'm not especially smart. I'm not violent (unless you stand between me and my first coffee), and I'm not a savant. I'm not particularly good with numbers (ask my accountant). I'm not cold or lack empathy (but I don't always agree with everyone being so emotional about everything either).

Don't get me wrong: I'm still weird. I'm still socially awkward. But I'm also independent, I haven't had to look for work in over 20 years, I often do public speaking engagements, blog regularly, and I found an amazingly loving wife who tolerates my quirks.

I also have three beautiful children, two of which are also on the autistic spectrum.

None of the things I have achieved in life make me prouder than seeing my children become amazing human beings.

Being autistic does not mean being condemned to never achieve anything in life. It comes with its own set of challenges, neither better nor worse than any person -- neuro-typical or not.

The Reason I Jump

The title of my post is a play on the book The Reason I Jump: The Inner Voice of a Thirteen-Year-Old Boy with Autism, by Naoki Higashida. If you haven't read the book yet, I strongly recommend it.

It amazes me how the author, as a 13-year old, was able to express his answers to the questions he imagines others most often wonder about him in a way much more eloquent than most of us with ASD would ever be able to. And he did so with the use of an alphabet grid to painstakingly spell everything out.

I cannot hope to have the same clarity or eloquence as he does in his book. He writes with such insights into the inner workings of his mind that reading his book helped me gain insights into my own mind.

It often feels like more of an autism user's manual than a book.

Where I have been

The purpose of this article is not to brag about the things I have done. I'm not trying to make people feel bad for me. I'm not trying to excuse the things I do or who I am.

I'm just trying to establish a bit about myself.

I grew up near Quebec city, speaking French. I started playing with computers when I was 12. It started as a way to bond with my dad -- he would read code from one of those old computer magazines, and I would type the code in. It quickly became my passion, because I felt that I could express myself through code.

I started consulting when I was 16. I eventually moved to Ontario to learn to speak English and I've been busy ever since.

If a 16-year old was hired to come into your place of work to teach you how to do your job -- which you have been doing longer than he has been alive -- you would probably resent it. I had to deal with people who felt threatened by me -- some even threatened to beat the crap out of me.

To avoid getting beat up, I learned to develop consulting "tricks" to integrate better with the clients' staff and achieve better success. I subtly mirrored people's postures and gestures, picked up the same way of speaking, used "us" (versus "us" and "them"). I learned time management skills, presentation skills, and anything else that would make me appear more "professional" (instead of "just a kid").

At first, I was one of those geeky IT people who did not tolerate people who don't understand computers or software.

It wasn't until I joined McKinsey & Company as a Senior Associate that I learned to appreciate the giant gap between business (i.e.: the people who did not understand computers), and technology (i.e.: the techy bits).

I had to learn to explain technical stuff to C-level executives in a way that would make them excited about technology and explain "business-y" stuff to developers and IT folks in a way that would make them understand the business needs.

In other words, I learned to be one of those geeky IT people who tolerate people who don't understand computers and software.

I had found a niche, it seems. At a time when 16% of software projects were delivered on-time, on-budget, and met the business needs, I learned to bridge that gap between business and technology -- which happened to be one of the leading causes for software project failures.

My work took me all over the world: China, Japan, Singapore, Hungary, Germany, France, UK, Finland, Malaysia, Indonesia, Australia, South-Africa, United States and Canada -- that's just the ones I can remember.

I have worked in various industries: Healthcare, Finance, Energy, Transportation, Education, Government, Manufacturing, Professional Services. Each with their own domains of expertise and processes.

I like to keep my engagements pretty short because I get bored when things are no longer challenging.
Usually, by the time a company brings me in to help with their projects, they have tried to implement a software solution and failed 2 or 3 times before.

Every engagement is different: different language, place, industry, company, background, problem and solution.

In fact, the only common thing about my work is the unknown.

So how does someone affected by a developmental disorder that predisposes them to fear the unknown and react negatively when faced with unpredictable situations make a living embracing the unknown and unpredictable solutions with every new engagement?

Dealing with Chaos

In his book, Naoki Higashida compares having autism with being in a room filled with radios that are all tuned to different radio stations, each one with the volume at full blast (I'm paraphrasing here, the author is much more eloquent than that).

Autism people often have difficulty prioritizing the various inputs. Sounds, smells, textures, colours are all like one of those radios blasting to compete for attention.

It requires a lot of focus and self-control to learn to prioritize each of those stimuli so that we can process the information.

As Higashida-san puts it best:

“When you see an object, it seems that you see it as an entire thing first, and only afterwards do its details follow on. But for people with autism, the details jump straight out at us first of all, and then only gradually, detail by detail, does the whole image float up into focus.”
Naoki Higashida, The Reason I Jump: The Inner Voice of a Thirteen-Year-Old Boy with Autism

I deal with the unknown of every new engagement by applying a pre-established framework for everything.

Seriously. It's annoying. (Well, to everyone but me, I'm sure)

Want to migrate to Office 365? I have a migration framework.

Project has a risk that we should address? I have a risk management framework.

Need to gather requirements? I have a framework.

Presentation needed? Framework.

And it goes on.

I didn't invent those frameworks. I have learned to stand on the shoulders of giants and use pre-established frameworks that people who are much more intelligent than I have developed.

I even have a framework for dealing with the unknown. One for solving problems.
Note that I call them frameworks, not methodologies or processes because they consist of a series of pre-established guidelines that evolve and adapt to changing needs and not a rigid set of steps that do not evolve.

Knowledge that does not evolve is dogma.

If I don't have a framework for something, I'll have an eponymous law to explain my recommendation.

Talking about user experience? There's Fitts's Law, Miller's Law, Hick's Law, etc.

Work durations? Parkinson's Law.

People and skills? Dunning-Krueger.

To quote my friend Luis: "Hugo has a law for everything". It usually sounds more of annoyed observation than a compliment, but I choose to accept it as a compliment.

I'm sure it is annoying to everyone who has to work with me, but it helps give credibility to your ideas when you can refer to a higher authority. It's not Hugo that says you should lay things out on a page to accommodate for the left-to-right, z-pattern movement of the eyes when looking at evenly distributed items, it's Gutenberg. He's a lot more credible than I am.

These laws, frameworks and rules help me make sense of the chaos and the unknown and give me structure. If you want to see me lose my cool, try taking that structure away.

My frameworks are the safety blankets that help me deal with the scary unknown.

And when I finally figure out what the solution to a problem is, and it involves coding, I get to convert those ideas into a language that computers can understand.

With code, there is no grey area. There are only zeroes and ones.

Done and not done.

It really upsets me when a co-worker says to me that there is a random bug. That "for some weird reason" something doesn't work. Or that a bug "just went away".

It's not random, it just appears random to us. It's not a weird reason, it's a very logical reason that we haven't quite figured out yet. It didn't go away, the condition that causes the bug went away temporarily -- the bug is still there.

But then I relish the idea of breaking down the problem and methodically find the problem until we solve the issue.

Standards of Success

The standard by which I measure my success is different from yours.

I get to play with computers every day. I get paid for doing that. At least I think I get paid.

I get to talk to audiences about my passion. I get to bond with people over software, just as I did when I was a kid.

On the other hand, I can't drive a car. I have a driver's license, but I don't drive. Instead of a feeling like a room filled with radios at full blast, driving is like a car filled with horns blasting in my ears.

I can't do taxes or most forms of paperwork. The questions are too vague. Yet, I can design forms for a complicated business process.

I can't talk on the phone. I can do conference calls because they're scheduled and the topic is often pre-defined, but I can't do a one on one call, because I can't see some of the social cues that I've learned to rely on. I could be on the phone and someone would say "Sorry I missed our meeting, I was at my mom's funeral" and I might say something stupid like "Haha, that's funny!", or "That's great!".

I don't get some social conventions. For example, I won't say "Bless you" when you sneeze, because I don't actually believe that your soul escaped your body. I'm not trying to be rude -- just rational.

I'm diabetic, and if I don't eat at regular intervals and control my blood sugar, I crash. Yet, when I'm working, I completely forget to eat, sleep, and go to the bathroom (until the very last possible moment).

People will say "why don't you just go eat when you're hungry?" but they don't seem to understand that the organ that's responsible for producing insulin in your body is also responsible for alerting you that you're running low on sugar. If you're diabetic, that organ is defective (for some weird reason).

If you're autistic and diabetic, your body is not doing a good job at warning you that you need to eat, and your brain isn't doing a good job at listening to those warnings.

"Why don't you set an alarm?", they'll suggest. That sound of an alarm blaring in the background may not even register when competing against the sound of the air conditioner fan, the electrical buzzing somewhere in the house, or the computer fan right in front of me. Any sort of rhythmic sound or pattern takes over -- even the sound of your own heartbeat -- and forces you to dig deeper and shut the world out just so it does not drive you crazy. An alarm isn't going to make a difference.

(Thankfully, I have a loving wife who once in a while makes sure I still have a pulse).

Some of these skills might be someone's idea of being a successful grown-up. They might even be vital to one's survival (like eating when one's blood sugar is low). I have learned to accept that they aren't part of my criteria for success.

And I'm ok with that. (Although I'm sure my wife would be happier if I drove)

Conclusion

I love my work. Occasionally, I have to deal with Lindas, but it is mostly awesome 🙂

I love it because it allows me to approach every problem with a structure. It gives me a sense of security, well-being, and self-worth that I crave when I'm not working.

I try to teach my kids to question everything and to try to find what makes them truly happy. If they can do
something that makes them happy, they'll never really work a day in their lives. It will always feel fun.

To quote Naoki Higashida:

“To give the short version, I've learnt that every human being, with or without disabilities, needs to strive to do their best, and by striving for happiness you will arrive at happiness. For us, you see, having autism is normal -- so we can't know for sure what your 'normal' is even like. But so long as we can learn to love ourselves, I'm not sure how much it matters whether we're normal or autistic.”
Naoki Higashida, The Reason I Jump: The Inner Voice of a Thirteen-Year-Old Boy with Autism

If your children have been diagnosed with ASD, you can help them succeed and be happy by helping them make sense of the world around them.

I'm not implying that every autistic child will end up a public speaker or an IT consultant -- I understand that autism is a spectrum, and I have had it pretty easy compared to others -- but a diagnosis does not instantly mean that they will never live a happy life either.

Remember that your "normal" isn't necessarily their "normal". Your idea of success may not be the same as theirs, but as long as they're happy, you should be too.

Credits

Header image by Gerd Altmann from Pixabay

By Oguh Reinreb, Evil Consultant

NOTE: Today's post was written by Hugo's evil twin Oguh. The views, information, or opinions expressed in this guest post are solely those of the evil twin involved and do not necessarily represent those of Hugo Bernier.

Introduction

Everybody can migrate SharePoint to Office 365, right?

All you need is a credit card to create an Office 365 tenant and an old SharePoint instance and you can get started migrating your files!

But if you're looking for job protection -- or if you're offering professional services -- you probably don't want the migration to happen too fast.

Why would you want the migration to complete quickly when you can just let it drag longer than it should?

This article will give you pro tips to guarantee your migration from SharePoint to Office 365 will fail.

This list is a compilation of pearls of wisdom that I have learned over the years with dealing with people who know better.

Follow this list and you'll be able to bleed your employer/client of all their money and not deliver anything of value. And if you do deliver something, it'll be completely useless.

You can always blame Microsoft if it doesn't work.

What do we mean by failing?

The best way to fail is to avoid establishing any kind of success metrics. If people don't know how to measure your success, they'll never know you failed.

But to make sure you fail real good, let's establish some failure criteria:

  • Costs overruns and delays: a study by Deloitte (I think it was Deloitte, but who cares, 82% of statistics are made up on the spot -- who will check?) found that the common practice of awarding a contract to the lowest bidder usually results in project delays and spending over budget. In fact, the average cost is equivalent to 5 times higher than the price of the highest bidder. Now that's something to strive for!
  • Poor user adoption: if you improve your user's lives and made them more productive, they'll keep on expecting more from you. Make sure that the migration you did is so bad that your users won't want to use SharePoint and that they'll find alternate ways to store documents and collaborate. That's what email is for anyways.
  • Lost files and metadata: If your old SharePoint library had 1,000 files, just make sure that you migrate 1,000 files over. That's enough QA. Nobody needs metadata.

Don't involve your users

Ugh. Talking to users is such a waste. of. time.

Why should you spend your valuable web-surfing time at work to interview your users and find out what their pain points are? That's like saying "please whine some more".

Just because your users spend all their time doing their daily job doesn't mean that they understand what they need better than you. I mean, they don't know anything about IT, probably less about SharePoint, so how can they possibly add value?

Remember: you know better

The worst part about interviewing your users and asking them for their pain points is that it would give you something to measure your success with. Users might actually expect you to deliver everything they ask for.

My advice: don't talk to users. You do you. They'll just have to adapt.

And don't even think about nominating so-called "Champions". They're the worst. "Champions" (read with air quotes) might think of things that you didn't think of and identify gaps, just because they know sooooo much about how they need to do their jobs. They might even help change other users' opinion by talking to their peers (and then everybody will expect you to deliver stuff).

If someone insists that you need to identify "Champions" in the organization, get the Executives to name people that don't really know anything -- don't let them name people who will actually use the system.

And whatever you do, if a group of people approaches you and they want to be early testers on your migration, steer clear of them! I've seen it too many times before: they may find issues before everybody else gets to use the system (and then you'll be expected to fix the issues !).

Even worse: "keeners" (that's what I call them) might generate excitement in the organization. The may get other groups (who would otherwise leave you alone) to get jealous, to get excited about features they have been whining about for a long time, and demand to be early adopters.

Don't communicate

This is just common sense. If you tell people that a new version of SharePoint is coming, they'll just get worried about the coming changes and they'll make your life miserable.

Remember that your users have jobs to do. They have probably worked really hard to learn the last version of SharePoint (they change everything between versions, don't they?) and if you tell them about the upcoming SharePoint migration, they'll fight you.

Even worse if you tell them about moving to the cloud! Remember: users don't know anything about the cloud, and they probably have concerns over safety and security. If you tell them in advance what's coming, you're probably going to have to explain to them why the cloud is better, blah, blah, blah.

Let the rumours fester. If you let users guess what's happening, they'll make up stuff and may not even have to deliver anything because they'll fight the migration before you even get started!

Best strategy: hope that your users enter a deep coma until your migration is done. When they wake up, dump the new platform on them and let them fend to themselves.

If you're a consultant and you were brought in to migrate, try to can keep the rest of the IT department in the dark. It is fun to watch them squirm and think that they're going to get laid off.

And they're always trying to show off with all the stuff they know about their legacy systems and why you shouldn't do this and that. Yecch!!!

Probably the best way to make your life easy is to never explain to users about the benefits of moving to the cloud and using Office 365. Don't tell them what's in it for them. Because they'll expect stuff from you!

Don't waste your time on a content audit

Why would you take the time to list all the sites, subsites, document libraries and file shares that you'll have to migrate when a cursory glance at the data will give you enough to make up an estimate?

Trust me, if you do an audit, people will expect you to make educated decisions about what content needs to move. Or even worse, they might expect you to consult them to find out what you should move.

Status Quo is good

Copy and paste. Don't think!

Don't start going through the content and try to identify duplicates, or content that you should migrate. Don't look at permissions and folder structure. That would be using your brain, and we don't want that.

So what if your users have eleventy layers of folders and sub-folders, that's their problem.

And if you move from a file share to SharePoint and change the folder structure, your users might whine that you lost "implicit metadata" (i.e.: metadata that is implied by where a document resides in a file structure). You would have to take extra time to apply metadata to documents, and that sounds like too much work.

Just copy and paste the documents the exact same way they are at the source. Don't start spring cleaning -- you can always clean next time you migrate.

Estimates are for losers

Estimates are hard. Like "work" hard.

Why would you take the time to analyze the data thoroughly to estimate what the effort will be to migrate the content when all you have to do is give an estimate that your boss/client/stakeholders will be happy with? Go ahead, underestimate.

Most cloud migrations take 6 to 9 months to be done right, but if you tell your bosses that, they won't like it and they'll demand smaller estimates. It is better to give them an estimate that they want to hear (like 1 month or 3 weeks).

Once they signed off on starting the migration, it's not like they can do anything if you take longer, right?

You know as well as I do that they won't stop asking you for shorter and shorter estimates until you tell them what they want to hear. Why go through the trouble when you can just skip to the estimate they want.

For extra bonus points, once you gave them a short estimate that you have no hope of meeting, try to rush the migration to keep deliver to that ridiculous timeline you committed to. The best part about that approach is that nothing will be done right, and you may spend the next several months or year (usually more than your most pessimistic estimate) fixing all the issues.

You could try to give them a reasonable estimate based on actual calculations, and ask your stakeholders to give you enough time to perform a few test migrations so that you can improve your estimates, but that sounds like too much like adulting.

Oh, and whatever you do: don't keep a master risk list or, if you're using Agile, a risk registry.

If you tell people about the risks, you'll need to have uncomfortable discussions about why you think something may impede your migration.

They might even expect you to create a mitigation plan to prevent risks from happening, or contingency plans to do if the risk actually happens. Sounds like more work to me!

Best thing to do: cover you're a* and mention the issue once, casually, whenever you have a chance. The less formal the better. For example, tell your boss when he's late for a meeting. Don't offer a constructive way to solve the issue, just complain*.

That way, if something goes wrong, you can always say "I told you so!".

Gap analysis shmanalysis

Gap analyses are so overrated!

Who needs to look at the differences between the old version of SharePoint and the new version.

For example, if you found out that your old sandbox solutions are no longer supported, or that your mission-critical application built on Access Services won't work anymore, people are going to expect you to find an alternative solution.

Don't waste your time with migration tools

Why would you use a migration tool when you can just drag and drop the documents from the old SharePoint to the new SharePoint Online?

Arrgh! Those annoyingly demanding users are going to complain that all the documents' Last Modified date and Author have changed.

And you better hope they don't notice that you lost their version history either!

I guess you can always look at Microsoft's free SharePoint Migration Tools, which handles migrating sites and file shares. But how good can it be when it is free?

I mean, what incentive can Microsoft possibly have to spend time and money building a decent tool to migrate to Office 365?

My advice: don't even look at the tools. If someone tells you about the free SharePoint Migration Tools, you can always use the age-old IT consultant trick. Just say the following words:

I've heard that [INSERT PRODUCT NAME HERE] is slow and buggy

No one will ever argue with you. If you say it is slow and buggy, it must mean that you used it and nobody will ever doubt your IT knowledge ever again.

Don't waste time researching the various tools available. Don't look at how well they support their products or how they take time to educate their users to migrate successfully.

Don't evaluate whether the tools support scripting or automation. That way, no one will expect you to test your migrations and save them to a script that you can re-run without introducing issues.

Nah. If you have to use migration tools, pick the first one you heard of without worrying about why it is better than any other tool. It's not like you're spending your money, right?

If the tool doesn't work, you can always blame the tool.

Users love surprises

If you tell your users that you are going to migrate their stuff, they will pester you with questions.

It is better to hope that you never have to talk to your users, and just migrate stuff without telling them.

They can't stop what they don't know about, amiright?

If you start telling people what you're doing, I can promise you that a lot of the managers and directors of employees who may be affected by your migration will start asking you hard stuff. Like "how much time do you need from my staff to help?". They may even ask you to schedule meetings in advance!

Pro tip: Users don't have anything better to do. When you finally realize that you need their help, just expect them to drop everything. They'll be excited at the opportunity to take on additional duties to help you.

Don't schedule maintenance windows... that's what lunch breaks are for!

If you're pretty sure that nothing is going to go wrong, don't even bother telling people when you're migrating stuff. I mean, everything should go perfectly, so why worry users?

If they lose something important that they were working on, we have a backup, right?

Uh, I never really checked that the backups actually work.

Just wait until people are away from their desks (lunchtime or late Friday afternoon is a great time to do this), that way you can hope that if you have to reboot a server or if you affect the network or server performance, users won't notice it.

Training is for babies

We've discussed this before: anything that you can do to avoid telling your users what's coming will reduce the risk of them asking more questions and demanding answers.

The best training strategy is: throw them in the deep end and expect them to swim.

Don't waste money and time on training people. That's time you can't be Facebooking or Instagramming.

Sure, Microsoft has tons of customizable training materials available for free, but how good can it be, really? I mean: it's free.

Let users figure it out on their own. They're all technical experts, right?

Users are stupid.

Start with the assumption that users are stupid.

Also: feel free to make decisions to disable functionality because people won't understand it. Don't reward users who are more technologically savvy. Instead, assume that if you give your users too many options, they will spend so much brain power trying to understand that they will probably lose basic motor functions and be unable to maintain bladder control.

I always admire it when a manager or a director says "oh, our users will never understand that". They understand how you and they are intellectually superior to everyone else and don't even give users the opportunity to learn.

For example, don't give people access to Microsoft Teams, because they are too stupid to understand how to use chat applications.

Don't let people post profile pictures, because they'll instantly post pictures of their private parts. Instead of revising your "IT Acceptable Usage Policy", just disable those functions immediately.

And don't get me started about letting people use emoticons and comments in sites. So unprofessional! Better to spend all your efforts finding ways to disable those features, otherwise every single one of those previously professional people will suddenly lose all sense of proper conduct.

It's a wonder that they never thought of doing this with email? I mean, e-mail was invented in 1971 and we let people send emails to "All employees" and external email addresses and it hasn't occurred to them that they could send inappropriate emails before? Stupid people.

I know that the workplace landscape is changing and that some statistics (somewhere, can't be bothered to check) say that over the next 3 years, 48% of the working population will reach retirement age. It means that the new wave of people entering the workforce will be used to new modern tools, but what do they know -- they're young.

Keep your antiquated old views

You started using SharePoint back in 2001. How much can it have changed?

Don't waste time learning new features that Office 365 offers. True SharePoint gurus stick to the OG functionality.

For example, when you create a new Team Site or Communication Site in SharePoint Online, it creates a new Site Collection. Talk about overkill! They're called "Sites", not "Site Collections" for a reason!

Microsoft could just create one giant site collection with all your sites in one place.

If a feature didn't work well in a previous version of SharePoint, don't waste time to see if it has improved; just dismiss it completely and never go back to it. That feature is dead to me.

I mean, really, all that the SharePoint team has done since then is make it look uglier. So much whitespace!

If you wrote a book or a blog article even remotely related to SharePoint 10 years ago, you are absolutely absolved from having to learn anything new. Just hold on to those views, just like you've been holding on to your BlackBerry because let's face it, it's better.

Also, grey areas are for sissies. You need to have an unwavering opinion about every topic, and it needs to be absolutely black or white. No wiggle room to account for the customer's unique needs.

For example, folders are bad. Period. Immediately dismiss the opinion of anyone who says otherwise. Don't even waste time to think about whether there are acceptable scenarios where folders should be used.

I once worked with someone who refused to learn this new "SharePoint" thing we were implementing at her work (even though her new role was "SharePoint Administrator"). When I asked her why she didn't want to learn it, she said: "because I'm retiring in 5 years and I want things to stay the same". Be more like that: don't learn anything new.

More wisdom

Here are more pearls of wisdom I have learned from people.

Waste valuable time trying to rename SharePoint.com

When you had your old SharePoint on premises, you had full control over your SharePoint's domain name and server name. You could call it sharepointy_mc_sharepoint_face if you wanted to.

When you switch to Office 365, your new SharePoint domain will be yourcompanyname.sharepoint.com.

That will confuse everyone. People won't be able to handle it. There will be desks flipped over and people will set things on fire.

The best thing to do is to spend all your energy to find a creative way to rewrite your SharePoint online URL to something that won't confuse users.

Never mind that Microsoft doesn't support this, they just don't know any better.

Rebrand SharePoint completely

Office 365 and SharePoint support the use of themes and some limited organization profile colours.

Again, Microsoft doesn't know any better and hasn't spent any time researching how to optimize content for legibility, accessible contrast, etc.

If you don't completely rebrand your SharePoint site so that it meets your corporate designs, your users will not understand that SharePoint is a tool for work.

If they aren't constantly reminded of what company they work for with a giant logo occupying the top 1/3 of the screen, they may even forget who they work for.

Best advice: do everything you can to rebrand SharePoint to meet your corporate design.

Microsoft says that you shouldn't change the master pages or inject CSS in the page because (read this next part in a whiny/mocking voice) "they reserve the right to change the page structure at any time".

So what, for example, if you change the master page to suit your designs and it blocks you from receiving any new features/fixes Microsoft releases? It's not like they release new features, and updates twice a week, right?

I mean, can Microsoft really have this whole release thing down so that they technically have the ability to do multiple releases a day? I don't think so!

Don't worry about what's supported. Do what feels right.

Spend time disabling features you don't understand

If you don't understand a feature that Microsoft offers, and they don't provide you with a way to disable it, spend as much energy and resources as possible to disable that feature, regardless of the impact.

For example, if you don't understand the benefits of using the News feature in SharePoint, you should stop every single team from using it.

Even if you have practically unlimited storage space in Office 365, don't let users create groups. God forbid they create a collaboration space where they can do their work.

I mean, people are going to stop doing all their work and spend their entire days creating team sites and groups and put bad content in there, and then they'll leave it there.

No, instead of spending time trying to create a proper governance process, and quotas and other easy solutions to help prevent issues, disable those functions completely.

Don't use all the tools at your disposal

Don't even look at the tools and features that are included in your Office 365 subscription. That would require you to spend time understanding what they do and -- sigh -- explain to people how to use them.

For example, don't use the built-in voice, video, and whiteboard features of Microsoft Teams that integrate natively into pretty much everything. Instead, insist on using another third-party video conferencing tool that you have to pay extra for without understanding what the gaps are. (Remember, say it is slow and buggy).

Instead, spend all your efforts to try to get integrate a third-party tool.

Don't use Advanced Threat Analysis features of Data Loss Prevention features. Insist on forcing users to use your old, unpatched VPN solution.

That way, Microsoft's machine learning algorithms can't identify potential threats and immediately take counter-measures to protect your information. That's something that's best left to a human.

Don't worry about URL limits... or any other kinds of limits

When you're planning your migration, don't worry that the URL of your documents may be over 400 characters (because your old file structure is like a Russian nesting doll of folders within folders).

Your users won't know the difference. They'll find out that their files are missing way too late to force you to do anything about it.

Force every single person to go on your home page

When users log in to Office 365, they can choose what their landing page is going to be.

For example, their SharePoint page can get them the list of news and updated documents that are relevant to them based on their group memberships and other smart algorithms.

That means that you can't force people to go to your home page and see what you decided was important to them.

You could configure an organizational news source and use the Set-SPOOrgNewsSite PowerShell command to help push your corporate news in the user's news feeds where it will reach them (SharePoint Home, Team Sites, Communication Sites, Hub Sites, Microsoft Teams, Mobile App), but isn't it better to find an unsupported way to force everyone to go to whatever page you deem important for your users?

Even better, you should force your user's workstations to launch a browser to your mandatory home page. It's not like you can make SharePoint become such a mission-critical tool for them that they'll naturally want to log-in to it every day.

Remember, they don't know any better.

Conclusion

I hope that I have given you enough material today to help you ensure a failed SharePoint migration to Office 365.

Remember that if you succeed, people will expect you to deliver again and again. If you're busy working, when are you going to surf the internet and watch videos on Youtube?

If anyone accuses you of trying to fail a SharePoint migration, don't forget that according to CMS Wire, you're in good company: nearly 50 percent of all Enterprise Content Management programs fail just from a technology perspective. And of the 50 percent that succeeds, half of those fail to really provide value to the business.

You can hardly be blamed for failing.

What other tips do you have to help ensure your SharePoint migration to Office 365 fails? Let me know in the comments -- as long as you don't expect me to read it or anything.

Whatever you do, don't read these articles

Avert your eyes! You might actually learn something and I'll have written this post for nothing

5 Mistakes To Avoid When Migrating from SharePoint to Office 365. Pffft, what do they know?!
5 Common Mistakes Migrating to Office 365
Top 5 Mistakes to Avoid When Migrating to Office 365
25 Mistakes to Avoid in SharePoint or Office 365 (and How to Fix Them) -- they're actually trying to fix problems?! What's wrong with them?!!!
The Why of ECM Failure and the How of ECM Success

Update

Thanks, everyone for your feedback, I've added a section on keeping your antiquated views by popular (i.e.: more than zero) demand.

Introduction

In software projects, we have a tendency to repeat the same mistakes over and over again.

Stakeholders ask for estimates. Usually something along the lines of "we don't know what we want. When can you have it done and how much will it cost?".

Sometimes we manage to extract some requirements before we answer, but often we just resolve ourselves to providing an estimate -- based on absolutely nothing.

We have that nagging feeling that we're doing something terribly wrong. We had that bad feeling on our last project, and our estimates ended up being wrong.

But we tell ourselves:

"this time, it'll be different".

So we foolishly give our estimates.

And the estimates end up being wrong. Again.

Somehow we manage to get through the project. We cut corners ("we can just take out that buffer", or "we don't need QA, right?"). We apologize for being late. But we get through it (most of the time).

After a well-deserved break (a.k.a. a weekend off), your stakeholders ask for another estimate.

And we tell ourselves:

"This time, it'll be different".

Why do we repeat these mistakes over and over again?

The Cone of Uncertainty

At the beginning of a software project, we know almost nothing about the product or work results, and so estimates are subject to large uncertainty.

As we do more research, we learn more information and the uncertainty decreases. Our estimates become more accurate.

When we start writing code, that uncertainty decreases even more. The more features we complete, the more that uncertainty decreases.

Eventually, the uncertainty reaches 0% -- when all risks have been mitigated.

This usually happens by the end of the project (when we no longer need an estimate)

This is a concept that Steve McConnell described as the "Cone of Uncertainty" in his book Software Project Survival Guide (McConnell 1997).

Cone of uncertainty

Software teams regularly sabotage their own projects by making commitments too early in the project cycle. An estimate at the project Inception stage is possibly wrong by a factor of 2 to 4 times. They invariably undermine predictability, increase risks, and reduce the chances that the project will be successful.

By the Elaboration stage, the estimates can already be twice as accurate as they were in the Inception phase. They can still be off by a factor of 2, but they are suitable.

An effective team will delay their estimates until they have done enough work to force the Cone of Uncertainty to narrow.

Unfortunately, stakeholders typically don't want to wait to get their estimates. They want to find out how much budget they should allocate (or to know how much they'll get for their budget).

The curse of estimates

Let's try an experiment, shall we?

Take a look at the two squares below. Without using any tools, estimate how big, in pixels, each square is.

Don't cheat. Don't use your browser tools or any rulers. Just try to estimate, with all your knowledge and skills, how many pixels each square is.

Two squares of different sizes with blurry lines

Pretty difficult, right?

A lot of things affect your ability to answer accurately. The blurry lines and the imperfect squares make it pretty hard to determine where your measurements should start and end.

Let's try again with cleaner, well-defined squares:
Two squares of different sizes with straight edges

Still difficult to guess their exact dimensions in pixels, right?

Unless you're a cyborg or a robot (in which case I salute our robot overlords), you do not have the ability to detect the square dimensions accurately.

Now let's try again with the fuzzy squares, but this time answer the following question: how much bigger is square B compared to square A?

Two squares of different sizes with blurry lines

You probably guessed that square B is twice as big as square A (or 4x as big if you're comparing areas.)

Two fuzzy squares, one twice as big as the other.

See what happened? Even if the squares were fuzzy (i.e.: requirements not clearly defined), and even if you didn't have measuring tools, you were able to guess their relative size to each other.

That's one of the key concepts to agile estimation: as humans, we're really bad at estimating absolute sizes, but we're naturally good at estimating relative sizes.

As humans, we're really bad at estimating absolute sizes, but we're naturally good at estimating relative sizes.

The value of sprints

By breaking your work into smaller units of deliverable functionality, and delivering that functionality in small iterations (1 to 2 weeks), you can establish a baseline for your estimates.

Start by estimating the relative size (something that is often described as Story Points) of some the product backlog item that you will deliver in your first sprints.

For example, you and your team may estimate that product backlog item B is twice as big as product backlog item A.

You can assign relative values of 1 story point to item A, and 2 story points to item B.

Deliver as many items within your first sprint as you can using a normal pace of work.

At the end of the sprint, add how many story points you delivered for all the product backlog items that got completed within the sprint.

Let's say that you delivered the following product backlog items (PBI):

PBI #Estimate (Story Points)
A1
B2
C2
D3
E2
Total10

If your sprint was 2 weeks long (10 working days), you can estimate that your velocity is 1 story point per day.

As you plan your second sprint, you can base your estimates by comparing the story point estimates for the work that you already did against the product backlog items you'll be delivering during this next sprint.

For example, let's pretend that you delivered a total of 8 story points in the second sprint:

PBI #Estimate (Story Points)
F1
G1
H2
I2
J2
Total8

Your sprint velocity for sprint #2 was 0.8 story points per day. Your average velocity is 0.9 (or 10+8 / 20) story points per day.

On your third sprint, you repeat again. This time, let's pretend you managed to deliver 12 story points (what can I say, your team is good!). Your average velocity is back to 1 story point per day (10+8+12/30).

As you do more sprints, your average sprint velocity average will become more and more accurate. Your estimates will also become more accurate.

Best of all, given your velocity average, you can start estimating how much longer it will take to deliver the rest of the product backlog items (as long as you have story point estimates for your backlog).

As we established earlier: even if the requirements are fuzzy (like those squares), you can still get a pretty good idea of the relative size estimates. As your requirements become clearer (for example, when you clarify the product backlog item prior to sprint planning), your estimates become more accurate.

Conclusion

Stop treating software estimates as a precise prediction of a project's outcome. It isn't.

The best scenario would be to fund enough of the project to establish a baseline (or velocity) so that your future estimates can be more accurate (because you can compare your remaining Product Backlog Items against the work you've already done and build relative estimates.

Best thing to do is be honest. Tell them that you don't know how long it will take. Tell them that your estimate is your best guess, but that at this time it can be off by as much as 4 times. Ask them to give you enough time to complete 2 or 3 sprints, after which time you'll be able to provide them with a more accurate estimate.

Do yourself (and everyone else in IT) a favour: stop treating your estimates as a precise calculation and start having a "Cone of Uncertainty" conversation. In my experience, most stakeholders are smart enough to understand that your estimates will become more accurate as the project evolves.

As Steve McConnell says:

The primary purpose of software estimation is not to predict a project’s outcome; it is to determine whether a project’s targets are realistic enough to allow the project to be controlled to meet them.
—Steve McConnell, Software Estimation: Demystifying the Black Art

References

The Cone of Uncertainty, Construx Software.

Introduction

I recently published the list of developer tools that I use as a SharePoint Framework developer. The list was inspired from Scott Hanselman's own list -- he deserves all the credit for the idea.

I received tons of great feedback about the article, and many of you gave me great suggestions. Thanks to everyone for helping, and I hope that -- together -- we can make a list that anyone starting with SPFx can use as a starting port.

Sam Culver kindly pointed out that the Fira Code font makes a great choice for the default font in Visual Studio Code:

Sam Culver (@samculver)
Replying to @bernierh and @vesajuvonen
Fira Code is also great in Visual Studio Code

He's absolutely right! I have it as my default font and I thought I should show the very simple steps to configure it as your default font in Visual Studio Code.

To configure Fira Code as your default Visual Studio Code font

  1. If you haven't done so, download Fira Code and install it by following these steps:
    1.1. Extract the Zip file you just downloaded
    1.2. In the files you just extracted, find and open the ttf folder
    Fira Code extracted files
    1.3. For every .ttf file in that folder, double-click to install the font.
    Installing a font on Windows
  2. Once you have installed the fonts, open Visual Studio Code. If you already had Visual Studio Code opened, you will need to restart it before it can use the newly installed fonts.
  3. From the Visual Studio Code menu, select File | Preferences | Settings. Alternatively, press CTRL+,
    File Preferences Settings
  4. From the Settings window, under Commonly Used, find the Editor: Font Family setting.
  5. In the text box, replace what is there for 'Fira Code' (including the single quotes). Alternatively, you can simply add 'Fira Code', at the start of the existing setting. You'll end up with 'Fira Code', Consolas, 'Courier New', monospace.

Keep the settings open if you want to enable font ligatures...

Enabling font ligatures

Font ligatures is a typography term to describe when two or more characters (or graphemes) are joined as a single glyph. For example, instead of: === you'll get: Font Ligature Example

It can help make your code more legible (and thus easier to spot bugs). Take a look at this example from FiraCode's GitHub repo:
Font Ligature Samples

Some people love ligatures. Some people can't stand them.

If you'd like to use ligatures, follow these steps:

  1. From the Settings page in Visual Studio Code, scroll to the Text Editor | Font section and look for Font Ligatures. (Or search for Ligatures in the search box).
  2. Check the checkbox labelled Enables/Disables font ligatures.

That's it.

Just uncheck the box if you don't want ligatures anymore.

Conclusion

Enabling Fira Code as your default Visual Studio Code font is incredibly easy, and barely deserves its own blog post.

I wish to thank Sam Culver for reminding me that I should document this. Sometimes I just tend to configure things on auto-pilot and I don't even notice it!

I hope that you'll enjoy Fira Code and ligatures in Visual Studio Code, and I hope that this will become one of those settings that you end up automatically doing without having to think about it.

If you like the idea of ligatures, but you aren't a fan of Fira Code, the good folks at Fira Code list some alternative fonts you may like.

Also, if you enjoy Fira Code, you should consider supporting them

Introduction

Scott Hanselman is someone that I admire, both as a Developer and as a Human Being. I never miss a chance to attend his presentations.

I'm not smart enough to understand everything he talks about all the time, but he's always entertaining to watch.

Everyone once in a while, he creates an ultimate list of developer and power tools which always has one or two new tools I didn't know about. Any serious developer should look at his list as a great starting point to configure their workstation.

When SPFx first came out, I had never touched Node.js, React, or TypeScript. Visual Studio Code was (in my mind) just a free/lightweight version of Visual Studio -- why would anyone use VS Code over the full-on Visual Studio?! And don't get me started about GitHub!

I had a steep learning curve ahead of me. And I had a whole new set of development tools to install on my workstation.

It took me a while to figure out what I needed to install to be efficient at creating SPFx solution.

I'm in the process of updating my Windows 10 workstation and, as I was writing down the list of things to re-install, I thought I'd share my list.

This list is specifically for people who want to write SPFx solutions on Windows. It isn't as comprehensive as Scott's list, but I hope that it will help anyone getting started with SPFx development.

I also work with .NET, Azure, VR and AR, and Dynamics 365, so I have other tools on my workstation, but I wanted to focus on SPFx development.

If you develop SPFx solutions and see that I forgot anything here, I would love to hear from you!

NOTE: I'm a bit of a minimalist when it comes to installing stuff on my workstation. I hate installing things that will automatically start when I launch windows (that's why I never install anything from Adobe anymore). You'll rarely find anything in this article that will completely change how your operating system works or takes over your machine... and if you do, it is because I feel that the trade-off is worth it.

I also don't like to pay for things if there are free alternatives. For example, it would take me 5 minutes to create my animated GIF screenshots using SnagIt, but I foolishly spend 25 minutes using free tools because I'm too stubborn (or too cheap?). Every tool in this article is free, just keep in mind that there may be better alternatives if you're willing to spend money.

Mandatory

  • Node.JS: Find the .MSI file in the list that suits your workstation (x86 or x64). As tempting as it may be to download the latest version, don't. 8.x is the only version that is officially supported for SPFx development.
  • Gulp: Gulp is a tool that helps automate building your solutions. Once NodeJS is installed, install Gulp by launching the Node.js command prompt and type the following:
    npm install -g gulp
  • Yeoman: Yeoman is a tool that scaffolds solutions. Think of it as the Visual Studio new project wizard, if the new project wizard was command-line driven, open-sourced, and contained a bazillion project types. To install it, use your Node.js command prompt and type:
    npm install -g yo
  • SharePoint Framework Yeoman Generator: Now that Yeoman is installed, you need to add what is essentially your "SPFx Project Wizard". To do so, use your Node.js command prompt and type:
    npm install -g @microsoft/generator-sharepoint

Recommended

  • Visual Studio Code: When I first started writing SPFx solutions, I refused to use Visual Studio Code (real developers use Visual Studio, right?). Unfortunately, Visual Studio messed up my SPFx solutions more than once and I quickly learned to appreciate Visual Studio Code. Visual Studio no longer messes with your SPFx solutions, but I still use VS Code for all-things-SharePoint.
  • Git for Windows: Even if your company uses Azure DevOps or TFS for source control, you should install Git to make your life easier when downloading SPFx code samples.
  • Cmder for Windows: If you've ever wondered what that cool command-line they use on the SharePoint Development Community calls, you have found it! Follow my instructions if you need help installing it.
    Cmder
  • Fira Code Font: This font will add the cool visualization to the Cmder command prompt. You should also configure it as your default Visual Studio Code font Fira Code Font
  • Cmder Powerline: When paired with the Fira Code font (above), it will add that cool command-prompt to Cmder.
  • Postman: Test your APIs using this awesome tool. It can even intercept calls and replay them. I use this tool all the time when I'm trying to understand how SharePoint does something.
  • Fiddler: Use Fiddler to capture your workstation's network traffic and diagnose issues. For example, if you want to know how the Microsoft Teams app retrieves your list of groups, use Fiddler to capture what calls it makes.
  • TeraCopy: If you've ever copied your SPFx project files, you know how frustratingly slow Windows can be. TeraCopy is insanely fast (so fast that you'll think it didn't work at first). Don't take my word for it: next time you have to copy your SPFx project files, start your copy with Windows. Then, download TeraCopy for Windows, install it, and copy the exact same files using TeraCopy to a separate folder. Then go have a coffee, tell your co-workers that they should install TeraCopy, and go back to your desk. Windows will still be copying the first set of files.
    TeraCopy
  • Office 365 CLI: A command-line interface that lets you do tons of stuff in Office 365 and SPFx solutions. To install, use your Node.js command prompt and type the following:
    npm i -g @pnp/office365-cli
  • SharePoint Online Management Shell: Use it to create SharePoint Online sites and add users, you can quickly and repeatedly perform tasks much faster than you can in the Office 356 admin center. You can also perform tasks that are not possible to perform in the Office 356 admin center.
  • SharePoint PnP Cmdlets: SharePoint Patterns and Practices (PnP) contains a library of PowerShell commands (PnP PowerShell) that allows you to perform complex provisioning and artifact management actions towards SharePoint. The commands use CSOM and can work against both SharePoint Online as SharePoint On-Premises.
  • PnP Yeoman Generator: If you find yourself always adding the PnP developer controls, PnP Property Controls, PnPJs, unit testing, etc. to your SPFx solutions, you should probably try the PnP Yeoman Generator. It is built on top of the SPFx Yeoman generator, so you're not missing out on anything the SPFx generator will give you, but it automatically adds many other useful features. To install it, use your Node.js command prompt and type:
    npm install -g @pnp/generator-spfx

Visual Studio Code Extensions

  • GitHub Pull Requests: Allows you to review and manage GitHub pull requests in Visual Studio Code.
  • Paste JSON as Code: Convert JSON object to typescript interfaces as you paste into Visual Studio Code. What, you thought I typed all those classes?
    Past JSON as Code
  • Rencore Deploy SPFx Package: Easily deploy a SharePoint Framework solution package to SharePoint Online directly from Visual Studio Code.
    Rencore Deploy SPFx Package
  • Rencore SPFx Script Check: Using the Rencore Script Check Visual Studio Code extension you can easily reference external libraries in SharePoint Framework projects the right way. Additionally, you can ensure, that the CDN they are using is well performing.
    Rencore SPFx Script Check
  • Rencore Tenant-Wide SPFx Extension Deployment: Easily add tenant-wide deployment information for your SPFx extension directly from Visual Studio Code.
    Rencore Tenant-Wide SPFx Extension Deployment
  • SPFx Debug Configuration: This Visual Studio Code extension can be used to add the required configuration for debugging your SharePoint Framework.
  • SPFx Essentials: This is an extension pack that contains useful extension for SharePoint Framework projects. Most of the extensions I listed here are already included in Elio's awesome list.
  • SPFx Localization: This extension for Visual Studio Code makes it easier to work with locale resource files in SharePoint Framework projects. The extension has the ability to export all locale labels to a CSV file to make translations easier to process. With this extension, you have absolutely no excuse to hard-code your text in English within your solutions.
    SPFx Localization
  • SPFx Snippet: I never realized how much I use this extension until I tried to write spfx-rcc for a web part on a machine that didn't have the extension installed. Take it from the World's Laziest Developer, you need this extension.
    SPFx Snippet
  • SPFx Task Runner: This extension allows you to easily run SharePoint Framework tasks with a couple of mouse clicks. At the moment you can for example list all the available gulp tasks in your project, start the local development server and create debug or release solution packages or pick a task to run from the list of available tasks.
    SPFx Task Runner
  • SharePoint Typed Item: Sergei's awesome extension generates interfaces based on list and content type fields. Also check out Sergei's SPFx Rest Client
    SharePoint Typed Item
  • Prettier -- Code Formatter: This extension format your JavaScript / TypeScript / CSS using Prettier.
  • SPGo for Visual Studio Code: SPGo allows you to develop SharePoint web solutions from your local PC using Visual Studio Code. It pulls down remote folders from SharePoint to your local workspace and automatically publishes files when you save. It is one of those "where has this been all my (SharePoint) life?!" extensions.

Nice to have

  • Zoomit: Be a considerate presenter! If you do presentations and show your code, you should consider installing this tool. Those people in the back of the room (who sat in the back so that they can exit quickly if they find you boring) may not exit so fast if they can actually see what you're doing.
  • Paint.NET: A great image editing tool that's fully featured and free. I use it to edit icons and other assets.
  • Inkscape: A powerful SVG editing tool. I use it to create SVG icons. If only I could get SVG icons to work consistently in SPFx, it'd be great.
  • ScreenToGif: Do everyone a favour and show an animated GIF of what your SPFx solutions can do in the README.MD file. It saves them having to install your solution to see what it does. This tool is a screen, webcam and sketchboard recorder with an integrated editor that makes it super-easy to create animated GIFs. Available as a Microsoft Store App or a WPF install.
    ScreenToGif

Chrome Extensions

  • SP Editor: A Google Chrome Extension for creating and updating files (js, css), injecting files to sites, modifying web/list property bag values (add, edit, remove, index) and creating webhook subscriptions, edit/add/remove web parts from publishing pages and run sp-pnp-js typescript snippets in SP2013, SP2016 and SharePoint Online from Chrome Developer Tools. This tool will help you create amazing SharePoint applications fast from your browser from any computer which runs Chrome!
  • React Developer Tools: Allows you to inspect the React component hierarchies in the Chrome Developer Tools. You get a new tab called React in your Chrome DevTools which shows you the root React components that were rendered on the page, as well as the subcomponents that they ended up rendering. It is great when trying to understand how the SharePoint team built a component.
  • Screen Reader for Google Chrome: Test your web parts for accessibility by giving you the same experience your users will get when they use a screen reader.
  • AXE: No, not the body spray. Use this tool to check for accessibility for WCAG 2.0 and Section 508 accessibility. If you aren't testing for accessibility, you're possibly making it difficult for 10 to 20% of your users.

Websites

  • SharePoint Look Book: Get inspired with cool looking SharePoint sites.
    SharePoint Look Book
  • SharePoint Design Guidance: Learn how to create great looking SharePoint solutions.
    SharePoint Design Guidance
  • Office UI Fabric: See what components are at your disposal when building awesome SharePoint web parts.
  • SharePoint Dev Platform Uservoice: Request new features and see what the team is working on.
  • PnP/PnPJS: PnPjs is a collection of fluent libraries for consuming SharePoint, Graph, and Office 365 REST APIs in a type-safe way. You can use it within SharePoint Framework, Nodejs, or any JavaScript project. This an open source initiative and we encourage contributions and constructive feedback from the community.
  • Reusable property pane controls for the SharePoint Framework solutions: This repository provides developers with a set of reusable property pane controls that can be used in their SharePoint Framework (SPFx) solutions.
    Reusable Property Pane Controls
  • Reusable React controls for your SharePoint Framework solutions: Provides developers with a set of reusable React controls that can be used in SharePoint Framework (SPFx) solutions. The project provides controls for building web parts and extensions. You should really try the ChartControl 🙂
    Reusable React Controls
  • SharePoint PnP Provisioning Service: The SharePoint PnP Provisioning Service lets you add samples, templates and solutions to your Office 365 tenant.
    SP PnP Provisioning Service
  • SharePoint Developer Community - PnP (YouTube): YouTube channel with SharePoint Dev Weekly videos, SharePoint Framework Tutorials and Training videos, SharePoint Framework and Extensions Tutorials, Getting Started videos, PnP Webcasts, etc. If you no like read, you watch these good.
  • Base64 Image Encoder: Use this site to encode your web part icons to base 64.
    Base64 Encoder
  • EzGif.com: If you don't want to install ScreenToGif but want to create animated GIFs to help people see what your web part will look like without having to install it, I recommend using this web site. Create a video of your web part in action, then use EZ GIF's Video to animated GIF converter to create your GIF. Add the GIF to your README.MD file and people will see how cool your web part really is!
    EzGif

GitHub Repos

  • Office UI Fabric React: When I want to learn how to create awesome React components, I take my inspiration from Office UI Fabric.
  • SP Dev FX WebParts: Almost every SPFx project I create starts from one of the many samples available on this awesome repo.
  • PnP Property Controls: Back when most of us were still learning React and SPFx, they were already creating re-usable controls for SPFx. Take some time to read their code for inspiration.
  • PnP Developer Controls: From the brilliant minds that brought you the PnP Property Controls. Read the code to learn tons!
  • SharePoint Starter Kit: Look at some of the best solutions used to build the perfect demo environments.

Conclusion

This article listed the various tools I use when building SPFx solutions on a Windows 10 workstation.

As I stated previously, I'm kind of a minimalist when it comes to installing stuff on my machine. I'm sure that there are many other tools that I should install, but I haven't found a need for it yet.

What other tools do you install on your workstation? Let me know in the comments.

Update

  • October 10, 2019: I added SPGo after attending Beau Cameron and David Warner II's great SharePoint Saturday New England session about SPFx Development Tips from the trenches. Watching those two present together reminded me that there is always room to learn more.
  • July 2, 2019: Thanks to Denis Molodtsov for suggesting ScreenToGif instead of using EzGif. It makes it so much easier to capture an animated GIF of your web part in action!
  • April 4, 2019: Added SharePoint Developer Community - PnP YouTube channel
  • March 28, 2019: Added [Sergei Sergeev's] cool SPFx Typed Item extension, which he demoed in the March 28th SharePoint Dev Ecosystem call. Watch out for those Kung-fu Gophers.
  • March 25, 2019: Thanks to Thomas Lamb for suggesting Prettier -- Code Formatter.
  • March 20, 2019: Thanks to Sam Culver for pointing out that Fira Code makes a great font in Visual Studio.
  • March 16, 2019: Added Paint.NET and Inkscape as graphical tools.
  • March 14, 2019: Thanks to Miguel Isidoro for pointing out that I had the wrong link to the Rencore SPFx Script Check.

    Credits

    Header image by Rudy and Peter Skitterians from Pixabay

Introduction

What happens when you file to plan

When migrating content from a network file share to SharePoint Online (or SharePoint on premises), remember SharePoint's URL length limitations.

This article discusses the current limitation with URL lengths in SharePoint. It discusses how this limitation manifests itself, and how it can impact you during your SharePoint migration.

A matter of legacy

In SharePoint, the limit for a document's URL is 400 characters long. We'll discuss this in greater length (see what I did there?) later, but for now, let's discuss how we get long path names.

To begin with, nobody in their right mind ever says "I think I'll name my file SuperInsaneLongFileNameThatIDontEverWantToTypeAgainBecauseItIsTooLong.docx".

That'd be just annoying.

Instead, people get long path names because of the legacy infrastructure dictated by file shares.

You see, on a network file share, users don't get the luxury of using metadata or versioning. As a result, users tend to get creative and create folder structures as a substitute to convey metadata.

We've used this example before. Imagine that your users store case files on a network file share. They want to group case files by status, so they case folders for each status under the Cases folder:

\Cases
    \Open
    \Closed
    \Pending review

They also want to group cases by year, so they create a folder for every year in each Cases > Status folder:

\Cases
    \Open
        \2017
        \2018
        \2019
    \Closed
        \2017
        \2018
        \2019
    \Pending review
        \2017
        \2018
        \2019

Each case is placed in the folder matching the year it was received, within the folder for the case status.

Now imagine that, in the process of managing a case, your staff receive electronic evidence, via USB sticks, removable drives, or email attachments -- or whatever way. To keep these files together, users start creating folders. Of course, they want to keep track of when they received the files, so they put the files in a folder called John Smith files received Jan 21, 2019

Also, because our users want to keep incoming documents (stuff they received during the case management process) from outgoing documents (letters and documents they send out through the case management process), they create separate folders, aptly named Incoming documents and Outgoing documents.

For example, let's pretend we have an open case, numbered CA-12345678, from 2019. The folder structure would look like this:

\Cases
    \Open
        \2017
        \2018
        \2019
            \CA-12345678
                \Incoming documents
                    \John Smith files received Jan 21, 2019
                        \Photo evidence 1.png
                        \Letter from complainant.pdf
                        \...
    \Closed
        \2017
        \2018
        \2019
    \Pending review
        \2017
        \2018
        \2019

Of course, they also want to keep track of outgoing documents they work on, so they name the files accordingly:

\Cases
    \Open
        \2017
        \2018
        \2019
            \CA-12345678
                \Incoming documents
                    \John Smith files received Jan 21, 2019
                        \Photo evidence 1.png
                        \Letter from complainant.pdf
                        \...
                \Outgoing documents
                    \Case Summary
                        \Case review v1 Feb 21.docx
                        \Case review v2 Feb 25.docx
                        \Case review v3 Feb 27.docx
                    \Case interviews
                        \Interview with John Smith Jan 23, 2019 raw.mp4
                        \Interview with John Smith Jan 23, 2019 transcript v1.docx
                        \Interview with John Smith Jan 23, 2019 transcript final.docx
                        \Interview with John Smith Jan 23, 2019 transcript final final.docx
    \Closed
        \2017
        \2018
        \2019
    \Pending review
        \2017
        \2018
        \2019

And so on.

Users don't do this to be annoying. They do this because they need a way to manage their information with the tools they have. Without metadata at their disposal, they use long file and folder names.

As long as they keep their file paths shorter than 255 characters, most Windows applications will be able to handle opening and saving files from the file share. This isn't a limitation of UNC paths, it is a limitation within the apps that try to open and save files (for example, your PDF reader). Depending on the version of Windows you use, and assuming you use applications from this century, you can probably get away with longer file paths.

Typically, organizations will map file shares to a drive to make it easier for users. So, instead of having a path that starts with:

\\servername\networksharename

They'll have a single letter pointing to the file share. For example, S:\. It helps keep the file paths shorter, thus preventing any issues.

Everyone is happy.

Until they try moving the files to SharePoint.

SharePoint URL Length Limitations

Prior to May 2017, the maximum URL length for a file stored in SharePoint was 255 characters.

Since then, Microsoft kindly increased the maximum path size to 400 characters.

When I say 400 characters, I really mean 400 unicode units. If you use International characters with multibyte values, you actually get less that 400 characters.

"So what? Our file names are less than 400 characters", you may ask.

You see, the URL is for the entire URL of the file, including:
https:\\yourtenant.sharepoint.com/sites/yoursitename/yourdocumentlibraryname/yourfolderpath/subfolderpath/Filename.extension/?parameters

For example, let's say your tenant is called Contoso, your site called Case Management, and your document library called Shared documents. On your file share, that file path that started with:

S:\

...when migrated to SharePoint, will become:

https://contoso.sharepoint.com/sites/case%20management/shared%20documents/

That's a whopping extra 71 characters added to your file names. Those file paths that didn't cause any issues on a network file share can suddenly be too long once migrated to SharePoint.

How long URLs manifest themselves

No issues when uploading documents

Here is the problem: you may be able to migrate your documents to SharePoint -- even if they have long file names that exceed the maximum path length.

Errors opening documents online

However, when you try to open the file, you get an error. For example, here is what happens when I try to open a document with Word online:
Word File Path Too Long

Errors opening documents using desktop applications

What's worse, if your users are on an older version of Windows, or if they use a Mac, they may experience issues with file paths around the 255 character length!

Also, if they try to Sync the files to OneDrive for Business, and they try to open documents using older desktop applications, they may get an error indicating that the "file could not be opened" or the ever-mysterious "an error has occurred".

"Some files work, some files don't"

Users may experience an issue when only a few documents (or folders) won't open, but other files open without problems.

This usually happens with file names that have different lengths. Shorter file names open without issues because the full paths are less than 400 characters. Longer file names don't open because the file names are longer than 400 characters.

To regular users, it seems like a random issue.

But you and I know better, don't we?!

How to solve the URL length issue

You migrated your file share to SharePoint without getting any errors while uploading the documents.

Everything looks good.

Then a user tries to open a document with a URL that exceeds the limit. And they get an error. Of course, this always happens with someone very high up in your company, usually minutes before a very important event.

Move files to a shorter URL

The easiest way to solve this problem: use SharePoint's Move functionality within a document library to move the file to a shorter URL.

Move to

For example, if the file is 5 folders deep in a document library, try moving the file to the root of that document library.

It may shorten the URL with enough precious characters to allow you to open the file.

Then have the talk (you know, the one about why you should use shorter folder names and file names) with your users.

Errors with custom code

If you use custom code to connect to SharePoint using REST, you may also run into issues when the full URL for the REST call exceeds the limit.

One way to solve this problem is to use GetFileById and pass the unique file id instead of trying to use the file name:

https://yourtenant.sharepoint.com/sites/yoursitename/_api/web/GetFileById('fileid')

How to test URL length limits

When I tested the maximum URL length for this article I manually created super-long file names.

Fortunately, you don't have to do that. Thanks to Rene Modery, you can use a script to create long file paths. It uses the SharePoint PnP cmdlets to connect to SharePoint and create long paths for you.

Conclusion

When planning a SharePoint migration, pay attention to long path names. You may be able to migrate the documents without issues, but your users will run into issues opening documents.

If your users work with older desktop applications, older versions of Windows, or Macs, you may want to stick to a maximum URL length of fewer than 255 characters.

If your users have the latest version of Windows and Office, you can safely plan for maximum server-relative URL lengths (i.e.: the URL without the https://[yourtenant].sharepoint.com) of 400 characters.

Of course, after your content is migrated, help your users understand why they shouldn't use silly long file and folder names anymore and show them how to use metadata.

I hope this will save you some headaches in the future.

Update

Contrary to Microsoft's own article, it appears the 400 character limit no longer counts the https://[yourtenant].sharepoint.com/ or the parameters (e.g.: ?web=1) in the URL length. The limit of 400 characters applies to the server-relative file path.

When a URL contains special characters, like space, %, #, etc., the characters get URL-encoded. For example, space will be encoded as %20. When verifying if your file URL to shorter than 400 characters, SharePoint uses the un-encoded URL (i.e.: special characters count as 1 character).

For more information

Introduction

Information Architecture (IA) is the structural design of shared information environment (source: Wikipedia).

In this series on Information Architecture, we discussed how a bad IA can affect your SharePoint success.

If your SharePoint users can't find the information they need quickly, they'll get frustrated.

In our first article, we explained why trying to create an IA that has only one dimension leads to creating the lowest common denominator IA instead of giving your users the best-of-breed experience they deserve.

Our second article explained how you should build an IA on 3 dimensions. We described how the physical IA should cater to authors. If you don't create an IA that allows your authors to place content in the right place, you'll end up with messy, unstructured data. Yes, it is data, not information, because it loses all meaning.

In the last article, we looked at the logical IA. While the physical IA caters to your authors, the logical IA should cater to your readers.

But there is one more dimension to consider: the metadata dimension.

The metadata dimension

The metadata dimension of IA helps the system deliver information for specific purposes.

For example, imagine that the HR department has a document library for their HR policies. Before employees can see the latest version of a policy document, they must be approved.

Marie the HR Manager is responsible for approving HR policy documents.

When she launches SharePoint, her HR site tells her how many documents are waiting for her approval.

If Mary clicks on a document waiting for her approval, it takes her directly to the document. From there, she can approve or reject the updated policy document.

SharePoint does not keep documents waiting for Mary's approval in a separate physical location. Yet, from Mary's perspective, SharePoint appears to bring all documents waiting for her approval in one convenient place for her.

In other words, the physical location of her documents doesn't change. SharePoint uses the metadata to identify which documents need Mary's approval. SharePoint then presents the documents in a separate logical location.

That's what creating the metadata IA is all about: making sure that your documents have the metadata they need to support specific goals.

You might say "Ok, I get it, so we just use the goals we identified when we [designed to logical IA]( (https://tahoeninjas.blog/2019/02/22/information-architecture-in-sharepoint-the-logical-dimension/) and create the metadata we need to support it?"

Well, yes ...and no.

(Remember, I'm a consultant. The answer is always "It depends")

Progressive Disclosure

In user experience (UX) design, there is a design pattern called Progressive Disclosure. It is a strategy for managing information complexity by gradually (or progressively) revealing more information as users indicate they wish to see more.

In other words, show only the information that is necessary at every point of interaction.

By reducing the amount of unnecessary information you show to users down to the essential, you make it easier for users to make sense of that information.

You see this all the time when using SharePoint. For example, the More button in the document library toolbar is a form of progressive disclosure. We don't need to show every single option in the toolbar. We only need to show the most common options. If users tell us they want to see more choices, we reveal more options.
Progressive Disclosure Example with the More menu

The News web part in SharePoint works the same way. When you go to a team site or communication site, you see the latest news. If users want to see more news, they can use the See all link, in the upper right corner.
SharePoint News

Summary, List, Details

When designing your metadata IA, you should consider creating the metadata structure required to build progressive disclosure in your design.

An easy way to do this is to present the information in 3 distinct views:

  • Summary
  • List
  • Details

We'll describe each view below.

Summary

As you evaluate every actor goal, ask yourself:

What is the least amount of information that this actor will need to meet their goal.

Another way to ask this question is:

How do I summarize the information this actor needs to meet their goal quickly

In our example, Mary the HR Manager needs to know quickly when documents need her approval.

We don't need to show her every single document. We just need the documents that have not been approved yet.

Keeping this in mind, we don't even need to show the Size, or Date Created. We probably only need the document's Title, Date Modified, and Modified By.

When building a summary view, consider providing the user information that will help them prioritize their tasks. For example, we can show Mary the list of documents that have been waiting for her approval the longest. Or, we can show the list of documents by how recently they were submitted for approval.

Every summary view should allow users to do at least two things:

  • Get the full information about an item in the summary (a.k.a. the Details view)
  • Get the full list of items (a.k.a. the List view)

Both are discussed further below.

How do I sort my summary view?

Almost every client engagement I work on, there is at least one business stakeholder who insists that their summary view of [whatever] must be sorted alphabetically -- from A to Z.

Their argument is usually "people need to be able to find [whatever] quickly. Sorting by alphabet is the fastest way to let them find it".

It may be a good approach for a list view of [whatever], but the summary view should boil it down to what matters now.

Instead, consider showing the list of latest [whatever]. Sorted in reverse descending date (i.e.: newest first). Doing so will allow repeat visitors to see the newest [whatever]. If they scan down the list, they may eventually see an item that they've seen before and assume that they have seen everything below that item.

Consider the alternative: users have to scan through the entire list of [whatever] to see if there is anything new.

How many items in my summary view?

There are no set rules for the number of items to show in a summary view. Luckily, there are brilliant people who have done some research on this.

The first rule is known as Miller's Law. In summary:

The average person can only keep 7 (plus or minus 2) items in their working memory.

Miller's Law is often misinterpreted. It doesn't mean that you should only present 7 (+/- 2) items only. If you need to present more, consider chunking the information into groups of 7 (+/- 2) items.

The second rule is Hick's Law. Hick's law says:

The time it takes to make a decision increases with the number and complexity of choices.

In other words: the more choices you give people, the longer it takes for them to make a decision.

So, when I build a summary view, I try to limit it to 7 +/- 2 items.

Sometimes I show 5, sometimes I show 7, and rarely I'll show 9 items. If I need more than 9 items, I'll always try to group the items into subsets of 5-9 items.

The more information I need to present with each item, the fewer items I'll show in my summary view.

Doing so will ensure that every user can make sense of the entire list and that they will make a decision quickly.

List

The list view allows users to view all items, possibly without filters or restrictions.

But don't think that you can just create a All items or All documents view and be done with your list view.

Keeping your user's goals in mind, you should design one or more list views to help your users accomplish their goals quickly without having to scan through the entire list of items.

For example, I'll often create views like:

  • Latest [whatever]
  • My [whatever]
  • [Whatever] waiting for my approval
  • [Whatever] by approval status
  • [Whatever] about to expire

...and the list goes on.

As with the summary view, you need to consider what metadata you'll need to support each list view.

Every item in the list should provide users with a link to the Details view for that item.

Details

The details view should provide all the information needed on an item so that users can achieve their goals.

Most often, the details view for a document is really the document itself -- because, most often, the user's goal is to read the document.

Sometimes, you need a different details view. For example, let's say we want to allow executives to do a second-level approval for expenses that approved by managers who report to them.

Instead of showing the full document, we'd need to show the expense summary, amount, manager's approval, and approval date. The view would probably make it easy to approve the expense without having to open it.

Keeping in mind progressive disclosure, and providing users with Summary, List, and Details views of information will help your users make decisions quickly and accomplish their goals.

Implicit metadata

If you're planning an IA to migrate a network file share to SharePoint, heed this warning:

Don't simply relocate document. The location of a document in a file share is a form of metadata that you may lose if you relocate documents.

That's implicit metadata.

Let's say that your file share contains legal cases for case management purposes. You have a folder for every case your company has ever processed.

Because of the large number of cases your company processes every year, there is a folder for every year in your file share. Cases are then placed in their respective folders, according to when you received the case.

Your company gives each case a unique number, e.g.: CA123456. To ensure privacy, case numbers do not convey any information about who the case parties are.

To make things even easier for your users to work through open cases only, your network file share groups every year folder into an Open cases, Closed cases, and Pending approval.

The folder structure looks as follows:

  • Cases
    • Open case
      • 2017
        • CA123456
          • CA123459
          • CA123464
        • ...
      • 2018
        • CA133456
          • CA123559
          • CA121464
          • ...
      • 2019
        • CA233456
          • CA323359
          • CA124464
          • ...
    • Closed cases
      • 2017
        • ...
      • 2018
        • ...
      • 2019
        • ...
    • Pending approval
      • 2019
        • ...

Because the documents you want to migrate are in a network file share, there may not be a lot of metadata -- if any -- for your documents.

But if you look carefully, the location of each document provides implicit metadata about each document: the status of the case, the year the case was received, and the case the document belongs to.

If you migrate your file share to SharePoint, you need to consider a way to apply the implicit metadata to each document.

You might say: "If I keep the same folder structure, I won't lose the implicit metadata". Sure, but what happens when users are looking for a document and use search? The only way they will be able to see that implicit metadata about every document in their search results is if they look at the document location for every. single. document.

Conclusion

As I have hopefully conveyed in today's post, you need to consider your metadata IA structure to support your users' goals.

The metadata dimension also needs to support both the physical and logical IA.

In my next post, I will explain how to put all this theory together using SharePoint's capabilities.

For More Information

Introduction

In my previous post on Information Architecture (IA) in SharePoint, I explained that you should build your IA on 3 dimensions.

I discussed how you should build the physical dimension around your authors, by creating sites, document libraries and -- if you must -- folders optimized for those who create and maintain content.

In this article, we'll discuss the logical dimension of IA.

Readers

If the physical dimension is for authors, the logical dimension is for readers -- those who consume your SharePoint content.

Authors are usually subject of matter experts on a given domain. For example, your Human Resources department people are experts in all things HR-related. They understand the subject, and they even use jargon that is specific to their domain of expertise.

In contrast, readers may not be subject of matter experts. They don't care about how to maintain content -- they just want to find the content.

And, unlike authors, readers don't share a common knowledge of a domain of expertise. That means that every reader navigates through content using their own logic.

For example, your company has employee benefits. Your Human Resources department handle some of those benefits, while your Finance department handles other benefits. Maybe your IT department offers additional benefits (like providing phones and computers for home use).

The HR folks, Finance folks, and IT folks maintain information about the respective benefits their department offer. Based on what we learned earlier, you would build a physical IA that would allow each department to store documents where it makes sense to them.

To keep things simple, let's pretend that you created three SharePoint team sites:

  • Finance
  • Human Resources
  • Information Technology

And let's pretend that, on each of those sites, you created a document library called Benefits.

People from the Finance department can update benefits documents in the Finance team site. People from the HR department can do the same in their Human Resources team site, and so on.

Meet Nancy

Nancy, the New Employee is a fictitious person.

Everyone that has worked with me knows the I like to build personas to help gather requirements.

She's a typical new employee. She's excited, nervous, and eager to learn.

Nancy is fresh out of college. She joined as a Junior Accountant. She recently got engaged. Like most people of her generation, she wants to get recognized for her hard work, but she also wants a proper work/life balance.

One of the reasons she accepted her new job with your company is because of the amazing employee benefits it offers.

This is Nancy's first real "grown-up" job. She's never had real employee benefits before. She doesn't know what to expect, or where to get started with taking advantage of her benefits.

Nancy doesn't care what department offers what benefits. She doesn't want to go through Finance, HR, and IT sites to learn about benefits.

She just wants a place the shows her everything related to benefits in a single, convenient location.

In other words: Nancy doesn't care where the benefits documents are physically located, as long as they are available together logically.

Nancy's Information Architecture needs as a reader are very different than those of an HR employee's IA needs as an author.

That's the difference between the physical dimension and the logical dimension.

How to design your IA's Logical dimension

The secret to building the ideal logical IA is to understand your users and their goals.

To do so, I always use a slightly modified version of the process I use for gathering Use Cases.

Here is the Use Case process:

graph LR
A[Identify actors] --> B[Identify goals]
B --> C[Define main success scenario]
C --> D[Identify extension conditions]
D --> E[Define recovery steps]

Here is the modified process for logical IA design:

graph LR
A[Identify actors] --> B[Identify roles]
B-->C[Identify goals]
C-->D[Identify information requirements]
D-->E[Define logical structure]

Let's discuss each step in details.

Identify actors

graph LR
A[Identify actors] --> B[Identify roles]
B-->C[Identify goals]
C-->D[Identify information requirements]
D-->E[Define logical structure]
style A fill:#f9f,stroke:#333,stroke-width:4px

I use the term actor instead of user because I don't ever want people to get confused give me a list of every single person who logs on to SharePoint.

In a play, an actor can often play multiple roles. Many people can also play the same role (think: understudies).

So, in our IA design process, an actor is a person who plays many roles. Your actors can be real people, or they can be fictitious people that you create for the IA design process.

For example, Nancy the New Employee is a fictitious actor we'll use to represent real new employees. We don't want to have a list of every single new employee, we just want to use Nancy as a proxy for all the other new employees.

Keep going through until you find all your actors. Resist the temptation to move on to the next step. If you move on to the next step now, you'll have an IA that suits one actor very well, but doesn't work for others.

If you're struggling to identify, here are some tips:

Look for extremes

Your actors should almost be caricatures of your real users. Look for people in your company that are stereotypical users.

Find the worst and the best people in your organization.

I once did this exercise for one of the top 5 accounting firms in the world.

The Firm had many partners, who --rightfully so-- ran everything.

Partners were often older men who were at the top of their profession.

Most of them were nice, but some of them were just grumpy old men.

Most of them didn't trust computers and wanted their assistants to print everything for them.

We took the oldest, grumpiest partner we knew, and used him as an actor. Of course, we changed his name to Perry the Partner.

Look for opposites

One trick is so easy it almost feels like cheating: find the opposite of an actor you already have.

You have a new employee actor, create an old employee actor -- their needs are different. The new employee doesn't know anything about the organization structure yet, the policies, the people. The old employee knows everyone in the company, knows every department, every policy, etc. They'll look for information differently.

You have a grumpy old partner that doesn't like technology actor, maybe you need a *younger partner who is technically savvy.

Primary actors and secondary actors

This is another trick I borrowed from capturing requirements: primary actors, and secondary actors.

Primary actors are the reason why your system exists. Without them, you wouldn't need SharePoint. This one is obvious -- that's probably everyone you have identified so far.

Secondary actors exist because the system exists. In this case, secondary actors are people who support SharePoint, create content, etc.

But secondary actors could also be people who already existed, but whose jobs become more important because of your new system.

Remember Peter the Partner? He hates technology. He needs everything printed (which I disagree with, but that's another topic). Who prints everything for him?

His assistant, Allison.

Don't confuse Primary and Secondary as Important and Less important. For our IA, Allison the Assistant is just as important as Peter if not more. Without Allison, Peter won't use the system.

Look for evidence

I'm a big fan of evidence-based requirements gathering.

When you interview people to gather requirements, they tend to tell you what they think you want to hear. They talk about features instead of goals. They try to translate things in geek speak for you because that's what they think you want to hear.

Evidence, doesn't lie. It doesn't change because you're looking (although Quantum physics disagrees)

If you want to get a list of typical people who use the system, look for evidence.

What kinds of evidence are there?

  • Active Directory groups
  • Distribution list
  • Newsgroups

Be careful: I have been in many organizations where the distribution groups and security groups combined outnumber the number of employees.

Look for life events

Remember we started this whole example about employees finding benefits.

What most HR professionals will tell you is that benefit needs for an employee tend to change around life events.

For example:

  • Getting married
  • Having a child
  • Getting divorced
  • Getting sick
  • Getting older
  • Retiring

Not every employee will experience the same life events, but life events definitely help shape a person's goals.

For example, Nancy recently got engaged. She will get married soon. She'll be thinking about adding her husband to her benefits. She might want to change her last name.

After getting married, her next life event might be to get pregnant. She'll want to find information about pregnancy leave, child care benefits, etc.

Please don't send me angry emails about my antiquated views of the world or calling me sexist. I know that it isn't always the case, and I agree with you. Those are the life event patterns HR professional often consider.

In our previous example, Perry the Partner is probably pondering his prospective retirement. (That's a lot of "p").

Life events can also be professional events. For example:

  • Getting hired
  • Promotion
  • Certification
  • Becoming a manager
  • Getting a bad performance review

Consider accessibility

Many countries have (or will have) a legal requirement for accessibility. How do people who are visually impaired access your information? How about those who can't use a mouse? What about people with cognitive impairments?

When I create my actors, I always create an actor with accessibility requirements. It may not affect our IA, but it will definitely impact our designs. But that's a different post.

How many actors should I get?

It depends on how different types of people you have. I tend to aim for 12 actors at most. Why? Because I'm lazy. If you aren't lazy, feel free to do more than 12, but I'm not sure how much more value you'll get.

For bonus points!

If you're up to it, create little posters with each one of your actors.

I usually create a fictitious profile, complete with picture, of each actor.

I post them around the team room so that everyone that works on my project sees the posters every day. They serve as a gentle reminder to our team that we're building this for people, not computers.

I'll often hear team members having discussions where they use our actors as if they were real people:

"Nancy will never know how to do this! Remember, she's new, she doesn't know what benefits are available to her yet!"

I bet you that if you talked to the people who worked with me on that accounting firm project, they'd remember Nancy the New Employee.

That's the point of defining actors: we're not building an IA in a vacuum. We're building it for people (even if those people are fictitious).

Identify roles

graph LR
A[Identify actors] --> B[Identify roles]
B-->C[Identify goals]
C-->D[Identify information requirements]
D-->E[Define logical structure]
style B fill:#f9f,stroke:#333,stroke-width:4px

Now that you have your list of actors, you can define roles for each one of them.

We're not talking about security roles here. We're talking about purposes or unique traits that will affect their goals.

In our example, Nancy plays many roles:

  • New employee
  • Junior accountant
  • Accounting department staff
  • Bride to be

"Why do we do all this work to define an IA?" you may ask. Or "Why identify actors just so that we can identify their goals". Your actors are there to serve as a reminder that you're building this for people.

If you define memorable actors for you and your team, your actors will become the litmus test for every design decision you make.

Roles help you identify goals in a way that helps you focus. Trust me, you'll get a more complete list of requirements by listing goals on a role-by-role basis than you would if you just spewed a list of random goals for your entire system.

What if you get duplicate roles? It happens. You should try to create actors with distinct roles, but don't stress about it. During this step, we often realize that we have duplicate roles and we combine multiple actors together, or trade roles between actors to make them more unique.

Once again, resist the urge to go to the next step before you complete this one.

Why?

If you move to the next step, you will not have any idea of the scale of effort involved in completing your IA. You will have an incomplete IA and no clue how much you didn't accomplish.

Instead, your goal should be to have a complete list of roles. That way, you'll know how much work is ahead of you and it will help you plan accordingly.

Identify goals

graph LR
A[Identify actors] --> B[Identify roles]
B-->C[Identify goals]
C-->D[Identify information requirements]
D-->E[Define logical structure]
style C fill:#f9f,stroke:#333,stroke-width:4px

For every role you identied, list the possible goals.

Complete this sentence "As a [role] I want to use SharePoint to ____".

Notice that this step isn't identify requirements. It is identify goals.

What's the difference?

Every single engagement I take on, there will be at least one person who will insist that "search" or "searching" is a goal.

"Search" isn't a goal. It is a requirement.

No one ever says "I think I'll go to Google and search for something and do nothing with the results".

You search because your goal is to find something.

So don't write "Search". Write "Find [something]". How you find something is through search, but keeping the end goal in mind may help shape your IA differently.

Here are some tips:

  • One role at a time: give each role the consideration they deserve.
  • Resist the urge to describe the goals in further details: you can do that later. We want speed and quantity here.
  • Duplicates are good: you may find the many roles have the same goal. That's a good thing. Write it down for every role. A goal that appears for multiple roles is a goal that is more probably important. When it comes time to implement, you may want to focus on common goals first.
  • Write the out of scope goals: Keep that list going. Include things that are obviously out-of-scope. You can always mark that goal as out-of-scope later. If that goal comes back over and over again, you should consider including it in scope.
  • Don't let the tools get in your way: I often find that people get stuck on the tool they should use for this step. This phase is more about brainstorming. You should use whatever tool allows you to capture goals as quickly as possible without worrying about fonts, formatting, or structure. I like to use a mind mapping tool or OneNote. I loooove OneNote.

For example, the new employee role might want to accomplish the following goals:

  • Learn about the company
  • Learn about the organization structure
  • Find out what my benefits are
  • Apply for benefits
  • Learn about policies and procedures I should know about
  • Learn about my team members
  • Learn the company jargon
  • Get office supplies
  • Learn about holidays

The bride to be role might want to accomplish these goals:

  • Learn about benefits for spouses or dependents
  • Add a beneficiary
  • Book time off (for wedding and honeymoon)

The junior accountant role's goals might be:

  • Get my certification
  • Complete my training
  • Get promoted

Identify information requirements

graph LR
A[Identify actors] --> B[Identify roles]
B-->C[Identify goals]
C-->D[Identify information requirements]
D-->E[Define logical structure]
style D fill:#f9f,stroke:#333,stroke-width:4px

Once you have an extensive list of goals, you can consolidate it into common goals. You can also filter out the list of goals that are out-of-scope.

For every goal, try to answer this question: "How will users accomplish [this goal]?". Or "What information do users need to accomplish [this goal]?".

As tempting as it may be, I'll resist the urge to list example information requirements.

Just think about every document, list, news, and people you may need to accomplish a goal.

"Wait a minute! Didn't you say in a previous post that people are knowledge?"

Wow, I didn't know you read that! Thank you! But no, people hold knowledge. Listing the people who know stuff is just information. Remember: I can write everything I know about a subject, it becomes information. You need to apply your own experience to make it your own knowledge.

Define logical structure

graph LR
A[Identify actors] --> B[Identify roles]
B-->C[Identify goals]
C-->D[Identify information requirements]
D-->E[Define logical structure]
style E fill:#f9f,stroke:#333,stroke-width:4px

I would love to be able to give you a magical formula to define your logical structure. Unfortunately, I don't, because every IA is different.

However, I can give you some tips:

Establish success criteria

Define what success will be before you even start.

For example, I always start with 3 clicks or 30 seconds.

That means that every actor should be able to achieve their goals within 3 clicks on their landing page, or within 30 seconds.

As you build your logical IA, test it with goals you identified in the previous step and see if it meets your success criteria.

Duplicates are OK

Let me be clear: in the physical IA, duplicating content is a no-no. You don't want two physical copies of the same data because it will invariably lead to content getting out of sync.

Logically, I should be able to see content in multiple places.

Every user will navigate your IA differently, so why force them down a path that doesn't make sense for them?

Think of a news post. It may physically be on an HR site, but it may show up logically on your SharePoint tenant's root site news feed, in the user's own news feed, in the HR site's news feed, etc.

Me-centric, not organization centric.

I can't stress this enough. Build an IA that is centred around the users. Don't try to copy your Org Chart.

Do an open card sorting workshop

If you need help getting started, try writing all the goals on individual index cards (or sticky notes).

Invite groups of actual users to review the goals and physically group the cards into groups that make sense to them. They can work together, or they can work individually. Tell them that they can to move other people's cards too.

Then watch how they work.

See if people disagree or fight about where a particular goal should go. Make sure to remember any contentious points.

When they are finished, ask them to give each group of goals a name they would use to describe it.

Record the results and repeat with different user groups. You'll be surprised to find some similar patterns, while some groupings will wildly vary across different user groups.

This will give you a good indication of how your users see your information.

IMPORTANT: Ask the users to do this. Avoid the we know better attitude.

I once conducted this workshop with folks from the IT department, then asked the actual users to do the same exercise. The results were wildly different. Embarrassingly so.

Do a closed card sorting workshop

If you think you have defined your logical IA, try it with users.

Just as before, print some user goals on index cards or sticky notes. If you want to change things up, you can list some of the information you identified in the previous steps instead of goals.

Now draw your logical IA on a giant whiteboard or on different colour index cards laid out on a big table.

Invite groups of users to read each user goal (or types of information), and to place each one where they think it belongs on your logical IA.

If they instinctively place the cards where you intended them to go in your logical IA, you're doing great.

If they struggle, your IA needs more work.

Conclusion

Your physical IA is for authors. Your logical IA is for readers.

Build your logical IA with users in mind.

Remember that every user has a different perspective when looking for information.

In our next article, we'll discuss the last dimension of IA: Metadata.

Introduction

I've been working in IT for 32 years.

Most of my time in IT has been focusing exclusively on Microsoft technologies.

Over 18 years of this time was working with SharePoint and CRM.

But something is happening at Microsoft. It has been happening for a while, but it is definitely happening.

Microsoft is changing from the company everyone (but me) loves to hate, and it is becoming cool. Well, cooler.

A shift to open-source

Microsoft is shifting its focus to open source.

Since 2004, Microsoft has increased its number of open-source projects. Their products and components went from closed-source that gave little consideration to third-party developers to open-source.

In fact, Microsoft is now the biggest open-source contributor in Github. Over Facebook, Google, Docker.

I never thought I'd see the day.

These open-source initiatives attract talented contributors. Developers who feel passionate enough to spend their own time and donate their time to make the products better.

Those contributors (Microsoft employees and volunteers) work together. Regardless of who they work for, where they live, their culture, or their degrees of expertise. Even people who work for competitors put their "differences" aside and work together.

And then something amazing happens: they become a community.

They work together to help each other. They also help anyone who wants to use the repositories for their own projects. They answer questions, they fix bugs, they help people diagnose issues.

The SharePoint Development Community

One such community is the SharePoint Development Community.

The SharePoint Development Community is a community built around an initiative called Patterns & Practices (PnP). The PnP initiative includes guidance on best practices around SharePoint, code samples, Office 365 APIs, Office Add-ins, SharePoint Frameworks, developer controls, starter kits -- and many more.

Someone who played a big role in making the community what it is today is Vesa Juvonen.

Vesa is a Senior Program Manager within SharePoint engineering. He works with the team responsible for the SharePoint customizatin model. He also leads the virtual teams who created the various (and awesome) PnP initiatives.

Patrick Rodgers, who is a Senior Program Manager with Microsoft FastTrack, and a lead for PnPJs also plays a big part in this community. Apparently, he's a mediocre bowler, but he's a great community builder.

I'm sure that there are many other people at Microsoft who deserve the credit for this, but Vesa and Patrick are the two "faces" that you see in most calls, presentations, videos, conferences, etc.

If anyone knows who else at Microsoft deserve credit for the SharePoint Development Community, please let me know. They definitely deserve our thanks.

Patrick, Vesa, and their teams do an amazing job educating and sharing with the community, but also listening to the community.

They also work hard empowering the community to build something better. If the times that Vesa accepts and closes pull requests on the SharePoint repos is any indication, they work above and beyond 9 to 5, Monday to Friday.

They don't have to go the extra mile. They don't have to be entertaining and engaging. They could just ignore non-Microsoft people and build a product without asking the community. After all, that was the standard m.o. for Microsoft for the longest time.

Superstars

And then there are all the other superstars of the SharePoint Development Community. Those who don't even work for Microsoft, but take an active role in the community.

People like (pulled from the top contributors in GitHub):

...and many more. There are too many to list (I actually feel guilty not listing everyone here. I'm sorry if I missed anyone).

All whom have real jobs. Most of them maintain active blogs, tweet, and contribute to the SharePoint Development Community. Oh, and they probably have a families and have to sleep, some time. (I also secretly believe that one of them works with SharePoint during the day, and fights crime at night).

All those people contribute because they care. Because they are passionate. Because they want to help.

More importantly: they are welcoming. I've contributed a few tidbits in the PnP and SharePoint repos and they have never made me feel out of place. I've never felt like I was an outsider trying to get in a tight clique.

Don't be nasty

So why am I writing such a lengthy post about the SharePoint Development Community?

Because I've noticed that people are getting nasty.

More and more, I see new issues in the various Github repos where people post rude comments, or use an otherwise unkind tone.

I don't know if it is because they think that every contributor is a Microsoft paid employee, and that it is everyone's job to drop everything to help them, or because they were raised by wolves, but there is no need to be nasty.

If you open a sample web part that someone built over a year ago -- code that they shared with everyone to help grow the community -- and it doesn't work because the libraries have since changed, you don't need to get offended.

They didn't break the web part on purpose.

If you try to use a PnP API and it doesn't quite work the way you expected, they didn't plan to break it so that it would ruin your day.

If the documentation for something that changes every week is not quite up-to-date, don't get offended. We don't keep a second set of secret documentation that is more accurate just to mess with you.

Don't turn this into a evil-Microsoft-is-at-it-again thing.

The people who contribute are all people, like you and I, who have jobs and families.

Be considerate

I get it.

Your boss is bugging you to get stuff done. Your clients are all in a hurry to see something delivered.

You run into an issue with SPFx, PnP, code samples, or documentation. It is frustrating.

Everyone who contributes to the repositories have experienced the same thing.

Everyone is willing to help. Otherwise, they wouldn't contribute.

So if you submit an issue, be kind. Be considerate.

We want to help. Don't submit an issue that says:

This stupid thing doesn't work

or

It doesn't work

...because we can't help you.

Follow instructions

Help us help you!

Take the time to fill the issue forms. They all have guidance. Take a second to read the guidance and follow instructions.

For example, here is the start of the sp-dev-fx-webparts issue form:

Use the following form to submit an issue only if it's related to samples in this repo. If you have an issue related to the SharePoint Framework or its documentation, please submit the issue at https://github.com/SharePoint/sp-dev-docs/issues/new. This will help us respond to your issue faster.


Thank you for reporting an issue or suggesting an enhancement. We appreciate your feedback - to help the team to understand your needs, please complete the below template to ensure we have the necessary details to assist you.

_(DELETE THIS PARAGRAPH AFTER READING

Notice that it says DELETE THIS PARAGRAPH AFTER READING ? Go ahead, delete it before submitting your issue.

Why? Because everyone who follows the repository gets a notification when you file an issue. If they are like me, they probably receive a notification on their mobile device. They check the issue on their way to a meeting, or while waiting for the barrista to mess up their names again.

You have very little of their time to get their attention. All that text you didn't remove gets mixed up with whatever information you provided about your issue. If they can't make sense of your issue, they may put your issue aside until they have time to make sense of it.

If you see a section like this:

## Category
- [ ] Question
- [ ] Bug
- [x] Enhancement

Just put a x in the right category and leave the other ones empty (with a space between the [ and the ]). It helps those who want to help quickly categorize and triage your issues.

If you have a problem with any of the Web Part Samples, take the time to fill the Authors section of the issue form. The instructions are quite clear:

Because of the way this repository is setup, samples authors do not get a notification when you create an issue. It makes it less likely for you to get your issue resolved or to get help. For the section above @mention any author of the sample. Authors' github handle can be found on the main sample documentation page, under the "solution" section. Use the PREVIEW tab at the top right to preview the rendering before submitting your issue.

For example, if I messed up one of my sample web parts, make sure to add @hugoabernier in that section. It will notify me, and I'll gladly help. As would almost every other person who contributed a sample.

Contribute and give back

If you find an error in the documentation of the code, feel free to make the changes and submit a pull request. The contributors are people just like you.

And if you can't contribute, because you're too busy or you don't know what the fix should be, keep in mind that everybody else who contributes probably doesn't have time, or may not have the right answer -- just like you. They all have to make the time to find a fix and make the changes.

More ahead of us than behind us

Microsoft has come a long way.

SharePoint -- from codename Tahoe to what it is today -- has changed a lot too. It constantly becomes better.

Someone at Microsoft took a leap of faith one day and decided to build this amazing SharePoint Development Community. They broke all the rules, broke from tradition, and gave us a place where -- together -- we can all build something even better. From what I would guess, they probably spent a lot of their own time to make this happen before gaining support from management.

Whoever took the first step, and all those who supported them (and continue to support them) deserve our thanks.

Everyone else who contributes to the community makes every one of our jobs easier every day by paving the way with samples and best practices.

They all deserve our respect and thanks as well.

I truly hope that other areas of Microsoft take the example from the SharePoint Development Community and builds their own community. I'm looking at you, Dynamics 365 -- we could use a CRM Framework to build better CRM solutions. (And I'll help!)

Until it becomes a widely adopted model at Microsoft, we should all appreciate what we have and treat everyone kindly and respectfully.

Please forward this message to everyone who deserves our gratitude.

Sincerely,
Hugo

Updates

Thanks to Yannick Plenevaux (who, frankly, also deserves to be on this list) for pointing out that I forgot to mention Bert Jansen in the list of Microsoft people who go above and beyond. Bert is a SharePoint service engineer who does a lot of work with SharePoint PnP (processing pull requests, issues, running test automation and teletry). To quote Yannick: "[Bert]'s an amazing coder! The father of modernization tooklit".

Introduction

In my previous post on Information Architecture (IA), I explained how using your organization's Org Chart leads to bad IA.

The best IA is one that allows every user, regardless of their job title, to find the information they need fast.

Unfortunately, creating an IA that caters to every user in your organization, then trying to fit it all in a single navigation structure would be difficult to achieve.

As a result, organizations usually end up adopting a single, one-size-fits-all IA, hoping that it will meet the needs of most people.

What you get is usually the lowest common denominator IA. An IA that meets the bare minimum for everyone.

In case I'm not making myself clear: lowest common denominator is bad.

Luckily, there is a solution.

The answer is simple. Don't try to fit it all in a one-dimensional IA.

Create an IA that has 3 dimensions:

  • Physical
  • Logical
  • Metadata

Physical, Logical, Metadata

Physical

The Physical dimension of information represents how you store your information physically. Your folder structure, document libraries, sites, and site collections are the physical dimension.

Except that the physical dimension isn't for everyone. It is for authors, the creators of content. Those who will maintain the information in SharePoint.

Go to each department and look at their files. Look at their file shares, their folder structure.

That'll be the start for your physical IA.

When people start creating their folders (because they need to do their jobs), they don't stop and think "What is the most efficient way to store my files".

No. They create folders that make sense to them.

It won't be perfect. People tend to do silly things sometimes.

But it is a good start. It helps you understand the needs of authors.

You need to apply a bit of finesse and break things down into sites, document libraries, and folders.

But how do you decide when you should create a site, a document library, or a folder?

Consider the following:

Security

Look at who should have access to edit files, and who should have access to view files.

Try to group documents into "containers" so that those with the same permissions stay together. I say "containers" because, at this point, we don't know exactly if we need sites, document libraries, folders, or subfolders.

Avoid the temptation to build a structure that requires individual permissions on every document.

For example, instead of assigning individual permissions on documents, try creating document libraries. Assign permissions to those document libraries. When people place documents in those document libraries, the documents will inherit permissions.

Magic!

If you need to change permissions, you can do so at the document library-level, instead of trying to manage individual document permissions.

Some SharePoint experts who are way smarter than I will tell you to avoid folders and sub-folders. According to them, folders make things more difficult to find.

I'm ok with folders, as long as you don't abuse them. If you need to create folders instead of document libraries, do so.

Try to keep it to less than 3 folders deep, otherwise, people will never find the information they need.

Metadata

If you need to store documents with custom metadata, you should.

Create content types, if you want. You should.

Keep one thing in mind: avoid putting documents with different custom properties in the same containers. By containers, I mean documents libraries or folders.

Make it easy for authors to know what metadata you'll expect them to enter by keeping documents with the same (or similar) metadata in the same document libraries. Documents with mandatory fields "Client Name", "Project Name", and "Project Code" shouldn't go with documents with "Department" and "Policy".

Otherwise, when users drop new documents in a library, they won't know what metadata they're expected to enter.

Sure, you can add many content types in that library, but make sure they need similar metadata.

Users that spent a few hours writing a document don't want to filter through 72 custom properties to see what they need to enter.

They'll either pick the wrong content type, or they'll enter bad metadata just to get things over with.

I should make something clear: I'm not saying that you can't do it. I'm saying that you shouldn't.

The success of your IA depends on how well people use it. If authors get confused when creating new content, you'll get a mess.

Garbage in. Garbage out.

Workflows

Put documents with similar workflows together.

By workflows, I mean whatever business process gets triggered when someone creates or updates a document. It could be whether documents need approval, or it could be a Microsoft Flow process.

Imagine if you have a document library that has super-important documents. Everyone in the company should see them as soon as possible.

But some documents in that library need publishing approval before everyone can see them. Some other documents don't need approval.

Someone is bound to make a mistake. They'll create a super-important document and upload it to the document library, expecting it to be visible right away. But the document doesn't get approved and sits there, invisible to everyone who needs it.

Or the other extreme: someone drops a draft copy of a document in that same document library. They think the document will need approval before it shows up for everyone. But it doesn't and becomes visible to everyone.

Keep it consistent: keep documents with similar workflows together. That way, content authors will know what behaviour to expect when they create new content.

Conclusion

The best SharePoint Information Architecture doesn't try to fit everything into a single navigation structure.

It relies on 3 dimensions.

In this article, we discussed how the Physical dimension targets the authors.

Let's be bold:

When building the physical dimension of your IA, your primary goal is to make life easy for authors.

If authors are happy, chances are your content will be better.

We'll take care of readers in the next article.

Introduction

"One size fits all" doesn't work for Information Architecture.

Especially not in SharePoint.

In a previous post, I discussed a lot of bad SharePoint implementations are due to bad Information Architecture.

As a general rule, if your users complain that they can't find the information they need, it is most likely because your Information Architecture (IA) needs tweaking.

Your IA may have been great when it was first implemented. But things change.

Your customers' needs change, and your business needs to change to adapt -- if you want to stay in business.

When your business changes, your IA should change as well.

In this article, I'll discuss how to avoid the "One size fits all" approach for IA, and how it should constantly evolve.

IA does not equal the Org Chart

The first instinct when building an IA is to mimic your company's Org Chart.

That would be good... if your company suddenly became sentient and started browsing your SharePoint site. If Contoso Inc became a living thing, it would feel right at home navigating an IA that looks like its Org Chart.

But you don't build IAs for companies.

You build them for people.

Your SharePoint users are those who will need to find information. They're the ones that need to be able to get to the stuff they need to do their jobs.

So, here is my bold statement:

Information Architectures need to be User-centric, not Organization-centric.

Now many of you are probably saying: "Do you expect us to build different navigation for every single person in my company?"

Ideally, yes! But it isn't as simple as that.

And how do you fit different navigations for every user in your IA? Won't it get too complicated to make sense?

Limitations of file structures

In the olden days, when people tried to build IAs for network file shares, there was only one dimension to work with. The folder structure.

With folders, if you want to create a folder structure that caters to different types of people, you don't have a lot of flexibility.

Let's take an example: employee benefits.

Most organizations offer various types of employee benefits. Here are some examples:

  • Health care
  • Retirement
  • Workplace flexibility
  • Wellness program
  • Tuition reimbursement

The Human Resources department handles some of the benefits above. Finance department handles some others.

When creating the folder structure, you want to give a place for our HR folks to share benefits information. Things like brochures, forms, standard operating procedures (SOPs), etc.

But you don't want people from the Finance dept to mess with HR's files. And you don't want HR people to mess with Finance's files.

So you create another folder for the Finance department to share information about the benefits they take care of. Another folder with brochures, forms, SOPs, etc.

But how do employees find information about benefits if all you have is a file folder structure? They need to know that there are files in both the HR and Finance department benefits folders.

That's the problem with using a file folder structure. If you don't want to duplicate documents, you need to put them in one place.

So you end up creating the lowest common denominator structure that will cater to most people.

But what if you could use more that one Information Architecture?

To be continued

As it turns out, your SharePoint Information Architecture has 3 different dimensions! That's 3 different types of Information Architecture at your disposal.

In our next article, we'll discuss the 3 dimensions of Information Architecture in SharePoint.

Introduction

2 words.

That's all people see when they scan links and headlines on your pages.

11 characters, on average.

That's all you get to attract people's attention on your SharePoint site.

That's what a Nielsen Norman Group study found when they studied how users read online content.

Why should you care?

Let's take the SharePoint news list for Contoso:

What you think they see

I'm sure you'll agree that every news article on the list above is important. Right?

Now, if people really see the first 11 characters of a headline, let's show what they actually see when they scan the news articles:

What they see

Hmmm, doesn't make much sense, does it?

The average length of Fortune 1000 company names is 14 characters long. Even if you don't work for a Fortune 1000, chances are your company name takes valuable space in headlines.

That's attention-grabbing space you could use to get your employees to pay attention to.

In this article, we'll discuss how to create news that will make your employees pay attention to.

Don't read further!

My mentor at McKinsey & Co once gave me the definition of communicate:

To communicate is to convey a message that results in a change in behaviour.

If you don't get the desired change in behaviour, you're making noise. You're not communicating.

This article assumes that you want to write news and headlines because you want a change in behaviour:

  • Start doing something they haven't been doing.
  • Stop doing something they have been doing.
  • Change their perception of things.
  • Raise their awareness.

If you don't care about changing behaviours, you don't need to read this article. You can post a comment to say how great this article was and I won't tell anyone.

Otherwise, read on!

Readers don't give a F

In another study, Neilsen Norman Group found that when people read online content, they read in a F-pattern.

That is: when reading online, people take some time to read the first few items in a list. As they continue to read through the list, they read less and less.

Eyetracking
Eye-tracking study, source: Nielsen Norman Group

Eventually, they scan through the left side of the list.

That's when they only see the first few words of a list item. They'll see a little more if you use shorter words, and less if you use long words.

They don't actually count 11 characters and stop reading.

Also, the F-shaped pattern is not the only reading pattern. There are others.

One thing is clear: people scan content when they read.

The importance of microcontent

Microcontent is a type of content that consists of short text fragments. You find microcontent in page titles, headlines, email subjects, etc.

In SharePoint, the News web part is a bunch of microcontent.

Microcontent is often shown out of context. For example, the aggregated news in your SharePoint start page, or in search results.

Microcontent helps readers when they scan. It lets them decide what they should click on.

Microcontent also helps readers search and save. They may find your news through search results and open each result in a new tab. Or they may add the links to their favourites. When they come back to your links, you need to provide them with context.

Whatever they do, you need to write your content so that it makes sense for users.

Elements of a news article

Every news article in your SharePoint site should consist of two microcontent elements:

Headline (or Hede)

The short text that grabs the user's attention. SharePoint uses the title of your news article as the headline.

When you write headlines, you should consider the following tips:

  • Use plain language: resist the temptation to be fancy. Even highly-educated users want succinct information that is easy to scan.
  • Remove non-essential words: to improve scanning.
  • Keywords at the front: to catch people's attention.
  • Follow a convention: write headlines in a consistent manner. It will help your users guess the rest of the sentence. Even if they only read the first 11 characters.
  • Use numerals: if you have to use numbers in your headline, don't write out the number. Write 2 instead of Two. It takes less valuable attention-grabbing space.
  • Don't be clever: be meaningful.

Consider skipping these words:

  • The
  • A
  • To

Even better, skip these words too:

  • Announcing
  • Introducing
  • Exclusive
  • Special
  • And any other made-up jargon that tempts you

Lead (or Lede)

The one or two paragraphs below the headline.

If the headline's job is to attract attention, the lead's job is to convince the user to click on the article.

They should be:

  • Useful: Be specific and provide facts to get your users interested.
  • Urgent: Provide a sense of urgency to push your users to read the article. Now company policy? Give them the deadline to adopt it.
  • Unique: fight information overload. Make it easy for your readers to know if they have already read this article.
  • Ultra-specific: use real numbers, real names and real ideas.

Most important: resist the temptation not to write a lead. Tell users what's in it for them.

Links are promises

Remember that links are promises. Every time a user clicks on a link, they expect that whatever page they go to will match what they clicked on.

Every time you break a promise by taking a user somewhere different than what they clicked. When you do, you chip away at their trust.

If you want people to use SharePoint, it needs to become a trusted and authoritative source of information. Every time you break users' trust, you lose credibility. People will stop going to SharePoint to find information.

You should allow your users to confidently predict what they'll get if they click. Do not be misleading or promise too much.

Don't click here

Whatever you do, don't use "Click here" in your headline or lead. That's soooo 1995!

Other than being uncool, here are reasons why you shouldn't use "Click here":

  • It isn't informative: when people scan your content, hyperlinks tend to grab attention. If your hyperlink doesn't say anything useful, chances are that users won't spend the time to find out if the link is worth their ti,e.
  • Not action-oriented: remember how we said that to communicate is to [get] a change in behavior? This is your opportunity to tell people what you expect them to do.
  • Insulting: people know what links are. They know what to expect. If you tell them to "Click here", you tell your users that you don't trust their intelligence.
  • Accessibility: Users who are visually-impaired often rely on screen readers. When navigating a web page using a screen reader, it will often read out the hyperlinks. You end up with a screen reader that says "Click here, click here, click here...".
  • Crappy search results: Remember that people scan search results as well. If all your headlines contain "Click here", you're making it more difficult to find results.

How to create a SharePoint news item with a headline and a lead

Enough theory. Let's create a news article!

  1. From your SharePoint site, select New followed by News post
    New | News Post
    (optionally, you can select Add from the News web part, then News post)
    Add | News post
  2. From the New Page page, enter the headline where it says Name your news post.
    New your news post
  3. From the toolbar at the top, select Page details
    Page details
  4. In the Page details pane that opens, enter your lead in the Description field. Do. Not. Skip. This.
  5. Write your content.
  6. When done, select Post news (or Save as draft if you aren't quite ready to publish).
    Post news

Enjoy your new news article!

How to create a link to a news article

Sometimes you just need to link people to news that are hosted somewhere else. You don't need an article, you just need a link.

Here's how to do this:

  1. From your SharePoint site, select New followed by News link (or Add|News link from the News web part)
    New | News link
  2. In the News link pane, enter the URL in the Link field. Make sure to include the https:// (or http://) prefix.
    News link pane
  3. SharePoint will attempt to verify the link and retrieve the news link's headline and lead. Make sure to update the Title and Description field with your headline and lead
    News link details
  4. Select Post.

Conclusion

SharePoint News is an awesome new feature of SharePoint modern sites.

If you want your employees to read your news, make sure your headlines attract attention.

In this article, I focused on headlines and leads. I did not discuss other aspects of the news. That's for another article.

I hope this article will help create news that will improve your SharePoint experience!

For more information

Introduction

When I tell people that I do most of my work with SharePoint and Dynamics 365, I'll often hear something like:

Oh, we have the SharePoint at work. It sucks!

To which I usually respond something like:

Oh, I just remembered! You're boring, and I'm leaving!

(...and then blame my autism for being so blunt).

Other times, I take the time to explain that SharePoint is actually a great product. They have seen a bad implementation of it.

But how bad can an implementation of SharePoint be? How hard is it to install SharePoint? And if you use SharePoint in Office 365, what's your excuse?

The answer is that the installation of SharePoint is not wrong. It is the content inside of SharePoint that lacks proper structure.

For example, let's look at Excel: when you launch Excel, you get a blank worksheet with empty columns and rows.

You don't blame Excel. You don't say "Excel sucks!".

You start putting your data in, write some formulas, and format some cells. Then you get value out of Excel.

The key is to put your data in Excel in a way that makes sense. Putting all data about an item is on the same row. Arranging the data into columns, so that you can sort, group, and filter the data in a way that you can make sense of it.

If you feel fancy, you can even format the data into tables, highlight cells, or create charts.

That's how you get value out of using Excel; By giving it some structure.

SharePoint works the same way: it starts with a (mostly) blank canvas, and you get to add your content.

But if you don't take the time to structure your content, you end up with a mess.

If you allow other people to create content without proper governance, you get a bigger mess. People can't find content, and whatever they find is often old, wrong, or duplicated.

That's what Information Architecture (IA) is all about. IA is designing a structure for your information so that users can use your site and find the content they need.

This is the first in a series of articles discussing various aspects of IA within SharePoint.

By the end of this series, I hope to prove that SharePoint doesn't suck -- maybe your IA does?

Data, Information, Knowledge

People often confuse the terms data, information, and knowledge and use them interchangeably.

Data, Information, Knowledge

Information and library scientists say that we should not confuse data, information, knowledge. They are very different. This is something called the DIKW Pyramid.

To build a better IA, you need to understand the difference between those terms.

Data

  • Data are the facts of the world.
  • Data has no meaning, it is a description of things.
  • We perceive data with our senses

For example, if you kept track of every time I refuelled my car and how much it cost, that's all you have. But that in itself has no meaning.

Information

When you take data and put it in context, it starts having meaning.

For example, if you look at the data you collected earlier in the context of time and dates, you may notice a pattern. You may notice that I refuel my car every Mondays and Fridays. That means that I probably commute for my work (because I empty a full tank in 5 days) and that I travel on the weekend (because I use the same amount of fuel within two days)... or maybe that I should buy a new car because that's a lot of refuelling!.

See what happened here? We took data that had no meaning in itself, added some context (time and date) and got information.

In order words:
Data + Context = Information

Knowledge

Knowledge consists of what we know. We start with information, then add our own experience, beliefs, rules of thumbs. It becomes our knowledge.

In other words:
Information + Experience = Knowledge

If I take everything I know and write it down, it becomes information again. Someone else has to use their own experience to make it their knowledge.

Unfortunately, we can't store knowledge in computers (yet).

Sample scenario

If you dump stuff in a SharePoint site, without structure. you create data.

I know, I know, I said that documents are information. If people can't make sense of the content in your site, it becomes data because it has no meaning.

If you want your content to have meaning, you need to put it in context.

For example, let's imagine the following scenario:

  • You keep a list of customers in SharePoint somewhere
  • You have a document library containing customer contracts, proposals, and specs.
  • You have a list of customer-related projects, with status reports, deliverables, etc.
  • You have an RSS feed web part somewhere that has industry news, customer news, etc.
  • There are account managers in your organization who each own one or more customers.
  • You have a list of which account manager has what customer
  • You have a list of experts and their industry expertise
  • Your customers work across many industries

You experience the following problems:

  • Your site users complain that they can't find the documents they need.
  • Account managers send the wrong versions of proposals
  • You find many copies of the same document, with file names containing _finalversion, _final_final, and _thisisreallythefinalversioniswear
  • Users don't know about the RSS feed web part, or they don't use it.
  • People find it difficult to find the right experts for a given industry.

One of the ways that you could re-arrange the information would be to put it in the context of customers.

For example, imagine if you had a place for every customer. For now, let's call it a site, but that's not the point of this exercise.

Every site would have the same information:

  • Customer information
  • Customer documents
  • Links to customer projects
  • An RSS feed that shows news about the customer, the customer's competitors, and their industry.
  • A contact card for the account manager
  • Contact cards for experts in the customer's industry

Somewhere else, you would have a list of all customers with a link to their individual sites.

Finally, every account manager would see a My Customers web part. The web part would show them their list of customers and links to their customer sites.

Here is a potential usage scenario:

Andrew is an account manager. He logs in to the SharePoint first thing in the morning.

Andrew is about to visit his customer and wants to see what's going on. He clicks on the customer's name in the My Customers web part to get the customer's site.

On the customer's site, he can review the status of the latest proposal and project status.

As he's getting to leave, Andrew looks at the RSS news. He notices that the customer's competitor is in the news for declaring bankruptcy. It may impact the proposal Andrew prepared.
Andrew wants to make sure is not affected.

Andrew looks at the list of experts and sees that Edward is an expert in the customer's industry. He sees that Edward is currently online and available. He chats with Edward to understand the impact of the competitor's news. Edward explains how Andrew should change the proposal.

Andrew makes the changes to the proposal. When he leaves for his customer meeting, he does so knowing he is well prepared.

This scenario is actually derived from an implementation I built many years ago. It uses the same data that already existed, but by putting it in the context of a customer, it has meaning for its users.

By putting the customer list in the context of each user, it helps account managers. They can get to their customer information with fewer clicks.

Adding a list of industry experts makes it possible to get in touch with the people with knowledge.

What it means

Add context

The secret to creating content that has meaning (information) is to give it context. Otherwise, it is data.

To avoid data overload -- and useless SharePoint content -- make sure that you put data in context.

Add links to people

You can't store people's knowledge in SharePoint. But if you provide links to people -- who have the knowledge, you will make it easier for your users to reach experts.

Personalize

Personalization is not adding the person's name on the top of the page and say Hello, [user]. That's useless -- I know my own name (except, maybe on Mondays)!

Think of personalization as putting data in the context of a user. What does the user care about? What information do they need to do their jobs?

For example, one of my clients had offices across the world, with head offices in Toronto. Every day, users would log in to the SharePoint and would see announcements like this:

  • "IMPORTANT: Parking structure will closing this weekend"
  • "Pastries in the lunchroom"
  • "Cafeteria menu for today is chilli!"
  • "CEO to announce new deal today"

You think that people in Honduras cared about the parking lot closures or the pastries in the lunchroom? No! They do care about the CEO announcing the new deal, though.

People in the head office got value out of the announcements (chilli day was always popular). Everybody else in the company -- those who made money for the company -- felt as if they were less important. They felt the news didn't matter to them.

As a result, most people outside of the Toronto offices didn't read the news. To them, the news became data, not information.

The fix was simple: create corporate news for everyone, and office news for each office, then roll them up as News. Combine corporate news with news related to the user's office. That way, every user could see all the news that matter to them.

If someone wanted to see news for other offices, they could visit the site for that office. The default behaviour was to show people the news in the context of their office.

Conclusion

People often confuse data, information, and knowledge. Content without context has no value. It becomes data -- noise that they have to filter through to get to what matters to them.

Information architecture strives to take all data and create context. It makes it information and gives it meaning.

You will find that by adding a little context to your content.

What if dictionaries contained all the words in the English language, but in random order? They would be useless.

Searching for a word would need you to scan the entire dictionary before you could find it.

It would be frustrating.

If you met me and I told you that I write dictionaries for a living, you would probably say:

Oh, we have dictionaries at work. They suck!

In our next articles, we'll discuss specific ways to create an information architecture. We'll focus on creating an information architecture that makes sense for users.

We'll also discuss how you can use SharePoint to create targeted content. You know, stuff that matters to users.

I hope this helps?

For more information

The DIKW Pyramid, Wikipedia