Tuesday, June 12, 2018

Dynamically load a web part from another web part

I coded a proof of concept last week on how to load a SPFx web part from another web part which I plan to use as pattern for display templates for a search web part I’m planning. By using SPFx parts as render templates we get better control of the code, and avoid script injection on the page.

I figured this approach might be useful for other scenarios so here’s the code to play with :)

The POC loads up two instances of the Modern Script Editor web part, and sets data in it. Which means the modern script editor web part has to be installed on the site you are testing on. Or replace with any of the oob web parts and set the correct properties.

Issues I haven’t gotten to yet as they are not a concern for my scenario are:

  • How to access web part properties of the dynamic loaded web parts in edit mode – can be worked around if the web part has a custom edit UI
  • How to persist the web part data and store it in the main web part – take a look at the serialize method of the ClientSideWebPartManager.
  • The text web part is a special kind of web part, so not sure how to dynamically instantiate it

Full sample can be found at https://github.com/wobba/spfx4fun/tree/master/DynamicLoad

import * as React from 'react';
import styles from './HelloWorld.module.scss';
import { IHelloWorldProps } from './IHelloWorldProps';
import { Guid } from '@microsoft/sp-core-library'
import { ClientSideWebPartManager, IWebPartManagerContext, IWebPartData } from '@microsoft/sp-webpart-base';
import { DisplayMode } from '@microsoft/sp-core-library';

import { IClientSideWebPartManifest } from '@microsoft/sp-module-interfaces';

let _webPartManager: ClientSideWebPartManager;
let _sampleIdOne = "mAdcOW" + Guid.newGuid().toString();
let _sampleIdTwo = "mAdcOW" + Guid.newGuid().toString();
export default class HelloWorld extends React.Component<IHelloWorldProps, {}> {

    public async componentDidMount(): Promise<void> {
        _webPartManager = new ClientSideWebPartManager(this.props.context.host);
        await _webPartManager.fetchWebPartManifests(); // Ensure all manifests are available
        this.addData();
    }

    private async addData() {
        // local webpart properties - in this case props for the modern script editor webpart
        let props = {
            script: "<div>Foo</div>",
            title: "The Modern Script Editor web part!",
            removePadding: false,
            spPageContextInfo: false
        }
        await this.loadWebPart("ScriptEditorWebPart", document.getElementById(_sampleIdOne), props);
        await this.loadWebPart("ScriptEditorWebPart", document.getElementById(_sampleIdTwo), props);
    }

    private async loadWebPart(alias: string, domElement: HTMLElement, webPartProperties: any) {
        let manifests = _webPartManager.getWebPartManifests();
        for (let i = 0; i < manifests.length; i++) {
            const manifest = manifests[i];
            if (manifest.alias === alias) {
                let instanceId = Guid.newGuid().toString();
                let wpManifest: IClientSideWebPartManifest<any> = manifest as IClientSideWebPartManifest<any>;
                let wpData: IWebPartData = {
                    id: wpManifest.id,
                    instanceId: instanceId,
                    title: "",
                    dataVersion: "1.0",
                    properties: webPartProperties
                };

                // Specify any as webpartLoadExtraLogInfo is not defined on the interface and has to be present
                let initialize: IWebPartManagerContext & any = {
                    domElement: domElement,
                    instanceId: instanceId,
                    manifest: wpManifest,
                    displayMode: DisplayMode.Read,
                    webPartData: wpData,
                    webpartLoadExtraLogInfo: {
                    }
                };
                await _webPartManager.loadWebPart(initialize);
            }
        }
    }

    public render(): React.ReactElement<IHelloWorldProps> {
        
        return (
            <div className={styles.helloWorld} >
                <div className={styles.container}>
                    <div className={styles.row}>
                        <div className={styles.column}>
                            <span className={styles.title}>Dynamic loading!</span>
                            <span id={_sampleIdOne}></span>
                            <span id={_sampleIdTwo}></span>
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}

Monday, May 28, 2018

How to reset a modern home page using PnP PowerShell


Photo by Nikita Kostrykin at Unsplash

Here’s a short snippet which will reset any modifications done to a modern home page back to the default layout.

Basically you clear out the CanvasContent1 field which stores the page layout and contents.

Connect-PnPOnline https://<tenant>.sharepoint.com/sites/mysite
# Get welcome page url
$web = Get-PnPWeb -Includes WelcomePage
# Load the page
$file = Get-PnPFile -Url $web.WelcomePage
# Get the page's item
$item = $file.ListItemAllFields
# Load the item id
Get-PnPProperty -ClientObject $item -Property Id
# Clear the content to reset
Set-PnPListItem -List SitePages -Identity $item.Id -Values @{"CanvasContent1"=$null} -SystemUpdate

Quickly clear Followed sites using PnP PowerShell

In my dev tenant I do a lot of testing with Groups and sites, and this has the effect that my demo user is following a lot of, often with the same name as seen in the image below.

image

The followed sites are actually stored in a list named Social in your OneDrive. This list was introduced with SharePoint 2013, and still alive and kicking. You can access followed sites from the following URL:

https://<tenant>-my.sharepoint.com/personal/<user site>/Social/Sites.aspx

This list also tracks followed documents and people, but that’s not the focus of this post.

First connect to your OneDrive using PnP PowerShell, then execute the code below to remove all followed sites, and be sure to set the correct URL in the first line.

$followedSitesUrl = "/personal/<user site>/Social/Private/FollowedSites"
$sites = Get-PnPListItem -List Social -Query "<View Scope='RecursiveAll'><Query><Where><Eq><FieldRef Name='FileDirRef'/><Value 
Type='Text'>$followedSitesUrl</Value></Eq></Where></Query></View>"
$sites |% { Remove-PnPListItem -List Social -Identity $_.ID -Force }

Once complete, the list is now empty.

image

Tuesday, May 22, 2018

Two SPFx manifest settings you might not be aware of

image

If you take a look at the manifest schema for a SPFx web part there are a couple of settings you might want to take a look at.

hiddenFromToolbox

If hiddenFromToolbox is set to "true", the web part will not be visible in the modern SharePoint toolbox. Very useful for web parts you provision automatically on pages, but don’t want users to add themselves.

supportsFullBleed

If supportsFullBleed is set to "true", the web part can be added to a full page width zone on a modern page in a communication site, spanning from the left margin to the right margin without any white space.

Friday, May 11, 2018

Caution when using $expand with Microsoft Graph

Using the $expand parameter with calls to the Microsoft Graph is very handy. In one API call you can retrieve both the object itself and additional properties.

Two examples are fetching a user and the direct reports, or a group and it’s members.

https://graph.microsoft.com/beta/users/061353c3-af75-4767-9b19-a5bceed85f53?$expand=directReports

https://graph.microsoft.com/v1.0/groups/79958190-024b-4c62-ab55-65dc9a066cac?$expand=members

image

The caution is that when you expand a property which has a collection of values, you will only get the first 20 items returned. This means that if you work in an organization with more than 20 people in it, you should not use $expand if you need all the values, but resort to two calls instead, one for the item, and one for the property you want to expand.

Summary

While using expand is very handy, it’s almost always better to break it into two API calls to avoid having issues if you can expect more than 20 items.

Tuesday, May 8, 2018

How to get the list item from a file URL using PnP PowerShell

image
Photo by Karly Santiago at Unsplash

This is one of the snippets I always forget, but which is very handy in cases where you have the absolute URL to an item in SharePoint. The magic is really line 6 which uses the static method WebUrlFromFolderUrlDirect to get the web URL of the file, so that you can connect properly.

$uri = [Uri]'https://contoso.sharepoint.com/sites/foo/bar/Shared%20Documents/mikael-is-cool.docx'
# Connect to the root site, or any other site
Connect-PnPOnline -Url ($uri.Scheme+'://'+$uri.Host)
$ctx = Get-PnPContext
# Get the web url for the file
$webUrl = [Microsoft.SharePoint.Client.Web]::WebUrlFromFolderUrlDirect($ctx, $uri)
Connect-PnPOnline -Url $webUrl
$fileItem =  Get-PnPFile -Url $uri.AbsolutePath -AsListItem

Sunday, May 6, 2018

Locating where a term set is used in SharePoint using search

Joanne Klein asked on Twitter if there is a way to get all places where a specific term set is used in SharePoint.

image

One way would be to iterate all site collections and check if the term set was present in the Hidden Taxonomy List, which stores all used terms on a site collection (and if you have no idea what that is, that’s ok as well).

image
Hidden Taxonomy List at /Lists/TaxonomyHiddenList/AllItems.aspx

Another option which is the solution I proposed, is to use search. Of course you need to have read access to the items where the term set is used for it to be 100% accurate as well items using terms from a term set has to be present in the search index.

Friday, May 4, 2018

Use Microsoft Flow to implement approval of site pages


Photo by Zachary Nelson at Unsplash

Again I’m late to the party, but Microsoft has released an action to Flow called “Set content approval status”. This action enables you to add content approval not only to list elements and documents in SharePoint, but also to news pages.

Microsoft has talked about content approval for ECM scenarios with communication pages for a while, but until a proper UI is in place, you can get started today!

This is how.

Thursday, May 3, 2018

Addendum to Chaks’ post “Deep dive into ‘Get items’ and ‘Get files’ SharePoint actions in Microsoft Flow”

Chaks recently wrote a post where he explained the difference between the two Microsoft Flow SharePoint actions Get items and Get files (properties only).

Behind the scenes they are both calling the following undocumented service which seems to wrap the regular OData call:

/_api/SP.APIHubConnector.GetListItems(listName='MyList',queryOptions=@q)?@q=''"

Good news is you can use both actions interchangeably. If you want to get the properties for a document library using the Get items action simply pick Custom value in the dropdown and enter the title or id for your document library and it works just fine (been using this already a while).

image

So, to me, having the Get files (properties only) action seems unnecessary, but for some users it might be easier to understand with two different actions targeted towards lists or libraries.

image

Thursday, April 26, 2018

The SharePoint Properties Pane (successor to DIP) is available for SharePoint 2013

Last year at Ignite we were all psyched that Microsoft gave us a replacement for the old Document Information Panel (DIP) in Word which allowed us to set metadata stored in SharePoint columns. DIP disappeared with Office 2016 (and you had to edit properties in the info settings page instead), and finally we got a replacement. However, it only worked for documents stored in SharePoint Online.

I did a proof on concept of this with a colleague last year as well, so it was just a matter of time until it was properly release.

The announcement slipped past me as there has not been a lot of whoo hah around it. Reading the release notes for the December 2017 CU for SharePoint 2013 it states:
  • Enables the new Document Information Panel feature that's visible in Word 2013.
Maybe a bit cryptic, and I haven’t tested if Word 2013 shows the SharePoint Property Pane, but at least Word 2016 shows it just fine against a document stored in SharePoint 2013.

So, patch away and enjoy SharePoint properties once again in Word!

Word SharePoint Properties Panel.PNG