Click here to Skip to main content
14,093,548 members
Click here to Skip to main content
Add your own
alternative version

Stats

22.5K views
16 bookmarked
Posted 2 Oct 2017
Licenced CPOL

A Folder Browser using the Bootstrap Treeview – Part 1

, 6 Oct 2017
Rate this:
Please Sign up or sign in to vote.
Use the Bootstrap Treeview plugin to build a folder browser widget

The article submission wizard is messing around with my Code Dowload link, so here is the code in GitHub:

https://github.com/bradykelly/bootstrap-folder-browser.git

Please ignore the FancyTree project in the source. That is an article in early progress and will feature here very soon.

Introduction

A short while ago, I had a coding requirement that my usual box of tricks couldn’t meet: a file system folder browser for a web application. The user needs to select a folder for backup to another location.
I looked around at several file/folder browser widgets I have used, or at least seen, before. JQuery and family have plenty, and one that deserves honourable mention is jQuery FancyTree. It’s almost a pity I won’t be covering that one yet, but for now, I will focus on my Bootstrap based folder browser. I chose the Bootstrap Treeview product because it is simple and easy to use, as well as it already being styled like the rest of my project, the “Twitter Bootstrap default look”. It is, of course, also free and open source.

My adaptation of this widget for my folder browser is far from feature perfect and has some drawbacks, but it was very quick and easy to implement, it works, and it allows me to choose a folder, the only real requirement here. This part of the article covers setting up a treeview to show file system data. At the end of this part, you should be able to create a web page on which people can browse folders. Once the important stuff is out of the way, in Part 2, I will show you how to package up all this cool into a new Folder Browser widget, with more features and more reusability.

Figure 1 - Example Folder Browsing

First Steps in Setting Up the Bootstrap Treeview

Include the Treeview in the Project

This is as simple as downloading or installing Bootstrap Treeview and referencing scripts and styles in your view (or layout page). The package is available via npm and bower, or you can clone or download the whole repo from github. My problem with npm is that it just downloads the package and adds it to the solution’s node_modules folder, and doesn’t install any files into the project itself. If I must go and pick and copy files from node_modules, I may as well download the whole repo and pick and choose my files from there. The files I do choose and copy, I place, ready for serving, in the wwwroot\lib\bootstrap-treeview folder.

Style and Script References

My style references for this project, for the Development environment, look like below. I always try and use non-minified files for development, but there is no non-minified file is this case:

<environment include="Development">
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
    <link href="~/lib/bootstrap-treeview/bootstrap-treeview.min.css" rel="stylesheet" />
</environment>

In a non-development environment, I use the minified versions of all sheets, where available.

My development script references look like this:

<environment include="Development">
    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
    <script src="~/lib/bootstrap-treeview/bootstrap-treeview.min.js"></script>
</environment>

I also only normally use non-minified scripts for development, except when not available, like here.

The Container View and Script

Place a container and script on your view, ideally a div, in which the Treeview must appear:

<div id="tree"></div>
function getTree() {
  // Some logic to retrieve, or generate tree structure
  return data;
}

$('#tree').treeview({data: getTree()});

Getting Data

The Bootstrap Treeview is very simplistic but tricky. It requires the tree graph data to be ready before you initialize the Treeview. The most basic data structure for one node has only two properties: Text is the name of a folder, and Nodes is a collection of node structures for the folder’s children.

The fact that we must have the data before initializing the widget means we can do the initialization of the treeview in the done jQuery Ajax method (or equivalent):

The Ajax Callback

$.ajax("@Url.Action("TreeData","TreeView")")
    .done(function(resp) {
        $("#bsTree").treeview({
            data: resp
        });
    })
    .fail(function(error) {
        console.log(error);
    });

The Structure

My simplified node class, TreeNode, is designed to represent a tree node, and is a very small subset of the full specification for a node object, as seen on the home page for the widget, under the heading Data Structure. There are properties for icons, but I was not able to get anything but ‘+’ and ‘-‘ on expanded and collapsed nodes. No other icons are visible on the online examples either, so I chose to omit the icon properties totally, to keep things as small and tidy as possible.

TreeNode Class

public class TreeNode
{
    [JsonProperty("text")]
    public string Text { get; set; }

    [JsonProperty("nodes")]
    public List<TreeNode> Nodes { get; set; } = new List<TreeNode>();
}

Using this node structure, a JSON reply to the Treeview might look something like this:

JSON Example

[
  {
    "text": "Folder1",
    "nodes": []
  },
  {
    "text": "Folder2",
    "nodes": [
      {
        "text": "FolderB",
        "nodes": [
          {
            "text": "FolderOne",
            "nodes": []
          }
        ]
      }
    ]
  },
  {
    "text": "Logs",
    "nodes": []
  }
]

Recursion

This treeview requires a recursive data structure (JSON) to represent a whole folder tree as the widget does not support lazy loading, or any Ajax beyond the initial load. It, therefore, needs all the data at once. I find this unnerving, because nesting is rife on a filesystem, to who knows what depth, and building and transferring huge JSON documents only hurts performance.

I achieved the required recursion using the following controller code:

The Controller

public class TreeViewController : Controller
{
    private FileTreeConfig _config;

    public TreeViewController(IOptions<FileTreeConfig> config)
    {
        _config = config.Value;
    }

    public IActionResult TreeData(string dir = "")
    {
        var browsingRoot = Path.Combine(_config.BaseDir, dir);
        var nodes = new List<TreeNode>();
        nodes.AddRange(RecurseDirectory(browsingRoot));
        return Json(nodes);
    }

    private List<TreeNode> RecurseDirectory(string directory)
    {
        var ret = new List<TreeNode>();
        var dirInfo = new DirectoryInfo(directory);

        try
        {
            var directories = dirInfo.GetDirectories("*", SearchOption.TopDirectoryOnly);
            foreach (var dir in directories)
            {
                if (dir.FullName.ToLower() == dirInfo.FullName)
                {
                    continue;
                }
                var thisNode = TreeNode.FromDirInfo(dir);
                thisNode.Nodes.AddRange(RecurseDirectory(dir.FullName));
                ret.Add(thisNode);
            }
        }
        catch (UnauthorizedAccessException ux)
        {
            // NB Log. 
        }
        return ret;
    }
} 

I think the above code is readable, given we know its exact purpose, so no more explanation is required.

The Browsing Root

Especially with this treeview, that only works on recursion, we don’t want the user navigating to the file system root, and have our poor code recurse all that. It seems ideal to have a browsing root, set to a file system folder, that the user cannot browse beyond. They can, using relative paths, but the complexity of checking these paths is beyond the scope of this simple exercise. We can only feel sorry for the poor soul that dreams up a real string of “..\..\..” type paths.

I have a config setting called BaseDir, which is the path to where the root of the treeview must begin. The Path.Combine in the controller code helps establish this as the root for the treeview.

Conclusion

If you have followed along this article together with the official documentation for the Bootstrap Treeview, you should easily be able to put together, if not a real browser widget, at least a page (e.g. Figure 1- Example Folder Browsing) that can browse folders. In the second and last part of this article, I will show you how to use your code to build a proper, reusable widget that can be plugged in anywhere you need folder picking or browsing.

History

This is the first public draft for Part 1 of 2.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Brady Kelly
Founder Erisia Web Development
South Africa South Africa
I am a software developer in Johannesburg, South Africa. I specialise in C# and ASP.NET MVC, with SQL Server, with special fondness for MVC and jQuery. I have been in this business for about eighteen years, and am currently trying to master Angular 4 and .NET Core, and somehow find a way to strengthen my creative faculties.
- Follow me on Twitter at @bradykelly

You may also be interested in...

Pro

Comments and Discussions

 
QuestionShowing the files in a folder Pin
Isaac Koomson14-Mar-18 3:32
memberIsaac Koomson14-Mar-18 3:32 
AnswerRe: Showing the files in a folder Pin
Isaac Koomson14-Mar-18 3:46
memberIsaac Koomson14-Mar-18 3:46 
AnswerRe: Showing the files in a folder Pin
Member 1150179424-Apr-18 20:30
memberMember 1150179424-Apr-18 20:30 
QuestionWell done Pin
Mike Hankey3-Nov-17 1:05
professionalMike Hankey3-Nov-17 1:05 
QuestionWhere's the code? Pin
Dewey2-Oct-17 15:28
memberDewey2-Oct-17 15:28 
AnswerRe: Where's the code? Pin
Brady Kelly2-Oct-17 19:08
memberBrady Kelly2-Oct-17 19:08 
GeneralRe: Where's the code? Pin
Dewey3-Oct-17 16:30
memberDewey3-Oct-17 16:30 
GeneralRe: Spoke too soon! Pin
Dewey3-Oct-17 16:33
memberDewey3-Oct-17 16:33 
GeneralRe: Spoke too soon! Pin
Brady Kelly3-Oct-17 20:30
memberBrady Kelly3-Oct-17 20:30 
GeneralRe: Spoke too soon! Pin
Dewey7-Oct-17 1:02
memberDewey7-Oct-17 1:02 
Good job putting it on Github.

Thanks!

GeneralRe: Spoke too soon! Pin
Brady Kelly7-Oct-17 1:34
memberBrady Kelly7-Oct-17 1:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web06 | 2.8.190518.1 | Last Updated 7 Oct 2017
Article Copyright 2017 by Brady Kelly
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid