# Questions on ignition-sdk-examples/perspective-component

**URL:** <https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399>\
**Category:** Module Development\
**Tags:** perspective\
**Created:** [January 9, 2023, 10:00pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399 "2023-01-09T22:00:38Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Jason\_Zhou](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@Jason\_Zhou](https://forum.inductiveautomation.com/u/Jason_Zhou)\
**Post date:** [January 9, 2023, 10:00pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/1 "2023-01-09T22:00:38Z")

</div>

So I'm using Ignition's perspective component examples as a close reference while I'm building my own module as I go. I was wondering if I could use this post as a general questioning post that others could potentially find useful to reference.

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

So this sample project comes with 3 components, Image, Messenger, and TagCounter. However in the designer subproject, there is only a file for the tag counter. I'm not sure what 'DesignDelegate' means exactly, and why the other two components don't seem to need a file. What purpose does this file serve exactly?

---

<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 9, 2023, 11:12pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/2 "2023-01-09T23:12:28Z")

</div>

The design delegate _java_ file is used to add UI to "decorate" the property editor or other areas as the selected component changes in the Perspective workspace. It's an optional nicety, not required at all for your component to work.

An example would be the flex container positioning buttons:

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/5/c/5cf5a8192ba5741ccd0055a5828414985ffe9b36.jpeg)

---

<div class="post-metadata">

**Author:** ![Jason\_Zhou](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@Jason\_Zhou](https://forum.inductiveautomation.com/u/Jason_Zhou)\
**Post date:** [January 10, 2023, 6:26pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/3 "2023-01-10T18:26:42Z")

</div>

So I've tried creating my own component by emulating what's been done for the sample components and successfully built the module! Kind of. After installing the module onto my gateway, it's faulted.

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

So for learning purposes, I went through and changed all the "fakester" and "rad" to the name of my company, Kanoa. Here's the last bit of the full error message:

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

So I think I must have not properly changed a variable name correctly, but when I go to the GatewayHook file, everything looks fine throughout the whole file and within the startup function too. I'm not sure how to continue to debug this.

---

<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 10, 2023, 6:30pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/4 "2023-01-10T18:30:51Z")

</div>

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

Static initialization (`clinit`) is failing in the `Image` class, on line 23.  
Looking at the sample:

> <https://github.com/inductiveautomation/ignition-sdk-examples/blob/4c4552578fec89082a207cca68f36d27e920c7d1/perspective-component/common/src/main/java/org/fakester/common/component/display/Image.java#L22-L23>

Line 23 is attempting to find a class resource, by fully qualified path.  
Specifically, _this_ file:

> <https://github.com/inductiveautomation/ignition-sdk-examples/blob/4c4552578fec89082a207cca68f36d27e920c7d1/perspective-component/common/src/main/resources/radimage.props.json>

Note it's location (`src/main/resources/radimage.props.json`). Ensure your changed code agrees on what that file is _called_, and _where it resides_.

---

<div class="post-metadata">

**Author:** ![Jason\_Zhou](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@Jason\_Zhou](https://forum.inductiveautomation.com/u/Jason_Zhou)\
**Post date:** [January 10, 2023, 6:46pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/5 "2023-01-10T18:46:34Z")

</div>

This is the code in the Image java file as well as the layout of the directory on the left.

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/6/6/6616e3b8be8c62d792440a152ae76f8c632e75d8.png)

On line 23, the project originally had the path as "/radimage.props.json". I've changed the file name and adjusted the path to match it. Is there something else I'm missing?

---

<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 10, 2023, 6:50pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/6 "2023-01-10T18:50:55Z")

</div>

Do a `./gradlew clean build`, then double check the build directory of `common` to ensure the files are there in the places you expect. You can also open the actual `common.jar` file in any ZIP tool to check its structure, as well.

---

<div class="post-metadata">

**Author:** ![Jason\_Zhou](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@Jason\_Zhou](https://forum.inductiveautomation.com/u/Jason_Zhou)\
**Post date:** [January 10, 2023, 7:33pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/7 "2023-01-10T19:33:54Z")

</div>

That did it, awesome! I was noticing that when I reran "gradlew build" it would go through the process but wouldn't even overwrite the previous generated modl. I would have to manually delete it, but it seems like "gradlew clean build" handles all that.

How can I get the component to have an icon? I noticed that none of the sample components have an icon so I wasn't sure where to set that.

---

<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 10, 2023, 7:39pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/8 "2023-01-10T19:39:01Z")

</div>

The icon to use in the designer should be added to your `ComponentBuilder` chained call. You need to supply a Swing Icon; we have an [`InteractiveSvgIcon`](https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.23/com/inductiveautomation/ignition/designer/navtree/icon/InteractiveSvgIcon.html) class you can use to create one from an SVG file in your module's resources.

---

<div class="post-metadata">

**Author:** ![Jason\_Zhou](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@Jason\_Zhou](https://forum.inductiveautomation.com/u/Jason_Zhou)\
**Post date:** [January 10, 2023, 7:46pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/9 "2023-01-10T19:46:27Z")

</div>

Got it, so is setting the icon a new separate setter, or is it in .addPaletteEntry?

And to use that class to create the swing icon, and I passing the entire svg code into the createIcon method?

---

<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 10, 2023, 7:52pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/10 "2023-01-10T19:52:21Z")

</div>

Store your .svg as a resource file (just like the component .json files).  
Pass the path to `InteractiveSvgIcon.createIcon(path)`; the return value will be the icon to use.

You would add that icon as an additional call, so e.g. between line 35 and 36 add `setIcon(yourSvgIcon)`.

---

<div class="post-metadata">

**Author:** ![Jason\_Zhou](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@Jason\_Zhou](https://forum.inductiveautomation.com/u/Jason_Zhou)\
**Post date:** [January 10, 2023, 10:07pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/11 "2023-01-10T22:07:13Z")

</div>

Might be a silly question but I don't think I'm importing and using the code right. Here's my attempt:

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

The error is : cannot find symbol, symbol being 'InteractiveSvgIcon'

---

<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 10, 2023, 10:56pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/12 "2023-01-10T22:56:30Z")

</div>

Note the package; com.inductiveautomation.ignition. **designer**.navtree.icon.InteractiveSVGIcon. The class only exists in the 'designer' scope, which you won't have available to you in your module's `common` subproject.

Probably the thing to do would be to load the icon only in your DesignerHook. When you register the component in your DesignerHook, create a new descriptor instance (you can use all the values from the one in `CopyToClipboard.DESCRIPTOR` and add your icon to it there.

---

<div class="post-metadata">

**Author:** ![Jason\_Zhou](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@Jason\_Zhou](https://forum.inductiveautomation.com/u/Jason_Zhou)\
**Post date:** [January 12, 2023, 4:43pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/13 "2023-01-12T16:43:28Z")

</div>

Hey Paul, so I've been working on a copy to clipboard button, and have gotten it to work! One thing I was wondering is, is it possible to import the copy to clipboard function as a module script instead? That way we wouldn't be limited by only being able to copying to clipboard through a button.

---

<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 12, 2023, 5:44pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/14 "2023-01-12T17:44:08Z")

</div>

In theory, a scripting function (Java code running on the gateway) could pass a message over the websocket to the running session, and your module could register a handler for that message, and react to it to run whatever JS code in the session.

I don't know, off the top of my head, whether all the APIs to do that are easily exposed to you as a module developer.

---

<div class="post-metadata">

**Author:** ![Jason\_Zhou](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@Jason\_Zhou](https://forum.inductiveautomation.com/u/Jason_Zhou)\
**Post date:** [January 12, 2023, 6:41pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/15 "2023-01-12T18:41:04Z")

</div>

Got it, so it seems like just having the logic contained in the custom component will be an easier time.

On more of an educational note, what are the notable differences between having a module be signed vs. unsigned. I'm developing a component that our clients could potentially be interested and use. If I were to just have them install the unsigned module that I currently have, what would be the consequences of that vs. if I were to be able to have the signed module?

---

<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 12, 2023, 6:45pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/16 "2023-01-12T18:45:10Z")

</div>

Signing is, ideally, a way to ensure that the code your customers install is the code you generated, as long as they can verify your signing key.  
It's not enforced by Ignition itself beyond a default requirement that modules _are signed_ - we don't check certificates for validity, or against a root CA, or anything.  
I would encourage you to sign any versions of your module you plan to distribute, though, so that end-users don't have to run a production gateway with the "allow unsigned modules" flag set.

---

<div class="post-metadata">

**Author:** ![Jason\_Zhou](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@Jason\_Zhou](https://forum.inductiveautomation.com/u/Jason_Zhou)\
**Post date:** [January 23, 2023, 5:37pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/17 "2023-01-23T17:37:20Z")

</div>

I'm currently working on customization of the copy to clipboard button. The TypeScript for it is literally just a button element:

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

How can I set up properties that allow for targeted styling of the button element itself? I do know that some perspective components like the Table for example have headerStyle, bodyStyle, etc. I'd want to be able to customize some basic button styling in the designer like size, border, color, and text/icon.

---

<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 23, 2023, 5:59pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/18 "2023-01-23T17:59:22Z")

</div>

If you type one of your props as a ` Style` from `'@inductiveautomation/perspective-client';`, the class instance should have some helper methods:  
 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/6/4/643b66f8456a687e8bea59cea892ca32e76d273b.png)  
Which you're able to use in your TSX:  
 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/6/5/652b2acbfe799b06d40ea98b238852b38eec3d3f.png)

---

<div class="post-metadata">

**Author:** ![Jason\_Zhou](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@Jason\_Zhou](https://forum.inductiveautomation.com/u/Jason_Zhou)\
**Post date:** [January 23, 2023, 6:13pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/19 "2023-01-23T18:13:51Z")

</div>

I'm still a little lost on how to fully implement that. Here's the full typescript file of the component.

```auto
/**
 * Example of a component which displays an image, given a URL.
 */
import * as React from 'react';
import {
    Component,
    ComponentMeta,
    ComponentProps,
    PComponent,
    PropertyTree,
    SizeObject
} from '@inductiveautomation/perspective-client';

// The 'key' or 'id' for this component type. Component must be registered with this EXACT key in the Java side as well
// as on the client side. In the client, this is done in the index file where we import and register through the
// ComponentRegistry provided by the perspective-client API.
export const COMPONENT_TYPE = "kanoa.display.copytoclipboard";

// This is the shape of the properties we get from the perspective 'props' property tree.
export interface CopyToClipboardProps {
    text: string; // the url of the image this component should display
    buttonStyle: object; // object of style properties for the button (?)
}

export class CopyToClipboard extends Component<ComponentProps<CopyToClipboardProps>, any> {
    render() {
        // The props we're interested in.
        const { props: { text, buttonStyle } } = this.props;
        // Read the 'url' property provided by the perspective gateway via the component 'props'.

        async function writeToClipboard() {
            await navigator.clipboard.writeText(text)
        }

        // Note that the topmost piece of dom requires the application of an element reference, events, style and
        // className as shown below otherwise the layout won't work, or any events configured will fail. See render
        // of MessengerComponent in Messenger.tsx for more details.
        return (
            <button onClick={writeToClipboard}>Copy</button>
        );
    }
}

// This is the actual thing that gets registered with the component registry.
export class CopyToClipboardMeta implements ComponentMeta {

    getComponentType(): string {
        return COMPONENT_TYPE;
    }

    // the class or React Type that this component provides
    getViewComponent(): PComponent {
        return CopyToClipboard;
    }

    getDefaultSize(): SizeObject {
        return ({
            width: 360,
            height: 360
        });
    }

    // Invoked when an update to the PropertyTree has occurred,
    // effectively mapping the state of the tree to component props.
    getPropsReducer(tree: PropertyTree): CopyToClipboardProps {
        return {
            text: tree.readString("text", ""),
            buttonStyle: tree.readObject("buttonStyle", {})
        };
    }
}

```

Here's my attempt at it so far from what I can understand. Would you have an example perhaps for reference?

---

<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 23, 2023, 6:17pm UTC](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399/20 "2023-01-23T18:17:15Z")

</div>

I think (I'm not an expert in this domain, by any means) your sample would instead look something like this:

```typescript
/**
 * Example of a component which displays an image, given a URL.
 */
import * as React from 'react';
import {
    Component,
    ComponentMeta,
    ComponentProps,
    PComponent,
    PropertyTree,
    SizeObject,
    Style
} from '@inductiveautomation/perspective-client';

// The 'key' or 'id' for this component type. Component must be registered with this EXACT key in the Java side as well
// as on the client side. In the client, this is done in the index file where we import and register through the
// ComponentRegistry provided by the perspective-client API.
export const COMPONENT_TYPE = "kanoa.display.copytoclipboard";

// This is the shape of the properties we get from the perspective 'props' property tree.
export interface CopyToClipboardProps {
    text: string; // the url of the image this component should display
    buttonStyle: Style; // object of style properties for the button (?)
}

export class CopyToClipboard extends Component<ComponentProps<CopyToClipboardProps>, any> {
    render() {
        // The props we're interested in.
        const { props: { text, buttonStyle } } = this.props;
        // Read the 'url' property provided by the perspective gateway via the component 'props'.

        async function writeToClipboard() {
            await navigator.clipboard.writeText(text)
        }

        // Note that the topmost piece of dom requires the application of an element reference, events, style and
        // className as shown below otherwise the layout won't work, or any events configured will fail. See render
        // of MessengerComponent in Messenger.tsx for more details.
        return (
            <button onClick={writeToClipboard} { ...buttonStyle.toStyleProps() }>Copy</button>
        );
    }
}

// This is the actual thing that gets registered with the component registry.
export class CopyToClipboardMeta implements ComponentMeta {

    getComponentType(): string {
        return COMPONENT_TYPE;
    }

    // the class or React Type that this component provides
    getViewComponent(): PComponent {
        return CopyToClipboard;
    }

    getDefaultSize(): SizeObject {
        return ({
            width: 360,
            height: 360
        });
    }

    // Invoked when an update to the PropertyTree has occurred,
    // effectively mapping the state of the tree to component props.
    getPropsReducer(tree: PropertyTree): CopyToClipboardProps {
        return {
            text: tree.readString("text", ""),
            buttonStyle: tree.readStyle("buttonStyle")
        };
    }
}

```

[Next page](https://forum.inductiveautomation.com/t/questions-on-ignition-sdk-examples-perspective-component/68399.md?page=2)
