Note that if you want your settings to appear in the same project properties UI as the other settings, you’ll (probably) want to create a singleton resource [1], and need to register your own property editor panel on the DesignerContext you get access to in your designer module hook (com.inductiveautomation.ignition.designer.DesignerContext#addPropertyEditor).
[1]: A singleton resource is just defined as a resource with no ‘folder path’; unlike standard resources that can be inside arbitrary folders, singletons are, as the name implies, always a single resource per project. Think gateway event scripts, client event scripts, custom session properties, etc.
This is exactly what I am looking for! Is there an example of this? I took a look at the Markdown example above, but that’s more of a resource than a property.
No public examples (yet?), but here’s the highlights…
Create a mutable, Java serializable class. Have a public static final ResourceType for it with your module ID.
Create your editor class, extending AbstractPropertyEditorPanel. In the constructor, set up your text fields/dropdowns/checkboxes/etc (make them fields). Call the protected listenTo methods on AbstractPropertyEditorPanel with your controls; that helps set up the editor panel’s apply/ok staging.
Implement initProps - the Object you receive will be null, or an instance of your custom resource type. If it’s not null, use it to set your various edit field’s initial value.
Implement commit - take the values of the various edit fields and write them back to your mutable prop object.
That should be enough to get you started; the rest should be pretty self explanatory. The AbstractPropertyEditorPanel will automatically serialize your resource into a data.bin file using Java serialization.
I can’t seem to get my editor to show up under the Project category no matter what I try. It just always shows up underneath Perspective.
The commit event does not fire when I hit OK, or Apply and I am not sure if I am missing something there as well, so I my changes never save and the Object in initProps always come back as null.
Here is a copy of the gateway scope of the module, it only really includes this so I hope its easy to see what I am missing.
Oh, right. For better or for worse, the way things currently work this is a strictly ordered list, there’s no actual hierarchy. You can provide your own ‘category’ by creating a subclass of com.inductiveautomation.ignition.designer.propertyeditor.CategoryPanel, but I don’t see any way (without gross reflection hacks) to put your own panel below Project.
You missed the last half of step 2 Call listenTo in your constructor to tell the panel what components it needs to ‘care’ about having changed; like this:
dataKeyTextField = new JTextField(16);
listenTo(dataKeyTextField);
Also, not that important, but your method in DesignerHook doesn’t make much sense:
public Class<? extends AbstractPropertyEditorPanel> PropertyEditor() {
return PropertyEditor.class;
}
public void init() {
context.addPropertyEditor(PropertyEditor());
}
That could just be:
public void init() {
context.addPropertyEditor(PropertyEditor.class);
}
This is what happens when you start learning java and within one week you end up attempting to build something… lol
Last question, This is a default property per project right, so how exactly do I get it back out based on what project it was called in? I would presume it would be somewhere in the client scope in my scripting module where I implement the rpc handlers?
So, since you’re defining a resource (that may or may not exist, but there will only ever be one of per project) - you would just interact with the project to retrieve your resource and deserialize it from the data.bin wherever you need to read this value. There’s a ResourceUtil helper class with some methods you can use.
Something like:
Out of curiosity, is the class used to define these label/combobox/refreshicon exposed in any way via the sdk? It looks like its simplemented as com.inductiveautomation.ignition.client.util.gui.AbstractProfileOptionDropdown I am just struggling to create it.
I recreated my own from scratch, but I feel like if I can latch into the pre-defined one that’s already used it may be better for future proofing.
To anyone following along at home, this looks like it was the answer I was looking for.
Creating an implementation of that class, and providing its contained dropdown with my options gave me the dropdown and refresh icon. I am guessing that the labels in the above image are just labels and not a part of the rest of the component.
Is there an obvious reason as to why these are reset anytime the module restarts? The binary files are still there, but the method I use to get the data out start returning null as soon as the module restarts and until I open the designer and specifically the property editor panel for it.
I tried moving my ResourceType outside of the designer scope into common, thinking potentially that was related but it doesn’t seem to have fixed the problem.
It seems like you’re maybe accidentally mutating a singleton? Your ResourceType field should be the only static field; it’s just a marker to the rest of the project resource system to identify your resource. You should be retrieving/creating fresh copies of the actual resource object as needed; see the ResourceUtil snippet above.
I ran through it and at least I think I am creating and retrieving copies as needed?
This is my resource
public class Neo4JProperties {
public static final ResourceType RESOURCE_TYPE = new ResourceType("com.kgamble.neo4j", "Neo4JProperties");
private String defaultDatabase;
public String getDefaultDatabase() {
return defaultDatabase;
}
public void setDefaultDatabase(String database) {
defaultDatabase = database;
}
}
Where I provide it to the script in the rpc handler:
Still didnt seem to resolve the problem, when I publish the module and it posts to the gateway (or restart the module or gateway) the record is null until I open the designer property editor.
The only other thing I can think of is if I potentially implemented the property editor incorrectly?
public class GeneralPropertyEditor extends AbstractPropertyEditorPanel {
private final ScriptingFunctions rpc;
private Neo4JProperties neo4jProps = new Neo4JProperties();
private final DatabaseDropdown dropdown;
public GeneralPropertyEditor(DesignerContext context) {
super(new MigLayout("fill", "[pref!][grow,fill]", "[]15[]"));
rpc = ModuleRPCFactory.create(
"com.kgamble.neo4j.neo4j-driver",
ScriptingFunctions.class
);
dropdown = new DatabaseDropdown(false, rpc);
add(HeaderLabel.forKey("GeneralPropertyEditor.Database.Header"), "wrap r");
add(new JLabel(BundleUtil.get().getString("GeneralPropertyEditor.Database.Label")), "");
add(dropdown, "wrap");
listenTo(dropdown.getDropdown());
}
public List<String> getConnections() {
return rpc.getConnections();
}
@Override
public Object commit() {
if ( dropdown.getSelectedIndex() != -1 ) {
neo4jProps.setDefaultDatabase(dropdown.getSelectedItem().toString());
} else {
return null;
}
return neo4jProps;
}
@Override
public String getCategory() {
return "Neo4J";
}
@Override
public ResourceType getResourceType() {
return Neo4JProperties.RESOURCE_TYPE;
}
@Override
public String getTitleKey() {
return "GeneralPropertyEditor.General.Title";
}
@Override
public void initProps(Object props) {
if ( props == null ) {
dropdown.setSelectedItem(null);
} else {
Neo4JProperties updatedProps = (Neo4JProperties) props;
this.neo4jProps = updatedProps;
dropdown.setSelectedItem(neo4jProps.getDefaultDatabase());
}
}
}
I don’t think that’s what you want. I think you should always return neo4jProps - but if the dropdown has nothing selected, set the default database on the instance to null and handle it when you try to make a connection.
Unfortunately no luck with that either, will keep digging, but unfortunately my logging doesn’t seem to want to work from inside that getRpcHandler method.
If I set a project timer script to execute every 10 seconds that just gives me the property setting value, it behaves almost like the property setting is instantiated by the PropertyEditor.
Steps to recreate:
Set the project property to “A” and apply/publish changes
I think I found the culprit in another comment of yours!
The event that identified the issue was a Gateway Timer Script in a project. If what you are saying here is true, then I wonder if that’s why I don’t actually get the project properties, or even the project name in the getRpcHandler method until AFTER some client is opened that instantiates the rpc handler.
The function executes, calls the rpc handler with no project name, can’t get the properties because the name is blank, returns a blank property set, and then can’t get the connection that its defined to get!
Does this sound remotely close to what may be happening? If so then I may just need to make a default database per gateway, and not per project.