# Creating a button component that triggers a navigation event

**URL:** <https://forum.inductiveautomation.com/t/creating-a-button-component-that-triggers-a-navigation-event/93735>\
**Category:** Module Development\
**Tags:** vision\
**Created:** [July 25, 2024, 10:56am UTC](https://forum.inductiveautomation.com/t/creating-a-button-component-that-triggers-a-navigation-event/93735 "2024-07-25T10:56:55Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![MG3](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mg3/32/84125_2.png) [@MG3](https://forum.inductiveautomation.com/u/MG3)\
**Post date:** [July 25, 2024, 10:56am UTC](https://forum.inductiveautomation.com/t/creating-a-button-component-that-triggers-a-navigation-event/93735/1 "2024-07-25T10:56:55Z")

</div>

Hi all,

I am attempting to make a simple button with pre-implemented navigation code in a module I am creating. But I cannot figure out how to get this scripting into the Java Module.

Basically, the component has 2x String Properties - **Window Path** and **Tag Path**. And will use these to open the popup at the vision location of **Window Path** and feed in the **Tag Path** as a parameter to the window. For example, a Jython Script for the same task on a button with 2 parameters "_WindowPath_" and "_TagPath_":

```python
DisplayHandle = system.gui.getCurrentDesktop()
DesktopNav = system.nav.desktop(DisplayHandle)

DesktopNav.openWindowInstance(event.source.WindowPath, {"TagPath" : event.source.TagPath})

```

I tried looking at documentation but just could not wrap my head around how to do it in Java. If anyone can offer and help I would be very appreciative.

---

<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:** [July 25, 2024, 12:18pm UTC](https://forum.inductiveautomation.com/t/creating-a-button-component-that-triggers-a-navigation-event/93735/2 "2024-07-25T12:18:44Z")

</div>

That is a _ **really** _ bad idea. And it is an internal implementation detail of the Vision module, so I doubt there's any way to get to it.

Leave human tasks (connecting components, binding values, arranging nav) to the human using the designer.

---

<div class="post-metadata">

**Author:** ![MG3](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mg3/32/84125_2.png) [@MG3](https://forum.inductiveautomation.com/u/MG3)\
**Post date:** [July 25, 2024, 1:18pm UTC](https://forum.inductiveautomation.com/t/creating-a-button-component-that-triggers-a-navigation-event/93735/3 "2024-07-25T13:18:08Z")

</div>

Me and a colleague have been looking at this for a hour or two and have found that we can get this to work using:

```python
NavUtilitiesDispatcher navUtilsDisp = new NavUtilitiesDispatcher(FPMIApp.getInstance());

PyDictionary parameters= new PyDictionary();
parameters.put("TagRoot", tagRoot);
	
navUtilsDisp.openWindowInstance(windowPath, parameters);
navUtilsDisp.centerWindow(this.windowPath);

```

And it seems to work perfectly well. However, are you saying that this has the change to break general parts of the vision module? Do Note: I am aware that the `FPMIApp.getInstance()` is depreciated but I currently see no alternatives.

Let me know what we are all thinking and if it has the potential to cause major issues I will just go with the trusty internal Jython Scripting on the component, but for the application an internal method would be perfect.

---

<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:** [July 25, 2024, 1:29pm UTC](https://forum.inductiveautomation.com/t/creating-a-button-component-that-triggers-a-navigation-event/93735/4 "2024-07-25T13:29:55Z")

</div>

My recommendation is to not do any navigation from within your component. Full stop.

Make sure your component properly supports ordinary event scripting for your designers to use.

---

<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:** [July 25, 2024, 4:21pm UTC](https://forum.inductiveautomation.com/t/creating-a-button-component-that-triggers-a-navigation-event/93735/5 "2024-07-25T16:21:58Z")

</div>

It's _less_ about access to internals (though that does have some fundamental risks on its own) and more about a fundamental design paradigm. Throughout Vision and Perspective, components provide _hooks_, and the developer working in the Designer chooses to implement (or not) whatever desired functionality they want in those hooks. By "pre-scripting" functionality in your components, you're taking that flexibility away from your end users.

You could probably achieve something _close_ to the desired result without any "unsafe" edges by creating a custom component with some scripts _automatically_ placed on it - the default behavior on drop is to do what you want, but end users are still able to change or disable this behavior if they want to.

This is also not to say that it's not _potentially_ okay to offer a "magic button" via a custom module - it's your module, and of course you're free to do whatever you want with it, and you know your intended audience better than us. But if it's intended for wider distribution than "people you can directly explain this to", I'd highly recommend sticking to exposing _hooks_ over exposing _functionality_ if you're creating something as fundamental as a button.

---

<div class="post-metadata">

**Author:** ![MG3](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mg3/32/84125_2.png) [@MG3](https://forum.inductiveautomation.com/u/MG3)\
**Post date:** [July 29, 2024, 8:06am UTC](https://forum.inductiveautomation.com/t/creating-a-button-component-that-triggers-a-navigation-event/93735/6 "2024-07-29T08:06:09Z")

</div>

> [@paul-griffith](#):
>
> You could probably achieve something _close_ to the desired result without any "unsafe" edges by creating a custom component with some scripts _automatically_ placed on it - the default behavior on drop is to do what you want, but end users are still able to change or disable this behavior if they want to.

Would you be able to elaborate or send some links to resources on how to do this? But thankyou for your insight it is really helpful!

---

<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:** [July 29, 2024, 4:01pm UTC](https://forum.inductiveautomation.com/t/creating-a-button-component-that-triggers-a-navigation-event/93735/7 "2024-07-29T16:01:41Z")

</div>

This was a bit of a voyage of discovery for me, so bear with me if any of these explanations are a bit half-baked.

So, it looks like you want your component class to implement `com.inductiveautomation.vision.api.client.components.model.SelfBinder` - as the name/Javadoc suggest, it's used for components that set up their own event scripts (like the Paintable Canvas).

The first thing you want to do in your implementation of `installBindings` is check that no bindings already exist on the component - by definition, since your component is a `SelfBinder`, that will only be true on brand new components:

```java
public void installBindings(InteractionController controller) {
    Adapter[] adapters = controller.getAllAdaptersForTarget(this);

    if (adapters.length == 0) {

```

Then, you will need to construct the event script wrapping object, an `ActionAdapter` and set it up with all the required elements.  
Again, taking from the Paintable Canvas:

```java
ActionAdapter adapter = new ActionAdapter();
adapter.setTarget(this);
adapter.setJythonCode(writer.toString());
adapter.setBuilderMode(ActionAdapter.MODE_SCRIPT);
adapter.setBuilderInfo(null);
adapter.setEventSet(new EventSetDescriptor(PMIPaintableCanvas.class, "paint", PaintListener.class, "repaint"));
adapter.setMethodDescriptor(new MethodDescriptor(PaintListener.class.getMethod("repaint", PaintEvent.class)));
controller.addAdapter(this, adapter);

```

`EventSetDescriptor` and `MethodDescriptor` are standard Java "beans" introspection classes, not Ignition/Vision specific.

As far as I can tell, that's basically it. Whenever your component is constructed by Vision, if it happens to be a `SelfBinder` we'll automatically run that initialization code to allow you to configure it.

---

<div class="post-metadata">

**Author:** ![MG3](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mg3/32/84125_2.png) [@MG3](https://forum.inductiveautomation.com/u/MG3)\
**Post date:** [July 30, 2024, 8:52am UTC](https://forum.inductiveautomation.com/t/creating-a-button-component-that-triggers-a-navigation-event/93735/8 "2024-07-30T08:52:07Z")

</div>

May i ask

[quote="PGriffith, post:7, topic:93735"]

```python
ActionAdapter adapter = new ActionAdapter();
adapter.setTarget(this);
adapter.setJythonCode(writer.toString());
adapter.setBuilderMode(ActionAdapter.MODE_SCRIPT);
adapter.setBuilderInfo(null);
adapter.setEventSet(new EventSetDescriptor(PMIPaintableCanvas.class, "paint", PaintListener.class, "repaint"));
adapter.setMethodDescriptor(new MethodDescriptor(PaintListener.class.getMethod("repaint", PaintEvent.class)));
controller.addAdapter(this, adapter);

```

[/quote]

May I ask, is the jython code kept in the `writer` object and if so how is this initialized? Is there any code available with the constructor used for this application?

Currently, in my code I have as instructed implemented SelfBinder in my component:

```python
public class ButtonComponentTest extends AbstractVisionComponent implements SelfBinder

```

and before my setter and getter methods, I have implemented the installBindings method:

```python
    //Section 5 - Adding the default jython script to the component.
    public void installBindings(InteractionController controller) {
    	Adapter[] adapters = controller.getAllAdaptersForTarget(this);
    	
    	String jythonString = "DisplayHandle = system.gui.getCurrentDesktop()\r\n" + 
        		"DesktopNav = system.nav.desktop(DisplayHandle)\r\n" + 
        		"parameters = {\"FolderRoot\": event.source.folderRoot}\r\n" + 
        		"DesktopNav.openWindow(event.source.windowPath, parameters)\r\n" + 
        		"DesktopNav.centerWindow(window)";
    	
    	if (adapters.length == 0) {
    		ActionAdapter adapter = new ActionAdapter();
    		adapter.setTarget(this);
    		adapter.setJythonCode(jythonString);
    		adapter.setBuilderMode(ActionAdapter.MODE_SCRIPT);
    		adapter.setBuilderInfo(null);
			try {
				adapter.setEventSet(new EventSetDescriptor(ButtonComponentTest.class, "action", ActionListener.class, "actionPerformed"));
			} catch (IntrospectionException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			try {
				adapter.setMethodDescriptor(new MethodDescriptor(ActionEvent.class.getMethod("actionPerformed", ActionListener.class)));
			} catch (NoSuchMethodException | SecurityException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

    		controller.addAdapter(this, adapter);
    	}
    }

```

The module builds and installs, and I can place the custom component also the _vision project browser_ indicates that I do in-fact have some form of script on the component. But, whenever I try to _CTRL+J_ or _right-click and select_ "_Scripting..._", absolutely **nothing** happens no IDE, nothing... See screenshot below of the component in the project browser:

![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/4/9/495435e3dcc6c99e78dc015ef072e3abc639e37f.png)

**See post below!**

---

<div class="post-metadata">

**Author:** ![MG3](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mg3/32/84125_2.png) [@MG3](https://forum.inductiveautomation.com/u/MG3)\
**Post date:** [July 31, 2024, 4:58pm UTC](https://forum.inductiveautomation.com/t/creating-a-button-component-that-triggers-a-navigation-event/93735/9 "2024-07-31T16:58:10Z")

</div>

So after finally a colleague reminded me that the Console exists in Designer, I have determined that when placing the component I am getting a **NoSuchMethod** Exception and if I try to get onto the Scripting menu I get a **java.lang.NullPointerException**.

Current code is:

```python
    //Section 5 - Adding the default jython script to the component.
    public void installBindings(InteractionController controller) {
    	Adapter[] adapters = controller.getAllAdaptersForTarget(this);

    	String jythonString = "DisplayHandle = system.gui.getCurrentDesktop()\r\n" + 
        		"DesktopNav = system.nav.desktop(DisplayHandle)\r\n" + 
        		"parameters = {\"FolderRoot\": event.source.folderRoot}\r\n" + 
        		"DesktopNav.openWindow(event.source.windowPath, parameters)\r\n" + 
        		"DesktopNav.centerWindow(window)";
    	
    	if (adapters.length == 0) {
    		ActionAdapter adapter = new ActionAdapter();
    		adapter.setTarget(this);
    		adapter.setJythonCode(jythonString);
    		adapter.setBuilderMode(ActionAdapter.MODE_SCRIPT);
    		adapter.setBuilderInfo(null);
			try {
				adapter.setEventSet(new EventSetDescriptor(MeterButtonComponent2.class, "action", ActionListener.class, "actionPerformed"));
			} catch (IntrospectionException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			try {
				adapter.setMethodDescriptor(new MethodDescriptor(MeterButtonComponent2.class.getMethod("actionPerformed", ActionEvent.class)));
			} catch (NoSuchMethodException | SecurityException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

    		controller.addAdapter(this, adapter);
    	}
    }

```

and the error exists on the line `adapter.setMethodDescriptor(new MethodDescriptor(MeterButtonComponent2.class.getMethod("actionPerformed", ActionEvent.class)));`, in the second _try_-statement.

Anyone able to point me in the direction to getting this resolved?

**Resolved** : in the line mentioned above I should have been using `adapter.setMethodDescriptor(new MethodDescriptor(ActionListener.class.getMethod("actionPerformed", ActionEvent.class)));`

Thanks in advanced,

MG3.
