Google Sheets driven client-side car stats app: Part 4
Currently, all I have on the app is a table that shows the data I receive from Google Sheet.
I would like to use Vue's component abilities to make that table a component. The goal is then to extend the app with various components that make up the whole site.
Organizing the code
To keep it simple, for now, I will put my components straight into the app.js file.
In the future, I could consider putting them in a components.js file, putting each component in its own file or use something like webpack that adds a "compile" step to my process.
Creating a basic Component in Vue.js
I add this to app.js:
Vue.component('trip-overview', {
data: function () {
return {
message: "Hello world!"
}
},
template: '<div>{{ message }}</div>'
});
And in index.html:
<div id="app">
<trip-overview></trip-overview>
...
</div>
This will show the div from the template of the component, with the appropriate message.
Bind some data into the Component
I can change my component and add a "prop":
Vue.component('trip-overview', {
props: ['rows'],
template: '<div>{{ rows }}</div>'
});
Here, I just expect as the value of rows
all the rows, as returned by getRows
I can bind that via the html declaration:
<trip-overview v-if="dataAvailable" v-bind:rows="getRows()"></trip-overview>
Note that I use v-if
to make sure the component is only rendered AFTER the data is loaded.
If I run getRows
before that, at the moment the data is not available. That function will fail. This also shows that if you exclude a component with v-if
is will not only hide it, but also not try to render it or run its lifecycle.
The above code will just dump the JSON data on the page, but it shows I have the data available in the component:
Copy the table into the component
The component code now looks as follows:
Vue.component('trip-overview', {
props: ['rows'],
template: `
<table class="pure-table">
<thead>
<tr v-for="row in rows.slice(0,1)">
<th v-if="row.cells[0].row == 1" v-for="cell in row.cells">
{{ cell.inputValue }}
</th>
<td v-if="row.cells[0].row > 1" v-for="cell in row.cells">
{{ cell.inputValue }}
</td>
</tr>
</thead>
<tbody>
<tr v-for="row in rows.slice(1)">
<th v-if="row.cells[0].row == 1" v-for="cell in row.cells">
{{ cell.inputValue }}
</th>
<td v-if="row.cells[0].row > 1" v-for="cell in row.cells">
{{ cell.inputValue }}
</td>
</tr>
</tbody>
</table>
`
});
Note the use of backticks to surround the code. This is mostly to include newlines in the variable.
Also notice I changed the getRows()
call to rows
. This also means I did an optimization, since now getRows()
is called only once instead of twice.
Now I can replace the whole <table>
element in my html with my component. Resulting in the following:
<div id="app">
Data available: {{ dataAvailable }}
<trip-overview v-if="dataAvailable" v-bind:rows="getRows()"></trip-overview>
</div>
Nothing has changed to the final result, but the index.html looks cleaner again and I have a re-usable component to show a table listing a number of trip entries.
If I would choose to show a second table, maybe with a list showing only trips of > 50 km I could add a second trip-overview
element. For example:
<div id="app">
Data available: {{ dataAvailable }}
<trip-overview v-if="dataAvailable" v-bind:rows="getRows()"></trip-overview>
<trip-overview v-if="dataAvailable" v-bind:rows="getFilteredRows()"></trip-overview>
</div>
Conclusion
Using components allows me to build the page up out of smaller re-usable building blocks. You can also use components inside other components and build up the complexity like that.
It automatically already gave me an optimization in efficiency. And I'm now set to add more components.
Next up, I will be having a look at organizing the data my components will be showing to the user. I don't want to have the logic of calculating stats, grouping things etc ... in my components. So I'll do that elsewhere.