Building a Web Based File Browser with jsTree, Angularjs and Expressjs

Tweet about this on TwitterShare on LinkedIn4Share on Google+0Share on Reddit0Buffer this pageFlattr the authorEmail this to someonePrint this page

Building a Web Based File Browser with jsTree, Angularjs and Expressjs

In this post, we are going to build a web based File Browser with Angularjs, Expressjs and jsTree (a super awesome jquery tree plugin). We will be using a jsTree directive named jsTree-directive written by me to leverage jsTree (jQuery Plugin) inside an Angularjs application. (You can find the complete documentation of the directive here.)

The main purpose of the file browser app is to show a tree view of a certain directory on a web page. And when the user clicks on a file, we will show the contents of the file. A simple application to showcase the power of jstree directive and also jsTree and Expressjs integration.

The final product will look like

Screen Shot 2014-08-27 at 7.15.45 am Screen Shot 2014-08-27 at 7.16.25 am Screen Shot 2014-08-27 at 7.17.06 am

You can find a live demo here and the completed code here.

So, Let us get started.

File Browser App

As described above, we will be building a tree using the jsTree directive. The content of the tree will be loaded via AJAX. The Express server, will read the contents of node_modules folder and will print out all the files in a given folder. And then, we will update the tree with the response. As the user drills down the folder, we fetch and show the files/folders in the current requested directory.

Now, when a user clicks a file, we will fetch the contents of it and display on the right hand side part.

Getting Started with Server Side

First, create a new folder named myFileBrowserApp. To keep things organized, we will create 2 folders named server and client. As the names suggest, the server will contain the Express/Node code and the client will contain the Angularjs/jsTree and UI code.

First, we will work on the server. Open a new terminal/prompt at the root of the project folder and run

npm init

And fill it up as applicable. Now, we will add a few dependencies to run the project. Execute

npm i express morgan ejs debug cookie-parser body-parser --save

This will install the required dependencies to run the project. To maintain our project, we will be using Gulp. Execute

npm i --save-dev gulp gulp-jshint gulp-livereload gulp-nodemon gulp-watch

to install gulp and its plugins as development dependencies. The final package.json will look like

Inside the server folder, create a new file named app.js. This file will contain the Express server configuration and initiation. Update it like

A very standard Expressjs config. You can read more about Expressjs here.

Next, we will add a new file named routes.js at the root of server folder. This file will contain all the routes. Updated it as

Things to notice

Line 10 : If the user hits the base URL  http://localhost:3000, we dispatch the index.html file to the client. This file will contain the Angularjs app.

Line 15 : This the end point, the jsTree plugin will look for data to render the tree. jsTree plugin is configured to send an id=1 when requesting the tree for the first time. Hence on line 17, we split the code into 2 pieces. If we are loading the tree for the first time on the page, we show all the first level folders of node_modules directory on the server. Else, we will fetch the requested folder’s files and display. Simple?

Line 19 : Here we invoke  processReq(), which is responsible for taking in a folder, reading all the files/folders in that folder. Once it reads all the files/folders, it will call processNode() on line 40.  processNode() will take a file/folder and return a Tree Node object for that file/folder.

Line 48 :  The tree node object should be in the below format.

Now, you have a good picture as what our server is going to respond based on the request from the tree.

Line 32 : We get the path of the file, passed in from the ID attribute of the node object (or you can configure the directive to send the file name in a different variable as well). And then we get the contents using  readFileSync() and dispatch it to the UI.

This closes most of our server side functionality.

To test this, we will run the server. Back to terminal/prompt and run

node server/app.js

Next, navigate to  http://localhost:3000/api/tree?id=1  and you should seeScreen Shot 2014-08-27 at 12.09.59 amThis is the folder listing (one level) of the node_modules folder. Sweet right?

To close the server side part, we will dispatch a web page, when the user navigates to  http://localhost:3000. Inside the server folder, create a new folder named views. Inside views folder, create a new file named index.html. Update it as below

Save all files and re-run node server/app.js  and navigate to   http://localhost:3000/ and you should see the page we built above. We will update this page, once we are done setting up the client.

Getting Started with Client Side

To manage most of our client side dependencies, we will be using bower. Install bower globally by running

Windows.Mac/*nix
npm i -g bower    sudo npm i -g bower

Once bower is installed, from the project root run

bower init

And fill it up as applicable. Next, we will create a new file named .bowerrc at the root of the project and update it as below

Next, we will install a few dependencies. Run

bower i --save jquery angular angular-route bootstrap jstree-directive

This will install the required dependencies.

Next up, we will add jsTree plugin dependencies, which are not available on bower. Inside the client folder, create a new folder named jstree. Next, Head to jstree.com, download the latest release and unzip it. Now, from the unzipped folder, copy the below to client/jstree

  • <unzipped>/dist/jstree.min.js file
  • <unzipped>/dist/themes folder

PS : Do note that if you want to make your app 100% Angular, load the jsTree Javascript dependencies using an Angular provider as a dependency.

The folder structure so far would look like Screen Shot 2014-08-27 at 12.38.55 amNow, create 3 new folders inside client named js, css and partials. The js folder will hold the Angular code. The css folder, some custom styles and finally partials to hold the angular templates.

Now, the updated server/views/index.html would look like

PS : Do note that if you want to make your app 100% Angular, load the jsTree Javascript source using an Angular provider as a dependency to jsTree.directive.

PS : We will add the missing resources soon.

Next, we will configure gulp. At the root of the project, create a new file named gulpfile.js and update it as

Save all file, back to terminal and run

gulp

This will start the server and reload the page on changes. Refresh  http://localhost:3000  and the output should not change.

Integrate the Server and Client

Now, create a new file name app.js inside client/js folder and update it as

This is our Angular module, that has ngRoute and jstree.directive as dependencies. If you are new to Angular refer this.

Next, create a new file named home.html inside client/partials folder and update it as

The above js-tree tag is the angular directive.  tree-ajax attribute provides the path to request for tree data. This is what we have built on our server side initially.  tree-events attribute will take in an jstree event separated by colon and then the scope function that needs to be called when the event is fired. (You can find more info here)

Next, create a file named controllers.js inside client/js and update it as

We include FetchFileFactory (an Angular factory responsible for fetching resources) as a dependency. We will create this in a moment.

Line 6 :  fileViewer  is a scope variable that shows the contents of file if present else the message.

Line 8 : Callback for the node click event. We check if the clicked node is a leaf or not based on the data we have sent from server and then call the  FetchFileFactory  to fetch the selected file’s content. Simple right!

And finally, we will create the FetchFileFactory. Create a new file named factory.js inside client/js folder and update it as

Now, we will update the styles. Create a new file named app.css inside client/css and update it as

As you can see from above, we are adding new icons for the folder and file. You can download the same from here and create a new folder inside client named imgs and dump them.

Save all the file and navigate to http://localhost:3000  and you should see

Screen Shot 2014-08-27 at 7.15.45 am

Screen Shot 2014-08-27 at 7.17.06 amHope you got a basic idea on how to integrate jsTree with Express and jsTree with Angularjs as well.


Thanks for reading! Do comment.
@arvindr21

Tweet about this on TwitterShare on LinkedIn4Share on Google+0Share on Reddit0Buffer this pageFlattr the authorEmail this to someonePrint this page
  • sonal

    I have multiple instances of jstree.
    var selectedNode = $(‘js-tree’).jstree(true).get_selected(true)[0];
    var parentNodeId = $(‘js-tree’).jstree(“get_parent”,selectedNode);
    but parentNodeId is returning false. It does not understand which tree is it on.

  • Igor Draskovic

    Awesome tutorial! I am just confused about the angular service. How do you hit an external api or a web server…? Lets say I want to browse the files on my github repo…where do I add the url?

  • Anderson

    Do you intend to create the jstree directive to angular 2.0? Do you believe is to hard to portate the jstree-directive to angular 2.0?

  • Daniel Suherman

    Arvind, thank you for sharing this. I’m a newbie with JScript. Would it possible to sort the files/directories based on name or mdate ?
    function processReq(_p, res) {

    var resp = [];
    fs.readdir(_p, function(err, list) {
    for (var i = list.length – 1; i >= 0; i–) {
    resp.push(processNode(_p, list[i]));
    }
    res.json(resp);
    });
    }

  • Zac

    Where can I include plugin options such as drag_target for dnd?

  • Bruno Bottazzini

    Is it possible to get a node from a path and set it selected ? Example: getNode(“/src/file.txt”).setSelected(true);

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      Instead, I would recommend triggering a click on the leaf and let the things flow from there on. For this you should already know a unique class, id or attribute on the leaf, and then triggering a click on that leaf using jQuery. This will executed all the required operations. This is the “programmatic” way of selection. Thanks.

  • abhishek kusnoor

    Hey Arvind!,
    Thanks for this great implementation. I was using this for a small college project. It would be useful if you give some insight on like, if I click on any of the file/folder, its unique url should show up in the address bar of the browser? Thanks :)

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      Thanks abhishek.

      As of now, when the user clicks on a tree node we get the data from server. So if you want to create bookmark-able URLS, you need to append the unique id or some key to the URL using the $location.hash().

      Later on, once the page loads, inside the required controller again use the $location.hash() you can get the hash, if any and make an appropriate request instead loading a blank pane on the right side.

      I have not tested it but, this should technically work.

  • Ash

    Require a sample function to call create_node,delete_node,rename_node

    • http://thejackalofjavascript.com/ Arvind Ravulavaru
      • ashu

        Thanks for the link,but if my event is createNode then how to provide the parent,text and ID using the below
        $scope.CreateNodeNB = function(e, data) {
        console.log(‘Create event call back’);
        };

        • http://thejackalofjavascript.com/ Arvind Ravulavaru

          You can use the data argument to extract the current node details. Refer : http://stackoverflow.com/a/12271060/1015046 and http://www.jstree.com/api/#/?f=create_node.jstree

          Are you programmatically looking to create a node? or you are creating a node and want a callback?

          • ash

            Thank u…
            How to use Open_all in angular?
            In jquery i used like below
            var instance = $(‘#jsTree’).jstree(true);

            instance.open_all();

          • http://thejackalofjavascript.com/ Arvind Ravulavaru

            You may need modify the directive : https://github.com/arvindr21/jsTree-directive/blob/master/jsTree.directive.js#L153 and add your logic once the tree has been initialized.

          • Zubair Idrees

            How can I call set_id method using your api? I want to update nodeId after adding new node

            Can you share some detail

          • http://thejackalofjavascript.com/ Arvind Ravulavaru

            You can follow the same approach as how you would do with the normal jsTree. Expect, you register the listener for a certain activity to be completed ( http://jstree-directive.herokuapp.com/#/events ). Let me know if you need more information.

  • Thabang Masigo

    Hello Arvind,

    I have my own restful server implementation in java. If I am pointing to one of my rest endpoints using tree-ajax will there be no complications?

    The UI is not reporting any errors and I am not any display of the tree. I might be that I have not included jstree and the directive properly but I would expect some error report on the console if this was the case.

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      Hello Thabang Masigo. No, consumption of a REST end point is agnostic to the server side language.

      If you have only added the directive tag on the page or included only the directive js file, nothing would happen. Angular will ignore these. You can need to have both along with jstree source code.

      Also, once you have confirmed the setup, navigate to the REST endpoint in the browser and see if the data is coming as expected.

      Thanks.

  • R G

    I would like the directive to call a method in my controller every time it wants to make an AJAX call instead of automatic call to Ajax endpoint using tree-ajax. Is that possible? Also is there anyway to set the icon property on the client instead of server setting it up in the payload? Thanks.

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      Hello R G,

      Yes, it is possible. You can bind an event named before.refresh and assign a scope function to that using tree-events( http://jstree-directive.herokuapp.com/#/events ) and then your method will be called before the AJAX call.

      More info : https://groups.google.com/forum/#!topic/jstree/7X8ODisUw5k

      Yes, you can use the types plugin http://www.jstree.com/plugins/

      Thanks.

      • R G

        Hi Arvind,

        Do you mean I can totally avoid tree-ajax attribute and rather let my controller take control using “before.refresh” event i.e. scope function on controller can then contact the server endpoint for data? If yes how would I bind the data from controller to jsTree? Can you please provide a code snippet?

        Thanks for the quick reply and useful links.

        • http://thejackalofjavascript.com/ Arvind Ravulavaru

          Hello R G,

          No, I would recommend not to by pass tree’s AJAX call. Then your code would be too tightly coupled and become messy.

          Alternatively, if you want to control the tree data, I recommend using the js-tree tag with tree being generated from scope ( Load Static Tree – From model in scope : http://jstree-directive.herokuapp.com/#/basic ). Then add events like node expanded or node collapsed using the same events attribute on the js-tree tag.

          Now when the node expands, a method in the controller will be called with the node’s details. Do the processing with it and create your own factory to fetch the data. Once the response comes back, update the scope variable which holds the tree.

          This will update the tree. This way your code would be lot cleaner.

          Let me know if you have other solutions in mind.

          Thanks.

  • liu

    how to create node with the ajax?

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      Hello Liu, Please refer this : http://jstree-directive.herokuapp.com/#/ajax

      • liu

        I means after i create a new node i want to trigger ajax,where i can write the ajax post to database?

        • http://thejackalofjavascript.com/ Arvind Ravulavaru

          You will provide the Ajax endpoint in tree-ajax="/tree" and when you expand this node, a AJAX call will be automatically fired to this endpoint with the parent id. You can see the same in the attached screenshot.

          And on your server side, you need to dispatch the corresponding child JSON depending on the parent ID, as shown in the above post.

  • Roman Kobzarev

    Very nice article. I like the directive, works great. I am having an interesting issue, however. I pulling the data from Firebase via AngularFire. All is fine, except, there are two Search Tree inputs. Now, I am using a promise to get the data since there is a bit of delay, is that the cause for two boxes?

    Thank you
    Roman

    • Roman Kobzarev

      Figured it out. I suspect same issue will come up regardless if working Firebase. Need to let the tree initialize.. then $watch. Just couple lines of code should do it. In the directive (for link/scope), I added a initialization to ignore initial load.

      var initializing = true;
      s.$watch(a.treeModel, function() {
      if (initializing) {
      $timeout(function() { initializing = false; });
      }
      else {
      tree config…
      }

      • http://thejackalofjavascript.com/ Arvind Ravulavaru

        Hello Roman, Thanks!

        Not sure why there was an issue in the first place. But since you are using AngularFire, I am assuming your code to be

        and your tree would be

        Does this setup cause an issue too?

        Thanks.

        • Roman Kobzarev

          I am using the Array instead. Object will need to be manipulated to work. So, I tried it and same issue. Not a problem though, since the initialization does work for me.
          Thanks for getting back!

          • http://thejackalofjavascript.com/ Arvind Ravulavaru

            Not an issue. Will see if any one else reports the same issue. Then will merge the solution you provided. — Thanks.

  • Christophla

    Why cloud the implementation up with nodejs?

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      Cloud as in?

  • Sanjeev

    Nice article on jstree directive Arvind!! …

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      Thanks Sanjeev!

  • Amenallah

    This helped me allot understanding angularJs and expressJs, thanks.

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      Thanks! Glad it helped!

  • Seth Williams

    when I run the command ‘gulp’ I get “No command ‘gulp’ found”…this is right after creating the gulpfile.js in the instructions.

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      Do you have gulp globally installed? Else run

      Thanks.

      • Seth Williams

        I followed this step

        This will install the required dependencies to run the project. To maintain our project, we will be using Gulp. Execute

        npm i –save-dev gulp gulp-jshint gulp-livereload gulp-nodemon gulp-watch

        to install gulp and its plugins as development dependencies. The final package.json will look like…

        • http://thejackalofjavascript.com/ Arvind Ravulavaru

          In this scenario, it would be ease of use. If you did install gulp globally, all you would need to do is run

          to trigger the default task. But since gulp is not globally available, you will need to run

          in a *nix machine and

          in a windows machine.

          Thanks.

      • Seth Williams

        Shouldn’t this install gulp locally…why do I need gulp installed globally?

  • Sumit Chawla

    I actually have a pure jQuery implementation to support old browsers:

    https://www.npmjs.org/package/file-browser

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      Nice! Thanks for sharing.

  • Oron Ben Zvi

    I don’t see the fileViewer on the html

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      Can you pls elaborate the issue?

      • Oron Ben Zvi

        Your home.html partial missing the fileViewer:

        Though it appears to be there in the github repository, it should be fixed inside the blog as well

        • http://thejackalofjavascript.com/ Arvind Ravulavaru

          Thanks! Fixed it.

  • Oron Ben Zvi

    Hi Great article, just some inconsistencies,
    1. if you chose to use ‘use strict'; and self executing functions in the server side, why not doing so in the client side as well?

    2. your angular controller is minification-safe using the array syntax while your factory is not.

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      Hello @oronbenzvi:disqus Thanks! Fixed the inconsistencies.