Insight Tutorial

Difficulty: Intermediate

Welcome to the tutorial on how to develop your own custom BlueConic plugin containing an Insight!
This tutorial is meant for programmers that are familiar with the basics of building custom components that extend the scope of BlueConic. If you want to catch up on the basics, take a look at the tutorials under Getting Started.

At the end of this tutorial you will have created your own custom "Explanation Insight" plugin to add to any Dashboard.

 


Introduction

On the Insights tab in BlueConic you can create Dashboards and place Insights. BlueConic comes with a range of Insights, but you can also create a custom Insight yourself.

In this tutorial we will create an Insight that will insert a line of text to a Dashboard, allowing the marketer to create an inline explanation for the Insights that are being shown.

Behold, the Explanation Insight!

Our Insight will have a simple settings popup where an explanation can be entered:

The Insight can then be dragged in between other insights to provide an explanation:

In creating this example Insight we will get acquainted with all basic building blocks that make Insights.


File structure

This plugin requires a number of files that are structured like in the schematic directory overview below:

insight_explanation/
  +-- explanation.png
  +-- insight_explanation.xml
  +-- widgets/
        +-- ExplanationInsight.css
        +-- ExplanationInsight.html
        +-- ExplanationInsight.js
        +-- Settings.html
        +-- Settings.js
        +-- nls
              +-- languageResource.js
              +-- nl-nl
                    +-- languageResource.js

The files can be downloaded here: insight_explanation-1.0.0.zip


XML definition file

<plugin>
  <id>insight_explanation</id>
  <version>1.0.0</version>
  <type>insightwidget</type>
  <name>
    <label locale="en_US">Explanation Insight</label>
    <label locale="nl_NL">Uitleg inzicht</label>
  </name>
  <description>
    <label locale="en_US">This insight makes it possible to add a header (or a piece of HTML) between insight rows</label>
    <label locale="nl_NL">Dit inzicht maakt het mogelijk om een kop (of een stuk HTML) tussen inzichten te plaatsen</label>
  </description>

  <!-- backend specific properties -->
  <backend>
    <image>explanation.png</image>
    <widget>widgets/ExplanationInsight.js</widget>
    <dimensions>
      <dimension>
        <width>4</width>
      </dimension>
    </dimensions>
  </backend>
</plugin>

This XML definition file contains the following general instructions detailing the plugin:

  • The unique <id> of our custom plugin is "insight_explanation".
  • The <version> version is "1.0.0".
  • The <type> "insightwidget" determines that our plugin contains the code for an Insight.
  • The <name> and <description> have language dependent labels.

The instructions specifically for the <backend> are:

  • The <image> "explanation.png" will be shown alongside the name and the description in the Insights Gallery. The image is optional, but it will be scaled to fit a 108x108 pixels box in the gallery when provided.
  • The <widget> tag contains a relative link to the Javascript file that defines the actual widget that will be displayed. More on widgets below.
  • The <dimensions> tag contains the definition of the dimensions of the Insight. Insights are placed on a virtual grid that consists of 4 columns and users can drag them around based on this grid. Three types of dimensions are supported:
    ValueDescription
    <width>1<width>
    <height>1<height>
    The 1x1 Insight is the smallest available size Insight. It spans one column and one row and you can have four of these next to each other on a single row.
    <width>2<width>
    <height>2<height>
    The 2x2 Insight is a medium size Insight. It spans two columns and two rows. Only two of them fit per row.
    <width>4<width> The full width Insight spans all four columns. There is no limit to its height because it defines its own row height.

    Our widget uses the full width dimensions, since we want the explanation to span the entire width of the Dashboard.


Widgets

To display the plugin on the BlueConic backend we use the Dojo framework and Dojo widgets in particular. The framework allows for the rapid development of BlueConic plugins as modular rich web applications. Widgets tie visual HTML design to underlying logic, making them perfect for BlueConic Insight plugins.

Explaining Dojo and widgets is beyond the scope of this tutorial, but the documentation for Dojo is an excellent starting point to learn more about the framework. This tutorial will of course explain relevant touch points with Dojo.

For our Insight we are going to need two custom widgets:

  1. The Insight itself, in our case a simple <div> holding text content. This widget will take care of display the text that was entered in the Insight settings menu.
  2. The Settings popup where the text content can be edited. This widget will take care of displaying an edit box and capturing the content that the user enters.

BlueConic will take care of the rest: displaying the widget, making it draggable, giving the Insight a clickable settings menu icon, etc.

We will delve deeper into the individual widgets below.

ExplanationInsight widget

This widget consists of three files: the main file "ExplanationInsight.js", the template "ExplanationInsight.html" and a custom stylesheeet "ExplanationInsight.css". This is the full HTML template file "ExplanationInsight.html":

<div class="explanationInsight">
  <div data-dojo-attach-point="_explanationNode"></div>
</div>

The file contains the HTML fragment that defines our widget; a pair of nested <div> tags. The outer <div> tag has a class to define a minimum height. This ensures the insight can always be dragged easily, even when there is no content to display.

Here is the full stylesheet file "ExplanationInsight.css" to style our template:

.explanationInsight {
  min-height: 50px;
}

The inner <div> defines a Dojo attach point. The value _explanationNode will be used from the Javascript file to write dynamic content to.

Below is the full Javascript file "ExplanationInsight.js":

// @formatter:off
define([
  "dojo/_base/declare",
  "dojo/_base/lang",
  "dojo/on",
  "dojo/text!./ExplanationInsight.html",
  "dijit/_TemplatedMixin",
  "dijit/_WidgetsInTemplateMixin",
  "blueconic/api/widgets/insights/_BaseInsight",
  "./Settings",
  "blueconic/api/plugins/css!./ExplanationInsight.css"

// @formatter:on
], function(declare, lang, on, template, _TemplatedMixin, _WidgetsInTemplateMixin, _BaseInsight, Settings, css) {

  // first argument: array of superclasses
  return declare("insight_explanation.widgets.ExplanationInsight", [_BaseInsight, _TemplatedMixin, _WidgetsInTemplateMixin], {
    templateString: template,

    postCreate: function() {
      // Call the super.
      this.inherited(arguments);

      // make sure the explanation HTML is updated, when it is changed in the settings
      on(this.parameters, "change", lang.hitch(this, this._updateView));

      // initial load of the explanation
      this._updateView();

      // make sure the loader is hidden
      this.showLoader(false);
    },

    _updateView: function() {
      var explanation = this.parameters.getValue("explanation");
      this._explanationNode.innerHTML = explanation;
    },

    getSettingsWidget: function() {
      // settingsWidget' property from the _BaseInsightWidget.
      if (!this._settingsWidget) {
        this._settingsWidget = new Settings({
          parameters: this.parameters
        });
      }
      return this._settingsWidget;
    }

  });

});

Now, let us break down this script into its components.

The script kicks off with a define() to create an AMD Module. The module makes sure that our plugin can be referenced via one single reference. It also makes sure that our functions and variables are local without the chance of interfering with other modules.

define([
  "dojo/_base/declare",
  "dojo/_base/lang",
  "dojo/on",
  "dojo/text!./ExplanationInsight.html",
  "dijit/_TemplatedMixin",
  "dijit/_WidgetsInTemplateMixin",
  "blueconic/api/widgets/insights/_BaseInsight",
  "./Settings",
  "blueconic/api/plugins/css!./ExplanationInsight.css"

], function(declare, lang, on, template, _TemplatedMixin, _WidgetsInTemplateMixin, _BaseInsight, Settings, css) {
   ...
})

As we see above, the function define() takes two arguments:

  1. An array of modules to load. These modules will provide us with a library of handy functions to use in our code:
    • "dojo/*": some core dojo functions.

      Notice that the "dojo/text" module takes an extra argument: "dojo/text!./ExplanationInsight.html". Behind the ! (pronounce as "bang") is the location of the HTML template for our Insight. The module will read the template in and return it as a string.

    • "dijit/*": some mixin libraries to work with widgets and templates.
    • "blueconic/api/widgets/insights/_BaseInsight": base module that we are extending, provides us with basic Insight properties.
    • "./Settings": our own settings widget (code and explanation follows below).
    • "blueconic/api/plugins/css!./ExplanationInsight.css": plugin to load a file with a custom stylesheet. Behind the ! is the location of the CSS file.
  2. A callback function that will be called when the modules have been loaded. The function should return an object with all public functions and properties of our module. The function takes as many parameters as modules that were included. The parameter names could be anything, but the convention is that parameter names take the same form as the module they belong to. So the parameter declare maps to the module dojo/_base/declare, lang maps to dojo/_base/lang, etc.

In the callback function we see the declaration of the module being returned:

  // Second argument: array of superclasses
  return declare("insight_explanation.widgets.ExplanationInsight", [_BaseInsight, _TemplatedMixin, _WidgetsInTemplateMixin], {
    ...
  });

Here, the function declare() takes three arguments (see dojo/_base/declare for the full documentation):

  1. The name of our module.
  2. An array of superclasses.
  3. An object exposing all public members of our module.

The object in the third argument contains the rest of our code. Notice that this is an object (not to be confused with a code block), which means all members should be written as Javascript literals!

So, instead of using the "var x = y;" notation:

  return declare(..., {
    // WRONG: this is not using literal notation!
    var templateString = template;
    var postCreate = function() {
      ...
    };
    var _updateView = function() {
      ...
    };
  });

we have to write "x: y,":

  return declare(..., {
    // CORRECT: using literal notation
    templateString: template,
    postCreate: function() {
      ...
    },
    _updateView: function() {
      ...
    }
  });

Our module plugs in on the dijit._WidgetBase lifecycle by defining four members; one variable and three functions:

  • The variable templateString is used by the mixin dijit/_TemplatedMixin which takes care of building the widget's DOM tree and replacing substitution variables for us. In our case we use the template that was loaded by the "dojo/text" module.
  • The function postCreate() is typically the workhorse of a custom widget. When it is called the widget has been rendered, although it may not be attached to the DOM yet.
  • The function _updateView() is a private function of our plugin, denoted by convention with a "_" as first letter. We call the function from postCreate() to update the HTML content of our Insight.
  • The function getSettingsWidget() implements our own version of the _BaseInsight getSettingsWidget() function.

postCreate()

Let us zoom in on the functions of our module, starting with postCreate(). We start off by calling the super module by calling this.inherited(), passing on the local arguments object:

    postCreate: function() {
      // Call the super.
      this.inherited(arguments);

This allows the _BaseInsight module to initialize properly.

Next, we bind a handler function to the "change" event of the parameters property that we inherited from the _BaseInsight class:

      // make sure the explanation HTML is updated, when it is changed in the settings
      on(this.parameters, "change", lang.hitch(this, this._updateView));

Here we use lang.hitch(this, this._updateView) to return a function that makes sure that this inside the function _updateView() refers to the same object as in the postCreate() function. Without that, this might refer to another object given the different context.

Then we update the view of our Insight for the first time by calling our function _updateView():

      // initial load of the explanation
      this._updateView();

Insight widgets can take some time to render their content. To give the user early feedback BlueConic will show a loader to indicate that the content is being renderd. So to conclude our postCreate() function, we make sure that the loader image is hidden:

      // make sure the loader is hidden
      this.hideLoader();
    },

_updateView()

We use our function _updateView() to determine what should be displayed, based on the parameters of the Insight:

    _updateView: function() {
      var explanation = this.parameters.getValue("explanation");
      this._explanationNode.innerHTML = explanation;
    },

First we retrieve the value for the parameter "explanation" using the this.parameters property that we inherited from blueconic.api.widgets.plugins._BaseInsight and the getValue() function. Then we assign this content to the inner HTML of the explanation DOM node in our template.

Notice that this._explanationNode corresponds to the DOM node <div data-dojo-attach-point="_explanationNode"></div> in our template file.

getSettingsWidget()

We want the Insight to have settings where the user can enter a text. To enable the settings icon on the insight, the function getSettingsWidget() must return the widget that defines the contents of the settings popup. So if no private property this._settingsWidget exists, we create a new Settings() widget, where we pass on our own parameters:

    getSettingsWidget: function() {
      // SettingsWidget' property from the _BaseInsightWidget.
      if (!this._settingsWidget) {
        this._settingsWidget = new Settings({
          parameters: this.parameters
        });
      }
      return this._settingsWidget;
    }

Packaging the plugin

Package the plugin by creating a ZIP file with the file structure laid out above. Make sure the files "insight_explanation.xml" and "explanation.png" are in the root of the ZIP file.

You should now have a file "insight_explanation-1.0.0.zip" that contains all files that we created above.

You can install the packaged plugin in BlueConic and then go to the Insights tab to add an "Explanation Insight" to a dashboard.

It is best to empty your browser cache and reload the page after updating a plugin to make sure the new JavaScript is actually being used.

If all went as planned, you should be able to add the Explanation Insight, drag it to the correct place and enter some content for it.


Summary

In this tutorial you learned how to add parameters to a custom BlueConic plugin and how to use their values from the plugin JavaScript.

If you want to learn more about BlueConic plugins, check the Getting Started page for an overview of available tutorials.