In this article, you'll walk through the process of building a custom tile for the Constituent page using the SKY Add-in and SKY UX frameworks. This add-in is a pure client-side implementation using the SKY UX framework. However, you can use any language or tech-stack you'd like.
Open the SPA that you previously created in VS Code and go to the folder cd addin-hello-world
Run the command ng generate component my-tile
my-tile
src\app
ng generate
Import AddinClientService into your SPA's app.module.ts
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; import { MyTileComponent } from './my-tile/my-tile.component'; import { AddinClientService } from '@blackbaud/skyux-lib-addin-client'; @NgModule({ declarations: [ AppComponent, MyTileComponent ], imports: [ BrowserModule ], providers: [AddinClientService], bootstrap: [AppComponent] } ) export class AppModule{}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { MyTileComponent } from './my-tile/my-tile.component';
import { AddinClientService } from '@blackbaud/skyux-lib-addin-client';
@NgModule({
declarations: [
AppComponent,
MyTileComponent
],
imports: [
BrowserModule
],
providers: [AddinClientService],
bootstrap: [AppComponent]
} )
export class AppModule{}
To add an alert to the tile component, go to the my-tile.component.html
<div> <h1>SKY UX Add-in Hello World</h1> <div> <p>Welcome to the SKY UX Add-in template.</p> <sky-alert alertType="info"> You've just taken your first step into a larger world. </sky-alert> </div> <p> <a href="https://developer.blackbaud.com/skyapi/docs/addins" rel="noopener noreferrer" class="sky-btn sky-btn-primary" > Learn more about SKY Add-ins </a> </p> </div>
<div>
<h1>SKY UX Add-in Hello World</h1>
<div>
<p>Welcome to the SKY UX Add-in template.</p>
<sky-alert alertType="info">
You've just taken your first step into a larger world.
</sky-alert>
</div>
<p>
<a
href="https://developer.blackbaud.com/skyapi/docs/addins"
rel="noopener noreferrer"
class="sky-btn sky-btn-primary"
>
Learn more about SKY Add-ins
</a>
</p>
</div>
Import AddinClientService
my.tile.component.ts
import { Component, OnInit } from '@angular/core'; import { AddinClientService } from '@blackbaud/skyux-lib-addin-client'; import { AddinClientInitArgs } from '@blackbaud/sky-addin-client'; @Component({ selector: 'my-tile', templateUrl: './my-tile.component.html', styleUrls: ['./my-tile.component.scss'] } ) export class MyTileComponent implements OnInit{ constructor( private addinClientService: AddinClientService ){} public ngOnInit(){ this.addinClientService.args.subscribe((args: AddinClientInitArgs) =>{ args.ready({ showUI: true, title: 'My tile' } ); } ); } }
import { Component, OnInit } from '@angular/core';
import { AddinClientService } from '@blackbaud/skyux-lib-addin-client';
import { AddinClientInitArgs } from '@blackbaud/sky-addin-client';
@Component({
selector: 'my-tile',
templateUrl: './my-tile.component.html',
styleUrls: ['./my-tile.component.scss']
} )
export class MyTileComponent implements OnInit{
constructor(
private addinClientService: AddinClientService
){}
public ngOnInit(){
this.addinClientService.args.subscribe((args: AddinClientInitArgs) =>{
args.ready({
showUI: true,
title: 'My tile'
} );
} );
}
}
Import RouterModule
app.module.ts
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { SkyAlertModule } from '@skyux/indicators'; import { AppComponent } from './app.component'; import { MyTileComponent } from './my-tile/my-tile.component'; import { RouterModule } from '@angular/router'; import { AddinClientService } from '@blackbaud/skyux-lib-addin-client'; @NgModule({ declarations: [ AppComponent, MyTileComponent ], imports: [ BrowserModule, SkyAlertModule, RouterModule.forRoot([ { path: 'addin-hello-world', children: [ { path: 'my-tile', component: MyTileComponent} ]} ]), ], providers: [AddinClientService], bootstrap: [AppComponent] } ) export class AppModule{}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { SkyAlertModule } from '@skyux/indicators';
import { AppComponent } from './app.component';
import { MyTileComponent } from './my-tile/my-tile.component';
import { RouterModule } from '@angular/router';
import { AddinClientService } from '@blackbaud/skyux-lib-addin-client';
@NgModule({
declarations: [
AppComponent,
MyTileComponent
],
imports: [
BrowserModule,
SkyAlertModule,
RouterModule.forRoot([
{ path: 'addin-hello-world', children: [
{ path: 'my-tile', component: MyTileComponent}
]}
]),
],
providers: [AddinClientService],
bootstrap: [AppComponent]
} )
export class AppModule{}
Add the following code to your SPA's app.component.html
<router-outlet></router-outlet>
<router-outlet></router-outlet>
After the module dependencies are installed, go to the folder cd addin-hello-world
ng serve -o
After compiling, the Angular CLI automatically launches the SPA in a new browser window or tab. In this case, port 4200 is chosen and the SPA runs at https://localhost:4200/
Note: For deploying to production hosts, the ng build
dist
To learn more about the available options when using the serve
The browser navigates to the root of the SPA at /addin-hello-world
/addin-hello-world/my-tile
Now that you have a web application running locally, you can register it as a SKY application in the SKY API Developer Portal. From the Developer Account menu, select My Applications, and then select the SKY application where you want to register the add-in.
To create a new add-in definition, on the application details page, in the Add-ins tile, select the Add button.
On the Add add-in modal, provide the metadata about your add-in. Enter a friendly name to help distinguish this add-in from others you might create in the future. Select the extension point where you want the add-in to appear in the Blackbaud solution. Then, enter the URL for the add-in itself.
https://localhost:4200/addin-hello-world/my-tile
That's all! After you define the add-in and save it, it now appears in the extension point location for any Blackbaud environment that has connected your application. In this tutorial, because you entered a local location for the add-in URL, the add-in is only visible while you serve the SPA locally.
To view the tile add-in, visit the page or area within the system where the add-in renders (according to what you defined for the the extension point). In this case, the tile renders on the constituent record page (most likely at the bottom).
The tile looks and behaves like any native tile within the system. Users can drag the tile around, and the position (left or ride side) and state (expanded or collapsed) persist across user sessions.
Now, let's update the UI to display the various contextual values that are made available to add-ins.
First, let's start by displaying the environment ID. You'll hear more about environments in the future, but for now you can think of an environment as a similar concept as tenant. As described in the SKY Add-in Client library documentation, the environment ID is provided as part of the args
init
addinClientService
Next, you'll learn how to display context values that are specific to the extension point itself. For the "Constituent Tile Dashboard" extension point, the context value is the ID of the constituent. This context value is provided, along with the environment ID, as part of the args sent to the init
addinClientService
To show these values, start by defining variables named environmentId
context
init
JSON.stringify
public environmentId: string | undefined; public context: string | undefined; public ngOnInit(){ this.addinClientService.args.subscribe((args: AddinClientInitArgs) =>{ this.environmentId = args.envId; this.context = JSON.stringify(args.context, undefined, 2); args.ready({ showUI: true, title: 'My tile' } ); } ); }
public environmentId: string | undefined;
public context: string | undefined;
public ngOnInit(){
this.addinClientService.args.subscribe((args: AddinClientInitArgs) =>{
this.environmentId = args.envId;
this.context = JSON.stringify(args.context, undefined, 2);
args.ready({
showUI: true,
title: 'My tile'
} );
} );
}
To show these values in the UI, insert the following code to the component HTML file.
<label for="environmentId" class="sky-control-label"> The environment ID is a context value that is available to all add-ins: </label> <div id="environmentId">{{ environmentId }}</div> <p></p> <label for="context" class="sky-control-label"> Additional context values vary for each extension point. For the current extension point, the following context is provided: </label> <div id="context">{{ context }}</div>
<label for="environmentId" class="sky-control-label">
The environment ID is a context value that is available to all add-ins:
</label>
<div id="environmentId">{{ environmentId }}</div>
<p></p>
<label for="context" class="sky-control-label">
Additional context values vary for each extension point. For the current extension point, the following context is provided:
</label>
<div id="context">{{ context }}</div>
You can find complete documentation for each extension point on the extension points page.
You can also see that as you change the component's HTML and TypeScript files, the changes are immediately visible on the constituent page. This is because when you serve the SPA, Angular monitors the file system for changes and automatically rebuilds and refreshes the SPA!
A final piece of contextual data available is the user identity token. This value is used to convey the Blackbaud user ID in a secure fashion to the add-in's backend, where it can be validated and decoded. Having this value on the server provides a means of mapping the Blackbaud user to a user identity in a 3rd-party system. Add-ins can obtain a user identity token by requesting it from the host page via the getUserIdentityToken
For the purpose of this demo, you'll fetch and display the user identity token value. Later tutorials demonstrate how to pass this token to the add-in's backend for validation.
To render a button in the UI for getting a user identity token and an element to display the token, add the following code to the component.
<p></p> <div>To request a user identity token for the current user, select the button.</div> <button type="button" class="sky-btn sky-btn-default" (click)="getUserIdentityToken()">Get user identity token</button> <div *ngIf="userIdentityToken"> <p></p> <div>The following string represents the identity of the current user and can be provided to the add-in's backend for validation:</div> <span>{{ userIdentityToken }}</span> </div>
<p></p>
<div>To request a user identity token for the current user, select the button.</div>
<button type="button" class="sky-btn sky-btn-default" (click)="getUserIdentityToken()">Get user identity token</button>
<div *ngIf="userIdentityToken">
<p></p>
<div>The following string represents the identity of the current user and can be provided to the add-in's backend for validation:</div>
<span>{{ userIdentityToken }}</span>
</div>
In the TypeScript for the component, provide the click handler for the button and a public property to hold the identity token value.
public userIdentityToken: string | undefined; public getUserIdentityToken(){ this.userIdentityToken = undefined; this.addinClientService.getUserIdentityToken().subscribe((token: string) =>{ this.userIdentityToken = token; } ); }
public userIdentityToken: string | undefined;
public getUserIdentityToken(){
this.userIdentityToken = undefined;
this.addinClientService.getUserIdentityToken().subscribe((token: string) =>{
this.userIdentityToken = token;
} );
}
The component's HTML code now looks like the following.
<div> <h1>{{ 'tile_header' | skyAppResources }} </h1> <div> <p>Welcome to the SKY UX Add-in template.</p> <sky-alert alertType="success"> You've just taken your first step into a larger world. </sky-alert> </div> <p> <a href="https://developer.blackbaud.com/skyapi/docs/addins" target="_blank" rel="noopener noreferrer" class="sky-btn sky-btn-primary" > Learn more about SKY Add-ins </a> </p> <label for="environmentId" class="sky-control-label"> The environment ID is a context value that is available to all add-ins. </label> <div id="environmentId">{{ environmentId }}</div> <p></p> <label for="context" class="sky-control-label"> Additional context values vary for each extension point. For the current extension point, the following context is provided: </label> <div id="context">{{ context }}</div> <p></p> <div>To request a user identity token for the current user, select the button.</div> <button type="button" class="sky-btn sky-btn-default" (click)="getUserIdentityToken()">Get user identity token</button> <div *ngIf="userIdentityToken"> <p></p> <div>The following string represents the identity of the current user and can be provided to the add-in's backend for validation:</div> <span>{{ userIdentityToken }}</span> </div> </div>
<div>
<h1>{{ 'tile_header' | skyAppResources }} </h1>
<div>
<p>Welcome to the SKY UX Add-in template.</p>
<sky-alert alertType="success">
You've just taken your first step into a larger world.
</sky-alert>
</div>
<p>
<a
href="https://developer.blackbaud.com/skyapi/docs/addins"
target="_blank"
rel="noopener noreferrer"
class="sky-btn sky-btn-primary"
>
Learn more about SKY Add-ins
</a>
</p>
<label for="environmentId" class="sky-control-label">
The environment ID is a context value that is available to all add-ins.
</label>
<div id="environmentId">{{ environmentId }}</div>
<p></p>
<label for="context" class="sky-control-label">
Additional context values vary for each extension point. For the current extension point, the following context is provided:
</label>
<div id="context">{{ context }}</div>
<p></p>
<div>To request a user identity token for the current user, select the button.</div>
<button type="button" class="sky-btn sky-btn-default" (click)="getUserIdentityToken()">Get user identity token</button>
<div *ngIf="userIdentityToken">
<p></p>
<div>The following string represents the identity of the current user and can be provided to the add-in's backend for validation:</div>
<span>{{ userIdentityToken }}</span>
</div>
</div>
Below, you can review the component's full TypeScript code.
import { Component, OnInit } from '@angular/core'; import { AddinClientService } from '@blackbaud/skyux-lib-addin-client'; import { AddinClientInitArgs } from '@blackbaud/sky-addin-client'; @Component({ selector: 'my-tile', templateUrl: './my-tile.component.html', styleUrls: ['./my-tile.component.scss'] } ) export class MyTileComponent implements OnInit{ public environmentId: string | undefined; public context: string | undefined; public userIdentityToken: string | undefined; constructor( private addinClientService: AddinClientService ){} public ngOnInit(){ this.addinClientService.args.subscribe((args: AddinClientInitArgs) =>{ this.environmentId = args.envId; this.context = JSON.stringify(args.context, undefined, 2); args.ready({ showUI: true, title: 'My tile' } ); } ); } public getUserIdentityToken(){ this.userIdentityToken = undefined; this.addinClientService.getUserIdentityToken().subscribe((token: string) =>{ this.userIdentityToken = token; } ); } }
import { Component, OnInit } from '@angular/core';
import { AddinClientService } from '@blackbaud/skyux-lib-addin-client';
import { AddinClientInitArgs } from '@blackbaud/sky-addin-client';
@Component({
selector: 'my-tile',
templateUrl: './my-tile.component.html',
styleUrls: ['./my-tile.component.scss']
} )
export class MyTileComponent implements OnInit{
public environmentId: string | undefined;
public context: string | undefined;
public userIdentityToken: string | undefined;
constructor(
private addinClientService: AddinClientService
){}
public ngOnInit(){
this.addinClientService.args.subscribe((args: AddinClientInitArgs) =>{
this.environmentId = args.envId;
this.context = JSON.stringify(args.context, undefined, 2);
args.ready({
showUI: true,
title: 'My tile'
} );
} );
}
public getUserIdentityToken(){
this.userIdentityToken = undefined;
this.addinClientService.getUserIdentityToken().subscribe((token: string) =>{
this.userIdentityToken = token;
} );
}
}
After saving the files, the context values are shown.
When you select the button, the user identity token displays and should be treated as an opaque string on the client.