Skip to content

Commit

Permalink
add components using model/store delegates and dynamic routes. (induc…
Browse files Browse the repository at this point in the history
…tiveautomation#28)

* wip, tag delegate functional.

* tag counter working

* add messenger scss, polish, documentation

* minor bug fixes and cleanup

* remove dev build tasks

* update plugin version, minor refactor of util method

* uncomment buildscript release repo

* minor cleanup, don't write empty string

* 8.0.3 final release versions

* update npm dependencies
  • Loading branch information
PerryAJ authored Sep 3, 2019
1 parent d57b5e9 commit 3a2fa0e
Show file tree
Hide file tree
Showing 39 changed files with 3,317 additions and 1,229 deletions.
2 changes: 2 additions & 0 deletions perspective-component/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -281,3 +281,5 @@ Ignition-Build/

private/
sign.props
gateway/src/main/resources/mounted/RadComponents.css
gateway/src/main/resources/mounted/RadComponents.js
35 changes: 29 additions & 6 deletions perspective-component/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
# Perspective Component Module Example

This is an example module which adds a component to the Perspective module. In an effort to minimize complexity, this
example follows the bare minimum required to create and register a Perspective component for Ignition 8.0's new
visualization module. As a result, this example does not follow _all_ best practices for modern web applications. For
example, the javascript bundled via webpack is not minified and obfuscated. That said, we've chosen tools and structure
that we've determined to be the best overall compromise in terms of convenience, maintainability and power.
This is an example module which adds some custom components to the Perspective module. There are 3 different components
in this example, each exercising different aspects of the Perspective component API, as well as demonstrating
a few different ways of dealing with data and configuration of the components in the gateway designer.

Additionally, this example is only one of countless ways a savvy developer can build a module targeting Perspective.
### Summary of Components

#### 1. Image

Most basic component, provides a reference for the 'bare minimum' required to create a component and register it in the
appropriate registries such that it's available on the palette in the designer and in the client at runtime.

#### 2. TagCounter

Demonstrates a component that provides a custom Java/Swing based configuration UI when the component is selected in the
designer. In addition, utilizes the gateway's `RouteGroup` api to create a web endpoint from which the component can
fetch data outside of the Perspective property tree system.

#### 3. Messenger Component

Somewhat silly example demonstrates the use of a Mobx based state class (component model/store) to contain state outside
of the PropertyTree system, as well as demonstrates the use of the Store/Model Message Delegate API, which is a way to
send data between the gateway and browser via perspective's 'real time' websocket communication channel.

These examples are only a few of the countless ways a savvy developer can build a module targeting Perspective.
Ultimately it's up to implementors to choose the tools they prefer.


Expand Down Expand Up @@ -160,3 +177,9 @@ Building this module through the gradle wrapper is easy!

How to configure and customize the build is outside the scope of this example. We encourage you to read the docs of
the various tools used and linked above to determine the appropriate build configurations for your project.

### SDK Tips

Perspective is a fairly complex system that is seeing regular changes/additions. While we consider the APIs 'mostly stable', there will likely be additions and/or breaking changes as it matures. As a result, we encourage testing modules against each version of Ignition/Perspective you intend to support.


13 changes: 8 additions & 5 deletions perspective-component/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ buildscript {
mavenLocal()
mavenCentral()
jcenter()
maven { url "https://nexus.inductiveautomation.com/repository/inductiveautomation-releases/" }
maven { url "https://nexus.inductiveautomation.com/repository/inductiveautomation-thirdparty/" }
maven { url "https://nexus.inductiveautomation.com/repository/inductiveautomation-releases/" }
maven { url "https://nexus.inductiveautomation.com/repository/inductiveautomation-snapshots/" }

}

ext.sdk_version = "8.0.3"

dependencies {
classpath("com.inductiveautomation.gradle:ignition-module-plugin:1.2.9")
classpath("com.inductiveautomation.gradle:ignition-module-plugin:1.2.10-SNAPSHOT")
}
}

Expand Down Expand Up @@ -72,7 +75,7 @@ allprojects {
// check for new versions of dependencies more frequently than default 24 hours.
configurations.all {
resolutionStrategy {
cacheChangingModulesFor(600, TimeUnit.SECONDS)
cacheChangingModulesFor(30, TimeUnit.SECONDS)
}
}

Expand All @@ -95,8 +98,8 @@ allprojects {
}

task deepClean() {
dependsOn clean

dependsOn allprojects.collect { "${it.path}:clean" }
description "Executes clean tasks and remove node plugin caches."
doLast {
delete(file(".gradle"))
}
Expand Down
9 changes: 4 additions & 5 deletions perspective-component/common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ targetCompatibility = JavaVersion.VERSION_11

dependencies {
// compileOnly is the gradle equivalent to "provided" scope.
compileOnly("com.inductiveautomation.ignitionsdk:ignition-common:8.0.1")
compileOnly("com.inductiveautomation.ignitionsdk:perspective-common:8.0.1")

// this one uses the "shorthand" string notation instead of the map notation above. Does same exact thing.
compileOnly("com.google.code.gson:gson:2.8.2")
compile("com.inductiveautomation.ignitionsdk:ignition-common:${sdk_version}")
compile("com.inductiveautomation.ignitionsdk:perspective-common:${sdk_version}")
compileOnly(group: 'com.google.guava', name: 'guava', version: '23.3-jre')
compileOnly(group: 'com.inductiveautomation.ignition', name: 'ia-gson', version: '2.8.5')
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@ public class RadComponents {

public static final String MODULE_ID = "org.fakester.radcomponents";
public static final String URL_ALIAS = "radcomponents";
public static final String COMPONENT_CATEGORY = "Rad Things";
public static final Set<BrowserResource> BROWSER_RESOURCES =
Sets.newHashSet(
new BrowserResource(
"rad-components",
String.format("/res/%s/js/RadComponents.js", URL_ALIAS),
BrowserResource.ResourceType.JS)
"rad-components-js",
String.format("/res/%s/RadComponents.js", URL_ALIAS),
BrowserResource.ResourceType.JS
),
new BrowserResource("rad-components-css",
String.format("/res/%s/RadComponents.css", URL_ALIAS),
BrowserResource.ResourceType.CSS
)
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@

/**
* Describes the component to the Java registry so the gateway and designer know to look for the front end elements.
* In a 'common' scope so that it's referencable by both gateway and designer.
*/
public class Image {
public class Image {

// unique ID of the component which perfectly matches that provided in the javascript's ComponentMeta implementation
public static String COMPONENT_ID = "rad.display.image";
Expand All @@ -28,7 +29,7 @@ public class Image {
* build the descriptor for this one component. Icons on the component palette are optional.
*/
public static ComponentDescriptor DESCRIPTOR = ComponentDescriptorImpl.ComponentBuilder.newBuilder()
.withPaletteCategory("Rad Things")
.withPaletteCategory(RadComponents.COMPONENT_CATEGORY)
.withPaletteDescription("A simple image component.")
.withId(COMPONENT_ID)
.withModuleId(RadComponents.MODULE_ID)
Expand All @@ -38,4 +39,5 @@ public class Image {
.shouldAddToPalette(true)
.withResources(RadComponents.BROWSER_RESOURCES)
.build();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.fakester.common.component.display;

import javax.swing.ImageIcon;

import com.inductiveautomation.ignition.common.jsonschema.JsonSchema;
import com.inductiveautomation.perspective.common.api.ComponentDescriptor;
import com.inductiveautomation.perspective.common.api.ComponentDescriptorImpl;
import org.fakester.common.RadComponents;


/**
* Common meta information about the Messenger component. See {@link Image} for docs on each field.
*/
public class Messenger {

public static String COMPONENT_ID = "rad.display.messenger";


public static JsonSchema SCHEMA =
JsonSchema.parse(RadComponents.class.getResourceAsStream("/messenger.props.json"));

public static ComponentDescriptor DESCRIPTOR = ComponentDescriptorImpl.ComponentBuilder.newBuilder()
.withPaletteCategory(RadComponents.COMPONENT_CATEGORY)
.withPaletteDescription("A component that uses component messaging and data fetching delegates.")
.withId(COMPONENT_ID)
.withModuleId(RadComponents.MODULE_ID)
.withSchema(SCHEMA) // this could alternatively be created purely in Java if desired
.withPaletteName("Gateway Messenger")
.withDefaultMetaName("messenger")
.shouldAddToPalette(true)
.withResources(RadComponents.BROWSER_RESOURCES)
.build();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.fakester.common.component.display;

import com.inductiveautomation.ignition.common.jsonschema.JsonSchema;
import com.inductiveautomation.perspective.common.api.ComponentDescriptor;
import com.inductiveautomation.perspective.common.api.ComponentDescriptorImpl;
import org.fakester.common.RadComponents;

/**
* Meta information about the TagCounter component. See {@link Image} for docs on each field.
*/
public class TagCounter {
public static String COMPONENT_ID = "rad.display.tagcounter";

public static JsonSchema SCHEMA =
JsonSchema.parse(RadComponents.class.getResourceAsStream("/tagcounter.props.json"));

public static ComponentDescriptor DESCRIPTOR = ComponentDescriptorImpl.ComponentBuilder.newBuilder()
.withPaletteCategory(RadComponents.COMPONENT_CATEGORY)
.withPaletteDescription("A component that displays the number of tags associated with a gateway.")
.withId(COMPONENT_ID)
.withModuleId(RadComponents.MODULE_ID)
.withSchema(SCHEMA) // this could alternatively be created purely in Java if desired
.withPaletteName("Tag Counter")
.withDefaultMetaName("tagCounter")
.shouldAddToPalette(true)
.withResources(RadComponents.BROWSER_RESOURCES)
.build();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"type": "object",
"properties": {
"messageConfig": {
"type": "object",
"description": "A set of key:value pairs where the key is the cutoff point for when a message should be displayed, and the value is a string containing the message displayed when the 'key' number of messages is reached.",
"propertyNames": {
"pattern": "^[0-9][0-9]*$"
},
"default": {
"0": "None",
"1": "Messages!",
"5": "Lots of Messages!",
"10": "Literally ten+ messages!",
"25": "Carpal Tunnel Warning!"
}
},
"style": {
"$ref": "urn:ignition-schema:schemas/style-properties.schema.json",
"default": {
"classes": ""
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
"type": "string",
"description": "url of image to display on component.",
"default": "/res/radcomponents/img/bananatime.gif"
},
"style": {
"$ref": "urn:ignition-schema:schemas/style-properties.schema.json",
"default": {
"classes": ""
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"type": "object",
"properties": {
"fontSize": {
"type": "string",
"description": "Size of font to display the tag count in, as a valid css size",
"default": "3rem"
},
"interval": {
"type": "number",
"description": "Rate (in ms) in which to get a new tag count. Only one request will happen at a time.",
"default": 1000
},
"style": {
"$ref": "urn:ignition-schema:schemas/style-properties.schema.json",
"default": {
"classes": ""
}
}
}
}
10 changes: 10 additions & 0 deletions perspective-component/designer-launcher/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
plugins {
id 'java'
}


dependencies {
compile "com.inductiveautomation.ignitionsdk:designer-launcher:${sdk_version}"
compile "com.inductiveautomation.ignitionsdk:ignition-common:${sdk_version}"
compile project(":designer") // local designer scoped subproject
}
8 changes: 4 additions & 4 deletions perspective-component/designer/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ targetCompatibility = JavaVersion.VERSION_11

dependencies {
compile(project(":common"))
compileOnly("com.inductiveautomation.ignitionsdk:designer-api:8.0.1")
compileOnly("com.inductiveautomation.ignitionsdk:perspective-common:8.0.1")
compileOnly("com.inductiveautomation.ignitionsdk:perspective-designer:8.0.1")
compileOnly("com.inductiveautomation.ignitionsdk:ignition-common:8.0.1")
compileOnly("com.inductiveautomation.ignitionsdk:designer-api:${sdk_version}")
compileOnly("com.inductiveautomation.ignitionsdk:perspective-common:${sdk_version}")
compileOnly("com.inductiveautomation.ignitionsdk:perspective-designer:${sdk_version}")
compileOnly("com.inductiveautomation.ignitionsdk:ignition-common:${sdk_version}")
}


Original file line number Diff line number Diff line change
@@ -1,27 +1,74 @@
package org.fakester.designer;

import com.inductiveautomation.ignition.common.BundleUtil;
import com.inductiveautomation.ignition.common.licensing.LicenseState;
import com.inductiveautomation.ignition.common.util.LoggerEx;
import com.inductiveautomation.ignition.designer.model.AbstractDesignerModuleHook;
import com.inductiveautomation.ignition.designer.model.DesignerContext;
import com.inductiveautomation.perspective.designer.DesignerComponentRegistry;
import com.inductiveautomation.perspective.designer.api.ComponentDesignDelegateRegistry;
import com.inductiveautomation.perspective.designer.api.PerspectiveDesignerInterface;
import org.fakester.common.component.display.Messenger;
import org.fakester.common.component.display.Image;
import org.fakester.common.component.display.TagCounter;
import org.fakester.designer.component.TagCountDesignDelegate;


/**
* The 'hook' class for the designer scope of the module. Registered in the ignitionModule configuration of the
* root build.gradle file.
*/
public class RadDesignerHook extends AbstractDesignerModuleHook {

private static final LoggerEx logger = LoggerEx.newBuilder().build("RadComponents");

public RadDesignerHook() {
LoggerEx.newBuilder().build("RadComponents").info("Registering Rad Components in Designer!");
static {
BundleUtil.get()
.addBundle("radcomponents", RadDesignerHook.class.getClassLoader(), "radcomponents");
}

public RadDesignerHook() {
logger.info("Registering Rad Components in Designer!");
}
private DesignerContext context;
private DesignerComponentRegistry registry;
private ComponentDesignDelegateRegistry delegateRegistry;

@Override
public void startup(DesignerContext context, LicenseState activationState) throws Exception {
PerspectiveDesignerInterface.getComponentRegistry(context)
.registerComponent(Image.DESCRIPTOR);
this.context = context;
init();
}

private void init() {
logger.debug("Initializing registry entrants...");

PerspectiveDesignerInterface pdi = PerspectiveDesignerInterface.get(context);

registry = pdi.getDesignerComponentRegistry();
delegateRegistry = pdi.getComponentDesignDelegateRegistry();

// register components to get them on the palette
registry.registerComponent(Image.DESCRIPTOR);
registry.registerComponent(TagCounter.DESCRIPTOR);
registry.registerComponent(Messenger.DESCRIPTOR);

// register design delegates to get the special config UI when a component type is selected in the designer
delegateRegistry.register(TagCounter.COMPONENT_ID, new TagCountDesignDelegate());

}


@Override
public void shutdown() {
removeComponents();
}

private void removeComponents() {
registry.removeComponent(Image.COMPONENT_ID);
registry.removeComponent(TagCounter.COMPONENT_ID);
registry.removeComponent(Messenger.COMPONENT_ID);

delegateRegistry.remove(TagCounter.COMPONENT_ID);
}
}
Loading

0 comments on commit 3a2fa0e

Please sign in to comment.