Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add components using model/store delegates and dynamic routes. #28

Merged
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -281,3 +281,5 @@ Ignition-Build/

private/
sign.props
gateway/src/main/resources/mounted/RadComponents.css
gateway/src/main/resources/mounted/RadComponents.js
@@ -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.


@@ -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.


@@ -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")
}
}

@@ -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)
}
}

@@ -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"))
}
@@ -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')
}
@@ -9,11 +9,17 @@

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
)
);
}
@@ -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";
@@ -28,7 +29,7 @@
* 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)
@@ -38,4 +39,5 @@
.shouldAddToPalette(true)
.withResources(RadComponents.BROWSER_RESOURCES)
.build();

}
@@ -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();
}
@@ -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();

}
@@ -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": ""
}
}
}
}
@@ -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": ""
}
}
}
}
@@ -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": ""
}
}
}
}
@@ -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
}
@@ -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}")
}


@@ -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);
}
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.