Notes App with Vue and Poi in 20 minutes
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:4000DONE Build 2d8232 finished in 177 ms!
Now, you can open link in your web browser and see the notes app in Vue.