Fill dropdown list with folder of tags

Hi everyone,

I would like to know if it is possible to fill a dropdown list with a all the tags of a folder, I did not find the syntax of a loop to browse the tags of a folder

Thank you in advance for your help.

Théo.

Welcome to the forum.

This should help you, system.tag.browse - Ignition User Manual 8.1 - Ignition Documentation

Hello!

Thank you for your answer.

I found that function and tried to use it but maybe it's not the right place or i executed it badly but it doesnt work

system.tag.browse('[default]',{"recursive":True})
for result in results.getResults():
print result

Should I put it in a 'For' loop ? Does it have to be used inside a script or in an expression ?

Do you have any example ?

Théo.

You don’t seem to be storing the returned value from the function in a variable. Also - and this could just be a formatting issue - your print statement must be indented. I’m surprised this code is not throwing an error/exception when you try to execute it; I would expect a NameError since results does not exist as a variable by the time you reference it.

Try this:

results = system.tag.browse(’[default]’,{“recursive”:True})
for result in results.getResults():
    print result

Following up on this. Should this be applied with an expression binding to the options object? Getting errors when trying to populate the list.

Thanks

This is a script, not an expression.

To populate a dropdown from a tag browse, I would not expect there to be any binding at all, rather, on some type of event run this script and write the list to the options object at that time.

I suppose you could use a binding to some dummy value and then use a script transform to run the script.

The provided script does not build the required dictionary structure that the options property expects, so there will need to be some additional code.

1 Like

Approaching it from the project library scripts, could you tell me what I'm missing?

def tagNames():
	results = system.tag.browse(path = "[default]_AlignmentPress1/MOD0_GEN/CycleTime")
	for result in results.getResults():
	    tagPath = str(result['fullPath'])
	    tagName = tagPath.replace("[default]_AlignmentPress1/MOD0_GEN/CycleTime/","")
	    print tagName

def tagPaths():
	results = system.tag.browse(path = "[default]_AlignmentPress1/MOD0_GEN/CycleTime")
	for result in results.getResults():
		tagPath = str(result['fullPath'])
		tagName = tagPath.replace("[default]_AlignmentPress1/MOD0_GEN/CycleTime/","")
		print tagPath

Would I need to create a method that writes to "value" and "label" for the options object?

Yes

Rather than printing the tag name, you need to construce a List of Dictionary objects.

The value is how you determine what has been selected, often this is some type of index value, though it can be anything. Label is what the end user will actually see in the dropdown list.

I would do something like this:

def tagNames():
	parentPath = "[default]_AlignmentPress1/MOD0_GEN/CycleTime"
	results = system.tag.browse(path = parentPath)
	return [{'value':idx, 'label':tagPath.replace(parentPath,'')} for idx, result in enumerate(results.getResults())]

You can set the value to the full tag path, or you could use a call to readBlocking to get the actual value of the tag, or you could set the value and the label to the same thing. Just depends on how you plan to use the drop down.

Since this is in a script library, it is possible to use an expression binding with the runScript expression function, to run it.

1 Like

Super helpful, and appears to have solved the issue. Thank you

1 Like

Don't forget to hit the "Solution" link on the answer that worked!

Don't think that @andrewk is the OP.

1 Like

I just did this and found a rather simple solution (for my use case). Can easily be adapted as needed.

I made an expression memory tag and used the runScript() function to fill a dataset with the description and name (parameters from my UDT). Then, a binding with a script transform on the options prop. This method is great because it will automatically add new objects to the dataset for use in the dropdown.

Get the list of all sensors in my "Analog_Sensors" folder:

def getSensorList():
	#get all tags in the analog folder
	tags2 = system.tag.browseTags(parentPath = "[default]Analog_Sensors",udtParentType="Scaled_Analog_2SP")
	tags4 = system.tag.browseTags(parentPath = "[default]Analog_Sensors",udtParentType="Scaled_Analog_4SP")
	
	tags = tags2 + tags4
	
	#extract tag names and descriptions
	names = [str(t.name) for t in tags]
	desc = [ str(d.value) for d in system.tag.readBlocking([str(t)+"/Parameters.Name_SA2SP" for t in tags]) ]
	
	#create dataset
	headers = ["Description", "Tag Name"]
	data = [[desc,name] for desc, name in zip(desc, names)]
	
	#print data
	
	dataset = system.dataset.toDataSet(headers, data)
	
	return dataset

Tag list dataset tag:
image

Binding to the options array:

def transform(self, value, quality, timestamp):
	options = []
	
	data = system.dataset.toPyDataSet(value)
	
	for row in data:
		desc = row["Description"]
		name = row["Tag Name"]
		options.append({"label":desc+" ("+name+")","tag":name})
		
	sortedData = sorted(options, key=lambda d: d[self.custom.sort_by])
	
	return [dict(item, **{'value':index}) for index, item in enumerate(sortedData)]

**self.custom.sort_by is an extra parameter that I used to allow users to change the sort order when they're looking at the list - sorting is an entirely optional step, just substitute options for sortedData in the return statement if you don't want to do any sorting.
(I also wanted buttons for advancing to the next and previous items in the list, so knowing the currently selected index was important - that's the reason that value is always equal to the index, it avoids having to search through the options list to find the value that matches the current selection.)

Then, just add one extra prop to the dropdown, and this prop can be used for indirect bindings.

selectedTag = {this.props.options}[{this.props.value}]["tag"]

@theo @andrewk

You can do this instead:

tags = system.tag.browseTags(
    parentPath="[default]Analog_Sensors",
    udtParentType="Scaled_Analog_*SP"
)

edit: I'm confused.
how does that even work ?

names = [str(t.name) for t in tags]
desc = [ str(d.value) for d in system.tag.readBlocking([str(t)+"/Parameters.Name_SA2SP" for t in tags]) ]
  • browse results don't have a .name property, only a ['name'] attribute.
  • casting a browse result into a string does not look like a path at all.

Is there another step I missed ?

Good catch. **edit: changing that results in an error because that parameter does not support wildcards. I think I did actually try that first.

Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<module:Lists>", line 7, in getSensorList
ValueError: tagPaths list should not be empty

browse is different than browseTags

.name is the literal name of the tag.

See the example in the manual.

Does system.tag.browse() work to filter on UDT type?

You all might find the jsonValues virtual property interesting. As in, system.tag.readBlocking(["path/to/folderTag.jsonValues"]). It returns a JSON structure describing all the tags in the folder.

1 Like

To answer my own question, yes you can filter on UDT type using typeId, but it also does not support the wildcard character.

I've not messed with JSON structures much, but I can see how that would be useful. That's alot of information to parse out all at once and my solution is working, so I it's just not something I'm willing to put time in to at the moment.

I also like that my solution is fairy straightforward and someone who's not me should be able to follow it in the future. Not everybody I work with is a text programmer (lots of ladder around here), so nested loops to parse out the JSON might cause more issues.

Is there a reason that browseTags() still directs to the 7.8 manual? - It also doesn't show up in the script auto-complete menu either.
image

browseTags is deprecated. Browse is the modern replacement.

Is there any real reason that I should switch over to browse() instead of browseTags(), considering that it's working and complete at the moment?

Getting links to it in the modern manual?

Basically... no, probably not. With pretty much only one exception I can think of, we never remove scripting functions and take great pains to make 'legacy' functions continue to work after an upgrade. It is possible we change that policy at some point in the future, so across a major version change (ex 8.3 -> 8.4/5) you'd have to change your scripts to the modern alternative function, but I would not consider it likely to happen. system.tag.browseTags is already gone from the autocomplete in the software, which is another consideration w.r.t maintenance.