-
Notifications
You must be signed in to change notification settings - Fork 2
SolarNode Webapp Customizing
This guide describes how you can extend the built-in SolarNode web settings UI by adding custom resources such as HTML and JavaScript.
The settings UI shows a list of configurable plug-ins:
The SettingSpecifierProvider API is how SolarNode plug-ins expose properties to the settings UI:
This API allows plug-ins to return any number of SettingSpecifier objects, each of which represents a property to the UI. Typically these are simple, mutable properties such as the TextFieldSettingSpecifier used for mutable string properties. The SolarNode web UI renders HTML form elements on behalf of the plug-in, and manages persisting changes to them via a SettingsService provided by the platform.
As a plug-in developer, you might like to provide a more complex UI than the built-in one can provide. Let's imagine you want to show a custom button that executes some custom JavaScript when pressed:
To provide a custom UI for a plug-in, a SetupResourceSettingSpecifier can be provided:
public interface SetupResourceSettingSpecifier extends SettingSpecifier {
/**
* Get the provider of setup resources for this specifier.
*
* @return The resource provider.
*/
SetupResourceProvider getSetupResourceProvider();
/**
* Get a set of properties to associate with the resources managed by this
* setting.
*
* @return A set of properties.
*/
Map<String, ?> getSetupResourceProperties();
}
This type of setting provides a SetupResourceProvider, which the SolarNode GUI will use to request the plug-in to provide custom resources to inject directly into the rendered HTML for that component:
/**
* Get a set of resources for specific context and content type.
*
* A {@code consumerType} represents the type of application the consumer of
* the setup resources represents. The {@link #WEB_CONSUMER_TYPE} represents
* a webapp, for example, and would be interested in resources such as
* JavaScript, CSS, images, etc.
*
* @param consumerType
* The consumer type to get all appropriate resources for.
* @param locale
* The desired locale.
* @return All matching resources, never <em>null</em>.
*/
Collection<SetupResource> getSetupResourcesForConsumer(String consumerType, Locale locale);
The consumerType
in this case will be web
, to signal to provider that resources appropriate for a webapp are needed. Notice that a java.util.Locale
object is passed in as well, so the provider can return localized resources:
Here's how a typical plug-in would provide the SetupResourceSettingSpecifier:
// this is configured via Blueprint XML
private SetupResourceProvider customSettingResourceProvider;
public List<SettingSpecifier> getSettingSpecifiers() {
List<SettingSpecifier> results = new ArrayList<SettingSpecifier>();
results.add(new BasicSetupResourceSettingSpecifier(
customSettingResourceProvider,
Collections.singletonMap("foo", "bar")));
return results;
}
A BasicSetupResourceSettingSpecifier is returned, which refers to a customSettingResourceProvider
that is configured in the plug-in's OSGi Blueprint XML:
<bean id="customSettingResourceProvider"
class="net.solarnetwork.node.setup.PatternMatchingSetupResourceProvider">
<property name="basenames">
<list>
<value>META-INF/settings/playpen-setting</value>
</list>
</property>
</bean>
In this case, the PatternMatchingSetupResourceProvider class is used, which uses a glob-like search path to find resources to expose. The files follow the same naming conventions as used by java.util.ResourceBundle
. In this case, files matching META-INF/settings/playpen-setting
will be provided by the plug-in:
The above screenshot shows a single localized resource playpen-setting
that matches that path:
- playpen-setting_en_NZ.html
- playpen-setting_ja.html
- playpen-setting.html
The most appropriate resource for the requested locale will then be used. This resource contains the HTML seen in the screen shots above:
<button type="button" class="btn btn-info playpen-setting-custom-button">
Press Me
</button>
<br />
<div class="alert alert-info">
This is a custom UI from the Setttings Playpen plugin.
</div>
In the example Java code earlier where the BasicSetupResourceSettingSpecifier
was configured, you may have noticed a Map
was also provided with a foo
key associated to a bar
value:
results.add(new BasicSetupResourceSettingSpecifier(
customSettingResourceProvider,
Collections.singletonMap("foo", "bar")));
Those properties will be rendered in a wrapper <div>
HTML element, as data-
attributes like this:
<div data-foo="bar">
<!-- Settings resource rendered here -->
</div>
This provides a way for the setting to pass runtime property values to the UI, for example to some custom JavaScript behavior as discussed in the next section.
You may have noticed that the HTML in the previous example did not contain any JavaScript to actually do anything when the button is pressed! Yet if you press the button, an alert appears like this:
The setting might have included JavaScript directly in the HTML resource via a <script>
tag, but
that is often inefficient. Instead, the plug-in can expose custom JavaScript and CSS resources that
get included in the overall page HTML.
Let's start with what we want to achieve: a JavaScript file playpen.js
should be included as a
linked <script>
in the <head>
area of the page HTML:
$(document).ready(function() {
'use strict';
function setupPlaypen(container) {
container.find('button.playpen-setting-custom-button').on('click', function(event) {
var btn = $(event.target),
foo = btn.parent().data('foo');
alert("Why, hello there! foo is " +foo +"!");
});
}
$('body').on('sn.settings.component.loaded', function(event, container) {
setupPlaypen(container);
});
setupPlaypen($());
});
This JavaScript uses jQuery to find any <button class="playpen-setting-custom-button">
and adds a
click
handler that shows a simple alert when the button is pressed that includes the value of the
foo
data property provided by the button's parent wrapper element.
Note the use of the custom
sn.settings.component.loaded
event to re-apply the custom initialization. This is needed because the SolarNode UI can dynamically load individual components, after the main UI has loaded. This event gives you visibility into those dynamically loaded components so you can integrate custom behaviors into the newly loaded component, like theclick
event handler shown in this example.
Our old friend SetupResourceProvider crosses our path again: any
SetupResourceProvider
registered as an OSGi service will be asked for web
resources when the
settings HTML is rendered, and application/javascript
and text/css
resources will be included in
the HTML's <head>
element, for example:
<script type="application/javascript" src="/a/rsrc/playpen.js"></script>
The path pattern /a/rsrc/{resourceUID}
is used to request localized resources from
SetupResourceProvider based on their UID
:
/**
* Get a specific resource for a resource UID.
*
* @param resourceUID
* The ID of the resource to get.
* @param locale
* The desired locale.
* @return The resource, or {@code null} if not available.
*/
SetupResource getSetupResource(String resourceUID, Locale locale);
In this example, the plug-in registers a provider via some more Blueprint XML:
<service interface="net.solarnetwork.node.setup.SetupResourceProvider">
<bean class="net.solarnetwork.node.setup.SimpleSetupResourceProvider">
<property name="resources">
<list>
<bean class="net.solarnetwork.node.setup.ClasspathSetupResource">
<argument value="playpen.js"/><!-- the resourceUID, e.g. URL like /a/rsrc/playpen.js -->
<argument value="playpen.js"/><!-- the resource filename -->
<argument value="net.solarnetwork.node.settings.playpen.SettingsPlaypen"/>
<argument value="#{T(net.solarnetwork.node.setup.SetupResource).JAVASCRIPT_CONTENT_TYPE}"/>
<argument value="#{T(net.solarnetwork.node.setup.SetupResource).WEB_CONSUMER_TYPES}"/>
<argument value="#{T(net.solarnetwork.node.setup.SetupResource).USER_ROLES}"/>
</bean>
</list>
</property>
</bean>
</service>
This time a SimpleSetupResourceProvider is registered, that exposes a single ClasspathSetupResource for the custom JavaScript file we want to inject. The SolarNode settings GUI will inject a link to that resource in the page HTML, making our custom JavaScript behavior available when viewing the setting page.