How to access to components within an embedded view from script

Hi,
I am a bit stuck, I want to access a component within an embedded view and call the function on it.

when using [myembeddedviewInstance].getSibling(‘my-component-name-xyz’), get: Null
when using [myembeddedviewInstance].getChildren(), get an empty Array

All seems to be ‘private’ within the embedded view.

There is a way to expose components to public/outside the embedded view?

Send a message with session or page scope, and have handler(s) on the inner components.

Directly grabbing sibling components is an anti-pattern that prevents easy restructuring of layouts.

3 Likes

What am I missing here. I use 2 embedded view (EV) within a popup modal view. I want to change datas inside each EV on receiving a payload from message-handler-type.
The Popup(onStartup) is triggering a class-method broadcasting a session message with a payload. (2 times for the two EV). The EVs doesn’t receive message (session scoped). I tried the handler on the root container where the two EV are emmbeded and message is received.

You should only need to "broadcast" once, as long as both are listening for the same messageType.

Check that both the message being sent is scoped for session and that the Message Handlers are likewise listening at the session scope. You could probably get away with just page, but session should indeed work.
Some screenshots of the individual pieces might help us diagnose what you're missing. Please provide screenshots of the "broadcast" script (or at least the relevant line of code) as well as screenshots of the message handler script (including the actual handler name).

Also, some points of clarification:
1.

Are you putting the handler on the Embedded View, or on the components within the View which is embedded? The handler should go on the components within the View which is being embedded

This will only work with the following layout, where the named sibling is at the same "level" within the same container:

someContainer
L__myembeddedviewInstance
L__my-component-name-xyz

This will always be an empty array because the only components which can have children are those under the container category. Embedding components only render Views - they don't actually contain ANY components.

The two 'receiver' has to show different data each other. I broadcast 2 identical messages with different payload. I added a property 'chart_id' in the message payload also on the Embedded View and check if the message have to be listened to.

Tried the three options scopes from message + handler script checkboxes: view, session, and page.

	def broadcast_chart_payload(self,payload):
		messageType = 'chart-payload-handler'
		system.perspective.sendMessage(messageType, payload, scope = 'session')
		
		return

The handler is on the component (xychart) which is in a Column View (screenshot below) with other components. That column view is embedded within a view used in a popup.

project_sc3

The Graph view is embedded 4 times (not only 2) within a MultiGraphView

project_sc4

On the view I put an action fired when startup (runAction):

	from generic.multi_graph import MultiGraph

	multiGraph = MultiGraph(self.view.rootContainer, self.view.params.graphlist)
	multiGraph.displayGraph()

Full Class Here:

from generic import logger
class MultiGraph:

	def __init__(self, p_view, p_graphInfolist):
	
		self.mainview = p_view
		self.graphInfolist = p_graphInfolist
		self.embeddedviews = ['EmbeddedView1','EmbeddedView2','EmbeddedView3','EmbeddedView4']
		
		self.graphPaths = {
			'bool' : 'ViewComponent/DeviceInfo/VCDeviceHistoryGraphSerieCompact',
			'float' : 'ViewComponent/DeviceInfo/VCDeviceHistoryStatusChartCompact'
		}
		
		self.hideGraphs()	
		
		return
		
	def displayGraph(self):
		logger.log('display')
		i = 0
		for graphInfo in self.graphInfolist:
			i_view = self.mainview.getChild(self.embeddedviews[i])

			i_view.props.path = self.graphPaths[graphInfo['data_type']]
				
			payload = {
				'chart_id' : (i + 1),
				'start_date' : graphInfo['start_date'],
				'end_date' : graphInfo['end_date'],
				'tag_device' : graphInfo['tag_device'],
				'tag_directory' : graphInfo['tag_directory'],
				'tag_property' : graphInfo['tag_property'],
				'date_format': self.mainview.session.custom.user_settings.date_format
			}
			logger.log(payload)
			self.broadcast_chart_payload(payload)
			i_view.meta.visible = True
			i = i + 1
		return
		
	def hideGraphs(self):
		for ev in self.embeddedviews:
			i_view = self.mainview.getChild(ev)			
			i_view.meta.visible = False
		return
		
	def broadcast_chart_payload(self,payload):
		logger.log('broadcast payload...')
		messageType = 'chart-payload-handler'
		system.perspective.sendMessage(messageType, payload, scope = 'session')
			
		return

Additional info:

  1. The user will choose to show or hide 1 to 4 graph from a table-list of tags (View Graph Button = PopUpView MultiGraph).
  2. The embedded view path is changing on 'runtime' because I need to use 2 different chart views according the user preferences.
  3. logger.log() is a custom function to write into a database.

As you can see on logs, the message is never received

Thanks a lot for the quick replies.

Do you have any errors in your Gateway logs when you attempt to run this? I see that you’re logging when you attempt to broadcast, but that logging is done before the call is actually made, so I’m not confident your sendMessage() call is actually being completed successfully.

Please place a logging call immediately after the sendMessage invocation to verify it completed.

What all of this might boil down to is the View which would “hear” the broadcast MUST be in the DOM of the browser to hear the message. If your attempts to hide it from view are removing it from the DOM then it will not hear the message. Since you’re only modifying the visibility this might not be the cause as the View should still be present, but if the View has not loaded when the message has been broadcast, or if you are setting the view path after the fact then the View would not be in place to hear the message. Sorry, but I can’t verify if this is the case or not because your use-case is extremely complicated.

It’s sort of like when you call out in a house, but the person you’re talking to hasn’t walked in through the front door yet. The message was broadcast, but they weren’t around to hear it.

1 Like

I solved the problem in adding a 10 second sleep time.sleep(10) before initializing the view with the broadcasting stuff. the onStartup method from the popupMainView is not the good place to broadcast. Indeed, the onStartup is executed before the popupMainView is ready!

edit: wait time to 1 sec is enough to get the PopUpView Ready to receive broadcast.

Do you have any other suggestions to wait the popUp.is.Loaded ?

Thanks

Stephane.

As a general rule, important messages should be sent by an action you have control over a bit more than an arbitrary onStartUp Event which is probably occurring as a result of a user action. I only broadcast on a user action (click, or hover), a state change (tag/property change script), or as part of a Gateway timer script.

1 Like

user clic ­> OpenPopUp > Wait 1 sec > broadcastMessages > Message received from EmbeddedViews (pop up)

user clic > OpenPopUp > broadcastMessages > Message DID NOT received from EmbeddedViews(not ready, not loaded, not in memory)

Right, so what looks like is occurring is the following sequence of events:

  1. Click Event
    1a. Action: open Popup
  2. Popup onStartUp Event (note that the Embedded Views are not in place because this is the VERY first item executed, before even the Popup is displayed)
    2a. Script Action (broadcast, but no Embedded Views exist to “hear” the broadcast)
  3. Popup render
    3a. THIS is where the Popup is built, and where the Embedded Views begin their existence

The introduction of a sleep within the onStartUp Event is allowing your setup to work because the onStartup Event (which contains the sleep) is on a different thread than that which handles the rendering. As a result, the rendering is occurring while your thread is sleeping.

A couple of options here:

  1. Move the data being broadcast into custom session properties, and then bind the Embedded Views to those session properties.
  2. Move the Popup content into a Docked View; Docked Views remain in the DOM even when the Docked View is collapsed (unless you have multiple docked views on a single side - then only the most recently opened docked view remains in the DOM).
1 Like
  1. Singleton Class and put dataModel stuff there? anti-pattern ?

It might be an anti-pattern because I’m not sure how you’d manage singleton per-session. Maybe it could be done? :man_shrugging:
The session property approach is guaranteed to be singleton for the session, and the Docked View approach is implicitly singleton.

1 Like

Did not know that. Thanks for the fact !