Skip to content

CustomDashboardWidgets

Steve Greatrex edited this page Apr 14, 2016 · 2 revisions

Authoring Custom Dashboard Widgets

User customisable dashboards support a range of built in widgets but it is possible to create your own environment-specific widgets that your users can then drop into any existing dashboard.

Note: you need to have permissions to publish custom views in order to author custom widgets. If you are not sure whether or not you have access, speak to the EDGE10 support team

Widget Content

Each custom widget is populated from a custom view. Within that view you will have access to view-level parameters (the standard ViewElementParameters values) and custom widget settings.

View Parameters

To access the view parameters you can inherit your view from the IViewElementContext interface. This will provide you with a Model.Parameters property containing the view parameters.

Note: whenever view paramters are updated your view will be fully re-loaded from the server

@model IViewElementContext

@{
	var viewParameters = @Model.Parameters;
} 

<pre>@Model.Parameters.ToJson(indent: true)</pre>

The sample view above will yield the output below:

{
  "viewName": "CustomWidget",
  "sessionId": null,
  "sessionFormat": null,
  "sessionMode": null,
  "queryName": null,
  "implicitQueryElementId": null,
  "implicitQueryElementType": null,
  "implicitChartType": null,
  "chartName": null,
  "stateId": null,
  "viewVersion": null,
  "entityId": null,
  "entityType": null,
  "dateRangeType": null,
  "date": null,
  "endDate": null,
  "dateOffset": null,
  "subEntityId": null
}

Widget Settings

Widget settings are stored in a JavaScript object and can contain any custom properties required for the widget (in addition to some built-in default properties).

These are accessed through a jQuery plugin that will attempt to locate the settings for the widget. If you were to attempt to access the custom view outside of a dashboard widget this will return an empty {} object so ensure that you account for this within your code.

@{
	var id = Guid.NewGuid();
}

<div id="@id"></div>

@using (Html.Scripts())
{
<script>
	require(['jquery', 'jquery.widgetSettings'], function($) {
		var $container = $('#@id');
		var settings = $container.widgetSettings();
		
		//write the settings out to the container div
		$container.append($('<pre>').html(JSON.stringify(settings, null 2)); 
	});
</script>
}

Best Practice: widgets can appear multiple times on a single page so all bindings should be applied to a per-view Guid as in the example above to prevent conflicts


Best Practice: all <script> tags in a custom view should appear within an Html.Scripts() using block. This appends scripts to the end of the page and prevents race conditions between asynchronously-loaded scripts

The sample view above will yield the output below (assuming you have configured a custom parameter named myCustomParameter):

{
  "useSharedEntityNavigation": true,
  "useSharedDateNavigation": true,
  "myCustomParameter": 123
}

Reacting to Settings Changes

Unlike view parameters, settings changes do not automatically result in a refresh of the view. To react to changes to the settings you can use another jQuery plugin as below.

$container.onWidgetSettingsUpdated(function() {
	var updatedSettings = $container.widgetSettings();
	
	//handle settings changes here
});

Widget Plugin

Now you have defined your custom widget you need to make it available through the dashboard dialog.

Registration

In order to register your new widget you need to implement a custom version of a special view named DashboardExtensions. This will automatically be loaded by EDGE10 and searched for custom widget implementations.

The content of this view will usually comprise 3 elements for each custom widget:

  • Registration of the widget type
  • Registration of the widget settings editor (see below)
  • Custom settings editor view template (see below)
<!-- custom editor template (if required) -->
<script id="my-widget-settings-editor" type="text/html">
<!-- template content here -->
</script>

@using (Html.Scripts())
{
   <script>
   require(['DashboardSourcesViewModel', 'WidgetSettingsEditorRegistry', '/view/resources/DashboardExtensions/MyCustomWidgetSettingsEditor.js'], 
   	function(DashboardSourcesViewModel, WidgetSettingsEditorRegistry, MyCustomWidgetSettingsEditor) {
   	 	//register the widget type with default values
   		DashboardSourcesViewModel.getSingleton().addMiscellaneousSource({
   			type: 'myCustomWidget',
   			displayName: 'Custom Name',
   			color: 'red', 
   			icon: 'youtube',
   			defaultOptions: {
   				layout: {
   					minWidth: 3,
   					minHeight: 3
   				},
   				settings: {
   					useSharedDateNavigation: true,
   					useSharedEntityNavigation: false
   				}
   			}
   		});
   		
   		//register the settings editor implementation
   		WidgetSettingsEditorRegistry.getSingleton().registerEditor('myCustomWidget', {
   			create: function(viewSource) { return new MyCustomWidgetSettingsEditor(viewSource); }
   		});
   	});
   </script>
}

Important points to note:

  • The type parameter for the first registration must match the name used in the registerEditor call
  • color can be any valid HTML color (e.g. `#fff', 'orange', etc.)
  • icon is the shortened name of a Font Awesome icon
  • defaultoptions.layout.minWidth is the number of 1/12 width blocks that the layout should take. All layout width properties work in the same way

Settings Editor

The first step in registration is to create a view model for editing the custom settings. We recommend that you use TypeScript for this due to the potential complexity of generated settings. If you choose to take this approach then you can request the .d.ts TypeScript definintions from EDGE10 support; if not, you can recreate the same using pure JavaScript but you must implement the members required on the interface.

Note: when referencing your custom widget registration you will ALWAYS need to reference the JavaScript output generated by the TypeScript compiler


Best Practice: it is recommended that all widget settings editors and their associated view templates are implemented within the DashboardExtensions view as resources.

Defining Widget Settings

Your widget can define as many or as few custom settings as needed but the settings object must inherit from the WidgetSettings.IWidgetViewSourceSettings interface:

/// <reference path="WidgetSettings.d.ts" />

interface MyWidgetParameters extends WidgetSettings.IWidgetViewSourceSettings {
	customParameter1: string;
	customParameter2: {
		nestedParameter1: number;
		nestedParameter2: string;
	}
}

Defining the Settings Editor

The settings editor view model (using Knockout bindings) must implement the generic interface WidgetSettings.IWidgetSettingsTypedEditor<T> where T is your custom settings interface defined above. If you have no custom settings you can skip the step above and use WidgetSettings.IWidgetViewSourceSettings directly. The class must also construct on an instance of WidgetSettings.IViewSource<MyWidgetParameters>.

The example below is the minimum implementation of this interface. Note that you need to import both knockout and knockout.validation; the latter does not expose any members directly so is imported using an amd-dependency tag.

//continued from above

/// <amd-dependency path="knockout.validation" />

import ko = require('knockout');


class MyCustomWidgetSettingsEditor implements WidgetSettings.IWidgetSettingsTypedEditor<MyWidgetParameters> {

    title                     = ko.observable<string>();
    useSharedEntityNavigation = ko.observable<boolean>();
    useSharedDateNavigation   = ko.observable<boolean>();
    validateGroup: KnockoutValidationErrors;
	
    constructor(private viewSource: WidgetSettings.IViewSource<MyWidgetParameters>) {
        
		//copy values from the initial view source into observable properties
		this.title(viewSource.title);
		this.useSharedEntityNavigation(viewSource.settings.useSharedEntityNavigation);
		this.useSharedDateNavigation(viewSource.settings.useSharedDateNavigation);
		
		//set up validation for this view model
		this.validateGroup = ko.validation.group(this, { deep: true, observable: false });
    }    

    getViewSource(): WidgetSettings.IViewSource<MyWidgetParameters> {
		//update the view source with the latest values from the observables
        this.viewSource.title                              = this.title();
        this.viewSource.settings.useSharedEntityNavigation = this.useSharedEntityNavigation();
        this.viewSource.settings.useSharedDateNavigation   = this.useSharedDateNavigation();
		
		//ensure that the name of our custom widget content view (above) is set on the view parameters
        this.viewSource.parameters = {
            viewName: 'CustomWidget'
        };
		
        return this.viewSource;
    }
}	

Important points to note:

  • The constructor is provided the current version of the settings. If you intend any properties to be editable you will need to copy the current values into appropriate observable properties
  • The getViewSource method will be used by the Save implementation to get the updated property values. This will not be invoked if any values fail validation
  • You can use Knockout.Validation to specify validation for any custom parameters

Custom Editor View

There is no requirement to implement a custom settings editor view for your widget but in most cases it will be necessary in order to support rich functionality.

The custom view is defined as a knockout template, usually within the default view.cshtml under DashboardExtensions:

<script id="my-widget-settings-editor" type="text/html">
<!-- template content here -->
</script>

This is then linked to the editor by setting the optional editorTemplate property on your editor view model created above.

class MyCustomWidgetSettingsEditor implements WidgetSettings.IWidgetSettingsTypedEditor<MyWidgetParameters> {
	
	editorTemplate = 'my-widget-settings-editor';
	
	//...as above
}

If you do not specify an editorTemplate the view will fall back to the standard settings UI that includes editors for the title and the useShared- properties.

If you want to include the standard settings UI as part of your custom UI you can pull in the existing common-settings-section template as below.

<script id="my-widget-settings-editor" type="text/html">
<!-- ko template: 'common-settings-section' --><!-- /ko -->
<!-- other custom markup here -->
</script>

To take advantage of built-in CSS styles for the settings you can structure your settings as below

<div class="setting">
	<label class="setting-label">
		<i data-font-icon="text"></i>
		Setting Label
	</label>
	<div class="setting-value">
		<input data-bind="textInput: exampleCustomStringProperty" />
	</div>
</div>

Clone this wiki locally