How to create an expense organizer with JavaScript in 10 minutes

Let’s use ES6 and the Dropbox API to keep our receipts from turning into chaos.

Per Harald Borgen
We’ve moved to freeCodeCamp.org/news

--

In this article, I’ll show you how to create a handy little app which keeps track of your receipts. It allows you to dump all your receipts into a Dropbox folder and then hit a button to organize them by month.

This is especially useful for when you’re doing accounting, as you normally want to do it on a month-by-month basis. Having all your receipts from a given month grouped together in a single folder can help you save a lot of time.

In this tutorial we’ll cover:

  • Creation of a Dropbox account and setting up our project development environment
  • UI creation with Vanilla JavaScript — including fetching data, rendering elements, basic state management, and simple navigation.
  • Some Dropbox API methods for getting and moving files.

If you’re more of a visual learner, you can also check out our free course below, which teaches you how to build this app using interactive Scrimba screencasts.

Click on the image to get to our Dropbox course.

1. Set up Dropbox

In order to build on top of Dropbox, you first need a Dropbox account. After you’ve registered, head over to the developer section. Choose My apps on the left-hand side of the dashboard and click Create app.

Choose the following settings, and give your app a unique name.

Preferred settings for this tutorial

In the dashboard, go to OAuth 2 section under Generated access token and click the Generate button to get an API accessToken, which we will save for later.

Now, let’s install the Dropbox desktop app. Login to the app with your new developer credentials and you should be able to see a folder with the same name as your newly created app. In my case it’s ExpenseOrganizer.

Drop some receipts and invoices into the folder, so we can access them via our API and organize them into neat folders when we finish our project. Like this:

2. Set up the codebase

Now we’ll need to set up our codebase. We’ll use the simplest structure possible: an index.html file with links to a JavaScript file and a stylesheet. We should also add a name of our app to the <title> tag.

Note: the final code for this tutorial can be found on this Scrimba playground (which you can clone if you’d like your own copy). However, you’ll need to add your own API key from Dropbox for it to work.

3. Install and add Dropbox

Now let’s install Dropbox library to our project. Normally you’d do something like this to achieve that:

npm install dropbox
# or
yarn add dropbox

In the Scrimba playground, however, we’ve simply added the Dropbox library as a dependency in the left sidebar, as that’s how you add npm packages in Scrimba.

The next step is to import Dropbox and create an instance of the Dropbox class. We’ll call it dbx and we’ll pass in our token and a fetching library of our choice, in our case fetch. If you prefer axios or any other fetching library, feel free to pass it instead.

import { Dropbox } from 'dropbox';const accessToken = '<your-token-from-dashboard>';const dbx = new Dropbox({
accessToken,
fetch
});

4. Get and display expenses

Fetching data

To display data in our app we need to fetch it first. Let’s get the receipts we have in our Dropbox folder.

We can use filesListFolder() method. It accepts a folder and returns a promise, which when resolved gives us the contents of the folder. There is a little gotcha with this method, because to specify a root path (the base folder that we’re in) we need to specify an empty string '' and not '/'.

dbx.filesListFolder({
path: ''
}).then(res => console.log(res))

When we call this method to retrieve files from our Dropbox account, we should see something similar to what I have on the screenshot below.

console.log of filesListFolder() response

So we have the entries array, which is an array of objects. Each object in the entries array is our file (and later some of them can be folders when we organize our data), with .tag, name, id, and numerous other properties. Let’s now write a function that displays each file. .tag is a property that contains a type of entry that we receive — file or folder.

When we fetch all the files, let’s store them in our app. We can create an object called state to do that. We can have an array where we will store files and also create a string, where we keep a track on our rootPath.

const state = {
files: [],
rootPath: ''
}

Rendering data

One of the ways to do that would be to add an unordered list <ul> to our HTML, select it with JavaScript and then populate it with fetched files from Dropbox.

Let’s add the required HTML index.html, with Loading... placeholder, which would get overwritten as soon as we display the fetched files.

<main>
<ul class="dbx-list js-file-list">Loading...</ul>
</main>

We can now create fileListElem that selects the unordered list.

const fileListElem = document.querySelector('.js-file-list')

We can add to fileListElem.innerHTML all the alphabetically sorted files, making sure that we put folders first. We then map every folder and file to a <li> and join using join('') to avoid rendering an array instead of a string.

But nothing gets displayed on the screen… That’s because we need to fetch data and then render files with renderFiles(). Let’s write a helper init()function to do that.

const init = async () => {
// fetch files
const res = await dbx.filesListFolder({
path: state.rootPath,
limit: 100
})
// update state, but first, keep the existing files,
// and then add newly received files
state.files = [...state.files, ...res.entries]
renderFiles()
}

We can extract the state update and renderFiles() into a separate method.

const init = async () => {
const res = await dbx.filesListFolder({
path: state.rootPath,
limit: 100
})
updateFiles(res.entries)
}
const updateFiles = files => {
state.files = [...state.files, ...files]
renderFiles()
}
// Let's also add a reset() function.
// It's always nice to be able to start from scratch.
const reset = () => {
state.files = []
init()
}

Now let’s add init() to the very bottom of the index.js to kick off the whole process and display our files.

And there we go, our list of files is displayed, although it does look a little lonely.

We can improve it, by giving those list items a default folder and file icon.

To keep things neat, we will create icons.js and add the base64 SVG icons there. Why this type of icon? They are just easier to use in this tutorial, so there is no need for you to go anywhere or download anything.

export const fileIcon = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0iZmVhdGhlciBmZWF0aGVyLWZpbGUiPjxwYXRoIGQ9Ik0xMyAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWOXoiPjwvcGF0aD48cG9seWxpbmUgcG9pbnRzPSIxMyAyIDEzIDkgMjAgOSI+PC9wb2x5bGluZT48L3N2Zz4='export const folderIcon = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0iZmVhdGhlciBmZWF0aGVyLWZvbGRlciI+PHBhdGggZD0iTTIyIDE5YTIgMiAwIDAgMS0yIDJINGEyIDIgMCAwIDEtMi0yVjVhMiAyIDAgMCAxIDItMmg1bDIgM2g5YTIgMiAwIDAgMSAyIDJ6Ij48L3BhdGg+PC9zdmc+'

We can now import the icons into index.js

import { fileIcon, folderIcon } from './icons.js'

Update .map in renderFiles to include our new icons.

const renderFiles = () => {
fileListElem.innerHTML = state.files.sort((a, b) => {
// ... this part stays the same
}).map(file => {
const type = file['.tag']
let thumbnail
if (type === 'file') {
thumbnail = fileIcon
} else {
thumbnail = folderIcon
}
return `
<li class="dbx-list-item ${type}">
<img class="dbx-thumb" src="${thumbnail}">
${file.name}
</li>
`
}).join('')
}

And now our files look a bit happier.

5. Organize files into folders

The main feature of our app is on a click of a button to move all the files into folders, neatly organized by year and then by month inside each folder.

First things first, let’s create the button in index.html

<button type="button" class="dbx-btn js-organize-btn">Organize</button>

Secondly, let’s add some JavaScript for it.

// select the button
const organizeBtn = document.querySelector('.js-organize-btn')
// add an event listener to it
organizeBtn.addEventListener('click', e => {
console.log(e)
})

We will come back to our button at the very end of this chapter to wire up the function that moves files to it.

To move files into folders we can use filesMoveBatchV2(). It moves files in batches from one folder to another. In our case, we’re moving from root folder to individually named folders.

This method works best when implemented as a part of an async function.

const moveFiles = async () => {
let response = await dbx.filesMoveBatchV2()
}

The method accepts entries, an array of objects, that consist of from_path and to_path properties.

// temporarily, pseudocode values for paths
const entries = [{
from_path: 'origin_folder',
to_path: 'destination_folder'
}]
const moveFiles = async () => {
let response = await dbx.filesMoveBatchV2({ entries })
}

filesMoveBatchV2() returns either success if the call was immediately successful, in case there are only a few files to process.

However, for bigger workloads, it’s going to return an object with a property async_job_id, and that means that your call is being executed and we will need to check up on it at a later stage, by calling filesMoveBatchCheckV2 until it’s complete and is not in_progress any more.

// still, pseudocode values for paths
const entries = {
from_path: 'origin_folder',
to_path: 'destination_folder'
}
const moveFiles = async () => {
let response = await dbx.filesMoveBatchV2({ entries })
const { async_job_id } = response
if (async_job_id) {
do {
response = await dbx.filesMoveBatchCheckV2({ async_job_id })
console.log(res)
} while (response['.tag'] === 'in_progress')
}
}

Let’s now implement correct paths for entries object and add it into moveFiles():

And to complete the feature, let’s wire up moveFiles() to our Organise button. It would also be nice to update the button text to indicate that the process of moving files has started and then change it back when we finish.

organizeBtn.addEventListener('click', async e => {
const originalMsg = e.target.innerHTML
e.target.disabled = true
e.target.innerHTML = 'Working...'
await moveFiles()
e.target.disabled = false
e.target.innerHTML = originalMsg
reset()
})

Now when we click the button, we can see it change and our console log’s in_progress message. This is just for us to see that Dropbox is moving files around.

When it’s all done, we have our year folder!

I’ve just started my receipt collection, so they are all in this year. Let’s look inside.

Hmm…but how do I look inside? This is just a <li> list item and if I click on it, nothing would happen.

Let’s implement navigation.

6. App navigation

Navigating Dropbox is effectively like navigating folders in the File Explorer on Windows or Finder on Mac. All we need to do is just to change the folder we’re in. Let’s just try something out manually and then we can add code to automate the process.

If we change rootPath in state and reload the page.

const state = {
files: [],
rootPath: '/2019'
}

We get all the files for April 2019!

One more time, let’s update our state manually to go to the April folder 4 and reload the page.

const state = {
files: [],
rootPath: '/2019/4'
}

Ah, here are all my receipts.

To make this process a bit simpler, let’s add an input field so I can type the folder I want to go to, rather than hard-coding it every time. We’ll wrap the input field in a <form>, which you’ll see below.

And while we’re at it, why not make things a bit prettier with a nice header in index.html?

<header>
<h1>Expense Organizer</h1>
<form class="root-path-wrap js-root-path__form">
<h2><label for="root-path"> Folder Path</label></h2>
<input
type="text"
name="root-path"
id="root-path"
class="js-root-path__input"
value="/"
/>
<button aria-label="Load path" class="dbx-btn" type="submit">
&rarr;
</button>
</form>
</header>

Here’s how it looks:

As you can see, I’ve also added a button to confirm the path we want to go to.

Inside index.js let’s get state back to what it should be:

const state = {
files: [],
rootPath: ''
}

And add some JavaScript to update rootPath with values from the input field:

const rootPathForm = document.querySelector('.js-root-path__form')
const rootPathInput = document.querySelector('.js-root-path__input')
rootPathForm.addEventListener('submit', e => {
e.preventDefault();
state.rootPath = rootPathInput.value === '/'
? ''
: rootPathInput.value.toLowerCase()

reset()
})

We’re done, we have working navigation!

Wrap up

Congratulations! If you’ve followed along all the way, you’ve now built an app which will prevent your receipts from turning into chaos. That’s quite an achievement!

If you still feel that there are concepts in this tutorial that you didn’t quite grasp, just check out the corresponding course on Scrimba, as it explains things more in detail.

Finally, I want to mention that the course above has, along with this post, been sponsored and paid for by Dropbox. This sponsorship helps Scrimba keep the lights on and it enables us to continue creating free content for our community throughout 2019. So a big thanks to Dropbox for that!

Happy coding :)

--

--