SKY UX tile

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.

You can find the source code for this example on GitHub.
  • Before you start this tutorial, we recommend that you first read the basics on creating a SKY Add-in in the Create an add-in tutorial.
  • Make sure you have gone through the steps in SKY UX Add-in getting started to create the SPA.

Open the SPA that you previously created in VS Code and go to the folder cd addin-hello-world. Next, you'll add a new tile component to the SPA.

Run the command ng generate component my-tile. This adds a my-tile component under the src\app folder. To learn more about the ng generate command, view the Angular generate documentation.

Generate a component

Import AddinClientService into your SPA's app.module.ts file and add it to the providers array.

src/app/app.module.ts TypeScript
    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 file and add the following code.

    <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 into your SPA's my.tile.component.ts file and then inject it into the constructor.

TypeScript
    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 into your SPA's app.module.ts file and add the route for the my-tile component.

TypeScript
  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 file.

TypeScript
    <router-outlet></router-outlet>
<router-outlet></router-outlet>

After the module dependencies are installed, go to the folder cd addin-hello-world and use the ng serve -o command to compile and serve the SPA locally.

To serve locally, enter 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/.

Angular CLI automatically launches your SPA in a new browser window/tab.

Note: For deploying to production hosts, the ng build command can be used to create a dist folder containing the static artifacts to deploy to your cloud.

To learn more about the available options when using the serve command, view the Angular serve documentation.

The browser navigates to the root of the SPA at /addin-hello-world. The actual tile is located one level deeper within the SPA at /addin-hello-world/my-tile. You can access that page directly, but it won't be fully functional since it's operating outside of the context of any host Blackbaud application:

Hello world 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.

Create a new add-in definition screen.

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.

  • Provide this as the fully qualified URL of the add-in: https://localhost:4200/addin-hello-world/my-tile.
  • Since you're building a custom tile for the constituent record page, you'll choose the "Constituent Tile Dashboard" extension point under Development Office, Constituents.

URL requirements

  • The URL must be an absolute and fully qualified URL, but it can include static parameter values.
  • The URL must use HTTPS to avoid mixed content problems in the browser.

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).

View the add-in in your custom tile.

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 sent to the init callback function. The SKY UX Add-in template makes this available as part of addinClientService that is injected into the component.

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 function. It is also made available through the addinClientService that is injected into the component.

To show these values, start by defining variables named environmentId and context in the Angular component. This is set within the init callback function and you'll JSON.stringify the context object that is provided to see the full shape of it.

TypeScript
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.

Markup
<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!

SKY UX rebuilds the SPA after every change and save.

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 method of the SKY Add-in Client JavaScript library.

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.

Markup
<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.

TypeScript
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.

Markup
<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.

TypeScript
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.

Updated add-in tile.

When you select the button, the user identity token displays and should be treated as an opaque string on the client.

The user identity token displays when you click the button.
  • Get an overview of the SKY Add-ins framework.
  • View the Getting started tutorial to learn more about how to build a SKY Add-in.
  • View the Hello World tile and Hello World button samples to see a detailed walkthroughs of building add-ins using simple HTML, CSS, and JavaScript.
  • View additional concepts and capabilities associated with the SKY Add-ins framework.
  • View our design guidelines to read about building an effective and compelling user experience for your add-in.