Let’s talk about folder tree reports

Peter Christensen
Box Developer Blog
Published in
7 min readJun 4, 2024

In my role as a Platform Solutions Engineer at Box I often come across customers who have edge requirements that cannot be solved by Box directly. In these cases we’ll often look for an existing integration but in some cases building a custom app with our APIs is the way to go and recently I had a chance to build a prototype that I will share in this article.

We can call it an Exportable Deal Room Overview (snappy name for sure)... or just a Folder Tree Report. The Deal Room concept is used in several industries like legal and financial to collaborate on a deal/project or similar. It will often consist of a fixed folder structure, with fixed permissions where documents are collaborated on. The requirement I am trying to meet with this prototype is to create an exportable deal room overview that lists the entire folder tree with files and shared links to the files. This can help due dilligence checks and serve as both a point in time reference for easy access and overview of what is in the deal room and also for access when deal is closed. There will inevitably be use cases outside the mentioned industries as well.

This is what it will look like…

The solution

The solution I came up with is implemented as a web app integration that can be invoked on a folder. For the API access I used the new TypeScript SDK that was published by Box earlier this year as part of our new autogenerated SDK push.

Web app integration and authentication

To create a Box web integration I first create an OAuth 2.0 app. The app will manage the authorization and authenication of my integration.

From the developer console I create a custom OAuth 2.0 app in two easy steps.

From the configuration tab I need the the clientId and clientSecret for my app.

Next I create the integration from the integrations tab. This is what will show up for the user in the Box web app.

The callback URL must be https so while I did a initial development and testing on my localhost, the app must eventually be deployed to a https endpoint. I used GitHub pages for this prototype as it is free and easy to setup. For the authentication piece I used a method I had used before which is a simple AWS Lamdba function which can be setup following the detailed instructions in this this GitHub repo.

The client callback URL I use is as below and parameters must be named as shown as these are used by the token generator. The clientId is required for the AWS Lambda function that does the token exchange. https://pcboxdemo.github.io/indexer/index.html?clientId=THE_CLIENT_ID_COPIED_EARLIER

When the app has been created and configured I have this new item on my Integrations menu for folders

The authentication happens in the AWS lambda function where the authorization code is exchanged for an access token using the token access request with authorization grant. I use the Node.js SDK in the code for the Lambda.

  var sdk = new BoxSDK({
clientID: clientId,
clientSecret: secret,
});
//Get the tokens from Box using the Auth code
await sdk.getTokensAuthorizationCodeGrant(
authCode, null, function (err, tokenInfo) {
tokens = {
token: tokenInfo.accessToken,
refreshToken: tokenInfo.refreshToken
};
}
);

The Lambda returns the token obtained during the token exchange above to the calling page and the authentication part on the index.html page is as below. I run it when the page loads so I have access to the Box API immediately

$.ajax({
method: 'get',
url:"https://MY_LAMBDA_GATEWAY_URL/default/box-node-token-generator",
data: { authCode: params.get('auth'), clientId: params.get('clientId') },
crossDomain: true,
cache: false,
success: async function (response) {
//set the token
token = response.token;
try {
//Create a Box client using the token
client = new BoxClient({auth: new BoxDeveloperTokenAuth({token }),
});
//Enable the generate button as we now have access to Box API
$("#generate").prop("disabled",false);
} catch (e) {
console.error(e);
}
}
});

The application

The application is created as a single web page.

Choose the shared link type

Across the top of the page I have the below to allow a user to choose what type of shared link they want for the report (defaults to none)

Page header with buttons and shared link options

Generate the folder structure

When the user clicks on the ‘Generate’ button the below function is called. To generate the entire folder structure I recursively call the List items in folder function.

  async function getItems(folderId,parentId) {
let items = await client.folders.getFolderItems(folderId);

Next I iterate through the items of the folder and if the item is a folder I call the function again to make sure I get all the data I need. For rendering I use an <ul> element for each folder as a container and a <li> element for the individual folder contained within.

$.each(items.entries, function(k, data) {
//If folder, add it to the list and get items for this folder
if(data.type=='folder') {
var folderListContainer = $("<ul id=" +data.id + "></ul");
//Append this folder to the list of folders
folderListContainer.append($("<li class='uf' parent=" +
parentId + ">"+data.name + "</li>"));
//Append the list of folders to the parent
$("#" + folderId).append(pfolderListContainer);
getItems(data.id,folderId);
}

If the item is a file I check if the request was to have a shared link with the file. If not I simply add the file name to an <li> element and append to the parent folder. Note the app comes with icons for standard Box files so based on file extension an appropriate icon is used.

    else if(data.type=='file') {
//If no shared link required, just output the file name and icon and attach to parent
if(sharedLink=='None') {
fileListContainer.append($("<li class='pf' id='u_" +
folderId + "'><img height=16 width=16 src='iconsbox/" +
data.name.split('.').pop().toLowerCase() + ".svg'/> "+
data.name +"</li>"));
}

If a shared link was requested, I generate (or reuse) the link and then append to the parent folder. Because I might have to create many links I do this asynchronously to make it dynamic using a placeholder text for the file until the shared link is created.

  else {
//If shared link requested - first check if shared link already exists
var link;
if(data.shared_link==null || data.shared_link.access!=sharedLink) {
//Create shared link and append file with place holder text.
//Will be updated by async function
link = createSharedLink(data.id,sharedLink,data.name);
fileListContainer.append($("<li class='pf' id='u_" + folderId + "'>" +
"<img height=16 width=16 src='iconsbox/" +
data.name.split('.').pop().toLowerCase() + ".svg'/> "+
"<a id=l_"+data.id + " href=" + link + " target=_blank>" +
"Generating link..</li>"));
}
else {
//Use existing shared link and append file
link = data.shared_link.url;
fileListContainer.append($("<li class='pf' id='u_" + folderId + "'>" +
"<img height=16 width=16 src='iconsbox/" +
data.name.split('.').pop().toLowerCase() + ".svg'/> "+
"<a id=l_"+data.id + " href=" + link + " target=_blank>" +
data.name +"</li>"));
}
}
}

The shared link is created asynchronously using the add shared link to file function.

async function createSharedLink(fileId,type,name) {
let sharedLinkObject = await client.sharedLinksFiles.addShareLinkToFile(
fileId,
{sharedLink: { access: sharedLink } }
,{ fields: 'shared_link' } );
//Set the URL for the file <li> to the shared link
$("#l_" + fileId).attr("href",sharedLinkObject.sharedLink.url);
//Set the text to the name of the file
$("#l_" + fileId).text(name);
};

Export to PDF

For exporting to PDF I use the html2pdf library. It will take any DOM element and convert to pdf and download for the user.

  //Generates a PDF from specified HTML DOM Element
$("#pdfgen").click( function() {
const element = document.getElementById('index');
// Choose the element and save the PDF for your user.
html2pdf().from(element).save();
})

The dynamic report generation looks like this when completed.

Thank you for getting this far. The complete repository for this prototype is here. It contains instructions for setting up the app as an integration in Box and also to run as a standalone app for local testing and development.

Github repo for Folder Tree Report

Learn from Box Platform champions! 🦄 Visit the Box Developer Community for support and knowledge sharing.

--

--

Peter Christensen
Box Developer Blog

Senior Staff Platform Solutions Engineer with Box, working with API, developer enablement, architecture and integrations