# I am looking for a tutor for module development

**URL:** <https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030>\
**Category:** Module Development\
**Tags:** ignition80\
**Created:** [January 14, 2022, 3:18pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030 "2022-01-14T15:18:39Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![bkarabinchak.psi](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bkarabinchak.psi/32/83567_2.png) [@bkarabinchak.psi](https://forum.inductiveautomation.com/u/bkarabinchak.psi)\
**Post date:** [January 14, 2022, 3:18pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/1 "2022-01-14T15:18:39Z")

</div>

Good morning,

I hope this doesn’t break any forum guidelines but I can’t think of a more appropriate place to ask - I’ve looked at other tutoring sites and though I can find people who can help me with Java or Swing, I can’t find any that also know about integrating them with the Ignition ecosystem.

I am looking for someone who can help guide me in the ways of module development for Ignition specifically. Some java specific things like POM files, and things related to the workflow I will need assistance on and how they relate to Ignition. I have some background knowledge Java/Algorithms/Data structures from college but using modern java workflow in practice is not something I have any real knowledge of and will need assistance on.

I am willing to negotiate on price, I don’t expect anyone will help me out for free. I was hoping maybe 1 hour a week for at least 3-4 weeks, preferably on the weekend, so I can work on what we talked about and regroup. All my previous efforts of self-learning eventually end up with me hitting a wall where I don’t know what certain things mean and need someone with experience to help guide me.

Feel free to DM me or leave a reply.

Again apologies if this breaks any terms of this forum but I can’t think of a better pool of people who might be able to help me out with learning this.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [January 14, 2022, 3:37pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/2 "2022-01-14T15:37:14Z")

</div>

No apologies required.

I’d offer, but you pointed out things that I refuse to use (maven, pom files), so I can’t help you there. (I’ll jump directly to gradle when I’m no longer supporting v7.9.)

Since you don’t seem to be pressed schedule-wise, why not just ask your questions here?

---

<div class="post-metadata">

**Author:** ![bkarabinchak.psi](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bkarabinchak.psi/32/83567_2.png) [@bkarabinchak.psi](https://forum.inductiveautomation.com/u/bkarabinchak.psi)\
**Post date:** [January 14, 2022, 4:03pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/3 "2022-01-14T16:03:40Z")

</div>

> [@pturmel](#):
>
> I’d offer, but you pointed out things that I refuse to use (maven, pom files), so I can’t help you there. (I’ll jump directly to gradle when I’m no longer supporting v7.9.)

I only say that because using a Maven archetype was the only way I was personally able to get the tutorial / archetypes that Ignition already built as example to work (though I wasn't able to do much more than import the jar file to the gateway and use the example in Designer). If there are other ways I am more than open to it - I don't know nearly enough to be opinionated on this sort of stuff yet lol.

I ask for a tutor because I know so little regarding what seems like basic principles re: java build workflow and integrating with Ignition's codebase that I don't even know where to begin asking questions if I am being honest.

I also seem to learn best with a tutor, it keeps me motivated week to week.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [January 14, 2022, 5:12pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/4 "2022-01-14T17:12:13Z")

</div>

> [@bkarabinchak.psi](#):
>
> Again apologies if this breaks any terms of this forum but I can’t think of a better pool of people who might be able to help me out with learning this.

Nah, you're fine. We have occasional job posts/job seekers as well; I think as long as they're not inundating the forum it's not a big deal.

I think at least part of the confusion for people who might be familiar with _programming_, but struggle to get started with module development, is that you're learning two separate things at the same time.  
There's a particular Java build stack (whether you choose Maven or Gradle or whatever), and then attached to it the Ignition API surface, which is fairly massive.

It's also a problem from the other direction: people tell us they want a 'module development' course, but it's almost impossible to target everyone's experience with the different aspects.

I guess I'll start with some very high level principles.  
Your build system of choice (_generally_ Gradle or Maven, there's other options but they're certainly the most common these days) is usually in charge of running the Java compiler over your Java source files to generate a jar file. JAR files are just ZIPs full of .class files, which are themselves _essentially_ just JVM bytecode. That's an important point of distinction - once it's a class file, it's not technically Java anymore. That's part of how alternate JVM languages like Kotlin and Scala work - they just have to emit valid bytecode, without the limitations of Java's own syntax and compiler. (There are also tools available to 'decompile' bytecode back to ~the input Java code, but there's always information lost, such as parameter names for method and other issues. IntelliJ has a pretty good decompiler built in, which can help to look into library code you don't have the underlying source for).

By **convention** , Maven (the big kahuna Java build system for a long time) looks for Java files to compile in `src/main/java`. Every directory under that root _by convention_ defines your package structure. Package structure is important, because there's a single global namespace in the final JVM - every class must have a unique fully-qualified name. _By convention_, the package name you use should be a reversed domain name that you control - that allows you to "prove" that you wrote the code, and is useful for library distribution (and part of the verification process on repositories like Maven Central). Gradle follows these conventions precisely because Maven was dominant for such a long time. However, in both Maven and Gradle, you _can_ readily change these things (it's just generally not a good idea unless you have a good reason).

Separate from the actual source file organization, you can organize your projects themselves into "subprojects" (what Gradle calls them, Maven may have a different name). Basically these are just folders at the top-level that contain their own build-system descriptors (`build.gradle(.kts)`/`pom.xml`). Somewhere on the top level, you'll define which subprojects you have, and your build system will automatically reach into those subfolders and build _their_ projects too.  
For instance, in the [Perspective Component example](https://github.com/inductiveautomation/ignition-sdk-examples/tree/master/perspective-component), there's four subprojects: `common`, `designer`, `gateway`, and `web`. Those are outlined in the [`settings.gradle`](https://github.com/inductiveautomation/ignition-sdk-examples/blob/master/perspective-component/settings.gradle) at the root.

So that's the _very_ broad overview of Java building. Pivoting a little to Ignition:

That `common`/`gateway`/`designer` split is a common _convention_ in the realm of Ignition, because 1. code re-use is nice, and 2. you almost always want to deliver different artifacts to the different Ignition scopes (because they have different APIs and different capabilities). So you build those subprojects, and each outlines their own dependencies on Ignition; e.g. `common` depends on Ignition's own `common` layer: [https://github.com/inductiveautomation/ignition-sdk-examples/blob/master/perspective-component/common/build.gradle#L12](https://github.com/inductiveautomation/ignition-sdk-examples/blob/master/perspective-component/common/build.gradle#L12)  
And then `gateway` can depend on both Ignition specific `gateway` apis **and the `common` subproject** :  
[https://github.com/inductiveautomation/ignition-sdk-examples/blob/master/perspective-component/gateway/build.gradle#L10](https://github.com/inductiveautomation/ignition-sdk-examples/blob/master/perspective-component/gateway/build.gradle#L10)  
_That_ is how you tell the build system (and your IDE) "this code I'm writing in `gateway` can use anything I've written in `common`.

The _only_ fundamental unit you need to provide as a module is _some_ implementation of the appropriate hook class for your desired scope. Basically, you author a class that implements the \*Hook (or, better extends the abstract class we provide) for the right scope, e.g. `AbstractGatewayModuleHook` for the gateway:

> <https://github.com/inductiveautomation/ignition-sdk-examples/blob/master/perspective-component/gateway/src/main/java/org/fakester/gateway/RadGatewayHook.java>

There are various lifecycle methods that will be invoked _for you_ - `setup`, `startup`, and `shutdown`. Those are how your module gets access to the rest of Ignition - from the GatewayContext you get in `setup`, you can get access to the myriad subsystems available in Ignition.

However, just implementing the class and putting it into a jar file isn't enough. Ignition doesn't "know" enough to know how to actually do anything with one or more plain .jar files. So there's one final piece - the actual module .modl file. The actual module file is nothing more than a ZIP file that contains a bunch of .jar files and a `module.xml` file. That `module.xml` file is crucial - it tells Ignition _how_ to load your various jar files, what scopes they belong to, and most importantly, what your actual hook classes are:

```python
<?xml version="1.0" encoding="UTF-8"?>
<modules>
	<module>
		<id>com.inductiveautomation.ignition.examples.hce</id>
		<name>Gateway Webpage Example</name>
		<description>Implements a fake "Home Connect" hub status and configuration page</description>
		<version>1.0.0-SNAPSHOT</version>
		<requiredignitionversion>8.1.0-SNAPSHOT</requiredignitionversion>
		<depends scope="G">com.inductiveautomation.opcua</depends>
		<jar scope="G">gateway-webpage-gateway-1.0.0-SNAPSHOT.jar</jar>
		<hook scope="G">com.inductiveautomation.ignition.examples.hce.GatewayHook</hook>
	</module>
</modules>

```

Generating these XML files by hand would be annoying, which is why we provide Maven and Gradle plugins to build modules for you. The 'new' generation is available [here](https://github.com/inductiveautomation/ignition-module-tools), and is the exact same tool we use internally.  
Your build system of choice should provide a place to configure this plugin, e.g. Gradle:  
[https://github.com/inductiveautomation/ignition-sdk-examples/blob/master/perspective-component/build.gradle#L34](https://github.com/inductiveautomation/ignition-sdk-examples/blob/master/perspective-component/build.gradle#L34)  
or Maven:

> <https://github.com/inductiveautomation/ignition-sdk-examples/blob/master/report-component/report-component-build/pom.xml#L36>

I just kinda rambled that all out, and my hand hurts now, but hopefully that helps a bit to orient.

If you've got particular questions on _how_ to approach something in Ignition, I'd definitely say to ask here on the forums. More general build system questions might be better suited for other places, but it doesn't really hurt to ask here either.

---

<div class="post-metadata">

**Author:** ![bkarabinchak.psi](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bkarabinchak.psi/32/83567_2.png) [@bkarabinchak.psi](https://forum.inductiveautomation.com/u/bkarabinchak.psi)\
**Post date:** [January 14, 2022, 7:08pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/5 "2022-01-14T19:08:23Z")

</div>

@paul-griffith Thank you for the high level overview.

I was able to make the vision-component-archetype so going along with your post I just had a few clarifying questions. Here is what I got by default

![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/4/8/48a317a435138b87d381699445feb53723ff3ccc.png)

> By **convention** , Maven (the big kahuna Java build system for a long time) looks for Java files to compile in `src/main/java` . Every directory under that root _by convention_ defines your package structure. Package structure is important, because there’s a single global namespace in the final JVM - every class must have a unique fully-qualified name.

So I circled two parts with `src/main/java` - both of these get put into the same jar/module you're saying? That Maven starts at my top level folder "DateSelector", then for every subfolder looks to see if it has a `src/main/java` and knows that that is what is meant to be turned into byte code yes?

> Package structure is important, because there’s a single global namespace in the final JVM - every class must have a unique fully-qualified name. _By convention_ , the package name you use should be a reversed domain name that you control - that allows you to “prove” that you wrote the code, and is useful for library distribution (and part of the verification process on repositories like Maven Central).

Ok reversed domain name used to confuse me but that's just `com.bkarabinchak` - reversed domain becuase a normal domain is `google.com` and a reversed domain name is `com.google`. And this must be unique if the package is every to be distrubted via Maven or Gradle - Maven/Gradle can't have two libraries that are `com.something` because how would it know which one to give you.

> Separate from the actual source file organization, you can organize your projects themselves into “subprojects” (what Gradle calls them, Maven may have a different name). Basically these are just folders at the top-level that contain their own build-system descriptors ( `build.gradle(.kts)` / `pom.xml` ). Somewhere on the top level, you’ll define which subprojects you have, and your build system will automatically reach into those subfolders and build _their_ projects too.  
> For instance, in the [Perspective Component example](https://github.com/inductiveautomation/ignition-sdk-examples/tree/master/perspective-component), there’s four subprojects: `common` , `designer` , `gateway` , and `web` . Those are outlined in the [`settings.gradle`](https://github.com/inductiveautomation/ignition-sdk-examples/blob/master/perspective-component/settings.gradle) at the root.

Ok so if I wanted to, and knew how, what I could do something like this -  
 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/9/4/948c23762c969200735b5b4414ecd9e764c95c94.png)  
and keep my actual module specific code in MySubProject and somehow modify the building instructions so that the `MySubProject` folder is included in my build process. This also isn't a necessity but is just a much cleaner way to keep your code organized yes (which is important to me but I just am trying to parse what is required and what is convention).

> Basically, you author a class that implements the \*Hook (or, better extends the abstract class we provide) for the right scope, e.g. `AbstractGatewayModuleHook` for the gateway:  
> ...  
> So there’s one final piece - the actual module .modl file. The actual module file is nothing more than a ZIP file that contains a bunch of .jar files and a `module.xml` file. That `module.xml` file is crucial - it tells Ignition _how_ to load your various jar files, what scopes they belong to, and most importantly, what your actual hook classes are:

Ok so the combination of my doing `extend AbstractGatewayModuleHook` and the `module.xml` in conjunction are what let Ignition know Hey this class is to be used on the Gateway context if I am understanding that right?

Stuff like this -

> There are various lifecycle methods that will be invoked _for you_ - `setup` , `startup` , and `shutdown` . Those are how your module gets access to the rest of Ignition - from the GatewayContext you get in `setup` , you can get access to the myriad subsystems available in Ignition.

Is there any documentation where I would find this sort of thing out? I've seen it before in tutorials and other module development questions about having to do the setup/startup/shutdown, but I never found where I would find that if I didn't have access to this forum.

---

<div class="post-metadata">

**Author:** ![bkarabinchak.psi](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bkarabinchak.psi/32/83567_2.png) [@bkarabinchak.psi](https://forum.inductiveautomation.com/u/bkarabinchak.psi)\
**Post date:** [January 14, 2022, 7:32pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/6 "2022-01-14T19:32:15Z")

</div>

I think maybe it would be helpful for me to talk about the first thing I am trying to build a simple Vision component that is really a combination of other components. It’s something a project I work on has a template for but is buggy and I thought it was a good starter project. It’s something that would be used for setting a datetime span. It would look like this -

![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/5/f/5f7c71b43db78a98bff56c090fdb74cde250d1eb.png)

The top is a dropdown of selections like this week, this month,this year, last week, etc, that when an item is seleted, the bottom two dates reflect the correct time span. The bottom two components are datepickers in the event someone wants to modify the date slight (at which point the top dropdown which is only strings goes to the Custom option).

Now I had started making this directly in Java Swing, made a JFrame with a JPanel with a dropdown on it and two text labels to show the date - because I had learned that Java Swing does not have datepickers.

At this point I was thing well Ignition DOES have very nice Vision Datepickers, how could I use those in this?

Then I thought about how the vision-component-archetype example you have to implement the AbstractVisionComponent, so surely my using of JPanel and JCombobox directly are also no good. But I don’t know 1) is it possible to borrow from the prebuilt components Ignition already has 2) where I would look to see what is available to me in this context - like is there an `IgnitionComboBox comboBox` I would use instead and if so where do I look to see this information.

I know this is a very simple example but I hope it will give me some experience with all of tis. It would be cool to first get this working, and then see how I could try separating my code into a subproject that is still incorporated into the build, and then anything else that would just make things easier for the build.

And then of course making it a signed module at some point so I could use it in the project without having to change the project’s .conf file to allow unsigned modules but that would be like step 3 or 4 after just getting this to work in a dev environment.

Any insights or answers to my questions on this specific first project would really help. Thanks for all the information already.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [January 14, 2022, 7:44pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/7 "2022-01-14T19:44:58Z")

</div>

> [@bkarabinchak.psi](#):
>
> Then I thought about how the vision-component-archetype example you have to implement the AbstractVisionComponent, so surely my using of JPanel and JCombobox directly are also no good. But I don’t know 1) is it possible to borrow from the prebuilt components Ignition already has 2) where I would look to see what is available to me in this context - like is there an `IgnitionComboBox comboBox` I would use instead and if so where do I look to see this information.

Since you've played with Swing, I presume you've gathered that Swing is really a layer cake, where JComponents are nested within other JComponents as needed to yield the desired appearance/behavior. So yes, you can embed anything you find in the Ignition JavaDocs that is any kind of subclass of JComponent. Some notes:

- Your user-accessible component must implement the VisionComponent interface in order to be added to any Vision container. Extending AbstractVisionComponent pulls that in for you, but you can extend some other Swing object if you implement that interface yourself.

- Nothing nested within your component will be exposed to users automatically, except that they can call .getComponent() with integers to traverse your inner hierarchy. You should expose getters/setters on the main component that delegate to your inner components intelligently.

- Ignition won't layout your inner components. You should use MigLayout to place and scale your inner components as the user manipulates the main component's size.

- You don't have to use inner components for everything--you can paint visual details as you please.

- There are optional interfaces for supporting custom properties and/or custom methods. Save that for later. (:

---

<div class="post-metadata">

**Author:** ![bkarabinchak.psi](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bkarabinchak.psi/32/83567_2.png) [@bkarabinchak.psi](https://forum.inductiveautomation.com/u/bkarabinchak.psi)\
**Post date:** [January 14, 2022, 8:09pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/8 "2022-01-14T20:09:51Z")

</div>

> [@pturmel](#):
>
> Since you’ve played with Swing, I presume you’ve gathered that Swing is really a layer cake, where JComponents are nested within other JComponents as needed to yield the desired appearance/behavior.

Yes at least for the pure Swing project I've been trying to learn with I find it's many JPanels inside of JPanels where the inner/second JPanel will have my components.

> find in the Ignition JavaDocs

Is this the current site? [ignition 7.9.3 API](http://files.inductiveautomation.com/sdk/javadoc/ignition79/793/index.html)  
The project I want to make this custom vision component for is 8.0.14, is there a separate javadoc link for that or is this one suitable?

> Nothing nested within your component will be exposed to users automatically, except that they can call .getComponent() with integers to traverse your inner hierarchy. You should expose getters/setters on the main component that delegate to your inner components intelligently.

Ok so my something like this

```auto
import java.util.Date
public DateSelector extends AbstractVisionComponent {
    public DateSelector () {//set up components here I think}
    public Date getStartDate() {
     //Some logic to get start date from inner components   
        return theStartDate;
    }

    public Date getEndDate() {
       //logic to get EndDate from inner components
        return theEndDate;
 }

```

implements the VisionComponent interface already - I don't need to do anything else re: the interface?  
And secondly the getStartDate / getEndDate would be how I expose the dates that I am showing in the bottom two datepickers to the person using Designer such that they can be binded with?

> You don’t have to use inner components for everything–you can paint visual details as you please.

Meaning manually paint the 2D graphics? That's good to know though I don't think/hope that what I presented doesn't need that yet - would be good to learn but I am hoping that this doesn't need it - hopefully I can just focus on composing the components together for now in a nice layout.

> There are optional interfaces for supporting custom properties and/or custom methods. Save that for later. (:

Haha thanks, I already feel like there's a mountain of stuff I don't know regarding all of this. One step at a time.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [January 14, 2022, 9:33pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/9 "2022-01-14T21:33:26Z")

</div>

> [@bkarabinchak.psi](#):
>
> So I circled two parts with `src/main/java` - both of these get put into the same jar/module you’re saying? That Maven starts at my top level folder “DateSelector”, then for every subfolder looks to see if it has a `src/main/java` and knows that that is what is meant to be turned into byte code yes?

`-client` and `-designer` are already distinct subprojects. There's no _code_ in the top level Maven project (there _could_ be, but it doesn't make sense for Ignition packaging).

> [@bkarabinchak.psi](#):
>
> And this must be unique if the package is every to be distrubted via Maven or Gradle - Maven/Gradle can’t have two libraries that are `com.something` because how would it know which one to give you.

Maven and Gradle don't really _distribute_ anything. Distinct from Maven (the build system) there's Maven Central, the de-facto standard repository for Java artifacts: [https://search.maven.org/](https://search.maven.org/) - both Gradle and Maven _typically_ fetch artifacts from Maven Central, but you _can_ tell them to fetch artifacts from anywhere (see [`build.gradle`](https://github.com/inductiveautomation/ignition-sdk-examples/blob/master/perspective-component/build.gradle#L83) or [`pom.xml`](https://github.com/inductiveautomation/ignition-sdk-examples/blob/master/vision-component/pom.xml#L39) where you have to manually specify the Inductive Automation repositories to fetch our dependencies from.  
The driver for globally unique class names is the JVM itself - reverse-qualified domain names are just a convention that's easy to follow and easy enough to enforce that it's stuck around.

> [@bkarabinchak.psi](#):
>
> Ok so if I wanted to, and knew how, what I could do something like this -  
> and keep my actual module specific code in MySubProject and somehow modify the building instructions so that the `MySubProject` folder is included in my build process

You absolutely could, yes - you would just then need to make sure that your code was marked as required (in the module.xml) for whatever scopes your module needs, so that _at runtime_, in Ignition itself, it loads successfully.

> [@bkarabinchak.psi](#):
>
> Ok so the combination of my doing `extend AbstractGatewayModuleHook` and the `module.xml` in conjunction are what let Ignition know Hey this class is to be used on the Gateway context if I am understanding that right?

Right - there's 'scopes' built in to the module.xml declaration - you can say something should be loaded on the gateway, in the designer, in the client, or on a mix of those (see also `com.inductiveautomation.ignition.common.model.ApplicationScope`).  
When you load a module, we look for a module.xml file. Once it's found and parsed, we look for the hook declarations. Once they're found, we create a classloader for your module (only on the gateway), load up your dependencies, then ask the classloader to instantiate the fully-qualified class name you provided in the `module.xml`. If the name was invalid, couldn't be found, or doesn't implement the appropriate \*Hook class, it'll throw an error and your module won't load.  
Once your hook class _instance_ has been created, we'll automatically call the `setup`, `startup`, and `shutdown` methods on it _for you_. You don't 'decide' when the module loads (with some exceptions) - you just respond to whatever Ignition does with your code.

> [@bkarabinchak.psi](#):
>
> Is there any documentation where I would find this sort of thing out? I’ve seen it before in tutorials and other module development questions about having to do the setup/startup/shutdown, but I never found where I would find that if I didn’t have access to this forum.

The short answer is no - there's [some documentation](https://docs.inductiveautomation.com/display/SE/Scopes%2C+Contexts+and+Hooks) available, but it's hard to tell what's still applicable and what's wildly out of date. The easiest thing to do is usually to start from an example.

> [@bkarabinchak.psi](#):
>
> is it possible to borrow from the prebuilt components Ignition already has

Yes, definitely.

> [@bkarabinchak.psi](#):
>
> here I would look to see what is available to me in this context - like is there an `IgnitionComboBox comboBox` I would use instead and if so where do I look to see this information.

For complicated legacy reasons, most components have a `PMI` prefix (from FactoryPMI). Most of the built-in Vision components are listed in the `com.inductiveautomation.factorypmi.application.components` package:

> **[com.inductiveautomation.factorypmi.application.components](https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.13/com/inductiveautomation/factorypmi/application/components/package-summary.html)**

I think you probably want [`PMIDateTimePopupSelector`](https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.13/com/inductiveautomation/factorypmi/application/components/PMIDateTimePopupSelector.html) (I think that's the right class) based on your graphic. The top dropdown should probably be a vanilla JComboBox.

> [@bkarabinchak.psi](#):
>
> Is this the current site? [ignition 7.9.3 API](http://files.inductiveautomation.com/sdk/javadoc/ignition79/793/index.html)

> **[Javadocs & Notable API Changes](https://github.com/inductiveautomation/ignition-sdk-examples/wiki/Javadocs-&-Notable-Api-Changes)**
>
> Ignition SDK Example Projects. Contribute to inductiveautomation/ignition-sdk-examples development by creating an account on GitHub.

> [@bkarabinchak.psi](#):
>
> I don’t need to do anything else re: the interface?

No, that's it for the interface (the IDE and compiler will complain at you if you haven't implemented some required methods).

> [@bkarabinchak.psi](#):
>
> `//set up components here I think`

You can do initialization in your constructor, but there's some tricky ramifications w.r.t Java serialization, which Vision still relies on. Depending on what you have to do during initialization, you might want to use `onStartup` and `onShutdown` instead (automatically provided by `AbstractVisionComponent`):

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/0/0/0091d070f2fc9166a8a86e7bb50fc3883c4e0c4d.png)

> [@bkarabinchak.psi](#):
>
> And secondly the getStartDate / getEndDate would be how I expose the dates that I am showing in the bottom two datepickers to the person using Designer such that they can be binded with?

Almost.  
Again, complicated Java legacy is involved here, but basically every property you can use for binding should have 3 things:

1. A no-argument getter
2. A single-argument setter, of the same type as the getter
3. Fire a `propertyChange` event with the name of the property when the setter is invoked.

For a couple of reasons, include upgrading, Vision components typically use an external BeanInfo file, that describes the "Java Beans" available on a given component instance, and allows you to customize which properties are available and other meta-information for the designer. See the [BeanInfos](https://github.com/inductiveautomation/ignition-sdk-examples/blob/master/vision-component/ce-designer/src/main/java/com/inductiveautomation/ignition/examples/ce/beaninfos/HelloWorldComponentBeanInfo.java) package in the example.

The first-party Oracle [Swing Tutorials](https://docs.oracle.com/javase/tutorial/uiswing/) are genuinely good resources, even if some of it is dated in terms of the rest of the Java ecosystem by now.

---

<div class="post-metadata">

**Author:** ![bkarabinchak.psi](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bkarabinchak.psi/32/83567_2.png) [@bkarabinchak.psi](https://forum.inductiveautomation.com/u/bkarabinchak.psi)\
**Post date:** [January 17, 2022, 1:51pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/10 "2022-01-17T13:51:30Z")

</div>

Than you for the very in depth answer. I am going to try my hand at this so I can come back with some code and more questions.

One last question though before I try my hand -

> You can do initialization in your constructor, but there’s some tricky ramifications w.r.t Java serialization, which Vision still relies on. Depending on what you have to do during initialization, you might want to use `onStartup` and `onShutdown` instead (automatically provided by `AbstractVisionComponent` ):

So I would leave my constructor blank and in onStartup is where I would initialize my swing components/Ignition components/layouts within a panel?

And onShutdown I'm not sure what would go on there.

Yea I learned just enough swing to be dangerous lol. I learned enough swing to make a window and make buttons that do things or do things on component via ActionListeners, then figured I should see how things work with Ignition, hence this post when I realized I don't know much. I definitely need to finish that tutorial.

Hopefully will have some code to post to this in a day or two. Thanks again!

---

<div class="post-metadata">

**Author:** ![bkarabinchak.psi](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bkarabinchak.psi/32/83567_2.png) [@bkarabinchak.psi](https://forum.inductiveautomation.com/u/bkarabinchak.psi)\
**Post date:** [January 18, 2022, 1:42pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/11 "2022-01-18T13:42:57Z")

</div>

Ok I started trying my hand and the first issue I run into is that I can’t seem to import the premade Ignition components.

I made my project template from  
 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/5/4/54d14c2637e9e4749bf1f9d61718f1fe48f14859.png)

and haven’t really touched anything besides importing a few other things like java.util.Date.

I tried

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/b/d/bd9a92321da388c3b851fcba5a4a2a000278e210.png)

but it seems like it cannot find this. How would could I make `com.inductiveautomation.factorypmi.application.components` usable to me in my module? So that I can then use the PMIDateTimePopupSelector.

---

<div class="post-metadata">

**Author:** ![bkarabinchak.psi](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bkarabinchak.psi/32/83567_2.png) [@bkarabinchak.psi](https://forum.inductiveautomation.com/u/bkarabinchak.psi)\
**Post date:** [January 18, 2022, 1:55pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/12 "2022-01-18T13:55:22Z")

</div>

@paul-griffith You can ignore the previous post.

I typed in `private PMIDateTimePopupSelector fromDate;` just to start and it did `import com.inductiveautomation.factorypmi.application.components.PMIDateTimePopupSelector;` for my automatically no problem.

---

<div class="post-metadata">

**Author:** ![bkarabinchak.psi](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bkarabinchak.psi/32/83567_2.png) [@bkarabinchak.psi](https://forum.inductiveautomation.com/u/bkarabinchak.psi)\
**Post date:** [January 18, 2022, 3:00pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/13 "2022-01-18T15:00:02Z")

</div>

I laid out a couple of components in a JPanel with MigLayout, no other logic going on yet.

I was able to build the jar and MODL file.

When I tried importing it to my local gateway I get the following errors -

`ModuleManager	18Jan2022 09:52:57	Error installing module.`

```auto
java.lang.Exception: Exception parsing "module.xml"

at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.parseModuleInfo(ModuleManagerImpl.java:1703)

at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.extractModuleInfo(ModuleManagerImpl.java:1696)

at com.inductiveautomation.ignition.gateway.web.pages.config.ModuleInstallPage.eulaCertCheck(ModuleInstallPage.java:120)

at com.inductiveautomation.ignition.gateway.web.pages.config.ModuleInstallPage$1.onSubmitInternal(ModuleInstallPage.java:71)

at com.inductiveautomation.ignition.gateway.web.components.CsrfPreventingForm.onSubmit(CsrfPreventingForm.java:67)

at org.apache.wicket.markup.html.form.Form$9.component(Form.java:1248)

at org.apache.wicket.markup.html.form.Form$9.component(Form.java:1242)

at org.apache.wicket.util.visit.Visits.visitPostOrderHelper(Visits.java:274)

at org.apache.wicket.util.visit.Visits.visitPostOrder(Visits.java:245)

at org.apache.wicket.markup.html.form.Form.delegateSubmit(Form.java:1241)

at org.apache.wicket.markup.html.form.Form.process(Form.java:923)

at org.apache.wicket.markup.html.form.Form.onFormSubmitted(Form.java:769)

at org.apache.wicket.markup.html.form.Form.onFormSubmitted(Form.java:702)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

at java.base/java.lang.reflect.Method.invoke(Unknown Source)

at org.apache.wicket.RequestListenerInterface.internalInvoke(RequestListenerInterface.java:258)

at org.apache.wicket.RequestListenerInterface.invoke(RequestListenerInterface.java:216)

at org.apache.wicket.core.request.handler.ListenerInterfaceRequestHandler.invokeListener(ListenerInterfaceRequestHandler.java:240)

at org.apache.wicket.core.request.handler.ListenerInterfaceRequestHandler.respond(ListenerInterfaceRequestHandler.java:226)

at org.apache.wicket.request.cycle.RequestCycle$HandlerExecutor.respond(RequestCycle.java:814)

at org.apache.wicket.request.RequestHandlerStack.execute(RequestHandlerStack.java:64)

at org.apache.wicket.request.cycle.RequestCycle.execute(RequestCycle.java:253)

at org.apache.wicket.request.cycle.RequestCycle.processRequest(RequestCycle.java:210)

at org.apache.wicket.request.cycle.RequestCycle.processRequestAndDetach(RequestCycle.java:281)

at org.apache.wicket.protocol.http.WicketFilter.processRequest(WicketFilter.java:188)

at org.apache.wicket.protocol.http.WicketFilter.doFilter(WicketFilter.java:245)

at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1596)

at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:545)

at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)

at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:590)

at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)

at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235)

at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1607)

at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233)

at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1297)

at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188)

at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:485)

at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1577)

at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186)

at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1212)

at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)

at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:59)

at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)

at org.eclipse.jetty.rewrite.handler.RewriteHandler.handle(RewriteHandler.java:322)

at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:59)

at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:146)

at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)

at org.eclipse.jetty.server.Server.handle(Server.java:500)

at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:383)

at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:547)

at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:375)

at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:270)

at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)

at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)

at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:117)

at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:336)

at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:313)

at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:171)

at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:129)

at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:388)

at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:806)

at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:938)

at java.base/java.lang.Thread.run(Unknown Source)

Caused by: java.lang.IllegalArgumentException: Invalid version: "1.0-SNAPSHOT"

at com.inductiveautomation.ignition.common.model.Version.(Version.java:93)

at com.inductiveautomation.ignition.common.model.Version.parse(Version.java:252)

at com.inductiveautomation.ignition.common.modules.ModuleInfoParser.endElement(ModuleInfoParser.java:138)

at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source)

at com.inductiveautomation.ignition.common.modules.ModuleInfoParser.fromXML(ModuleInfoParser.java:242)

at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.parseModuleInfo(ModuleManagerImpl.java:1701)

```

followed by

`ModuleInstallPage	18Jan2022 09:52:57	java.lang.Exception: Exception parsing "module.xml"`

```auto
java.lang.Exception: Exception parsing "module.xml"

at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.parseModuleInfo(ModuleManagerImpl.java:1703)

at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.extractModuleInfo(ModuleManagerImpl.java:1696)

at com.inductiveautomation.ignition.gateway.web.pages.config.ModuleInstallPage.eulaCertCheck(ModuleInstallPage.java:120)

at com.inductiveautomation.ignition.gateway.web.pages.config.ModuleInstallPage$1.onSubmitInternal(ModuleInstallPage.java:71)

at com.inductiveautomation.ignition.gateway.web.components.CsrfPreventingForm.onSubmit(CsrfPreventingForm.java:67)

at org.apache.wicket.markup.html.form.Form$9.component(Form.java:1248)

at org.apache.wicket.markup.html.form.Form$9.component(Form.java:1242)

at org.apache.wicket.util.visit.Visits.visitPostOrderHelper(Visits.java:274)

at org.apache.wicket.util.visit.Visits.visitPostOrder(Visits.java:245)

at org.apache.wicket.markup.html.form.Form.delegateSubmit(Form.java:1241)

at org.apache.wicket.markup.html.form.Form.process(Form.java:923)

at org.apache.wicket.markup.html.form.Form.onFormSubmitted(Form.java:769)

at org.apache.wicket.markup.html.form.Form.onFormSubmitted(Form.java:702)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

at java.base/java.lang.reflect.Method.invoke(Unknown Source)

at org.apache.wicket.RequestListenerInterface.internalInvoke(RequestListenerInterface.java:258)

at org.apache.wicket.RequestListenerInterface.invoke(RequestListenerInterface.java:216)

at org.apache.wicket.core.request.handler.ListenerInterfaceRequestHandler.invokeListener(ListenerInterfaceRequestHandler.java:240)

at org.apache.wicket.core.request.handler.ListenerInterfaceRequestHandler.respond(ListenerInterfaceRequestHandler.java:226)

at org.apache.wicket.request.cycle.RequestCycle$HandlerExecutor.respond(RequestCycle.java:814)

at org.apache.wicket.request.RequestHandlerStack.execute(RequestHandlerStack.java:64)

at org.apache.wicket.request.cycle.RequestCycle.execute(RequestCycle.java:253)

at org.apache.wicket.request.cycle.RequestCycle.processRequest(RequestCycle.java:210)

at org.apache.wicket.request.cycle.RequestCycle.processRequestAndDetach(RequestCycle.java:281)

at org.apache.wicket.protocol.http.WicketFilter.processRequest(WicketFilter.java:188)

at org.apache.wicket.protocol.http.WicketFilter.doFilter(WicketFilter.java:245)

at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1596)

at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:545)

at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)

at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:590)

at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)

at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235)

at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1607)

at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233)

at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1297)

at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188)

at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:485)

at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1577)

at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186)

at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1212)

at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)

at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:59)

at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)

at org.eclipse.jetty.rewrite.handler.RewriteHandler.handle(RewriteHandler.java:322)

at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:59)

at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:146)

at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)

at org.eclipse.jetty.server.Server.handle(Server.java:500)

at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:383)

at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:547)

at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:375)

at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:270)

at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)

at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)

at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:117)

at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:336)

at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:313)

at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:171)

at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:129)

at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:388)

at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:806)

at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:938)

at java.base/java.lang.Thread.run(Unknown Source)

Caused by: java.lang.IllegalArgumentException: Invalid version: "1.0-SNAPSHOT"

at com.inductiveautomation.ignition.common.model.Version.(Version.java:93)

at com.inductiveautomation.ignition.common.model.Version.parse(Version.java:252)

at com.inductiveautomation.ignition.common.modules.ModuleInfoParser.endElement(ModuleInfoParser.java:138)

at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(Unknown Source)

at java.xml/com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source)

at com.inductiveautomation.ignition.common.modules.ModuleInfoParser.fromXML(ModuleInfoParser.java:242)

at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.parseModuleInfo(ModuleManagerImpl.java:1701)

```

Not sure which of the POM files or other files you would need to see from me to trouble shoot this but let me know and I can show you anything.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [January 18, 2022, 3:21pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/14 "2022-01-18T15:21:30Z")

</div>

> [@bkarabinchak.psi](#):
>
> `Caused by: java.lang.IllegalArgumentException: Invalid version: "1.0-SNAPSHOT"`

Probably just the root POM. You're specifying a version that's not a valid semantic version - should be `1.0.0-SNAPSHOT`, or something similar - major.minor.patch.

---

<div class="post-metadata">

**Author:** ![bkarabinchak.psi](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bkarabinchak.psi/32/83567_2.png) [@bkarabinchak.psi](https://forum.inductiveautomation.com/u/bkarabinchak.psi)\
**Post date:** [January 18, 2022, 3:32pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/15 "2022-01-18T15:32:50Z")

</div>

Ok I changed it in this POM file only

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/0/2/02d7afc45ab33b573f6883da3e9fcbe1913534e9.png)

to

```auto
    <groupId>org.example</groupId>
    <artifactId>DateSelector</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

```

And now when I run `mvn package` I get

```auto
[INFO] Scanning for projects...
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[FATAL] Non-resolvable parent POM for org.example:DateSelector-build:1.0-SNAPSHOT: Could not find artifact org.example:DateSelector:pom:1.0-SNAPSHOT and 'parent.relativePath' points at wrong local POM @ line 7, column 13
[FATAL] Non-resolvable parent POM for org.example:DateSelector-client:1.0-SNAPSHOT: Could not find artifact org.example:DateSelector:pom:1.0-SNAPSHOT and 'parent.relativePath' points at wrong local POM @ line 7, column 13
[FATAL] Non-resolvable parent POM for org.example:DateSelector-designer:1.0-SNAPSHOT: Could not find artifact org.example:DateSelector:pom:1.0-SNAPSHOT and 'parent.relativePath' points at wrong local POM @ line 7, column 13
 @
[ERROR] The build could not read 3 projects -> [Help 1]
[ERROR]
[ERROR] The project org.example:DateSelector-build:1.0-SNAPSHOT (/mnt/c/Users/bkarabinchak/IdeaProjects/DateSelector/DateSelector-build/pom.xml) has 1 error
[ERROR] Non-resolvable parent POM for org.example:DateSelector-build:1.0-SNAPSHOT: Could not find artifact org.example:DateSelector:pom:1.0-SNAPSHOT and 'parent.relativePath' points at wrong local POM @ line 7, column 13 -> [Help 2]
[ERROR]
[ERROR] The project org.example:DateSelector-client:1.0-SNAPSHOT (/mnt/c/Users/bkarabinchak/IdeaProjects/DateSelector/DateSelector-client/pom.xml) has 1 error
[ERROR] Non-resolvable parent POM for org.example:DateSelector-client:1.0-SNAPSHOT: Could not find artifact org.example:DateSelector:pom:1.0-SNAPSHOT and 'parent.relativePath' points at wrong local POM @ line 7, column 13 -> [Help 2]
[ERROR]
[ERROR] The project org.example:DateSelector-designer:1.0-SNAPSHOT (/mnt/c/Users/bkarabinchak/IdeaProjects/DateSelector/DateSelector-designer/pom.xml) has 1 error
[ERROR] Non-resolvable parent POM for org.example:DateSelector-designer:1.0-SNAPSHOT: Could not find artifact org.example:DateSelector:pom:1.0-SNAPSHOT and 'parent.relativePath' points at wrong local POM @ line 7, column 13 -> [Help 2]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException
[ERROR] [Help 2] http://cwiki.apache.org/confluence/display/MAVEN/UnresolvableModelException

```

The inner pom files still all have the the version as `1.0-SNAPSHOT`.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [January 18, 2022, 4:12pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/16 "2022-01-18T16:12:12Z")

</div>

Not a maven guy - maybe you will need to bump all the inner versions too.

---

<div class="post-metadata">

**Author:** ![bkarabinchak.psi](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bkarabinchak.psi/32/83567_2.png) [@bkarabinchak.psi](https://forum.inductiveautomation.com/u/bkarabinchak.psi)\
**Post date:** [January 18, 2022, 7:57pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/17 "2022-01-18T19:57:39Z")

</div>

I had to do replace in files to replace every instance but then it worked. I was able to load it. Though I don’t see anything when I drag it into a project  
 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/d/9/d9c9be1f644b75c593bf95c9b461aaa3ba031166.png)

Here is my current code. Should I be extending AbstractVisionPanel instead and attaching everything to that panel instead of what making a JPanel inside of AbstractVisionComponent and adding to that?

Also, not sure what I am supposed to do in my onShutdown - perhaps nothign yet since it’s so barebones. I want to make it look and appear correct before I start adding the logic.

Code:

```auto
package org.example.client;

import com.inductiveautomation.factorypmi.application.components.PMIDateTimePopupSelector;
import com.inductiveautomation.vision.api.client.components.model.AbstractVisionComponent;
import net.miginfocom.swing.MigLayout;

import javax.swing.*;
import java.awt.*;
import java.util.Date;

//Should this be extending AbstractVisionPanel instead? 
public class DateSelectorComponent extends AbstractVisionComponent {

    private JPanel container;
    private JLabel dropdownLabel;
    private JComboBox<String> dateSelections;
    private JLabel startDateLabel;
    private JLabel toLabel;
    private PMIDateTimePopupSelector startDate;
    private PMIDateTimePopupSelector endDate;
    private String[] dateSelectionOptions = {"All", "Today", "This Week", "This Month", "This Year", "Last Week", "Last Month", "Last Year", "Last 3 Months", "Last 6 Months", "Last 12 Months", "Two Week Period", "Post Sept. 2014"};

    public DateSelectorComponent() {
        //Leftover from archetype example. setPreferredsize seems to do stuff, but unsure what else is appropriate to go here over onStartup.
        setFont(new Font("Dialog", Font.PLAIN, 16));
        setPreferredSize(new Dimension(250, 100));
    }

    protected void onStartup() {
        container = new JPanel(new MigLayout("wrap 4"));
        //Dropdown row
        dropdownLabel = new JLabel("Date:");
        dateSelections = new JComboBox<>(dateSelectionOptions);
        container.add(dropdownLabel, "grow");
        container.add(dateSelections, "span");
        //Datetime row
        startDateLabel = new JLabel("Start Date:");
        startDate = new PMIDateTimePopupSelector();
        toLabel = new JLabel("to");
        endDate = new PMIDateTimePopupSelector();
        container.add(startDateLabel, "grow");
        container.add(startDate, "grow");
        container.add(toLabel, "grow");
        container.add(endDate, "grow");
        //Is this necessary?
        container.setVisible(true);
    }

    public void onShutdown() {

    }

// public Date getStartDate() {
// //TODO implement later
// return new Date();
// }
}

```

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [January 18, 2022, 8:09pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/18 "2022-01-18T20:09:20Z")

</div>

You’re creating a container, but not actually adding that container to your primary component.

I would say you can probably put all of your field initializations into the constructor (and make them `final`, IntellIJ will probably tell you do that anyways).

Then at the end of the constructor, make sure to `this.add(container)` or similar.

`onStartup` is _more_ useful for stuff like creating polling mechanisms or RPC calls or more complex stuff. You also want to be careful about e.g. `setPreferredSize` in the constructor - if you look at the window XML now, you should see a call to your constructor, and a `setFont` and `setPreferredSize` method call below that. It’s fine for generic JComponent methods, but when you start adding your own bean properties it can get tricky. You can also implement [`DesignerInitializable`](https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.13/com/inductiveautomation/vision/api/client/components/model/DesignerInitializable.html) to have a designer-specific runtime hook.

---

<div class="post-metadata">

**Author:** ![bkarabinchak.psi](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bkarabinchak.psi/32/83567_2.png) [@bkarabinchak.psi](https://forum.inductiveautomation.com/u/bkarabinchak.psi)\
**Post date:** [January 18, 2022, 8:29pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/19 "2022-01-18T20:29:19Z")

</div>

> [@paul-griffith](#):
>
> You’re creating a container, but not actually adding that container to your primary component.

Whoops. I make this mistake too often in swing.

I did what you said re: putting it all in the constructor. I also had to change it to `extends AbstractVisionPanel` instead of AbstractVisionComponent - the former works but the latter does not.

However, having said that -

![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/7/f/7fe7d2d946233c7fc3c95b3258c01f5d68b3e1bd.png)

It's alive! Now for the fun part of programming date logic 😅

On that note though - as these date calculations will take place when a new dropdown selection is made - where would I put my JComboBox ActionListener? The constructor? onStartup?

Here's the current code (onStartup and onShutdown are empty)

```auto
public class DateSelectorComponent extends AbstractVisionPanel {

    private final JPanel container;
    private final JLabel dropdownLabel;
    private final JComboBox<String> dateSelections;
    private final JLabel startDateLabel;
    private final JLabel toLabel;
    private final PMIDateTimePopupSelector startDate;
    private final PMIDateTimePopupSelector endDate;
    private final String[] dateSelectionOptions = {"All", "Today", "This Week", "This Month", "This Year", "Last Week", "Last Month", "Last Year", "Last 3 Months", "Last 6 Months", "Last 12 Months", "Two Week Period", "Post Sept. 2014"};

    public DateSelectorComponent() {
        //Leftover from archetype example. setPreferredsize seems to do stuff, but unsure what else is appropriate to go here over onStartup.
        setFont(new Font("Dialog", Font.PLAIN, 16));
// setPreferredSize(new Dimension(250, 100));
        container = new JPanel(new MigLayout("wrap 4"));
        //Dropdown row
        dropdownLabel = new JLabel("Date:");
        dateSelections = new JComboBox<>(dateSelectionOptions);
        container.add(dropdownLabel, "grow");
        container.add(dateSelections, "span");
        //Datetime row
        startDateLabel = new JLabel("Start Date:");
        startDate = new PMIDateTimePopupSelector();
        toLabel = new JLabel("to");
        endDate = new PMIDateTimePopupSelector();
        container.add(startDateLabel, "grow");
        container.add(startDate, "grow");
        container.add(toLabel, "grow");
        container.add(endDate, "grow");
        //Is this necessary?
        container.setVisible(true);
        this.add(container);
        //not sure if the following is necessary
        this.setVisible(true);
    }

```

Exited this thing's actually coming to fruition now - thanks for all the help.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [January 18, 2022, 9:22pm UTC](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030/20 "2022-01-18T21:22:30Z")

</div>

> [@bkarabinchak.psi](#):
>
> The constructor?

The constructor is fine; it's a permanent part of the component and it doesn't make sense to have a component without the listener wired up.

If you want a slightly more ergonomic Swing experience, you can pass a lambda to any of the static methods on the `Listen` class, e.g:

```java
Listen.toCombobox(dateSelections, newValue -> updateDropdowns(newValue));

```

And, no, you don't have to explicitly `setVisible(true)` on your components/containers.

[Next page](https://forum.inductiveautomation.com/t/i-am-looking-for-a-tutor-for-module-development/55030.md?page=2)
