Create a custom control

1. Overview

This sample will show how to create a custom sticky note control. An adapter is a class used to add a control or custom content into a Dundas BI view such as a dashboard. This is part 1 of 2, where the focus will be on a control that does not bind directly to Dundas BI data. You will learn the following:

> How to get started
> How to create a Dundas BI extension
> How to create a custom sticky note control

A custom dashboard with sticky note adapters, and a cork board background image.
A custom dashboard with sticky note adapters, and a cork board background image.

2. Getting started

The following prerequisites must be installed on your computer:

  • Visual Studio 2012, or higher.
  • Microsoft .NET Framework 4.5
  • Dundas BI Server

2.1. Downloading file

To download the Custom sample control solution click here.

2.2. Extracting sample to SDK folder

This sample is designed to automatically publish the extension to the instance. First you must extract the SampleControl.zip to the SDK folder within the instance. To extract the SampleControl.zip do the following:

  1. Find the SDK folder for your instance. It is located at [instance root]\sdk.
  2. Extract the SampleControl.zip to the SDK folder.
  3. You should see the following result:

    Samples added to SDK folder
    Samples added to SDK folder

    With the following structure:

    [instance root]
        • sdk
            • Samples
                • SampleControl

Important
If the sample is not in the exact folder, it will not work correctly

2.3. Opening Solution

To open the Visual Studio solution simply do the following:

  1. Right click on the Microsoft Visual Studio shortcut, and click run as administrator.
  2. Click the File Menu, then Open Solution.
  3. Double click the solution located at: [instance root]\sdk\Samples\SampleControl\SampleControl.sln

3. The project

The project is a class library.

  • dundas.controls.sample.stickynote.adapter.js - Contains the sticky note adapter class.
  • dundas.controls.sample.stickynote.css - The custom style sheet for the sticky note adapter.
  • dundas.controls.sample.stickynote.info.js - Gets the information about the control (such as what adapters it supports).
  • PublishExtension.targets - Used for auto publishing the extension after the build succeeds. This also copies the stylesheets, and hooks them up to the style override file.
  • SampleControlExtensionPackageInfo.cs - Contains the package information about the extension package.
  • StickyNoteAdapterInfo.cs - Contains the connection to JavaScript files, and defines the full JavaScript name.
  • StickyNoteExtension.cs - Contains the information about the adapters.
  • stickyNoteIcon.png - An icon to represent the sticky note in the dashboard designer toolbar.

3.1. Publish Extension Targets

This sample has a mechanism to automatically publish the extension, and copy the dundas.controls.sample.stickynote.css file to the style overrides folder. Also, the stickyNoteIcon.png will be placed in the instance's images folder. Next it will add the following line to the styles override file if it does not exist:

@import url("dundas.controls.sample.stickynote.css");    

This mechanism is the PublishExtension.targets file which overrides the AfterBuild target. This will create the following files after successfully compiling the solution:

    • [instance root]\www\BIWebsite\App_Data\Extensions\SampleControl\bin\Dundas.BI.Sample.SampleControl.dll

    • [instance root]\www\BIWebsite\Content\Override\dundas.controls.sample.stickynote.css

    • [instance root]\www\BIWebsite\Content\Images\icons\stickyNoteIcon.png

After it will then touch the web.config to force the web application to reset.

Important
Some instances will not have a Dundas.BI.Web.Core.dll assembly in the [instance root]\sdk\bin folder. On first load of the project you will see a broken reference. In this case one will be copied to this folder in the BeforeBuild event when building the project for the first time.

3.2. ExtensionPackageInfo class

In order for a custom adapter or package to be read by Dundas BI it needs to first contain a class that extends the ExtensionPackageInfo class. This class contains the package information about the extension package.

/// <summary>
/// This class contains the package information about the extension package.
/// </summary>
public class SampleControlExtensionPackageInfo : ExtensionPackageInfo
{
	#region Public Properties
	/// <summary>
	/// Gets the name of the extension package author.
	/// </summary>
	public override string Author
	{
		get { return "Dundas Data Visualization Sample Author"; }
	}
	/// <summary>
	/// Gets the copyright text associated with the extension package.
	/// </summary>
	public override string Copyright
	{
		get { return "Dundas Data Visualization, Inc."; }
	}
	/// <summary>
	/// Gets the localized display name of the extension package.
	/// </summary>
	public override string DisplayName
	{
		get { return "Sample Control Extension Package Info"; }
	}
	/// <summary>
	/// Gets the unique identifier of the extension package.
	/// </summary>
	public override Guid Id
	{
		get { return new Guid("38e2037a-6f0e-43d0-88d1-336d93374674"); }
	}
	/// <summary>
	/// Gets the name of the extension package.
	/// </summary>
	public override string Name
	{
		get { return "Sample Control Extension Package"; }
	}
	/// <summary>
	/// Gets the version of the extension package.
	/// </summary>
	public override Version Version
	{
		get { return new Version(0, 0, 1); }
	}
	#endregion Public Properties
}
        

3.3. Defining the AdapterInfo

In order to create a custom adapter or control you will need to extend the AdapterInfo Class. In the constructor you should give the name of the JavaScript Info file. In the case of the sticky note this is dundas.controls.sample.stickynote.Info.

using Dundas.BI.Web.Extensibility.Parameters;

  ...

namespace Dundas.BI.Sample.SampleControl
{
    /// <summary>
    /// This class represents a stickynote adapter.
    /// </summary>
    public class StickyNoteAdapterInfo : AdapterInfo
    {

        /// <summary>
        /// Initializes a new instance of the <see cref="StickyNoteAdapterInfo" /> class.
        /// </summary>
        public StickyNoteAdapterInfo()
        {
	        this.FullJavaScriptName = "dundas.controls.sample.stickynote.Info";
        }

        
        ...
    }
}

3.3.1. Overriding the GetResources method

The GetResources method is where the javascript file contents are defined. This sample demonstrates how to read the project's embedded resource JavaScript files, and return them as a list of strings.

/// <summary>
/// Gets all the JavaScript resources as <see cref="T:System.string" />s for this info. 
/// The order they are returned should be the ordered they need to be included in.
/// </summary>
/// <returns>
/// The JavaScript resources as <see cref="T:System.string" />s. This might be null.
/// </returns>
public override IList<string> GetResources()
{
	return new List<string>
	{
		GetResourceFromCurrentAssembly(
                    "Dundas.BI.Sample.SampleControl.dundas.controls.sample.stickynote.info.js"
                ),
		GetResourceFromCurrentAssembly(
                    "Dundas.BI.Sample.SampleControl.dundas.controls.sample.stickynote.adapter.js"
                )
	};
}

/// <summary>
/// Gets the resource as string from current assembly.
/// </summary>
/// <param name="name">The full path to the resource.</param>
/// <returns>The resource as <see cref="T:System.string" />.</returns>
private string GetResourceFromCurrentAssembly(string name)
{
	System.Reflection.Assembly currentAssembly =
		typeof(StickyNoteAdapterInfo).Assembly;
	using (StreamReader streamReader =
		new StreamReader(currentAssembly.GetManifestResourceStream(name)))
	{
		return
			streamReader.ReadToEnd();
	}
}

3.4. Implementing the IAdapterExtension interface

The IAdapterExtension Interface is used to define all the adapter info objects for a particular extension. The following demonstrates this interface implementation for the sticky note:

/// <summary>
/// The class represents the Sticky Note extension.
/// </summary>
public class StickyNoteExtension : IAdapterExtension
{
    /// <summary>
    /// Gets the all the adapter info objects for this particular extension.
    /// </summary>
    /// <returns>
    /// The adapter info objects for this extension.
    /// </returns>
    public IList<Web.Extensibility.Parameters.AdapterInfo> GetInfos()
    {
        return
            new List<AdapterInfo>()
            {
                new StickyNoteAdapterInfo()
            };
    }
}

3.5. JavaScript Classes

It is highly recommended that the JavaScript class use the "use strict" Directive. Strict mode makes it easier to write "secure" JavaScript. Strict mode changes previously accepted "bad syntax" into real errors. For more information on Strict Mode, click here.

"use strict";

3.6. The JavaScript info class

The sticky note info class extends the dundas.view.controls.Info Class. In the example below only the getControlInfos method was overridden. In this method we are passing the meta information about the control and what adapters it supports.

"use strict";

window.dundas.controls.sample = {};
window.dundas.controls.sample.stickynote = {};

// Sandbox.
(function ($) {
    // The sticky note control class infos.
    dundas.controls.sample.stickynote.Info = dundas.view.controls.Info.extend({
        init: function () {
            /// <summary>
            /// Initializes the Info. Called during construction.
            /// </summary>
            
        },
        getControlInfos: function () {
            /// <summary>
            /// Gets the information about the control (such as what adapters it supports).
            /// </summary>
            /// <returns type="Object">
            /// Meta-information about the control and what adapters it supports.
            /// </returns>
            var infos = [
                {
                    "categoryName": "Components",
                    "subCategoryName": "Fun",
                    "caption": "Stickynote extension",
                    "description": "Simple stickynote sample control",
                    "adapterObjectName": 'dundas.controls.sample.stickynote.Adapter',
                    "defaultWidth": 350,
                    "defaultHeight": 200,
                    "toolbarIconUrl": 
                        dundas.Utility.getContextualUrlPath(
                            'Content/Images/icons/stickyNoteIcon.png'
                        )
                }
            ];
            return infos;
        }
    });
})(jQuery);

Note
"adapterObjectName": 'dundas.controls.sample.stickynote.Adapter' refers to the class we are defining in the next section.

The sticky note adapter in the toolbar.
The sticky note adapter in the toolbar.

3.7. The JavaScript adapter class

The sticky note adapter class extends the dundas.view.controls.Adapter Class . The following demonstrates how to create this class:

(function ($) {
    dundas.controls.sample.stickynote.Adapter = dundas.view.controls.Adapter.extend({
        init: function (adapterOptions) {
            /// <summary>
            /// Initializes the Adapter. Called during construction.
            /// </summary>
            /// <param name="adapterOptions" type="Object" optional="true">(optional) 
            /// An object literal parameter specifying default values.
            /// </param>

            // Call base.
            this._super(adapterOptions);
        }
})(jQuery);

3.7.1. Creating the control and elements

The place to define the control and elements is on the adapter's onLoaded method. This is called when the adapter is loaded and set up. The control should be set up by the adapter now and added to the DOM (container property). Implementers of this method must call this._super() at the end if they want actions and events to be automatically set up.

In the following example we create three DOM elements in a _createControl method if our control has not been initialized. Each element is a div element.

onLoaded: function () {
    /// <summary>
    /// Called when the adapter is loaded and setup.
    /// The control should be setup by the adapter now and added to the DOM (container property).
    /// Implementers of this method must call this._super() at the end if they want actions
    /// and events to be automatically setup.
    /// </summary>

    // Setup my UI control here.
    if (this.control == null) {
        this._createControl();
    }

    this._super();

    this._stickyNoteContainer.click(function () {
        $(this).fadeOut( "fast" );
    });
},

_createControl: function() {
    // Control.

    this._stickyNoteContainer =
        $(document.createElement("div"))
        .addClass(Constants.StickyNoteContainerCssClass)
        .appendTo($(this.container));

    this.control = $(document.createElement("div"))
        .addClass(Constants.StickyNoteControlCssClass)
        .appendTo(this._stickyNoteContainer);

    // Label.
    this._label = $(document.createElement("div"))
        .addClass(Constants.StickyNoteControlLabelCssClass)
        .appendTo(this.control);

    // Set default font family and size
    this.fontFamily = "'Reenie Beanie'";
    this.fontSize = "36px";

    // Set defaults properties for the controls.
    this.stickynoteText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";

    var randomAngle = Math.floor((Math.random() * 4));
    randomAngle = (Math.random() < 0.5) ? -randomAngle : randomAngle;

    this.angle = randomAngle;

    var randomColorIndex = Math.floor((Math.random() * 3));

    switch (randomColorIndex) {
        case 0:
            this.background = new dundas.controls.SolidColorBrush(dundas.controls.Color.fromString("#ffc"));
            break;
        case 1:
            this.background = new dundas.controls.SolidColorBrush(dundas.controls.Color.fromString("#cfc"));
            break;
        case 2:
            this.background = new dundas.controls.SolidColorBrush(dundas.controls.Color.fromString("#ccf"));
            break;
    }
}

3.7.2. Defining the properties

Note
The properties are those displayed in the Properties window in the UI, for users to be able to set graphically on each control. Defining properties is optional, and is not required to make a control.

The next step is to override the getControlProperties method. In this method we will return an Object Descriptor that is an object with the list of properties on the desired element. The following example shows the implementation of this method for the text, angle, and background properties for the sticky note adapter:

getControlProperties: function (path) {
    /// <summary>
    /// Called to get the object with the list of properties on the root control, or any sub-element
    /// of the control.
    /// </summary>
    /// <param name="path" type="Array" elementType="dundas.view.controls.PropertyPathItem" optional="true">(optional) The path to the sub-element 
    /// of this control. Undefined if the root is requested, otherwise an array of plain objects with properties.</param>
    /// <returns type="dundas.controls.ObjectDescriptor">An object with the list of properties on the desired element.</returns>

    var objectDescriptor,
        propertyDescriptor,
        elementName;

    // Base path properties.
    if (!path) {
        // Object descriptor.
        objectDescriptor = this._super(path);

        // Label text.
        propertyDescriptor = new dundas.StringPropertyDescriptor();
        propertyDescriptor.name = "Stickynote Text";
        propertyDescriptor.id = "stickynoteText";
        propertyDescriptor.category = "GS_PropertyGrid_Category_Common".localize();
        propertyDescriptor.description = "Sample Sticky note control";
        propertyDescriptor.value = this.stickynoteText;
        propertyDescriptor.defaultValue = "Lorem Ipsum";
        propertyDescriptor.section = dundas.controls.PropertyGridSections.Text;

        objectDescriptor.addProperty(propertyDescriptor);

        // Angle.
        propertyDescriptor = new dundas.NumericPropertyDescriptor;
        propertyDescriptor.name = "Angle";
        propertyDescriptor.id = "angle";
        propertyDescriptor.category = "GS_PropertyGrid_Category_Common".localize();
        propertyDescriptor.description = "The angle.";
        propertyDescriptor.value = this.angle;
        propertyDescriptor.defaultValue = -3;
        propertyDescriptor.section = dundas.controls.PropertyGridSections.Look;

        objectDescriptor.addProperty(propertyDescriptor);

        // Background.
        propertyDescriptor = new dundas.PropertyDescriptor();
        propertyDescriptor.name = "Background";
        propertyDescriptor.id = "background";
        propertyDescriptor.category = "GS_PropertyGrid_Category_Common".localize();
        propertyDescriptor.description = "The background";
        propertyDescriptor.value = this.background;
        propertyDescriptor.defaultValue = new dundas.controls.SolidColorBrush(dundas.controls.Color.fromString("#cfc"));
        propertyDescriptor.isNullable = true;
        propertyDescriptor.section = dundas.controls.PropertyGridSections.Look;

        objectDescriptor.addProperty(propertyDescriptor);
    }
           
    return objectDescriptor;
}

After defining the property descriptors, set up the properties on the class itself. Each class should implement a __classtype property. The following is the implementation of this for the sticky note adapter:

__properties: {
    "__classType": {
        get: function () {
            return "dundas.controls.sample.stickynote.Adapter";
        }
    },

    "stickynoteText": {
        /// <summary>
        /// Gets or sets the text label displayed in the control.
        /// </summary>
        /// <value type="String">The value to display.</value>
        get: function () {
            return this._stickynoteText;
        },
        set: function (value) {
            this._stickynoteText = value;
            if(this._label) {
                this._label.text(value);
            }
        },
        notEnumerable: true
    },

    "background": {
        /// <summary>
        /// Gets or sets the background property of the control.
        /// </summary>
        /// <value type="dundas.controls.SolidColorBrush" mayBeNull="true"></value>
        get: function () {
            return this._background;
        },
        set: function (brush) {
            this._background = brush;
            if (this.control) {
                if (brush) {
                    brush.applyToElement(this.control.get(0), 'background');
                }
                else {
                    $(this.container).css({ background: '' });
                }
            }
        },
        notEnumerable: true
    },

    "angle": {
        /// <summary>
        /// Gets or sets the angle of the control.
        /// </summary>
        /// <value type="Number" mayBeNull="true">The angle of the control.</value>
        get: function () {
            return this._angle;
        },
        set: function (value) {
            this._angle = value;
            if (this.control) {
                this.control.css("-o-transform", "rotate(" + value + "deg)");
                this.control.css("-webkit-transform", "rotate(" + value + "deg)");
                this.control.css("-moz-transform", "rotate(" + value + "deg)");

            }
        },
        notEnumerable: true
    }
}

Note
The setter of the properties are modifying the DOM elements directly that were created on the loaded event.

3.7.3. Setting the adapter name prefix

It is recommended to override the _getAdapterNamePrefix method to return a nice name for when the control is added to the dashboard. For example, the following code example will result in the name sequence (stickynote1, stickynote2, etc.):

_getAdapterNamePrefix: function () {
/// <summary>
/// Should be overridden by implementing adapters to return a nice name for name generation 
/// (just prefix).
/// </summary>
/// <returns type="String">The prefix to use for any generated names.</returns>
return "stickynote";
}

The sticky note adapter name prefix.
The sticky note adapter name prefix.

3.7.4. Getting and saving the state of the control

Note
If properties were made available by implementing the section above, and you want to save the values to be restored when reloaded, it is required to save the state of the control. Getting and saving the state of the control is optional, and is not required to make a control.

In order for the state to be saved the adapter must override the stringifyControl method. This is to instruct this adapter to get a string version of the underlying control. In order to load the state of the control the adapter must override the parseControl method. This is called to instruct the adapter to set the control up based on the string version. The expectation in these methods is to handle the adapter's properties that are directly defined in the adapter, and use the this._super calls to handle properties defined in the base object.

The following is the sticky note implementation for getting and saving the state of the control.

// Helper methods.
var Utility = {
    parseItem: function (item) {
        return Class.fromJSON(JSON.stringify(item || {}));
    },
    parseItems: function (items) {
        return $.map(items || [], function (item, index) {
            return Utility.parseItem(item);
        });
    }
};

   ...

stringifyControl: function (isStyleString) {
    /// <summary>
    /// Called to instruct this adapter to get a stringified version of the underlying control.
    /// </summary>
    /// <param name="isStyleString" type="Boolean" optional="true">(optional) Indicates whether this stringify request is for a style.</param>
    /// <returns type="String">A string representation of the underlying control which can be stored.</returns>
           
    // Call the base.
    var o = JSON.parse(this._super()) || {};

    o.background = this.background;
    o.angle = this.angle;
    o.stickynoteText = this.stickynoteText;

    return JSON.stringify(o);
},

parseControl: function (objectString, isStyleString) {
    /// <summary>
    /// Called to instruct the adapter to set the control up based on the stringified version.
    /// </summary>
    /// <param name="objectString" type="String">The stringified version of the underlying control.</param>
    /// <param name="isStyleString" type="Boolean" optional="true">(optional) Indicates whether this stringify request is for a style.</param>
            
    var o = JSON.parse(objectString);

    if (this.control == null) {
        this._createControl();
    }

    this.background = Utility.parseItem(o.background);
    this.stickynoteText = o.stickynoteText;
    this.angle = o.angle;

    // Call the base.
    this._super(objectString);
}

4. What's Next

To continue with part two of this article that focuses on adding data binding to the control, click here.

5. See Also

.NET

JavaScript

 

Dundas Data Visualization, Inc.
500-250 Ferrand Drive
Toronto, ON, Canada
M3C 3G8

North America: 1.800.463.1492
International: 1.416.467.5100

Dundas Support Hours: 7am-6pm, ET, Mon-Fri