The application will need to manage 2 different kinds of data: bills and categories.
Bills will be displayed in the BillsTable component, while categories will be shown in the NavBar component.
Despite being displayed in separate components, they will be both handled inside the App component, because that’s where we’ll centralize the data management.
Let’s do the first step and add the categories
and bills
data properties in the App component:
<script>
//...
export default {
name: 'app',
//...
data() {
return {
bills: [],
categories: [],
}
},
}
</script>
Now, before being able to do anything in the app, when we start we want to show a box where the user adds the first category: the AddCategory component.
We do this by checking with the v-if
and v-else
directives if we should show that component or not. I’m going to add a property called shouldShowAddCategory
to the state, and check that:
<template>
<main>
<AddCategory v-if="shouldShowAddCategory" v-on:addCategory="addCategory" />
<div v-else>
<NavBar />
<div class="container flex">
<div class="w-1/2">
<BillsTable />
</div>
<div class="w-1/2">
<Chart :bills="activeBills" />
</div>
</div>
</div>
</main>
</template>
<script>
//...
export default {
//...
data() {
return {
bills: [],
categories: [],
shouldShowAddCategory: true,
}
},
}
</script>
Let’s add a form to the AddCategory
component now. We’ll make it simple, just a single input field, and a button to add the new category.
<template>
<div>
<h1>Enter a category of bills</h1>
<p>E.g. 'Electricity' or 'Gas' or 'Internet'</p>
<input placeholder="Add category" v-model="category" />
<button @click="handleClick">Add</button>
</div>
</template>
We have a v-model
directive on the input field, which makes sure that everything we add into it gets mapped to the category
property of the component data.
Also, there’s a @click
event handler, which fires the handleClick
method whenever the button is pressed.
Let’s define the data and methods of the component:
<script>
export default {
name: 'AddCategory',
data: function () {
return {
category: '',
}
},
methods: {
handleClick: function () {
if (!this.category) {
alert('Enter a category')
return
}
this.$emit('addCategory', this.category)
},
},
}
</script>
handleClick
checks if the category input contains something (if not, alerts the user and returns) and then it calls the $emit()
method on $this
. In module 3 we used that method on this.$root
, which basically transformed the Vue root component into an event bus.
In this case, calling this.$emit()
passes the event to the parent.
The parent is the App component, and in there we edit the way we include the AddCategory component:
<AddCategory v-if="shouldShowAddCategory" v-on:addCategory="addCategory" />
when the addCategory
event is emitted from AddCategory, we call the addCategory
method in App:
<script>
//...
export default {
name: 'app',
data() {
return {
bills: [],
categories: [],
shouldShowAddCategory: true,
}
},
components: {
//...
},
methods: {
addCategory(category) {
this.categories.push(category)
this.shouldShowAddCategory = false
},
},
}
</script>
Now when you enter a category name, the form goes away and you see the rest of the application.
The form, however, is quite ugly! Since we use Tailwind, we just need to add some classes to the template to make it look good:
<template>
<div class="h-100 w-full flex items-center justify-center font-sans">
<div class="bg-white rounded shadow p-6 m-4 w-full lg:w-3/4 lg:max-w-lg">
<div class="mb-4">
<h1 class="text-gray-800">Enter a category of bills</h1>
<p>E.g. 'Electricity' or 'Gas' or 'Internet'</p>
<div class="flex mt-4">
<input
class="shadow appearance-none border rounded w-full py-2 px-3 mr-4 text-gray-700"
placeholder="Add category"
v-model="category"
/>
<button
class="flex-no-shrink p-2 border-2 rounded bg-teal-700 text-white border-teal hover:text-white hover:bg-teal-700"
@click="handleClick"
>
Add
</button>
</div>
</div>
</div>
</div>
</template>
I basically added some classes, plus an additional container div
. Every class in Tailwind performs a single thing. So for example m-4
adds 1rem
of margin, p-6
adds a 1.5rem
padding, and so on. You are welcome to discover all those classes on the official Tailwind docs, they are not Vue related, but rather add some nice styling to our app.
If you think those classes clutter the HTML, you’re right, but take a look at how we didn’t add a single line of CSS to make the form look good.
You can see the app at this point in this CodeSandbox.