6/17/2019

React Templates Using SPFx Library Components. Part I. Basics.

Templating is a pretty powerful approach to provide extensibility to a project/component/library. It can be used to deliver different behaviors and/or look and feel to different customers, or provide extendable open-source libraries.
There are a lot of template libraries out there that can be used in your project. One of the most popular is Handlebars.js. The problem with these libraries is that in most cases they provide you an ability to define "static" content (basically, HTML and CSS). And if you want to include some actionable content (e.g. handle events from outside the template, etc.) - it might be either tricky or impossible.
In the next several blog posts I want to showcase how to create fully functional React templates (with props, events handling etc.) and dynamically load them using new SharePoint Framework project type - Library Component.
Technically, this approach can be exposed to non-SPFx projects as well, but it is not the purpose of this blog posts series.
If you don't want to read the posts - the code is available here. Feel free to use it in any way you want.

Second post - Implementation

Objective

So, as mentioned above, templating allows us to provide extensibility to existing projects and implement different behaviors and look and feel for different customers separately of the core component.
In these posts we'll be implementing a Tasks List web part that shows a list of tasks and selected task details.
We want the web part to have some default UI and also allow to provide custom templates for the components.
Default UI will look like that:

And alternative UI:

Initial Implementation Details

From components perspective we'll have
  1. Task - to display a single task item in the list
  2. Tasks List - to display a collection of tasks
  3. Task Details - to display details of the selected task
Default rendering of these component will be included in the web part's project, but we'll also provide an ability to dynamically connect to SPFx Library Component and get these components from there.
As we're going to have different implementations of the same components in different projects/libraries we also need to define "contracts" - interfaces and naming conventions that must be used in all implementations. It will allow to dynamically load elements and "know" what classes/functions/etc. we can use.
Here are the common interfaces and types we need:
  • TemplateType - type to define all possible templates: task-list, task, task-details
  • ITask - interface to define Task properties
  • ITaskListProps - TaskList React component's props
  • ITaskProps - Task React component's props
  • ITaskDetailsProps - TaskDetails React component's props
  • ITemplateFactory - Templates Factory interface, main entry point for templates libraries.
Below is the implementation of the contracts. In my case I just created a separate TypeScript file - CommonTypes.ts that can be copied to any project. There are other possible approaches here like type definition file (d.ts) or creating a separate module and linking it to each library.
import * as React from 'react';

/**
 * Available types of templates
 */
export type TemplateType = 'task-list' | 'task' | 'task-details';

/**
 * Task statuses
 */
export enum TaskStatus {
  NotStarted,
  InProgress,
  Resolved,
  Closed
}

/**
 * Task interface
 */
export interface ITask {
  id: string;
  title: string;
  description: string;
  status: TaskStatus;
  estimate: number;
  spentTime: number;
  dueDate: Date;
  assignedTo?: string;
}

/**
 * Props for TaskList component
 */
export interface ITaskListProps {
  /**
   * Tasks to display
   */
  tasks: ITask[];
  /**
   * React component (template) to use to display tasks
   */
  taskTemplate: React.ComponentClass<ITaskProps>;
  /**
   * Task selected handler
   */
  onTaskSelected: (task: ITask) => void;
  /**
   * Localized strings
   */
  strings: any;
}

/**
 * Props for Task component
 */
export interface ITaskProps extends ITask {
  /**
   * Selected handler
   */
  onSelected: () => void;
  /**
   * Localized strings
   */
  strings: any;
}

/**
 * Props for TaskDetails component
 */
export interface ITaskDetailsProps extends ITask {
  /**
   * Localized strings
   */
  strings: any;
}

/**
 * Main entry point of the template library - Template factory
 */
export interface ITemplateFactory {
  /**
   * Get the template component (React component) based on TemplateType
   */
  getTemplateComponent: (templateType: TemplateType) => React.ComponentClass<any> | null;
}

React Dynamic Components

React by its nature allows to use dynamic components names right in JSX and TSX files - any tag name starting with Capital letter will compile in createComponent method call. So, you can have something like:
public render(): React.ReactElement<IProps> {
  const MyComponent = components[componentName];
  return <MyComponent {...someProps} />;
}
And it will be compiled into:
public render(): React.ReactElement<IProps> {
  const MyComponent = components[componentName];
  return React.createElement(MyComponent, someProps);
}
Keep in mind the value used as a tag name must be a component or class reference, not just a string. That's why we defined return type of Factory method as React.ComponentClass<any> | null.
In our case we can use React dynamic components (or dynamic tag names) and ITemplateFactory interface to render needed component with dynamic template:
public render(): React.ReactElement<IProps> {
  const TaskList = this.props.templateFactory.getTemplateComponent('task-list') as React.ComponentClass<ITaskListProps>;
  return <TaskList {...someProps} />;
}

SharePoint Framework Library Components

The last but not least part of the puzzle is SPFx Library Components.
From the official documentation: Library component type enables you to have independently versioned and deployed code, which is served automatically for the SharePoint Framework components with a deployment through app catalog. Library component provides you alternative option to create shared code, which can be then used and referenced cross all the components in the tenant. You can think of Library Components as DLLs - separate packages that can be included (statically or dynamically) to other projects. And that is a great fit for our needs.

Note: current latest version of SharePoint Framework is 1.8.2. Library Components feature is in preview and is subject to change. It is not currently supported for use in production environments.

So, we can (and will) use Library Component to implement alternative rendering for our tasks list and task details, and dynamically load that library by the web part.

Next Steps

The next post walks you through the whole implementation, including Templates library loader, default Template Factory, Library Component with alternative templates, and dynamic rendering of tasks list and task details.

That's all for today!
Have fun!

No comments:

Post a Comment