Copy & move SharePoint documents/folders using PnPjs

copy move

The latest release of PnPjs contains 4 new methods. They allow you to copy and move, files and folders, to a different folder on the same or a different site collection. And they are incredibly fast!

Motivation

I have been recently asked by a client to develop a feature replacement to the default SharePoint copy and move to features. The OOB versions had some limitations that were causing problems to end users. With this in mind, I started by creating a proof-of-concept solution (SPFx list view command set extension). But soon realized that the functionality to copy and move files/folders between sites was not available in PnPjs. And so I added it and submitted a pull request (my first ever for the main PnPjs repository)!

Update: I wrote a blog post providing more details about the copy and move extension. You can read more here.

New copy and move features

  • Copy and move documents and folders to the same site. PnPjs already had methods to do this, but using a different API endpoint
  • Copy and move documents and folders to a different site
  • If a file with the same name already exists, you can decide to replace or keep both. When keeping both files, a numeric value will by added to the name of the new file
  • If a folder with the same name already exists when moving, you can decide to keep both.  When keeping both folders, a numeric value will by added to the name of the new folder. The replace option is not available for folders
  • Copying will not persist the version history of the source file
  • Moving will persist the version history of the source file

Usage

As part of the pull request to add the new capabilities I have also updated the documentation (and tests) so people can easily understand how to use them. In short, your code will look similar to this (or use promises if you prefer):

Copy file by path
await sp.web.getFileByServerRelativePath(srcPath).copyByPath(`${destPath}/${name}`, shouldOverWrite, KeepBoth);
Move file by path
await sp.web.getFileByServerRelativePath(srcPath).moveByPath(`${destPath}/${name}`, shouldOverWrite, KeepBoth);
Copy folder by path
await sp.web.getFolderByServerRelativePath(srcPath).copyByPath(`${destPath}/${name}`, keepBoth);
Move folder by path
await sp.web.getFolderByServerRelativePath(srcPath).moveByPath(`${destPath}/${name}`, keepBoth);

Extra: Contributing to PnPjs

I use PnPjs for a very long time as it immensely helps me on my daily job, so I’m very happy that I was able to add a little bit more to it.

The project is so big that the first feeling I had was that adding the code there would take me longer than doing it on my own solution – but it clearly was the right thing to do. But guess what? The code is so well structured that this is actually pretty simple! All I had to do was to find the file where my functions should go (just follow the logical structure of folders that mirror the different packages) and find a similar function that I used as a starting point. Update the reference to the target REST API endpoint, update the data passed in the body (for POST requests) and all was done! All that was left to do after that was to update the documentation and tests, where I followed a similar approach.

We can all use it on any project going forward without having to worry about it! Sharing is caring 🙂

SPFx Workbench Customizer

custom-workbench

With SharePoint Framework, Microsoft also introduced a really good development story for creating custom web parts: the Workbench page.
This page is not only available when you are developing solutions locally, but also on a SharePoint site. This gives you the option to access data on a SharePoint site from code running on your machine. Let’s be honest, it’s great!

Unfortunately, the Workbench page also has some limitations for some development scenarios. One of them being, in my opinion, how the overall page styles differ from a normal modern SharePoint page.

SharePoint Workbench page

As you can see on the image above, the styles are really not great. I personally don’t find a reason why they differ so much from a modern page, and why, for example, the page layout is limited to a maximum width of 924px .

When building custom web parts, I had previously (in some occasions) included some code into the project to deal with this in different ways. But this was never a great approach, as I used to comment/delete that code after the work was done…

And this led me to think on a better solution that would allow me to not having to worry about doing it again. So I thought on just creating a web part for it and always add it to the Workbench whenever I’m working on it.

Workbench Customizer web part

The image above is an example of the workbench page when the web part is added to it (the text on the page is just multiple text web parts to test different zones).

When added to the workbench page, the web part will apply the following changes by default:

  • Change the max page width, allowing the editable area to be the same width as a modern page (1236px)
  • Center the canvas zones on the page, in line with modern pages
  • Update overflow, allowing the scrollbar to appear on the right side of the page
  • Remove additional padding that is introduced by having the page in edit mode by default

Every of the items above is controlled by a web part property that can simply be disabled on the web part properties panel. This is currently achieved using dynamic imports. Every customisation is kept on a separate SASS file that is dynamically imported when the property is enabled. When a property is disabled, a message will be displayed asking you to refresh the page.
I will be looking on improving the experience over time, but it does it’s job at the moment…

Global CSS overrides? Is this not bad?

The CSS changes to the overall page are done using the :global approach. I absolutely know this is not a recommended approach for customising SharePoint, but remember that we are only customising the Workbench page! Who cares! If it breaks, you can simply take it out of the page…

Code

The web part is obviously open-source and is currently on my personal GitHub account
https://github.com/joelfmrodrigues/spfx-workbench-customizer

I’m planning on submitting a PR to the PnP web part samples repository over the next days and will update the post if the PR is accepted.

Update: The web part is obviously open source and is available under the PnP web part samples repository.

If you have any ideas on things that may be missing or any feedback about the current implementation, please reach out. Any feedback is welcome.

Hope you find it useful and use it while building your own web parts.

I have published another blog post with an update. You can read more here: SPFx Workbench Customizer update

SPFx solution using PnPjs for Project Online REST API

SPFx Project SharePoint

If you know me or follow me on Twitter/LinkedIn, you must have realized by now how much I like the PnPjs library. Enough to venture myself to speak about it on 3 SharePoint Saturday events last year. The library has packages for SharePoint and Graph endpoints and can be easily used on SPFx solutions. But if you need an SPFx solution that consumes Project Online API, what options do you have?
Kudos to Paweł Hawrylak who started creating the Project module for PnPjs and currently already offers support for a wide range of endpoints. The module is currently in a dev branch and requires additional work and testing, but it’s already a phenomenal effort.

This blog post will cover the required steps to generate a local PnPjs Project package to consume Project Online REST APIs and create a SPFx web part that uses it.

Published npm packaged

2019-12-31 update
If you only want to give the current version a try, there is now a packaged published to npm that you can simply install. Don’t forget to also install the required dependencies of @pnp/common, @pnp/odata, @pnp/logging and you are ready to rock!

npm i pnpjs-project-online-package @pnp/common@1.3.7 @pnp/logging@1.3.7 @pnp/odata@1.3.7 --save-exact

Get the code from GitHub

2019-11-14 update: I now have a repository forked from the main PnPjs project where I added the code for the project module. The good news is that this is using the latest PnPjs version (1.3.7 at the time)

https://github.com/joelfmrodrigues/pnpjs/tree/feature/project-online-api

From here, you can download, clone or fork it, what ever makes sense for what you plan to do with it. Just be aware that if you plan to fork it to contribute, and you have also previously forked the original PnPjs repository, you may need to delete the last and fork after that.
You can find the original code under Paweł’s GitHub fork of the main PnPjs project on GitHub.

Build PnPjs packages

Since we have the PnPjs source code, we need to build and generate local packages. You can get additional information of the PnPjs gulp commands on the official documentation.

Run the following commands in the order specified:

  1. npm install – to install the npm packages required by PnPjs
  2. gulp build – to ensure that the solution can build successfully
  3. gulp package – to generate all the different library packages, including the new Project package

Peer dependencies

We are going to install the local Project package that we have just build in our SPFx solution. Unfortunately, the peer dependencies don’t seem to work as expected when you do so and you get errors when trying to use the packages on your web part.
If you inspect the package.json file for the Project package created (dist/packages/project), you can find the following required peerDependencies which are not resolved by default

  "peerDependencies": {
        "@pnp/common": "1.3.7",
        "@pnp/logging": "1.3.7",
        "@pnp/odata": "1.3.7"
    },

Not the ideal solution for sure, but a simple way to get around this problem is to simply install the packages yourself.
Remember that we are only testing the new Project package and all the others are kept untouched, so seems sensible to me to install them directly from npm.
Ensure that your command prompt is on the
dist/packages/project directory and run the following command

npm install @pnp/common@1.3.7 @pnp/logging@1.3.7 @pnp/odata@1.3.7

The Project package is now ready to be consumed by our solution.

Create new SPFx solution

I’m not going to provide any specific instructions here as the official documentation is excellent and give you all the information you need. If you are new to SharePoint Framework development, please check it out and learn how to get started.

Add PnPjs packages to SPFx solution

Again, remember that Project is the only package that is not published to npm, so is the only one that we need to install from a local path.
Start by installing all the common PnPjs packages that you usually install. In this case I’m using the 1.2.5 version as it’s the version that matches the local version.

npm install @pnp/common@1.3.7 @pnp/logging@1.3.7 @pnp/odata@1.3.7 --save-exact

Next, install the local Project package by providing a relative path to the package folder. In my example:

npm install ../PnPjs/dist/packages/project

Establish Context

Following the official guidance, we also need to establish the context when using the library in SPFx. Simply add the following block of code into your web part main file as provided on the documentation:

import { project } from "pnpjs-project-online-package"; // or import from your local package for development

// ...

public onInit(): Promise<void> {

  return super.onInit().then(_ => {

    // other init code may be present

    project.setup({
      spfxContext: this.context,
      project: {
        baseUrl: 'https://XXXXXXXXXX.sharepoint.com/sites/pwa'
      }
    });
  });
}

// ...

Please note that we are setting the baseUrl property to be the PWA site. This is to allow the solution to work from any SharePoint sites, not only Project Online.

Use it!

It’s all done and you can now use the fluent library to interact with project online!
All you need to do is to import the Project package when you need it

import { project } from "@pnp/project";

Some usage examples:

// get all projects
const projects = await project.projects.get();

// get projects, filtering by name, returning only the Id and Name properties, and limiting the results count to 1
const projectInfo = await project.projects.filter(`Name eq '${projectName}'`).select('Id,Name').top(1).get();

// create timesheet
const createResult = await project.timeSheetPeriods.getById('XXXXXXXXXXXXXXXX').createTimeSheet();

Reusable ARM template for Web Application

azure resources

SharePoint Framework is the new King of enterprise solutions. Since version 1.4.1, it allows you to easily connect to APIs secured with Azure Active Directory.
As a SharePoint developer, you will likely have to create Azure Web Apps or Functions to be consumed by your SharePoint Framework application. So why not use an ARM template for common scenarios? 
The following ARM template will, hopefully, come handy and save you some time.

Despite the SharePoint mention, this template is completely generic and has no dependencies on SharePoint. You can use it for any work that uses a web application.

You can get the source code from my ARM Templates repository on GitHub

Resources

The following Azure resources are deployed:

  • Application Insights
  • Storage Account (and container)
  • App Service
  • Web Application
  • Key Vault

Parameters

The following parameters can be configured for the template via the parameters.json file:

  • userId – the Id of the admin user deploying the template
  • components_Insights_name – the name of the Application Insights resource
  • vaults_KeyVault_name – the name of the Key Vault resource
  • storageAccounts_name – the name of the Storage Account resource
  • storageAccounts_Container1_name – the name of the sample storage container 
  • serverfarms_AppService_name – the name of the App Service resource
  • serverfarms_AppService_skuName – the SKU name to use for the App Service
  • serverfarms_AppService_skuCapacity – the SKU capacity to use for the App Service
  • sites_WebApp_name – the name of the Web Application resource
  • sites_WebApp_kind – the kind of Web Application to create (app or api)
  • sites_WebApp_clientId – the client Id of the Azure AD App Registration to use for authentication
  • hostNameBindings_WebApp_name – the host bindings for the web application
  • secrets_KeyVaultSecret1_name – the name of the sample secret to create on Key Vault

Configurations

The following configurations are included on the ARM template:

Location

All resources use the same location as the Resource Group. Ensure that all resource types are available on a given location before deploying.

Storage

The storage account is deployed with a sample “logs” container, in case you want to use it for logging.

Authentication

Azure Active Directory authentication is configured by default. This is done using the Client Id of an Azure AD App Registration that you provide as a parameter to the template.

Logging

Logging uses file system as default and application logs are turned on. Please remember that logs are automatically turned off after 24 hours. There are retention policies for 90 days or 100 Mb.
Due to additional complexity around shared access signatures via ARM templates, I decided to use file system as default for logging, but you can manually switch to use the logs container provided.

Key Vault

We do not add any sensitive information to the parameters file. We input a sample secret during deployment that is added as a Secret to the Key Vault.

Access policies are set so that the Web App is given Get permissions (using Managed Service Identity) to read Key Vault Secrets and the user running the script is given full access to the Key Vault.

Web App extensions

he Application Insights extension is added to the Web App. 

If you are deploying a .Net application, make sure you enable it

Web App settings

The following settings are being set during deployment:

  • APPINSIGHTS_INSTRUMENTATIONKEY – the instrumentation key required to connect Application Insights with the Web App
  • StorageConnectionString – storage account connection string
  • AzureWebJobsDashboard – storage account connection string
  • AzureWebJobsStorage – storage account connection string
  • keyVaultSecret1 – a sample entry containing the url to the Key Vault secret created during deployment

Deployment

Before you deploy the script, ensure that you update the parameters.json file to your needs.
You can use the following PowerShell commands to retrieve the subscription Id and User Id required for the deployment:

  • Connect-AzureRmAccount
  • Get-AzureRmSubscription
  • Get-AzureRmADUser -Mail ‘{AzureAdminEmailAddress}’

Additionally, an Azure Active Directory App Registration is required in order to configure Authentication. Please access the Azure Portal and create one. Copy the Client Id from the app registration to the parameters.json file. Alternatively, you can follow this blog post to create it using Azure CLI.

You can deploy the template using any of the deployment files provided for your platform of choice by passing the required parameters to it.
The following example demonstrates how to deploy using PowerShell:

.\deploy.ps1 -subscriptionId "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" -resourceGroupName "Demo123AppDev" -resourceGroupLocation "West Europe" -deploymentName "Demo123AppDev" -templateFilePath "template.json" -parametersFilePath "parameters.json"

The resource group should have the following resources once the script completes

After the deployment script runs, you need to access your Web App and enable Authentication/Authorization for Azure Active Directory as this is not part of the script (yet).

Feedback

Please submit feedback if you think something important is missing. This is a basic version intended to be a starting point to evolve over time based on feedback and client projects.
I hope you find the template easy to use and adapt to your needs. You can easily also replace the Web Application with a Function if that is what you need or simply add a database to it.

Resources from my session at SPS Leicester 2018

SPS Leicester

Last weekend, I had the pleasure of speaking at SharePoint Saturday Leicester (SPS Leicester). Very well organized event and with a good number of attendees, especially considering that it was the first event.

You can find the slides from my presentation below.

All the code is available under my GitHub account here and there is a readme file with all the steps that I covered during my demo here.

Please get in touch or leave a comment below if you have any issues or questions.


PnP PeoplePicker reusable control disabled

The PnP PeoplePicker reusable control is one of the amazing reusable controls available from the open-source @pnp/spfx-controls-react project. You can easily include it in your SharePoint Framework projects. Unfortunately, and at the moment, there is a bug that prevents you from completely disabling the control.

Continue reading “PnP PeoplePicker reusable control disabled”

SPS Barcelona – Resources from my session

Had an amazing time at SharePoint Saturday Barcelona (SPS Barcelona) last weekend! The event was very well organized and full of really nice sessions to attend. Really hope that everyone who attended had a great time.

You can find the slides from my presentation below.

All the code is available under my GitHub account here. A readme file contains all the steps that I covered during my demo here.

Please get in touch or leave a comment below if you have/find any issues.

If you are one of the persons who attended my session, thank you!

SPS Barcelona is a great event! But you don’t need to take my word for it, go next year and see that for yourself!


PnP TaxonomyPicker reusable control as a required field

The PnP TaxonomyPicker reusable control doesn’t have a property to let you mark the input control as a required field on a form, but fortunately, this can be easily addressed.

If you have used the PnP TaxonomyPicker reusable control before, you may have noticed that it doesn’t have a property to make it required, nor does it have a property that lets you add a custom CSS class to it. The problem is that your other required input controls on the form will have a ‘*’ after the label, but not the TaxonomyPicker controls.

But there is a very simple way to solve this, because the control also has a Label control, so we can mimic the styles from other Office UI Fabric input controls.

Simply create a new CSS rule that includes a class and targets a label as a child item

.required label::after {
    content: " *";
    color: rgb(168, 0, 0);
    padding-right: 12px;
}

Now add a new element wrapping the TaxonomyPicker (a div, for example)

TaxonomyPickerCode

And now your TaxonomyPicker field will look exactly like the other required fields.  

RequiredField

  All you have to do now is implement validation.    

Setting the field as required may be an option in the near future, but until then, this is a very simple workaround

Are you also using the PnP PeoplePicker control? Then you may want to check this blog post.

PnP reusable controls, property pane controls and PnPJS library

If you are a SharePoint developer creating SPFx solutions, you must have heard about the PnP reusable controls and PnPJS library. They help you accelerate the development of custom solutions by providing tools that help you overcome common problems/requirements.

Last week I have delivered an internal session at work, and this time the focus was on the PnP reusable controls, property pane controls and PnPJS library.

You can find the presentation slides and a link to the demo project below. The slides contain a Resources section with all the relevant links.

Please be aware that most of the content on the slides was extracted from the documentation available, as the purpose was only to provide a quick introduction to the controls available. The demo is based on the PnP Controls sample web part, located on the PnP sp-dev-fx-webparts repository.

Presentation slides


Demo

The demo requires that you have a SharePoint site with a document library (you can use the default Documents library). Add a managed metadata column to the library and upload some documents (don’t forget to populate the managed metadata field when uploading the documents).

You can find the demo resources on my GitHub repository:

https://github.com/joelfmrodrigues/demos/tree/master/PnP%20Controls

The repository contains two folders: start and final.

The start folder is an empty SPFx web part project where I have already added the required npm modules as dependencies and completed the required configurations (as per the documentation) so that I could save some time during the demo. The guide on the link provided will guide you through the code changes required to achieve the final solution.

The final folder is a complete solution with a functional web part, in case you just want to see it working.

Conclusion

The PnP reusable controls, property pane controls and PnPJS library are the “Swiss Army Knife” of SharePoint Framework solutions, and if you are not using them, you are probably doing SharePoint development the hard way.

Hope you find this post useful.

The time has come to create my own blog

After seriously considering the idea multiple times over the last years, I have finally decided to create my own blog. The idea of not having the time to regularly create new content put me off before, but not this time. This time I had to do it, no matter what!

I have been a huge consumer of technical blogs for quite a few years now. Most of them are related to SharePoint and Office 365 for professional reasons, but others relate to software development or technology in general, and they often provide key information to make my life easier or completely drive the success of my work.

What makes most of those blogs great is that they do not belong to professional bloggers or authors. They belong to people who simply love their jobs and don’t mind doing the extra mile in order to help others, without expecting anything in return.

And that is exactly where I want to be. It’s about time I start “paying” back to the community by sharing my knowledge and my ‘learnings‘.

This blog will mainly focus on Office 365 and SharePoint development and I really hope I can help other people in the same way other blogs helped (and still help!) me.

Stay tuned!