Notes App with Vue and Poi in 20 minutes

Robert Guiscard
4 min readNov 15, 2017

--

I am not an expert in Javascript and not familiar with its tool chain. So I often end up using Rails 5.1 with webpacker for developing Javascript application.

Now, I found Poi as a very good solution for people who want to quickly start writing codes instead of setting up development environment.

Here, I use this excellent tutorial “Learn Vuex by Building a Notes App” to introduce how to set up a simple Vue application in 20 minutes.

Note: I wrote these steps from memory and might miss something.

First, create a directory and install poi:

$> mkdir note_app
$> cd note_app
$> yarn init # fill up information. you might not need this step.
$> yarn add poi --dev
$> yarn add vuex

Then, you can copy most files from git of original tutorial. My directory look like this one:

note_app
- index.ejs
- node_modules
- package.json
- poi.config.js
- src
- index.js
- components
- App.vue
- Editor.vue
- NotesList.vue
- Toolbar.vue
- vuex
- store.js
- style.css
- yarn.lock

You will notice that it is slightly different from original code. I did some adjustment to make it work with poi.

I have poi.config.js like this:

module.exports = (options, req) => ({
entry: './src/index.js',
webpack(config) {
config.resolve.alias['vue$'] = 'vue/dist/vue.esm.js'
return config
}
})

Note that vue.esm.js is used to avoid complaints from Vue about ‘compiler-included build’

index.ejs with minor modification to include bootstrap and app tag:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title><%= htmlWebpackPlugin.options.title %></title>
<% if (htmlWebpackPlugin.options.description) { %>
<meta name="description" content="<%= htmlWebpackPlugin.options.description
%>"/>
<% } %>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6
/css/bootstrap.min.css">
<link rel="stylesheet" href="styles.css">

</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<app></app>
</body>
</html>

index.js

import Vue from 'vue'
import store from './vuex/store.js'
import App from './components/App.vue'
new Vue({
store, // inject store to all children
el: "app",
template: "<app></app>",
components: { App }
})

App.vue is the same:

<template>
<div id="app">
<toolbar></toolbar>
<notes-list></notes-list>
<editor></editor>
</div>
</template>
<script>
import Toolbar from './Toolbar.vue'
import NotesList from './NotesList.vue'
import Editor from './Editor.vue'
export default {
components: {
Toolbar,
NotesList,
Editor
}
}
</script>

The rest three component (Toolbar, NoteLists and Editor) use mapState and mapAction to map actions from vuex store. The HTML templates are all the same.

Toolbar.vue:

<template>
<div id="toolbar">
<i @click="addNote" class="glyphicon glyphicon-plus"></i>
<i @click="toggleFavorite"
class="glyphicon glyphicon-star"
:class="{starred: activeNote.favorite}"></i>
<i @click="deleteNote" class="glyphicon glyphicon-remove"></i>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
computed: mapState([
'activeNote'
]),
methods: {
...mapActions([
'addNote',
'deleteNote',
'toggleFavorite'
])
}
}

</script>

NoteLists.vue:

<template>
<div id="notes-list">
<div id="list-header">
<h2>Notes | coligo</h2>
<div class="btn-group btn-group-justified" role="group">
<!-- All Notes button -->
<div class="btn-group" role="group">
<button type="button" class="btn btn-default"
@click="show = 'all'"
:class="{active: show === 'all'}">
All Notes
</button>
</div>
<!-- Favorites Button -->
<div class="btn-group" role="group">
<button type="button" class="btn btn-default"
@click="show = 'favorites'"
:class="{active: show === 'favorites'}">
Favorites
</button>
</div>
</div>
</div>
<!-- render notes in a list -->
<div class="container">
<div class="list-group">
<a v-for="note in filteredNotes"
class="list-group-item" href="#"
:class="{active: activeNote === note}"
@click="updateActiveNote(note)">
<h4 class="list-group-item-heading">
{{note.text.trim().substring(0, 30)}}
</h4>
</a>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex'
export default {
data () {
return {
show: 'all'
}
},
computed: {
filteredNotes () {
if (this.show === 'all'){
return this.notes
} else if (this.show === 'favorites') {
return this.notes.filter(note => note.favorite)
}
},
...mapState({
notes: state => state.notes,
activeNote: state => state.activeNote
})

},
methods: {
...mapActions([
'updateActiveNote'
])
}

}
</script>

Editor.vue

<template>
<div id="note-editor">
<textarea
:value="activeNoteText"
@input="editNote"
class="form-control">
</textarea>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
computed: mapState({
activeNoteText: state => state.activeNote.text,
}),
methods: {
...mapActions([
'editNote'
])
}
}

</script>

store.js now also includes actions.js in a single file.

mport Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)// the root, initial state object
const state = {
notes: [],
activeNote: {}
}
// define the possible mutations that can be applied to our state
const mutations = {
ADD_NOTE (state) {
const newNote = {
text: 'New note',
favorite: false
}
state.notes.push(newNote)
state.activeNote = newNote
},
EDIT_NOTE (state, text) {
state.activeNote.text = text
},
DELETE_NOTE (state) {
state.notes.$remove(state.activeNote)
state.activeNote = state.notes[0]
},
TOGGLE_FAVORITE (state) {
state.activeNote.favorite = !state.activeNote.favorite
},
SET_ACTIVE_NOTE (state, note) {
state.activeNote = note
}
}
const actions = {
addNote (context) {
context.commit('ADD_NOTE')
},
editNote (context, e) {
context.commit('EDIT_NOTE', e.target.value)
},
deleteNote (context) {
context.commit('DELETE_NOTE')
},
updateActiveNote (context, note) {
context.commit('SET_ACTIVE_NOTE', note)
},
toggleFavorite (context) {
context.commit('TOGGLE_FAVORITE')
}
}
// create the Vuex instance by combining the state and mutations objects
// then export the Vuex store for use by our components
export default new Vuex.Store({
state,
mutations,
actions
})

For some reasons, I have no poi executable installed. Thus, I just use node to run its cli like this:

node node_modules/poi/bin/cli.js

And the output like this one:

Asset       Size  Chunks                    Chunk Names
638049af011656e18934.hot-update.json 44 bytes [emitted]
vendor.js 1.77 MB 0 [big] vendor
client.js 81.6 kB 1 client
manifest.js 31.2 kB 2 [emitted] manifest
1baa6f62a415e46109fb.hot-update.json 35 bytes [emitted]
index.html 720 bytes [emitted]
> Open http://localhost:4000
> On Your Network: http://192.168.10.53:4000
DONE Build 2d8232 finished in 177 ms!

Now, you can open link in your web browser and see the notes app in Vue.

--

--

Responses (1)