SharePoint SPFx extension – Advanced copy and move

A client recently asked me to create an advanced version of the default “Copy to” and “Move to” SharePoint capabilities available on every document library. This blog post will cover the main decisions, challenges, and tools that I used to achieve this.

After our client went live with a new SharePoint site to be used as the main “landing page” for the company, they started receiving some feedback from end users. I created some custom SPFx web parts and extensions for the site, so was expecting some feedback on my work. Instead, the most common feature that users were providing feedback on was the out-of-the-box “Copy to” and “Move to”.

UPDATE: Unfortunately the images for this post were lost when I had to migrate the contents of the blog. Hope things still make sense as you read through.

I explained to the client that it would not be possible to customise or disable out-of-the-box features, so they decided to create alternative versions: “Advanced copy” and “Advanced move”

Limitations of default features

The following points were the most significant limitations behind the decision to create the new feature:

  • Slow performance when displaying a large number of folders
  • No option to filter/search a large list of folders
  • No target location set by default
  • Limited space to display folder names

Requirements

Based on the user feedback and limitations identified during testing, we defined the following key requirements:

  • Look and feel should not be very different from out-of-the-box features
  • It should provide a better performance, mainly when loading and displaying a large number of folders
  • Provide an option to filter a list of folders by name
  • It should use the current folder as the default location. Users were often copying/moving documents to folders related to the current location
  • Before completing the operation, the user should be able to specify new names for the documents or folders

Implementation

While I’m working on getting permission from my client to publish the solution to the open-source PnP community repositories, so that others facing similar challenges can benefit from it, I decided to publish this information that may help others on a similar journey.

I implemented the solution as a custom SPFx list view command set with two options that share the same code base. Depending on the option selected, the relevant action (copy/move) label is displayed on the screen. The appropriate service function is then called, but other than that, all the other code is shared.

SharePoint API calls

I have used PnPjs for all the SharePoint REST API calls.
Copy document, move document, copy folder, and move folder options use new capabilities that I added to PnPjs for this purpose. You can read more about it in a separate blog post that I published recently.

Performance was never an issue. And often, large collections of folders are loaded multiple times faster than the out-of-the-box counterparts.

// get list of libraries within a site:
await sp.site.getDocumentLibraries(webAbsoluteUrl)
// get list of folders within a folder/library
await web.getFolderByServerRelativeUrl(folderRelativeUrl).folders.select('Name', 'ServerRelativeUrl').orderBy('Name').get()
// add new folder
await web.getFolderByServerRelativeUrl(folderRelativeUrl).folders.add(name)
// copy file
await sp.web.getFileByServerRelativePath(srcPath).copyByPath(`${destPath}/${name}`, shouldOverWrite, KeepBoth);
// move file
await sp.web.getFileByServerRelativePath(srcPath).moveByPath(`${destPath}/${name}`, shouldOverWrite, KeepBoth);
// copy folder
await sp.web.getFolderByServerRelativePath(srcPath).copyByPath(`${destPath}/${name}`, keepBoth);
// move folder
await sp.web.getFolderByServerRelativePath(srcPath).moveByPath(`${destPath}/${name}`, keepBoth);

Main User Interface components

Breadcrumb

A breadcrumb component is available at the top of the panel to allow the user to navigate to a previous folder within the hierarchy level. It displays a node for each section of the path to the folder currently selected and an option to select a different “place” – sites or OneDrive (in the future).

The breadcrumb is based on the Breadcrumb control from Office UI Fabric.

<Breadcrumb items={breadCrumbItems} className={styles.breadcrumbPath} maxDisplayedItems={3} overflowIndex={overflowIndex} />

And the function to generate the array of breadcrumb items. Each item has an onClick callback function to get the list of sub-folders for that path.

  /**
   * Get breadcrumb items
   * @returns an array of IBreadcrumbItem objects
   */
  private _getCurrentBreadcrumbItems = (): IBreadcrumbItem[] => {
    let items: IBreadcrumbItem[] = [];
    let rootItem: IBreadcrumbItem = { text: 'Places', key: 'Places', onClick: this._showPlacePicker, };
    items.push(rootItem);
    let siteItem: IBreadcrumbItem = { text: this.state.selectedSite.Title, key: 'Site', onClick: this._getSiteLibraries.bind(this, this.state.selectedSite) };
    items.push(siteItem);
    if (this.state.selectedLibrary != null) {
      let libraryItem: IBreadcrumbItem = { text: this.state.selectedLibrary.Title, key: 'Library', onClick: this._getRootFolders.bind(this, this.state.selectedLibrary) };
      items.push(libraryItem);
    }
    if (this.state.selectedFolder) {
      const folderPathSplit = this.state.selectedFolder.ServerRelativeUrl.replace(this.state.selectedLibrary.ServerRelativeUrl + '/', '').split('/');
      let folderPath = this.state.selectedLibrary.ServerRelativeUrl;
      folderPathSplit.forEach((folderName, index) => {
        folderPath += '/' + folderName;
        let folderItem: IBreadcrumbItem = { text: folderName, key: `Folder-${index.toString()}`, onClick: this._getSubFolders.bind(this, { Name: folderName, ServerRelativeUrl: folderPath }) };
        items.push(folderItem);
      });
    }
    items[items.length - 1].isCurrentItem = true;
    return items;
  }
Folders list

This is the core component. It lists the sub-folders currently available under the current path (either a library or a folder) and allows the user to click on one of the sub-folders to move one level deep into the hierarchy.

A simple filter is available at the top of the list to filter the data array based on user input.

At the bottom, a custom control lets the user create a new folder if he wishes to do so (similar to the OOB feature)

Progress panel

Once the user selects a target folder using the “Copy here” button, the interface replaces the list of folders with a progress panel.

An array stores each file/folder selected by the user. Each object of the array has a “status” property that controls the displayed status for each item on the screen.
For example, if the data service reports that the file already exists on the target folder, the corresponding array item is updated accordingly. A friendly message is then displayed to the user, next to that item, with buttons to replace or keep both files.

Limitations

It’s not possible (at least at the moment?) to disable the out-of-the-box “Copy to” and “Move to” capabilities. This leads to “duplicated functionality” for end users that need to be trained on what feature to use. It would be great if it was possible for administrators of a site to disable specific default features.

12 Replies to “SharePoint SPFx extension – Advanced copy and move”

  1. Hello,

    This looks like a very handy extension. Can you share the repo for this extension?
    Thanks

    1. Hello, this was a client project and unfortunately I can not share the code. Perhaps later if my client agrees to it once they understands open source benefits.

    1. Hi, apologies for the late reply. I will try to ask my client for permission to publish the solution as a sample to the PnP repositories and share the link here if they allow it

    1. Hi, apologies for the late reply. I will try to ask my client for permission to publish the solution as a sample to the PnP repositories and share the link here if they allow it. Can not guarantee that it will happen but will do my best

    1. Sorry for the late reply, had an issue with comments…
      Yes, it should be possible to move to different site collections, but be aware as I think there is a file size limit (50 Mb if I’m not wrong)

  2. I use the copyByPath method to copy a file from one site collection to another. This returns a result object, but I cannot see how I can get the resulting file and do something with it – for example open it so the user can edit it.
    How do I obtain an id or something else that identifies the newly created file?

  3. Hello I copied the document set with same metadata and changed the file name and kept both the files basically. But my problem is i do not want to keep those documents present inside the document set. so is there any way to remove those documents present inside the document sets

    1. Hi, not sure I understood your question. Did you want to make a copy of a document set but exclude the contents from the source document set used for the copy?

Leave a Reply

Your email address will not be published. Required fields are marked *