# Allow user to browse for network file and return path?

**URL:** <https://forum.inductiveautomation.com/t/allow-user-to-browse-for-network-file-and-return-path/102511>\
**Category:** Ignition\
**Tags:** perspective, ignition81\
**Created:** [February 18, 2025, 7:22pm UTC](https://forum.inductiveautomation.com/t/allow-user-to-browse-for-network-file-and-return-path/102511 "2025-02-18T19:22:06Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![David\_Nicks](https://avatars.discourse-cdn.com/v4/letter/d/8491ac/32.png) [@David\_Nicks](https://forum.inductiveautomation.com/u/David_Nicks)\
**Post date:** [February 18, 2025, 7:22pm UTC](https://forum.inductiveautomation.com/t/allow-user-to-browse-for-network-file-and-return-path/102511/1 "2025-02-18T19:22:06Z")

</div>

I'm expecting a no answer on this, but I thought I'd ask anyway...

The end user stores PDFs on a network file share that is accessible by both the client and the gateway. From our Perspective UI, they want to be able to browse to that file location and retrieve the path to a specific PDF, so we can save the path in our database.

We don't want to duplicate this source of record by uploading the PDF using the FileUpload component. We just want to retrieve the filepath, e.g. `\\server\folder\file.pdf`.

Since we know the path to the file share, is there a way to pull a directory listing and build a surrogate file browser to allow the user to select from?

It's just too much to ask for the end user to open File Explorer in Windows and copy/paste the file path into Perspective.

---

<div class="post-metadata">

**Author:** ![Transistor](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/transistor/32/3380_2.png) [@Transistor](https://forum.inductiveautomation.com/u/Transistor)\
**Post date:** [February 18, 2025, 7:27pm UTC](https://forum.inductiveautomation.com/t/allow-user-to-browse-for-network-file-and-return-path/102511/2 "2025-02-18T19:27:30Z")

</div>

[Gateway scripts cannot access to mapped drives - #6 by pturmel](https://forum.inductiveautomation.com/t/gateway-scripts-cannot-access-to-mapped-drives/78213/6) has some points you should be aware of regarding this topic (even though you're only reading).

---

<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:** [February 18, 2025, 7:28pm UTC](https://forum.inductiveautomation.com/t/allow-user-to-browse-for-network-file-and-return-path/102511/3 "2025-02-18T19:28:33Z")

</div>

Since the gateway has access, you can enumerate the files there and present the results in a Perspective tree component or series of tables. That gives you total control over what to do with those file paths.

---

<div class="post-metadata">

**Author:** ![David\_Nicks](https://avatars.discourse-cdn.com/v4/letter/d/8491ac/32.png) [@David\_Nicks](https://forum.inductiveautomation.com/u/David_Nicks)\
**Post date:** [February 18, 2025, 7:43pm UTC](https://forum.inductiveautomation.com/t/allow-user-to-browse-for-network-file-and-return-path/102511/4 "2025-02-18T19:43:51Z")

</div>

What's the best way to enumerate the files (and folders) from within Perspective?

---

<div class="post-metadata">

**Author:** ![David\_Nicks](https://avatars.discourse-cdn.com/v4/letter/d/8491ac/32.png) [@David\_Nicks](https://forum.inductiveautomation.com/u/David_Nicks)\
**Post date:** [February 18, 2025, 7:47pm UTC](https://forum.inductiveautomation.com/t/allow-user-to-browse-for-network-file-and-return-path/102511/5 "2025-02-18T19:47:21Z")

</div>

Odd - that link points to a thread that contains this same post, which recursively links to itself.

---

<div class="post-metadata">

**Author:** ![Transistor](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/transistor/32/3380_2.png) [@Transistor](https://forum.inductiveautomation.com/u/Transistor)\
**Post date:** [February 18, 2025, 7:49pm UTC](https://forum.inductiveautomation.com/t/allow-user-to-browse-for-network-file-and-return-path/102511/6 "2025-02-18T19:49:51Z")

</div>

> [@David\_Nicks](#):
>
> enumerate the files

Oops, so that's where it went. The editor allows one to look up another question while typing a reply. Usually I have to select which post the reply should go to. I didn't see the prompt and the post didn't show up here so I reposted it.

I'll try to fix it.

---

<div class="post-metadata">

**Author:** ![Transistor](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/transistor/32/3380_2.png) [@Transistor](https://forum.inductiveautomation.com/u/Transistor)\
**Post date:** [February 18, 2025, 7:50pm UTC](https://forum.inductiveautomation.com/t/allow-user-to-browse-for-network-file-and-return-path/102511/7 "2025-02-18T19:50:58Z")

</div>

Meanwhile, try this in Script Console (which will run on your local machine).

```python
import os
directory = "C:\<path to some directory>"
files = [f for (dir, subdirs, fs) in os.walk(directory) for f in fs if f.endswith(".pdf")]
print files	

```

---

<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:** [February 18, 2025, 7:53pm UTC](https://forum.inductiveautomation.com/t/allow-user-to-browse-for-network-file-and-return-path/102511/8 "2025-02-18T19:53:37Z")

</div>

> [@David\_Nicks](#):
>
> What's the best way to enumerate the files (and folders) from within Perspective?

I would ask ChatGPT/et al for "a Jython function to traverse a given directory, given as a string parameter, returning a list of file names inside that directory, using the Java NIO Files API". Should get you at least 90% of the way there.

Python's standard library builtin `os` should work, but isn't really maintained, where relying on the Java standard library means you get updated code as you update Ignition, among other performance and correctness benefits.

---

<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:** [February 18, 2025, 8:12pm UTC](https://forum.inductiveautomation.com/t/allow-user-to-browse-for-network-file-and-return-path/102511/9 "2025-02-18T20:12:45Z")

</div>

Use this:

[java.io.File.listFiles()](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/File.html#listFiles())

Check each result object for `.directory` (boolean) to do recursion, if necessary.

---

<div class="post-metadata">

**Author:** ![David\_Nicks](https://avatars.discourse-cdn.com/v4/letter/d/8491ac/32.png) [@David\_Nicks](https://forum.inductiveautomation.com/u/David_Nicks)\
**Post date:** [February 19, 2025, 7:02pm UTC](https://forum.inductiveautomation.com/t/allow-user-to-browse-for-network-file-and-return-path/102511/10 "2025-02-19T19:02:45Z")

</div>

This worked like a champ. Posting full implementation here for the benefit of others. I'll list the utility function that gets the directory info and then the binding transform that loads a tree component.

> **Utility function to build directory structure**
>
> ```python
> def getDirectoryStructure(unc_path, as_json=False):
> '''
> Traverses a directory at the given UNC path and returns its structure either as a dict/array object or JSON object.
> 	
> Params:
> unc_path: UNC path as a string
> as_json: Set TRUE to generate JSON output.
> 	
> Returns:
> directory structure as either JSON string or array of dictionaries
> '''
> def traverse_directory(directory):
> dir_dict = {
> "name": directory.getName(),
> "path": directory.getAbsolutePath(),
> "subdirectories": [],
> "files": []
> }
> 		
> if directory.isDirectory():
> for file in directory.listFiles():
> if file.isDirectory():
> dir_dict["subdirectories"].append(traverse_directory(file))
> else:
> dir_dict["files"].append(file.getName())
> 		
> return dir_dict
> 	
> root = File(unc_path)
> 	
> if not root.exists() or not root.isDirectory():
> error_output = {"error": "Invalid path or not a directory"}
> return json.dumps(error_output, indent=2) if as_json else error_output
> 		
> result = traverse_directory(root)
> return json.dumps(result, indent=2) if as_json else result
> 	
> # Example usage:
> # Get raw Python dictionary output
> # Notice use of r"" to force raw string interpretation so double \\ aren't required
> # print(get_directory_structure(r"\\server\shared\folder"))
> #
> # Get JSON string output
> # print(get_directory_structure(r"\\server\shared\folder", as_json=True))
> 
> ```

I have this function bound to a custom property `dirStructure`, and the transform below loads a Tree Container.

I also have a view param `validFileTypes`, which is a comma-delimited string of file types (e.g. "txt,pdf") that the transform can use to filter out the results based on file type. There's also a Boolean view param `showOnlyValidFileTypes` that toggles this behavior on and off.

> **Transform script to load Tree Container**
>
> ```python
> def transform(self, value, quality, timestamp):
> 	
> # if valid file types are provided and we want to filter by file type,
> # build a set of filetype extension we can use for string matching later (e.g. {".pdf",".txt'}
> if len(self.view.params.validFileTypes)>0 and self.view.params.showOnlyValidFileTypes:
> validExtensions = {"." + ext.strip().lower() for ext in self.view.params.validFileTypes.split(',')}
> else:
> validExtensions = None # null result means include all
> 	
> # build a single tree node here - we will call this recursively to build the tree
> def createNode(name, path, subdirectories, files, validExtensions):
> node = {
> "label": name, # filename only
> "data": {"path": path}, # full path to file
> "expanded": False,
> "items": []
> }
> 	
> # recursively build nodes for each folder
> for subDir in subdirectories:
> node["items"].append(createNode(
> subDir["name"],
> subDir["path"],
> subDir["subdirectories"],
> subDir["files"],
> validExtensions
> ))
> 		
> # build node for each file
> # here we are highlighting PDF files with an icon
> # this can be extended for other file types of interest	
> for file in files:
> 		
> # filter by file type if desired
> if not validExtensions or file.lower().endswith(tuple(validExtensions)):
> item = {
> "label": file,
> "data": {"path": path + "\\" + file},
> "expanded": False,
> "items": []
> }
> if file.lower().endswith(".pdf"):
> item["icon"] = {
> "path": "material/picture_as_pdf",
> "color": "#F40F02",
> "style": {}
> }
> node["items"].append(item)
> 
> return node
> 		
> if "error" in value:
> return [{"label": "Error: " + value["error"], "data": {"message": value["error"]}, "items": [], "expanded": True}]
> 		
> treeStructure = [createNode(value["name"], value["path"], value["subdirectories"], value["files"], validExtensions)]
> 		
> return treeStructure
> 
> ```
