Scrollable Popup Menu

Hi folks,

I'm working with the Inductive created Ad Hoc trend from the exchange. Looking at the "Load" feature for loading saved charts, we see the following:

'Load' Button Script
database = ""
hasCharts = 1
try:
	chartCount = system.db.runPrepQuery("SELECT COUNT(*) FROM saved_graphs",[], database)
except:
	hasCharts = 0

if not hasCharts or chartCount[0][0]<1:
	system.gui.messageBox("No saved graphs currently exist. Use adjacent save button to store current graph.")
else:
	database = ""
	username = system.security.getUsername()	
	
	names = []
	funcs = []
	
	privateNames = []
	privateFuncs = []
	
	create = 0
	
	try:
		charts = system.db.runPrepQuery("SELECT id, title FROM saved_graphs WHERE username = ? ORDER BY title ASC", [username], database)
		for row in charts:
			cNames = ["Load", "Delete"]
			#this if-else seems redundant - ARH
			if event.source.parent.databaseType == 'ORACLE':
				rowId=row["ID"]
			else:
				rowId=row["id"]
			def load(event, id=rowId):
				import system
				info = system.db.runPrepQuery("SELECT title, tagpens, axes, chartmode, startdate, enddate, rangestartdate, rangeenddate, unit, unitcount FROM saved_graphs WHERE id = ?", [id], database)
				if len(info):
					chart = event.source.parent.getComponent('Easy Chart')
					chart.title = info[0][0]
					chart.tagPens = system.dataset.fromCSV(info[0][1])
					chart.axes = system.dataset.fromCSV(info[0][2])
					chart.updateMinMaxAvg()
					chart.chartMode = int(info[0][3])
					chart.startDate = info[0][4]
					chart.endDate = info[0][5]
					chart.outerRangeStart = info[0][6]
					chart.outerRangeEnd = info[0][7]
					chart.unit = int(info[0][8])
					chart.unitCount = int(info[0][9])
					def doLater():
						chart.chartMode = int(info[0][3])
						chart.startDate = info[0][4]
						chart.endDate = info[0][5]
						chart.outerRangeStart = info[0][6]
						chart.outerRangeEnd = info[0][7]
						chart.unit = int(info[0][8])
						chart.unitCount = int(info[0][9])
					system.util.invokeLater(doLater, 1000)
			if event.source.parent.databaseType == 'ORACLE':
				rowId=row["ID"]
				rowTitle=row["TITLE"]
			else:
				rowId=row["id"]
				rowTitle=row["title"]
			def delete(event, id=rowId, title=rowTitle):
				import system
				if system.gui.confirm("Are you sure you want to delete chart '%s'?" % title):
					system.db.runPrepUpdate("DELETE FROM saved_graphs WHERE id = ?", [id], database)
			cFuncs = [load, delete]
			privateNames.append(rowTitle)
			privateFuncs.append([cNames, cFuncs])	
	except:
		create = 1
	
	publicNames = []
	publicFuncs = []
	
	try:
		charts = system.db.runPrepQuery("SELECT id, title FROM saved_graphs WHERE username IS NULL ORDER BY title ASC", [], database)
		for row in charts:
			pubNames = ["Load", "Delete"]
			#this if-else seems redundant - ARH
			if event.source.parent.databaseType == 'ORACLE':
				rowId=row["ID"]
			else:
				rowId=row["id"]
			def load(event, id=rowId):
				import system
				info = system.db.runPrepQuery("SELECT title, tagpens, axes, chartmode, startdate, enddate, rangestartdate, rangeenddate, unit, unitcount FROM saved_graphs WHERE id = ?", [id], database)
				if len(info):
					chart = event.source.parent.getComponent('Easy Chart')
					chart.title = info[0][0]
					chart.tagPens = system.dataset.fromCSV(info[0][1])
					chart.axes = system.dataset.fromCSV(info[0][2])
					chart.updateMinMaxAvg()
					chart.chartMode = int(info[0][3])
					chart.startDate = info[0][4]
					chart.endDate = info[0][5]
					chart.outerRangeStart = info[0][6]
					chart.outerRangeEnd = info[0][7]
					chart.unit = int(info[0][8])
					chart.unitCount = int(info[0][9])
					def doLater():
						chart.chartMode = int(info[0][3])
						chart.startDate = info[0][4]
						chart.endDate = info[0][5]
						chart.outerRangeStart = info[0][6]
						chart.outerRangeEnd = info[0][7]
						chart.unit = int(info[0][8])
						chart.unitCount = int(info[0][9])
					system.util.invokeLater(doLater, 1000)
			if event.source.parent.databaseType == 'ORACLE':
				rowId=row["ID"]
				rowTitle=row["TITLE"]
			else:
				rowId=row["id"]
				rowTitle=row["title"]
			def delete(event, id=rowId, title=rowTitle):
				import system
				if system.gui.confirm("Are you sure you want to delete chart '%s'?" % title):
					system.db.runPrepUpdate("DELETE FROM saved_graphs WHERE id = ?", [id], database)
			pubFuncs = [load, delete]
			publicNames.append(rowTitle)
			publicFuncs.append([pubNames, pubFuncs])	
	except:
		create = 1
		
	names.append("Public")
	funcs.append([publicNames, publicFuncs])
	names.append(username)
	funcs.append([privateNames, privateFuncs])
	
	menu = system.gui.createPopupMenu(names, funcs)
	menu.show(event, 0, 28)

This effectively creates a multi-level popup menu, with User / Public as the top tier, a list of saved charts as the second tier, and Load/Delete as the third tier.

The client has managed to create enough saved charts to fill the list (currently 45), and yet there is no scroll bar, so they cannot access components off screen. Reading the other forum thread on this topic, it seems like scrollbars aren't supported for the built-in script, and we need to rework the script to use JidePopupMenu or JYPopupMenu, but I am at a bit of a loss for how to best transform the above for loops into something that will produce a working list.

I was hoping it would be a simple ask to provide basic function, but it seems like its more than I was expecting. Any advice on how to best approach this and what the core of that code would look like?

Thank you in advance.

What version of Ignition are you using? As of 8.1.25, system.gui.createPopupMenu automatically creates one that scrolls. There are a few minor issues with deeply nested menus/menus near the edge of the screen, resolved in 8.1.32, but the core functionality should be there.

1 Like

Go figure! The gateway in question is 8.1.24 :man_facepalming:

Sounds like another reason to get the minor revision updates scheduled if we can.

I went ahead and ran the upgrade on a test bench (8.1.32), but I'm not seeing the scroll bar behavior. Is there some additional configuration we need to apply to the popup?

The top level menu scrolls. Submenus don't. :man_facepalming:

We've already got an internal ticket going for the fix.

Ahh. Thank you for the clarification.