Step 4: Create & View Accounts

This entry is part of Vue.js Application Tutorial - Creating a Simple Budgeting App with Vue

This is part 4 of an 18 part series titled Vue.js Application Tutorial - Creating a Simple Budgeting App with Vue. It is highly recommended that you read the series in order, as each section builds on the last.

  1. Step 0: Intro
  2. Step 1: Planning Your Application
  3. Step 2: Data Architecture
  4. Step 3: Setup & Project Structure
  5. Step 4: Create & View Accounts
  6. Step 5: Edit & Delete Accounts
  7. Step 6: Adding LocalStorage to our Vue.js Application
  8. Step 7: Interlude & Refactor
  9. Step 8: Budgeting
  10. Step 9: Racing Through Budgets
  11. Step 10: Styling & Navigation
  12. Step 11: Finishing Budgets with Vue.js Dynamic Components
  13. Step 12: Planning for Transactions
  14. Step 13 - All Aboard the Transaction Train
  15. Step 14 - User Testing

The next entry is expected to be published on 03 August 2017. Sign up using the form at the bottom of this post to receive updates when posts are published.


Now that almost all of the project structure is in place it's time to build the first segment of our app, Accounts. (Finally!) The core of this module will be a simple data structure representing a bank or credit account the user holds. Each account will be attached to multiple transactions, and will record its overall balance, as well as other meta data.

An Account object should end up looking something like this:

'jcijeojde88': {
 "id": "jcijeojde88",
 "category": "Credit Card",
 "name": "Chase Card"
 "balance": -237.94
}

It may seem redundant to store the ID as both the key of the object, and as a value within it. Later we will be passing around the object, without its key, and it's important that the object itself contains its own ID. Using the ID as the object key allows us to easily look up an object based on its ID.

The user needs to be able to

  • view a list of all the accounts with their balances
  • add an account
  • edit an account
  • delete an account
  • filter by each account and view all of the transactions attached to it

And that is all for now. Take a minute and think through what the first steps should be to build out this section of the application.

Back now? Here's what I came up with.

  1. add an account object to the store (remember we are working inside-out)
  2. automatically generate an ID for the new account
  3. create an interface so that the necessary components can safely access the accounts a user adds
  4. create a component for each of the above actions that a user will take (add account, view accounts)
  5. add a route for each component
  6. begin building a navigation bar for the application with Accounts

Now we have some clear direction on what to do. Taking the time to sit and think through these steps is crucial to avoiding mistakes and working efficiently!

Vuex appears quite complicated at first glance, possibly even more structured than we need for this simple application. There is some validity to that thought, but it's better to start with a good infrastructure than to try to insert it ex post facto. It will also help us to structure how we are saving information to and loading from the database. Once you see it in action its functionality and benefits become quite clear.

We add an accounts object to our state.

// src/app/accounts/vuex/index.js

const state = {
 accounts: {}
};

Because of how we are loading up our store, this accounts object will be accessible via state.accounts.accounts rather than state.accounts. We could load all of our accounts directly into state.accounts, but then we could not store additional information from our accounts module without infringing on the accounts records.

We are going to create a simple mutation to add an account. But wait! We need to generate a random ID for each account we add. We'll use a simple little utility script that we'll place in a utils.js file in our source root since we will use it in multiple modules.

import { guid } from '../../../utils';

export default {
 ADD_ACCOUNT (state, payload) {
 let id = guid();
 state.accounts[id] = Object.assign({ id: id }, payload.account);
 }
};
// thanks to http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
export const guid = function () {
 function s4 () {
 return Math.floor((1 + Math.random()) * 0x10000)
 .toString(16)
 .substring(1);
 }
 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
 s4() + '-' + s4() + s4() + s4();
};

In our ADD_ACCOUNT function we add the new account with the ID as the key. We also add the ID as an attribute. Object.assign({ id: id }, payload.account) creates a new object with just the ID, then extends it with the account the Vuex action will submit to the mutator. Next up we need to create that action.

// src/app/accounts/vuex/actions.js

export const addAccount = ({ commit }, data) => {
 commit('ADD_ACCOUNT', { account: data });
};

Right now all addAccount does is pass the data on to the correct mutator. Later we will need to perform asynchronous operations such as saving to localStorage. Then it will become clear why the actions are a necessary part of Vuex.

The store is now all prepped to add accounts. We need to create the user interface for this. We'll call it CreateEditAccount.vue. Creating and editing an object like our Account is very similar - we are using the same form fields - so I almost always combine the two. We'll add in the ability to edit after we can create accounts. The new view also needs to be routed.

<template>
 <div id="accounts-create-edit-view">
 You can create and edit accounts with me, yippee!
 </div>
</template>

<script>
import { mapActions } from 'vuex';

export default {
 name: 'accounts-create-edit-view',

 methods: {
 ...mapActions([
 'addAccount'
 ])
 }
};
</script>

<style scoped lang='scss'>
#accounts-create-edit-view {
}
</style>
export { default as AccountsListView } from './AccountsListView';
export { default as CreateEditAccount } from './CreateEditAccount';
...
export default [
 {
 path: '/',
 component: components.AccountsListView
 },
 {
 path: '/accounts/create',
 component: components.CreateEditAccount
 }
];

Now you can visit /accounts/create and you'll see a lovely little message about creating an account. Note that we're using the accounts list page as our de facto home page for now, but we add /accounts to the beginning of the create URL.

To create an account the user needs to enter an identifying name, a category, and a beginning balance. For now we will give them a static list of categories to choose from. We won't worry about form validation yet. I'd like to be able to see the account I add. So I will add our first iteration of viewing the accounts on the list page, and some navigation links between list and create.

<template>
 <div id="accounts-create-edit-view">
 You can create and edit accounts with me, yippee!

 <router-link :to="{ name: 'accountsListView' }">View all accounts</router-link>

 <form class="form" @submit.prevent="saveNewAccount">
 <label for="name" class="label">Name</label>
 <p class="control">
 <input type="text" class="input" name="name" v-model="newAccount.name">
 </p>
 <label for="category" class="label">Category</label>
 <p class="control">
 <span class="select">
 <select name="category" v-model="newAccount.category">
 <option v-for="value, key in categories" :value="key">{{ value }}</option>
 </select>
 </span>
 </p>
 <label for="balance" class="label">Balance</label>
 <p class="control">
 <input type="text" class="input" name="balance" v-model="newAccount.balance">
 </p>
 <div class="control is-grouped">
 <p class="control">
 <button class="button is-primary">Submit</button>
 </p>
 <p class="control">
 <router-link :to="{ name: 'accountsListView' }"><button class="button is-link">Cancel</button></router-link>
 </p>
 </div>
 </form>
 </div>
</template>

<script>
import { mapActions } from 'vuex';
import { CATEGORIES } from '../../../consts';

export default {
 name: 'accounts-create-edit-view',

 data: () => {
 return {
 categories: CATEGORIES,
 newAccount: {}
 };
 },

 methods: {
 ...mapActions([
 'addAccount'
 ]),

 saveNewAccount () {
 this.addAccount(this.newAccount).then(() => {
 this.newAccount = {};
 });
 }
 }
};
</script>

<style scoped lang='scss'>
#accounts-create-edit-view {
}
</style>
<template>
 <div id="accounts-list-view">
 I'm a list of accounts!

 <router-link :to="{ name: 'createEditAccount' }">Add an account</router-link>

 <ul>
 <li v-for="account, key in accounts">
 {{ account.name }}
 <span class="tag is-small is-info">{{ categories[account.category] }}</span>
 ${{ account.balance }}
 </li>
 </ul>
 </div>
</template>

<script>
import { mapState } from 'vuex';
import { CATEGORIES } from '../../../consts';

export default {
 name: 'accounts-list-view',

 data () {
 return {
 categories: CATEGORIES
 };
 },

 computed: {
 ...mapState({
 'accounts': state => state.accounts.accounts
 })
 }
};
</script>

<style scoped lang='scss'>
#accounts-list-view {
}
</style>
// I also added names to our account routes
export default [
 {
 path: '/',
 component: components.AccountsListView,
 name: 'accountsListView'
 },
 {
 path: '/accounts/create',
 component: components.CreateEditAccount,
 name: 'createEditAccount'
 }
];
// I forgot to actually load the Bulma CSS earlier, so I'm going that now
// Import Vue
import Vue from 'vue';

// Import App Custom Styles
import AppStyles from './css/app.css';

// Import App Component
import App from './app';

import 'bulma/css/bulma.css';
...
// We might want to create constants for each application and load them up our chain, but for now this is sufficient.
export const CATEGORIES = {
 'CREDIT_CARD': 'Credit Card',
 'CHECKING': 'Checking',
 'SAVINGS': 'Savings'
};

There's a lot going on here! I think it will be easy to follow if you have done the basic Vue.js tutorial. It may also be easier to view the code on GitHub through the diffs, or by pulling each different commit as you follow the tutorial.

We define a form that parallels the Account model. We are attaching a v-model to each field with a newAccount object in the component. This is my preferred way of dealing with forms and models in Vue. There are other approaches, namely where you could auto-save after each field edit. With Vuex, you never edit the store directly - you pass in new values through actions and mutators. You could certainly pass in one field at a time as you are editing the newAccount object, but this complicates the code, and you would have to remember to delete the stored object if the user canceled their entry.

The next step is to actually save the form data. We are finally using the action and mutator we defined. Our code will load and fire off the addAccount action, which then calls the ADD_ACCOUNT mutator that actually updates the store. Once that is done we clear the newAccount object and programmatically navigate to the accounts list.

d569fce

Continue to step 5, Edit & Delete Accounts

Originally published on

Last updated on