It's true. Clippy is back! I've come to find that there are three types of people when it comes to opinions of our favorite Microsoft Office productivity friend. The first type of person is "oh no, please don't!". The second type of person is "Wow, this is great! Very retro!". Followed up by the people who are too young to even know who Clippy is.
Well it's 2017 and it's about time he came back to our Microsoft products. A few months ago, a friend tempted me to incorporate Clippy into a SharePoint Framework application customizer. It didn't take long before I found a Clippyjs Library online. This was quite fantastic and it even lets you configure multiple agents, I fell in love.
Now before I started adding clippyjs into my SharePoint Framework, I took a leap of faith and I checked out NPM. I thought maybe if someone was willing to turn Clippy into a JS framework... someone HAD to of created a package in NPM. They did and you can find it here!
The Goal
The goal of this project was more than just reliving my past. Office 365 has introduced a ton of new features for developers and end user's alike. The introduction of the Bot Framework is really what drove me to develop this SPFx customizer. In this demo, I will not be using the Bot Framework but it is ultimately my end goal, to hook Clippy up as a fully functioning AI personal assistant.
In this demo I am just going to show you how I set up Clippy in a SharePoint Framework customizer using the Microsoft Graph client to pull in Office 365 Group related information. To make it a bit easier to digest, I have removed all of the functionality with the exception of one service call.
Without going into too much detail, I am going to assume you have set up your SPFx Application customizer.
import clippy from NPM
npm install clippyjs
Within my Application Customizer ts file, I am going to import clippy at the top
You'll also notice that I am importing ClippyService. This is a custom service that I will be using to call the Microsoft Graph API. If we take a look inside the file
import { BaseExtension } from '@microsoft/sp-extension-base';
import { GraphHttpClient, HttpClientResponse} from '@microsoft/sp-http';
export default class ClippyService {
public static getOwner(context: any, groupId:any):Promise {
return context.graphHttpClient.get(`v1.0/groups/${groupId}/owners`, GraphHttpClient.configurations.v1).then((response: HttpClientResponse) => {
if (response.ok) {
return response.json();
} else {
console.warn(response.statusText);
}
}).then((result: any) => {
var returnValue = {'value':'','email':''}
if(result.value.length > 0){
returnValue.value = result.value[0].displayName;
returnValue.email = result.value[0].mail;
}
return returnValue;
});
}
}
you'll see I am importing from the GraphHttpClient. The GraphHttpClient will allow me to call the Microsoft Graph and it will handle all of the authentication for me!
Inside, I create a simple function called "getOwner" and I will be passing in the context of my application customizer and the ID of the group of which Clippy will be running! The Graph call is going to retrieve a list of owners from this Office 365 group. Once returned, I'll create a single returnValue object and return it back to my application customizer.
Let's take a look at the onInit() function of my Application Customizer.
public onInit(): Promise {
SPComponentLoader.loadCss('https://gitcdn.xyz/repo/pi0/clippyjs/master/assets/clippy.css');
this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceHolders);
return Promise.resolve();
}
private _renderPlaceHolders():void{
clippy.load('Clippy', (agent) => {
this.properties.agent = agent;
this.properties.agent._balloon.CLOSE_BALLOON_DELAY = 5000,
this.properties.agent.show();
this.properties.agent.speak("Hey, I'm clippy. I'm your personal assistant.", false);
this.properties.agent.animate();
//get group owner
this._getOwner();
});
}
private _getOwner():void{
// retrieve group owner
let groupId = this.context.pageContext.legacyPageContext.groupId;
if(groupId) {
// Get group data from graph via new GraphHttpCLient
ClippyService.getOwner(this.context, groupId).then((result:any) => {
if(result != null){
this.properties.agent.speak("The owner of this group is:" + result.value,false);
this.properties.agent.speak("You can email the owner at:" + result.email,false);
this.properties.agent.animate();
}
});
}
}
Most of the onInit and _renderPlacholder() function is setting up my Clippy Agent. I load some css from the CDN for clippy and then enter my renderPlaceHolder function where all the work is being done.
To make Clippy feel a bit more "interactive" I have made sure clippy would do a couple animations as he loaded. The ClippyJs library has quite a bit of functionality but calling an empty animate function will make clippy do a random animation that we've all come to love from him!
Once Clippy has introduced himself, we call this._getOwner(), which will call the ClippyService we created before. I am going to grab the Group ID from the pageContext.
We'll take this value and passing it along with the page context object. Once the service call runs, we'll tell Clippy to "speak" and let the current user know who the current owner of the group is and how they can contact that person.
I hope you found this post interesting. The current iteration includes quite a few new functionalities such as grabbing group conversations and recent files. I'm currently in the process on trying to hook some Bot Framework functionality up to it. I will be posting all this code on GitHub.
Share