Creating Workbook Plugins – The Basics

In this short series, we’ll walk through creating a simple Workbook plugin.

Part 1 : The Basics (this one)
Part 2 : Reading data using the Sql Script Web Service API
Part 3 : Writing data using the Sql Script Web Service API
Part 4 : Publishing events to the Workbook
Part 5 : Conditional execution of GenericAction

Download : Source code and sample database

Profitbase InVision is built using AngularJS (1.0 right now), so some experience with AngularJS is required for building plugins.

You can download the source code and sample database for this demo from the link above. I won’t talk about how I built the UI since there’s nothing InVision specific about it, it’s just standard HTML, CSS and AngularJS.

We’ll create a Workbook used by Sienar Fleet Systems to register incidents with their TIE Fighter starships. The Workbook will contain a plugin for registering the incidents, and two standard charts to visualize the types and number of incidents. When a new incident is added, we’ll update the charts immediately, showing how plugins and standard components can interact with each other.

For those of you not familiar with InVision, I want to point out that you can create this type of app by just using the standard components. You don’t need to create a plugin for it. However, I didn’t want to create some complex plugin that would probably overshadow the point of this series, which is to show the concepts of creating a plugin and how to use the InVision programming model. 

Here’s a taste of what it will look like when we’re done. Yes, I know, it’s an amazing piece of software 🙂

Sienar

Setting up the environment

Before we can start creating a plugin, we need to set up our development environment. I’ll use Visual Studio Code and Typescript for writing code, but you can use any editor you’d like. Also, we’ll need to install NodeJS, the Node Package Manager (npm), git and the TypeScript compiler.

  1. Install NodeJS from nodejs.org
    1. Make sure the installer adds the path to the NodeJS executable to the PATH environment variable. It usually only adds npm.
      Edit the system Environment Variables and add the path to nodejs (usually C:Program Filesnode)
  2. Install git for Windows from https://git-scm.com/download/win
    1. If you left the command prompt open, close the window and repoen it
  3. Grant at least Modify, Read and execute, Write permissions to the Client install folder. You can do this by opening Windows File Explorer and go to the InVision install folder, usually C:Program Files (x86)ProfitbaseProfitbase InVision 2.0 xxx. Right click the Client folder, Properties -> Security -> Edit -> grant at least Modify, Read and execute + Write permissions to a user group you belong to. (Users (xxx) will normally work)
  4. Open a command prompt as Administrator and go to the Client web site folder, usually C:Program Files (x86)ProfitbaseProfitbase InVision 2.0 xxxClient. (Note! If you left the command prompt open after installing nodejs or git, close and reopen it.
  5. Run npm install
    This command will install the node modules required to run the plugin build script.
  6. Install TypeScript globally by running npm install -g typescript
  7. Install Visual Studio Code

Add a plugin component to a Workbook

To display a plugin in a Workbook, you need to add a plugin component which acts as the placeholder for indicating its type, position and size. You can do this step before or after you have created the plugin, but I’ll do it upfront this time.

  1. Open / create an InVision Solution
  2. Create a new Workbook and add a page
  3. In the Workbook Designer, expand the Extension folder in the Resources Toolbox.
  4. Drag and drop a Generic Plugin onto the page, and size it to a desired size
  5. In the Part Properties window, set the following properties:
    1. Name : SFS Incidents (optional and not important, the name is just for us mortals, InVision does not use it for anything)
    2. Selector : sienar-incidents. This property is important. It indicates the type of our plugin, which angularjs uses to render our view and bootstrap our plugin code.

 

Developing the plugin

Having defined where we want the plugin to go, and with our development environment is up and running, we’re ready for the fun part – developing the plugin.

When InVision is installed, a sample plugin is installed which can be used as a template for creating a new plugin. We’ll use the sample plugin as a starting point to get up and running quickly. Let’s call our plugin SienarIncidents (Sienar Fleet Systems Incidents).

  1. Navigate to the <install path>ClientPluginSrc folder
  2. Copy the SampleOne folder and rename it to SienarIncidents
  3. Open the folder and delete SampleOne.js and SampleOne.js.map
  4. Rename SampleOne.ts to sienarincidents.ts
  5. Open the folder in Visual Studio Code. The easiest way of doing this is right clicking an empty space and choose “Open with Code”.
  6. In VS Code, expand the .vscode node and open tasks.json
    1. Find the “args” property and change the item in the array from “SampleOne.ts” to “sienarincidents.ts”. This tells the TypeScript compiler which source code files to compile / transpile into JavaScript.
  7. in VS Code, add a new file, “sienarincidents.html”. This fill will contain our user interface code (HTML). Add a simple text to the document, “Sienar Incidents”.
  8. In VS Code, open sienarincidents.ts and uncomment the contents of the file.
  9. Modify the sample code so that it looks like the code below.

The basic code required for our (or any) plugin looks like what’s shown below. Since learning angularjs is outside the scope of this post, I won’t go into details about the angular parts of the code, but rather focus on the key concepts that are InVision specific.

/// <reference path="../../sdk/ts/invisionFull.d.ts"/>
/// <reference path="../../typings/tsd.d.ts"/>

module SienarFleetSystems {
    'use strict';

    import interaction = Invision.Workbook.Interaction;

    class SienarIncidentsController {
        static $inject = ['$scope', '$attrs', 'applicationContext', 'sienarIncidentsComponent'];
        constructor(scope, attrs, applicationContext: Invision.IApplicationContext, model: SienarIncidentsComponent) {
            scope.model = model;
        }
    }

    class SienarIncidentsDirective {
        static instance(): ng.IDirective {
            return new SienarIncidentsDirective;
        }

        restrict = 'E';
        controller = SienarIncidentsController;
        scope = {};
        templateUrl = 'pluginsrc/sienarincidents/sienarincidents.html';        
    }

    
    class SienarIncidentsComponent extends Invision.PluginComponent {

        protected onGenericAction(e: any): ng.IPromise<number> {
            var deferred = this.applicationContext.$q.defer();                                   
            deferred.resolve(1);            
            return deferred.promise;
        }        
    }

    angular.module('invision').controller('SienarIncidentsController', SienarIncidentsController);
    angular.module('invision').directive('sienarIncidents', SienarIncidentsDirective.instance);
    angular.module('invision').service('sienarIncidentsComponent', ['applicationContext', (applicationContext) => {
        return new SienarIncidentsComponent(applicationContext);
    }]);
}

Let’s walk through the code

The reference directives at the top ( /// <reference path=”….) gives you IntelliSense while typing. The d.ts-files contains the API definition for InVision and the 3rd party libraries included with InVision.

The templateUrl property of the directive points to the .html-file we added to the folder. It must contain the path relative to the pluginSrc folder.

The SienarIncidentsComponent (class) is where we’ll write our business logic. The class extens the PluginComponet base class provided by InVision. By extending the PluginComponent base class, your plugin gets initialized properly and the InVision application services will be available within the plugin.

The onGenericAction gets called when the GenericAction of the plugin is called by some other Workbook component or process. The action event argument passed to the function contains information about the event so that you can decide what to do when the function gets called. The action must always return a promise.

The calls to angular.module(‘invision’) registers the controller, directive and the component itself so that angular can instantiate our plugin and provide dependency injection.
A very important part is the line angular.module(‘invision’).directive(‘sienarIncidents’,….); This is where we specify the plugin type. It directly reflects the selector name we provided in the earlier step when we added the plugin placeholder to the Workbook.
We specified the selector name to be sienar-incidents, and we specified the directive to be sienarIncidents. Angular uses attribute normalization, so InVision will insert a <sienar-incidents></sienar-incidents> HTML element into the document. So by convention, angular expects “sienarIncidents” directive to be registered.

Compile and publish the plugin

With our code ready, it needs to be compiled and published so it gets loaded to the browser.

  1. In VS Code, hit Ctrl + Shift + B to compile. If you get any “error TS2503: Cannot find namespace ‘ht‘” errors, just ignore them. This will compile your TypeScript code into JavaScript code. (sienarincidents.ts to sienarincidents.js)
  2. In the command prompt, go to the folder where the client application is installed (<install path>Client) and run npm run plugins. Running the command will do the following:
    1. Bundle all .js files in subdirectories of PluginSrc into a single plugins.js file and copy it to Scriptsplugins
    2. Bundle all .html files in subdirectories of PluginSrc into a single plugins.views.js file and copy it to Scriptsplugins
    3. Bundle all .css files in subdirectories of PluginSrc into a single plugins.css file and copy it to Scriptsplugins.

View your amazing work come to life

Open or reload the workbook we created earlier. The plugin should apprear and display the text we added. If it does not appear, open the browser dev tools (F12) and review the Console (log) for any errors.

Next step

Part 2 : Reading data using the Sql Script Web Service API