Apr 08, 2019

Add in-place scripted tutorials in your SPFx components

Hi SharePoint devs !

In this post, I' going to share the basics of a proof of concept I implemented in a SPFx solution recently. It actually targets SPFx solutions but, really, there is nothing specific to SPFx and it can be used in any client-side solution. It's a common trend now to see in-place tutorials in applications, it allows the user to get up to speed quite quickly with the features of an application.

how it looks like

howitlookslike

Next to the elements of our application we want to highlight, a popup appears with a description and what the user needs to know to get started with a particular feature.

Static user guide vs in-place tutorial

From a user perspective

Static user guide often fail in their intent... Why ? First of all, because they typically  reside in an external document so the users (lazy as they are, but we're devs, we can get that :) ) won't probably read it until it's really needed... But when it becomes really needed, it is probably already a bit too late because the user already had that bad feeling about the application... that's not a good start...

Moreover, the user guide sometimes happen to not being accurate enough or relevant to the specific context of a user (specific configuration, enabled or disabled features,... ). Then, once again, the user tends to lose trust in the user-guide and in the application. An in-place tutorial instead, even if that might be skipped, kind of enforces the user to check the features before he/she starts using them. and a well defined scripted tutorial may ensure the user achieves the actions the way they are intended to be, that will probably avoid some confusion.

From a maintainer perspective

A user-guide documentation is, in a way, decoupled from the actual implementation, which means that entire new features or breaking changes could be simply forgotten and might never appear in the document. On the other hand, an in-place tutorial is actually part of the software, it has to be maintained as well but it feels more natural for the development team to update the tutorial at the same time as updating or adding a feature

A tutorial flow

The idea is that whenever a user checks out an application for the first time, he/she gets the relevant hints to get started without the need of analyzing and browsing randomly the available buttons and links.

The proof of concept implementation

Thanks to Office UI Fabric TeachingBubble component, we can easily display such highlight to any element of the DOM. The proof of concept I implemented is a simple component that plays a script. display subsequently a teaching bubble next to each highlighted element.

Mark the items to be presented in the tutorial

In the implementation of your client-side application, you need to mark the HTML elements with a data-tutorial-anchor="key" attribute, the key identifies the tutorial item in the script. For instance, here is a piece of the command bar actions definition in my sample application tutorial_markups

Tutorial script

A tutorial script is a JavaScript array (it could also be pure JSON) that contains the definition of each tutorial step. The key property must match the value of the data-tutorial-anchor attribute mentioned above

import { ITutorial } from "../../ui-fabric-tutorial/index"; 
 export default {    
    id: 'tuto_sample',     
    items: [         
      {             
        key: 'newItem',             
        caption: 'New Item',             
        content: 'This action allows to add a new item',
        delay: 3000,             
        nextTrigger: 'delay'         
      },
      {
        key: 'upload',
        caption: 'Upload',
        content: 'This action allows to upload a new document',
        delay: 3000,
        nextTrigger: 'delay'
      },
      {
        key: 'share',
        caption: 'Share',
        content: 'Share the document with a contact',
        delay: 3000,
        nextTrigger: 'delay'
      },
      {
        key: 'download',
        caption: 'Download',
        content: 'This action allows to download a document',
        delay: 3000,
        nextTrigger: 'delay' 
      },
      {
          key: 'dateModifiedColumn',
          caption: 'Modified date',
          content: 'This columns displays the last modification date',
          delay: 3000,
          nextTrigger: 'delay'
      },
      {
        key: 'modifiedByColumn',            
        caption: 'Modified by',           
        content: 'This columns displays the last person who modified the document',    
        delay: 3000,          
        nextTrigger: 'delay'       
      }   
    ] 
  } as ITutorial;

In this case, each item will display automatically after the specified delay of the previous item. the nextTrigger property can also be set to "action" which will make the component display a button the user has to click to see the next item.

See the tutorial only at first usage

In order for the user to see the tutorial only at the first time he visits the page, a key will be added to the localStorage, as soon as that key is present, the tutorial will not show up again.

The tutorial React component

In my context, I intend to use the tutorial in a SPFx solution using React, so I created a React component to add in my main component. Here is the implementation of this component.

import * as React from "react"; 
import { TeachingBubble, autobind, Button, Link, IButtonProps } from "office-ui-fabric-react";  
//... import { ITutorial } from "../model/ITutorial"; 
import playTutorial, { shouldPlayTutorial } from "../core/TutorialCore"; 
//... export interface ITutorialState {     shouldPlayTutorial: boolean; }  
export interface ITutorialProps {     
  showReplayTutorial?: boolean;     
  replayTutorialLabel?: string;     
  replayTutorialLabelProps?: any;    
   tutorial: ITutorial; 
}

export class Tutorial extends React.Component<ITutorialProps, ITutorialState> {
  constructor(props: ITutorialProps) {
    super(props);
    }

    public componentDidMount() {
      if (shouldPlayTutorial(this.props.tutorial)) {
        this.setState({ shouldPlayTutorial: true });
      }
    }

    public componentDidUpdate() {
      if (this.state.shouldPlayTutorial) {
        playTutorial(this.props.tutorial);
        return;
      }
    }
    
    public render(): React.ReactElement<ITutorialProps> {
      const content: any[] = [];
      if (this.props.showReplayTutorial) {
        const replayLabel = this.props.replayTutorialLabel || "Play tutorial again";
        content.push(<Link onClick={() => this._requestPlay()} {...this.props.replayTutorialLabelProps}>{replayLabel}</Link>);
        }
      
      return <div>{content}</div>;
    }
          
    private _requestPlay() {
      this.setState({
        shouldPlayTutorial: true
        });
    }
}

Anyway it actually does not require to be added in a React component, the core functions can be called in any non-React implementation. Notice the call to the playTutorial function in the React component above.

Add the tutorial

And here is the calling component render() method implementation

//...
public render(): React.ReactElement<ITutoTestProps> {
  let { items, columns, isCompactMode, isModalSelection } = this.state;
  return (
    <div className={styles.tutoTest}>
    <CommandBar items={this.getItems()} />
    <MarqueeSelection selection={this._selection}>
      <DetailsList items={items} compact={isCompactMode} columns={columns}
      selectionMode={isModalSelection ? SelectionMode.multiple : SelectionMode.none} 
      setKey="set"
      layoutMode={DetailsListLayoutMode.justified}
      isHeaderVisible={true}
      selection={this._selection}
      selectionPreservedOnEmptyClick={true}
      onItemInvoked={this._onItemInvoked}
      enterModalSelectionOnTouch={true}
      ariaLabelForSelectionColumn="Toggle selection"
      ariaLabelForSelectAllCheckbox="Toggle selection for all items"
      />
    </MarqueeSelection>
    <Tutorial tutorial={this.getTutorial()} 
      showReplayTutorial={true}
      replayTutorialLabel="I need some help !"
      replayTutorialLabelProps={{ className: styles.replayTutorialLink }} 
    />
    </div>
  );
}     

private getTutorial: () => ITutorial = () => {     return tutorialScript;   }   
//...

The tutorialScript variable is imported from the tutorial script in its own file

The result

Here is a small animation of what it looks like when using the application for the first time

spfx-tuto

The Tutorial React component has also the feature to display a link that replays the tutorial You can check out the whole implementation here. It is still a proof of concept and probably needs some polishing and improvements but I am convinced it is really a handy way to implement an in-place tutorial. Let me know what you think about it ! :)

Best regards,

Yannick

Other posts