-
Notifications
You must be signed in to change notification settings - Fork 0
CustomDashboardWidgets
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
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.
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 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
Guidas in the example above to prevent conflicts
Best Practice: all
<script>tags in a custom view should appear within anHtml.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
}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
});Now you have defined your custom widget you need to make it available through the dashboard dialog.
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
typeparameter for the first registration must match the name used in theregisterEditorcall -
colorcan be any valid HTML color (e.g. `#fff', 'orange', etc.) -
iconis the shortened name of a Font Awesome icon -
defaultoptions.layout.minWidthis the number of 1/12 width blocks that the layout should take. All layout width properties work in the same way
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
DashboardExtensionsview as resources.
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;
}
}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
getViewSourcemethod 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
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>