Configure a module with project resources

Hello!
I made a scripting function module that needs some improving. This function has some parameters that I would like to erase, and get the information from somewhere else. The plan is to make a new folder in the Designer’s Project Browser which would open a window similar to “Transaction groups” . In this window I would like to drag tags and maybe enter some parameters. Can I get any input in how to do it?
For now I have read this guide: https://docs.inductiveautomation.com/display/SE/Working+with+Project+Resources
and this post:
Project Resources
but I don’t really know how to make this happen.

Thank you!

The SDK example is completely outdated, as the forum post you linked mentioned. The details in the forum post are mostly correct, but don’t answer a lot of your questions.

If you want something to work off of, I actually recently did a standalone project resource example - the only (potential) problem being that it’s in Kotlin, rather than Java: https://github.com/paul-griffith/markdown-resources

Briefly, Kotlin is an alternate language that can compile down to Java bytecode to run on the JVM. Its syntax is roughly similar to Java, but without a lot of boilerplate - so that may be enough of a hint to get you started, but maybe not.

For a start:

Notice how my DesignerHook registers a ResourceWorkspace implementation:

That MarkdownResourceWorkspace, when constructing its parent class, creates a ResourceDescriptor:

Thank you so much @PGriffith, this is really useful. I will look into this thoroughly.

Cheers!

Hello again @PGriffith,

I’m having some trouble understanding this part of the code:

override fun createWorkspaceHomeTab(): Optional<JComponent> {
        return object : WorkspaceWelcomePanel("Markdown Notes Workspace Title", null, null) {
            override fun createPanels(): List<JComponent> {
                return listOf(
                    ResourceBuilderPanel(context, "markdown note", MarkdownResource.RESOURCE_TYPE.rootPath(), listOf(
                        ResourceBuilderDelegate.build(
                            "markdown note",
                            VectorIcons.get("resource-note"),
                            newMarkdownNote
                        )
                    ), this@MarkdownWorkspace::open),
                    RecentlyModifiedTablePanel(context,
                        MarkdownResource.RESOURCE_TYPE,
                        "markdown notes",
                        this@MarkdownWorkspace::open)
                )
            }
        }.toOptional()
    }

I can’t see clearly what panels is the method creating.
Specially this part:

this@MarkdownWorkspace::open

What would be the Java substitute?

Also, is there any templates available for a panel similar to the Transactions group one? Or any template at all :slight_smile: I’m having troubles seeing where can I add components with JFrame.add(JComponent)

Thank you again,

Miguel Luna

this@MarkdownWorkspace::open

Is just a non-local method reference - you can do method references in Java (8+), but with some slightly different restrictions - the more typical lambda syntax (which the method reference is just syntactic sugar for) would be something like resourcePath -> this.open(resourcePath) - you’re providing a lambda, that conforms to the Consumer<ResourcePath> functional interface, and thus ‘accepts’ a ResourcePath argument. Method references are allowed as sugar, because as long as your interface only defines one method, and that method only defines one argument, there’s no ambiguity for the compiler to resolve - so you could also use a Java method reference, eg:
PipelineBlockWorkspace.this::openPipeline

There’s no examples for sql bridge - it’s a one-off workspace that was basically ‘ported’ over from the original implementation in FactorySQL. Are you just looking for a ‘singleton’ resource editor? You don’t have to extend TabbedResourceWorkspace - it’s just that that’s usually the simplest path and covers most use cases.

Thank you for your response,
Right now I’m trying to make a resource from where I can get different settings for my module, extending TabbedResourceWorkspace. To simplify the process of making the layout, I’m using Netbean’s swing workspace, making something similar to this:


Which results in the following code:

        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(48, 48, 48)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addComponent(jLabel3, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(jScrollPaneIrradiance, javax.swing.GroupLayout.DEFAULT_SIZE, 292, Short.MAX_VALUE)
                    .addComponent(jScrollPaneTemperature)
                    .addComponent(jScrollPaneEnergy))
                .addGap(93, 93, 93)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jLabel4)
                    .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(jLabel5)
                    .addComponent(jTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(jLabel6)
                    .addComponent(jTextField3, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(jComboBox1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap(290, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(40, 40, 40)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabel1)
                    .addComponent(jLabel4))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(jScrollPaneTemperature, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(18, 18, 18)
                        .addComponent(jLabel2)
                        .addGap(18, 18, 18)
                        .addComponent(jScrollPaneIrradiance, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(18, 18, 18)
                        .addComponent(jLabel5)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(jLabel6)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jTextField3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addComponent(jComboBox1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(jLabel3)
                .addGap(18, 18, 18)
                .addComponent(jScrollPaneEnergy, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(60, Short.MAX_VALUE))
        );

        pack();

Then, I add that layout to the method init() in MarkdownEditor, using this.setLayout(…). Keep in mind that this is in an extended ResourceEditor class. The problem that I’m finding is, when I try to create a new Resource Editor, I find the following error:

If this method of making the layout is wrong, please, let me know! I’ve looked into MigLayout but it seems pretty complicated, at least compared to Netbean’s graphic workspace.
Thank you so much for your help!
Miguel Luna

That layout is fine - or at least, the error message isn’t indicating any problem in the layout. Your PREditor class isn’t handling null data coming in properly - most likely because the resource you’re creating is malformed, or has the wrong data keys, or something along those lines. Notice the stack trace going into ResourceEditor#loadResource, then deserialize. When implementing a ResourceEditor for a custom resource type, you need to choose between:

  1. Leaving the resource ‘generic’ - your resource object must be Java serializable (implementing the Serializable interface, and a few other limitations) and will be stored as an opaque data.bin file - meaning no ‘nice’ diffing between versions for other users.
  2. Implement serialize(T): byte[]/deserialize(byte[]): T - your resource will still be stored in the data.bin file, but you can choose to use your own object serialization/deserialization strategy.
  3. Go fully custom and implement serializeResource and deserialize(ProjectResource), as I did in the MarkdownEditor class. That way, you can choose to store your resource file(s) as their own attributes on disk - in the markdown editor, this is just the note.md file.

Assuming you’re still working directly off my example, then the most likely issue is that you have a malformed resource present - you should delete your existing TestResource, and ensure that the action to create a new note (in the workspace configuration) is creating a resource with the appropriate data key - which, I just noticed, my example is not :man_facepalming:.

1 Like

I used mostly MigLayout("fill") and MigLayout("") for my Ethernet/IP workspace. Easy-peasy.

2 Likes

What would be the right data key? I mean, how do I know it’s correct?

Hello Phil. Did you find it hard adding JComponents to your layout using MigLayout? Any advices?

The example creates a Consumer<ProjectResourceBuilder>, which puts a readme.md key into the resource; that should instead be note.md/the constant value of MarkdownResource.DATA_KEY.

And I’ll echo MigLayout - there’s a learning curve, but it can do basically everything you could possibly want for layout, and it’s a whole lot less verbose than what most layout builders spit out. But, that’s a secondary concern compared to actually getting this working.

2 Likes

Okay, thanks!