# Musson Industrial's Embr-Periscope Module

**URL:** <https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047>\
**Category:** 3rd Party Modules\
**Tags:** perspective\
**Created:** [November 24, 2024, 10:26pm UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047 "2024-11-24T22:26:31Z")\
**Posts on this page:** 20\
**Page:** 3

<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:** [April 7, 2025, 5:04pm UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/42 "2025-04-07T17:04:02Z")

</div>

> [@bmusson](#):
>
> It works in the designer, but complains in the gateway.

I'm guessing it's a classloader thing; only the gateway enforces classloader separation for modules. We probably never thought about/tested third party modules trying to use our schemas.

You _might_ be able to, in some semi-hacky way, set the thread context classloader to Perspective's, but I'm not actually sure where you would do that and where you might want to _undo_ it.

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [April 7, 2025, 11:22pm UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/43 "2025-04-07T23:22:06Z")

</div>

> [@paul-griffith](#):
>
> set the thread context classloader to Perspective's

🧙‍♂️

More testing required, but this seems to make the gateway happy (no more errors in the logs).

```kotlin
    override fun startup(activationState: LicenseState) {
        ...

        logger.debug("Registering components...")
        val originalClassLoader = Thread.currentThread().contextClassLoader
        Thread.currentThread().contextClassLoader = context.perspectiveContext.javaClass.classLoader

        ...
        componentRegistry.registerComponent(...)
        modelDelegateRegistry.register(...)
        ...

        Thread.currentThread().contextClassLoader = originalClassLoader
    }

```

Edit: The current hack. This has the benefit of also resolving my own reusable schemas.

```kotlin
class DelegatedClassLoader(parent: ClassLoader, private vararg val delegates: ClassLoader) :
    ClassLoader(parent) {

    override fun findClass(name: String): Class<*> {
        for (delegate in delegates) {
            try {
                return delegate.loadClass(name)
            } catch (_: ClassNotFoundException) {}
        }
        throw ClassNotFoundException(name)
    }

    override fun findResource(name: String): URL? =
        delegates.firstNotNullOfOrNull { it.getResource(name) }

    override fun findResources(name: String): Enumeration<URL> =
        Collections.enumeration(delegates.flatMap { it.getResources(name).toList() })
}

fun withContextClassLoaders(vararg delegates: ClassLoader, block: () -> Unit) {
    val originalContextClassLoader = Thread.currentThread().contextClassLoader
    Thread.currentThread().contextClassLoader =
        DelegatedClassLoader(originalContextClassLoader, *delegates)

    block()

    Thread.currentThread().contextClassLoader = originalContextClassLoader
}

```

```kotlin
    // GatewayHook
    override fun startup(activationState: LicenseState) {
        ...

        logger.debug("Registering components...")
        withContextClassLoaders(
            this.javaClass.classLoader,
            context.perspectiveContext.javaClass.classLoader,
        ) {
            componentRegistry.registerComponent(...)
            modelDelegateRegistry.register(...)
        }

        ...
    }

```

This fix is now included in `0.7.2`.

> **[Release @embr-modules/periscope@0.7.2 · mussonindustrial/embr](https://github.com/mussonindustrial/embr/releases/tag/%40embr-modules%2Fperiscope%400.7.2)**
>
> Patch Changes
> 
> 
> 40ad4a7: Fix Perspective component schema validation errors on startup. #265
> 
> This patch uses DelegatedClassLoader to simultaneously resolve schema definitions from both Perspective...

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [April 8, 2025, 3:05am UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/44 "2025-04-08T03:05:21Z")

</div>

Hey @bmusson, annoying js question again 😬

Toastify: how would I be able to put words on new lines in the content? Usually I have to set the css `white-space` to `pre*`, but I have no idea where to start with this one..

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [April 8, 2025, 3:27am UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/45 "2025-04-08T03:27:02Z")

</div>

Option 1: You can add classes to the toast.

```python
system.perspective.runJavaScriptAsync('''() => {
	periscope.toast('My\\nContent\\nOn\\nDifferent\\nLines', {
		className: 'psc-MultiLineToast'
	})
}''')

```

Option 2: You can use inline styles.

```python
system.perspective.runJavaScriptAsync('''() => {
	periscope.toast('My\\nContent\\nOn\\nDifferent\\nLines', {
		style: {
			whiteSpace: 'pre',
			pointerEvents: 'all'
		}
	})
}''')

```

Edit: If using inline styles, you have to add `pointerEvents: 'all'` for any interaction to work. This is because I'm using inline styles on the `ToastContainer` for the positioning and disabling interaction. This could be improved in a future version.

---

<div class="post-metadata">

**Author:** ![hunterdg](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/hunterdg/32/67774_2.png) [@hunterdg](https://forum.inductiveautomation.com/u/hunterdg)\
**Post date:** [April 8, 2025, 4:02am UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/46 "2025-04-08T04:02:55Z")

</div>

Question regarding Portal:

> [@Perspective - AlarmStatusTable - Visibility on Acknowledge Button Click Event](https://forum.inductiveautomation.com/t/perspective-alarmstatustable-visibility-on-acknowledge-button-click-event/98017/6):
>
> perhaps dumb question - I unsuccessfuly tried to replace the call to `perspective.sendMesssage()` with `perspective.print()`, `util.sendMessage()` and `self.session.refreshBinding()`
> 
> function whitelist issue or context issue?

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [April 10, 2025, 10:12pm UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/47 "2025-04-10T22:12:00Z")

</div>

> **[Release @embr-modules/periscope@0.7.3 · mussonindustrial/embr](https://github.com/mussonindustrial/embr/releases/tag/%40embr-modules%2Fperiscope%400.7.3)**
>
> Patch Changes
> 
> 
> 3ea4d36: (FlexRepeaterPlus) Add instance key as an implicit parameter to the instance view.
> 
> 
> 1bd2a1a: (FlexRepeaterPlus) Improve ViewModel caching.
> 
> Move ViewModel caching from the...

### Patch Changes

- [3ea4d36](https://github.com/mussonindustrial/embr/commit/3ea4d369582573e51904d501cdeaad525dbba5d4): (FlexRepeaterPlus) Add instance `key` as an implicit parameter to the instance view.
- [1bd2a1a](https://github.com/mussonindustrial/embr/commit/1bd2a1ae533bb1e2251418fd7eb90bd55b1df992): (FlexRepeaterPlus) Improve ViewModel caching.
  - Move ViewModel caching from the instance level to the component level, allowing the ViewModel reference to be retained for the lifetime of the component.
  - Previously, a ViewModel instance was only cached for the lifetime of its associated InstancePropsHandler, and not much care was taken to remember InstancePropsHandlers.This resolves a bug that would occur when simultaneously (in a single update to `props.instances`):

  1. Moving existing instances.
  2. Adding new instances.
  3. Changing the final size of the instances array.

- [3b53009](https://github.com/mussonindustrial/embr/commit/3b530093bbcae0c1ae1a6552ac14a23e493c6b33): (Toasts) Move `pointerEvents` setting from inline styles to CSS.
  - This makes it easier for users to use the `style` property of the toast function.
  - Users no longer need to add `pointerEvents: 'all'` to every inline style definition.

- Updated dependencies [[3ea4d36](https://github.com/mussonindustrial/embr/commit/3ea4d369582573e51904d501cdeaad525dbba5d4)]
- Updated dependencies [[3b53009](https://github.com/mussonindustrial/embr/commit/3b530093bbcae0c1ae1a6552ac14a23e493c6b33)]
  - @embr-modules/periscope-web@0.7.3

---

<div class="post-metadata">

**Author:** ![gregory.sachs](https://avatars.discourse-cdn.com/v4/letter/g/e47774/32.png) [@gregory.sachs](https://forum.inductiveautomation.com/u/gregory.sachs)\
**Post date:** [May 29, 2025, 3:27pm UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/48 "2025-05-29T15:27:40Z")

</div>

Ok, I'm trying to use FlexRepeaterPlus, and I cannot get the instances to populate correctly....  
I have a view with 3 input parameters, and I'd like to use two of them as common, and one as variable on each instance.

```python
I'm feeding this transform a list of the instances I want:
	a={}
	counter=0
	value.sort(reverse=True)
	for i in value:	
		instance={"viewParams":{"Station":i},
  			"viewStyle": {"classes": ""},
  			"viewPosition": {},
  			"key": counter
			}
		a[counter]=instance
		
		counter=counter+1
	
	return a

```

My array looks correct if I setup a custom property with the transform,

```python
{
  "0": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 180
    },
    "key": 0
  },
  "1": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 172
    },
    "key": 1
  },
  "2": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 171
    },
    "key": 2
  },
  "3": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 170
    },
    "key": 3
  },
  "4": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 160
    },
    "key": 4
  },
  "5": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 150
    },
    "key": 5
  },
  "6": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 130
    },
    "key": 6
  },
  "7": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 110
    },
    "key": 7
  },
  "8": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 100
    },
    "key": 8
  },
  "9": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 93
    },
    "key": 9
  },
  "10": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 91
    },
    "key": 10
  },
  "11": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 90
    },
    "key": 11
  },
  "12": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 81
    },
    "key": 12
  },
  "13": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 80
    },
    "key": 13
  },
  "14": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 70
    },
    "key": 14
  },
  "15": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 60
    },
    "key": 15
  },
  "16": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 50
    },
    "key": 16
  },
  "17": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 41
    },
    "key": 17
  },
  "18": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 21
    },
    "key": 18
  },
  "19": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 20
    },
    "key": 19
  },
  "20": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 10
    },
    "key": 20
  }
}

```

But I cannot get the actual instances list to change, regardless of using a binding to the array or using the script push method as a startup event. I'm currently using periscope 0.7.4 (b2025050202)  
Thanks!

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [May 29, 2025, 4:17pm UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/49 "2025-05-29T16:17:34Z")

</div>

Could you provide a project export that demonstrates the problem?

I can’t really follow your example.

---

<div class="post-metadata">

**Author:** ![gregory.sachs](https://avatars.discourse-cdn.com/v4/letter/g/e47774/32.png) [@gregory.sachs](https://forum.inductiveautomation.com/u/gregory.sachs)\
**Post date:** [May 29, 2025, 5:25pm UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/50 "2025-05-29T17:25:45Z")

</div>

Found it!  
the a={} needed to be a=  
I had the wrong keys due to how it looks if you copy the JSON to notepad...

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [May 29, 2025, 5:51pm UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/51 "2025-05-29T17:51:10Z")

</div>

I’m glad you figured it out.

TIL that open/closed brackets will render as a square. `[]`

---

<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:** [May 29, 2025, 6:32pm UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/52 "2025-05-29T18:32:04Z")

</div>

Today **I** learned that if you have rights to edit a post, clicking that square will actually check the checkbox 😆

---

<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:** [May 29, 2025, 6:42pm UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/53 "2025-05-29T18:42:11Z")

</div>

oooo!

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [June 3, 2025, 9:45pm UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/54 "2025-06-03T21:45:02Z")

</div>

Cross linking this post:

> [@Three Javascript injection](https://forum.inductiveautomation.com/t/three-javascript-injection/106832/7):
>
> Here's a super untested component that will render user-supplied JavaScript as a React Function component. It uses Babel to transpile JSX on the client-side, no idea yet what the performance implications are. Props are passed in like you'd expect. You also currently have access to the Bable TransformOptions through options.babel I spent more time on the component icons than the component itself, so I'm sure this has lifecycle problems somewhere man_shrugging. The attached view shows an exam…

---

<div class="post-metadata">

**Author:** ![martin.johansson](https://avatars.discourse-cdn.com/v4/letter/m/7feea3/32.png) [@martin.johansson](https://forum.inductiveautomation.com/u/martin.johansson)\
**Post date:** [June 4, 2025, 8:52am UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/55 "2025-06-04T08:52:57Z")

</div>

Testing out the Swiper-component and I really like it. Much smoother then Carousel. I have it all setup like I want it but I can't figure out how to jump to a specific index/slide.

I'm opening Swiper as a popup and want to jump to a specific slide depending om the link clicked. "initialSlide" has to be set with a persistent value, otherwise it gets set after the component loads and does nothing. "activeIndex" doesn't seem to exist?

Is there any way to run the methods om Swiper? Like "swiper.slideTo()" or "swiper.init()"

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [June 4, 2025, 4:43pm UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/56 "2025-06-04T16:43:10Z")

</div>

The Swiper component currently doesn't have JavaScript proxy support, I'll change that.

> <https://github.com/mussonindustrial/embr/issues/298>
>
> Periscope's Swiper component should support JavaScript proxy objects in order to… access the API of the Swiper instance.

Once updated you'll be able to get a [JavaScript proxy](https://docs.mussonindustrial.com/ignition/embr-core/javascript-proxy/accessing) for the Swiper instance that you can use to call the API methods.

> **[Swiper - The Most Modern Mobile Touch Slider](https://swiperjs.com/swiper-api#methods--properties)**
>
> Swiper is the most modern free mobile touch slider with hardware accelerated transitions and amazing native behavior.

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [June 4, 2025, 10:09pm UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/57 "2025-06-04T22:09:27Z")

</div>

Ta-da.

> **[Release @embr-modules/periscope@0.7.5 · mussonindustrial/embr](https://github.com/mussonindustrial/embr/releases/tag/%40embr-modules%2Fperiscope%400.7.5)**
>
> Patch Changes
> 
> 8ae60a4: (Swiper Component) Add JavaScript proxy support via component.getJavaScriptProxy('swiper').
> Updated dependencies \[8ae60a4\]
> 
> @embr-modules/periscope-web@0.7.5

```python
def runAction(self, event):
	component = self.getSibling("Swiper")
	
	proxy = component.getJavaScriptProxy("swiper")
	proxy.runAsync('''() => {
		this.slideNext()
	}''')

```

In the future (i.e. correlating with the ApexCharts component release), support will be dropped for multiple-proxyable properties, meaning you will no longer need to pass the object name to `getJavaScriptProxy` (e.g. you will be able to do `getJavaScriptProxy()` instead of `getJavaScriptProxy("swiper")`).

The current function signature will continue to work, but the name parameter will be ignored.

---

<div class="post-metadata">

**Author:** ![martin.johansson](https://avatars.discourse-cdn.com/v4/letter/m/7feea3/32.png) [@martin.johansson](https://forum.inductiveautomation.com/u/martin.johansson)\
**Post date:** [June 5, 2025, 6:11am UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/58 "2025-06-05T06:11:24Z")

</div>

Wow! That was fast =) It's working as intended. Huge thanks!

---

<div class="post-metadata">

**Author:** ![James\_Caudle](https://avatars.discourse-cdn.com/v4/letter/j/5f9b8f/32.png) [@James\_Caudle](https://forum.inductiveautomation.com/u/James_Caudle)\
**Post date:** [June 6, 2025, 12:52pm UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/59 "2025-06-06T12:52:51Z")

</div>

@bmusson Love this. Can you share the perspective project that has the toast constructor with all the options?

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [June 6, 2025, 2:17pm UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/60 "2025-06-06T14:17:09Z")

</div>

> [@Musson Industrial's Embr-Periscope Module](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/23):
>
> The Toasts Update [Periscope version 0.7.0](https://github.com/mussonindustrial/embr/releases/tag/%40embr-modules%2Fperiscope%400.7.0) includes toasts powered by [react-toastify](https://github.com/fkhadra/react-toastify). The toasts also work in the designer wink. Creating a Toast These are client-side toasts, therefore all the interaction is done via [system.perspective.runJavaScriptAsync](https://docs.mussonindustrial.com/ignition/embr-periscope/scripting/perspective/system-perspective-runJavaScriptAsync). periscope.toast is directly mapped to react-toastify's toast object, all features are supported. Simplest Example system.perspective.runJavaScriptAsync('''() =\> { periscope.toast('Your first toast!') }''') Embedded View To…

Hey! It available in the Example Views section of the original post, kind towards the bottom.

---

<div class="post-metadata">

**Author:** ![martin.johansson](https://avatars.discourse-cdn.com/v4/letter/m/7feea3/32.png) [@martin.johansson](https://forum.inductiveautomation.com/u/martin.johansson)\
**Post date:** [June 19, 2025, 10:01am UTC](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/61 "2025-06-19T10:01:16Z")

</div>

Is it possible to add lazy load to Swiper? Swiper documentation says to add "loading = 'lazy'" on each image, but in Ignition we load views and can't really add it this way?

[Previous page](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047.md?page=2)

[Next page](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047.md?page=4)
