Valo Connect is a product I was working on in Valo. It is a Microsoft Teams application that, among many features, delivers a user-friendly dashboard that can be customised with OOTB widgets and extended with custom ones. In this article, I’m going to describe the process of developing new widgets. The working example is available in my GitHub repository.
Valo Connect and extensibility model
Valo Connect allows users to create their own dashboard, called Connect Me, built on widgets that integrate Office 365 features and more.
The solution allows the creation of custom widgets with an extensibility model – a set of methods and interfaces to communicate with Connect Me. This way anyone can integrate their own services and applications into Teams.
Starting the project
Create a new project using Yeoman Generator, select the “extension” project type, and then, an application customizer. The next step is to install dependencies.
Mandatory:
@valo/extensibility – Access to Connect Me API
Recommended:
@fluentui/react-northstar – Connect Me is built with northstar controls. They can be used for consistency
@fluentui/react-northstar-emotion-renderer – This package is needed to ensure correct styling of the northstar components.
npm i @fluentui/react-northstar @fluentui/react-northstar-emotion-renderer @valo/extensibility
Registering widgets
In the <extension_name>ApplicationCustomizer.ts file, I started with creating an object of a widget registering class:
export default class CustomWidgetsApplicationCustomizer extends BaseApplicationCustomizer<{}> {
@override
public onInit(): Promise<void> {
const loader = new WidgetLoader(this.context);
IconUtils.registerIcons();
loader.registerWidgets();
return Promise.resolve();
}
}
I will get back to the icons later.
export class WidgetLoader {
private connectWidgetService: ConnectWidgetService;
constructor(private context: ApplicationCustomizerContext) {
this.connectWidgetService = ConnectWidgetService.getInstance();
}
public async registerWidgets() {
for(let w of this.widgets) {
this.connectWidgetService.registerWidget(w);
}
}
private widgets: ConnectWidget<any>[] = [
new WordPressWidget(),
new CryptoWidget()
];
}
WidgetLoader class uses ConnectWidgetService from @valo/extensibility npm package to register custom widgets in the Connect Me dashboard. Widget definitions are implemented in separate files/classes. Context is not used, yet, but it may be useful for some widgets.
export class CryptoWidget implements ConnectWidget<CryptoProps> {
public title: string = "Cryptocurrency";
public id: string = "kr-crypto-widget";
public size: ConnectWidgetSize = ConnectWidgetSize.Single;
public description?: string = "Shows the cryptocurrency rates";
public iconProps?: IIconProps = { iconName: "kr-crypto" };
public category?: string = "Elnathsoft";
public widgetComponentsFactory = (config: CryptoProps) => {
return [
{
id: "kr-crypto-widget-1",
title: "Rates",
content: <CryptoRates {...config} />,
},
];
}
public widgetConfigComponentFactory = (currentConfig: CryptoProps, onConfigUpdated: (config: CryptoProps) => void) => {
return <CryptoConfig onConfigurationUpdated={onConfigUpdated} widgetConfiguration={currentConfig} />;
}
}
In the widget registration class, one has to specify an id and size, widgetComponentsFactory, and widgetConfigComponentFactory. A few optional properties are available and explained in Valo Connect documentation. In this article, I’m describing the most important ones.
widgetComponentsFactory returns an array of tabs for a widget. Each tab has an id, a title, and content which is a react component. If only one tab is implemented, then the widget displays the content without tabs.
widgetConfigComponentFactory returns just one component for the widget configuration panel. The onConfigUpdate method is used to save the configuration.iconProps allows setting a widget icon. It can be a Fluent UI icon, image, or a custom icon.
Registering custom icons
Custom icons can be created in a vector graphics tool, saved in SVG format, and converted to JSX objects. They can be registered in Fluent UI with a custom and unique name, and then, accessed by this name, as I did in the code snippet above.
import * as React from "react";
import { registerIcons } from "office-ui-fabric-react/lib/Styling";
export class IconUtils {
public static registerIcons(): void {
registerIcons({
icons: {
"kr-crypto": (
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122 122" >
<path
d="M61 0a60.69 60.69 0 1 0 61 60.69A60.84 60.84 0 0 0 61 0ZM49.41 84.36a30.47 30.47 0 0 0 8.2 2.36 41.76 41.76 0 0 0 6.22.44h1.83a35.16 35.16 0 0 0 8.2-1.21A21 21 0 0 0 87.28 75h12c-3.26 9-11.45 16.18-25.47 18.53v8h-8.2v-7.19h-2.33c-2 0-3.87-.09-5.72-.24v7.43h-8.2v-8.67c-17.76-4.22-26.72-16.8-26.72-31.77 0-14.33 8.88-27.54 26.77-32.11V19.8h8.2v7.77c2-.18 4-.28 6.17-.28h1.88V19.8h8.2V28c15 2.32 23.29 9.91 25.5 19h-12c-2.29-5.07-6.26-9.24-13.5-11.23a36.51 36.51 0 0 0-8.2-1.21c-.76 0-1.55-.05-2.36-.05a39.43 39.43 0 0 0-5.69.39 29.72 29.72 0 0 0-8.2 2.32c-9.94 4.52-14.21 14-14.21 23.61 0 9.28 4.06 18.92 14.21 23.53Z"
style={{
fill: "#34C7D1",
}}
/>
</svg>
),
},
});
}
}
Ensuring correct styling for different themes
To ensure correct styling, rendered components have to be wrapped with RendererContext provider, using emotionRenderer and with FluentUIThemeProvider to ensure theming. I’ve created a wrapping component to achieve this. It can be implemented as a HOC component as well.
import * as React from "react";
import { Provider as FluentUIThemeProvider } from "@fluentui/react-northstar/dist/es/components/Provider/Provider";
import { RendererContext } from "@fluentui/react-bindings/dist/es/renderer/RendererContext";
import { createEmotionRenderer } from "@fluentui/react-northstar-emotion-renderer";
import { ThemeManager } from "../../managers/ThemeManager";
export interface ThemeProviderWrapperProps {
className?: string;
}
export const ThemeProviderWrapper = (props: React.PropsWithChildren<ThemeProviderWrapperProps>) => {
return (
<div className={props.className}>
<RendererContext.Provider value={createEmotionRenderer()}>
<FluentUIThemeProvider theme={ThemeManager.getTheme()}>{props.children}</FluentUIThemeProvider>
</RendererContext.Provider>
</div>
);
};
ThemeManager class determines whether Connect Me uses a dark, light, or high contrast theme.
public static getTheme(): ThemePrepared {
if (document.querySelector('body').classList.contains('valo-theme-dark')) {
return teamsDarkTheme;
}
if (document.querySelector('body').classList.contains('valo-theme-contrast')) {
return teamsHighContrastTheme;
}
return teamsTheme;
}
Deployment
The solution should be bundled, like every other SharePoint Framework project, and deployed to the tenant’s app catalogue.
Build a package:
gulp bundle --ship; gulp package-solution --ship;
Deploy it to the app catalogue and make it available to all sites.
The widgets are available in Connect Me:
Debugging
The easiest way to debug widgets is to serve a project with gulp serve, open Teams in a browser, and execute the following command in the browser’s dev tools console.
document.querySelector("iframe[name='embedded-page-container']").src += '%26debug%3Dtrue%26noredir%3Dtrue%26debugManifestsFile%3Dhttps%3A%2F%2Flocalhost%3A4321%2Ftemp%2Fmanifests.js'
If you forget this command you can always follow these steps as an alternative:
- Serve solution and copy debug URI from a console
?debug=true&noredir=true&debugManifestsFile=https://localhost:4321/temp/manifests.js
- Replace the question mark with an ampersand and pass it as an argument of the encodeURIComponent command. Copy the result to the clipboard.
encodeURIComponent("&debug=true&noredir=true&debugManifestsFile=https://localhost:4321/temp/manifests.js")
- Inspect website DOM and find the Connect Me iframe.
- Edit the iframe src attribute and paste the URI component from your clipboard.
My Widgets
I’ve implemented two simple widgets that one may find useful.
Cryptocurrencies
Displays the current rate of a few cryptocurrencies: Bitcoin, Ethereum, Polkadot, Solana, FTXToken, Terra. Every currency can be hidden.
WordPress
Downloads latest posts from blogs and websites based on WordPress. Works with WordPress-hosted blogs and blogs operating on private servers, if CORS is configured accordingly.
I hope this article will help you create your own Valo Connect widgets. Feel free to use examples shared on GitHub. If you like my widgets, you can use them on your tenant, too.
I’m SharePoint enthusiast working currently at Avenga. I like to test new technologies, unconventional solutions and share my ideas. I’m dealing with the online version of SharePoint, Azure, and some other Office 365 applications but I have experience with on-premises as well.