Progressive Vue.js Development
Vuenna.js
Basic Vue.js Features
Component Based Architecture
<html>
<head>
<title>Hello World</title>
</head>
<body>
<div id="app">
{{ message }}
</div>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
</script>
</body>
</html>
Data-Binding
Let's create a Todo App!
<html>
<head>
<title>Vue Todo App</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="i in items">{{i}}</li>
</ul>
<footer>
<input type="text" v-model="newItem" @keyup.enter="addItem">
<button @click="addItem">Add</button>
</footer>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
newItem: '',
items: [],
},
methods: {
addItem() {
this.items.push(this.newItem);
this.newItem = "";
}
}
})
</script>
</body>
</html>
List Rendering
Event Binding
2-Way Data-Binding
Uhm. That's a nice list, but…
…I want to mark items as 'done'.
<ul>
<li v-for="(item, index) in items">
<strike v-if="item.done">{{item.content}}</strike>
<span v-else>{{item.content}}</span>
<button @click="toggleItem(index)">toggle</button>
<button @click="removeItem(index)">remove</button>
</li>
</ul>
var app = new Vue({
el: '#app',
data: {
newItem: '',
items: [{content: 'foo', done: false}],
},
methods: {
addItem() {
this.items.push({content: this.newItem, done: false});
this.newItem = "";
},
removeItem(i) {
this.items.splice(i, 1);
},
toggleItem(i) {
this.items[i].done = !this.items[i].done;
}
}
})
Conditional Rendering
List Index
Complex Data
Let's get fancy!
<div>{{numCompleted}} / {{items.length}} completed</div>
<div class="progress">
<div class="bar" :style="{ width: (numCompleted / items.length)*100 + '%'}"></div>
</div>
<ul>
<li v-for="(item, index) in items">
<input type="checkbox" v-model="item.done" />
<span :class="{ done: item.done }">{{item.content}}</span>
<button @click="removeItem(index)">remove</button>
</li>
</ul>
computed: {
numCompleted() {
return this.items.reduce(function(total, item) {
return item.done ? ++total : total;
}, 0);
}
}
Computed Properties
Style and Class Binding
Form Input Binding
.progress {
width: 200px;
height: 5px;
}
.bar {
background-color: #0F0;
height: 5px;
display: block;
transition: width ease 1s;
}
li .done {
text-decoration: line-through;
color: #666;
}
Sum it up
{{message}}
v-model
v-for="item in items"
@click @keyup
:style :class
Hello World
Small App
Hello World
Small App
???
Component Based Architecture
<html>
<head>
<title>Vue Todo App</title>
<style>
.progress {
width: 200px;
height: 5px;
}
.bar {
background-color: #0F0;
height: 5px;
display: block;
transition: width ease 1s;
}
li .done {
text-decoration: line-through;
color: #666;
}
</style>
</head>
<body>
<div id="app">
<div>{{numCompleted}} / {{items.length}} completed</div>
<div class="progress">
<div class="bar" :style="{ width: (numCompleted / items.length)*100 + '%'}"></div>
</div>
<ul>
<li v-for="(item, index) in items">
<input type="checkbox" v-model="item.done" />
<span :class="{ done: item.done }">{{item.content}}</span>
<button @click="removeItem(index)">remove</button>
</li>
</ul>
<footer>
<input type="text" v-model="newItem" @keyup.enter="addItem">
<button @click="addItem">Add</button>
</footer>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
newItem: '',
items: [{content: 'foo', done: false}],
},
methods: {
addItem() {
this.items.push({content: this.newItem, done: false});
this.newItem = "";
},
removeItem(i) {
this.items.splice(i, 1);
},
},
computed: {
numCompleted() {
return this.items.reduce(function(total, item) {
return item.done ? ++total : total;
}, 0);
}
}
})
</script>
</body>
</html>
CSS
HTML
JavaScript
index.html app.js style.css
<html>
<head>
<title>Vue Todo App</title>
<style>
.progress {
width: 200px;
height: 5px;
}
.bar {
background-color: #0F0;
height: 5px;
display: block;
transition: width ease 1s;
}
li .done {
text-decoration: line-through;
color: #666;
}
</style>
</head>
<body>
<div id="app">
<div>{{numCompleted}} / {{items.length}} completed</div>
<div class="progress">
<div class="bar" :style="{ width: (numCompleted / items.length)*100 + '%'}"></div>
</div>
<ul>
<li v-for="(item, index) in items">
<input type="checkbox" v-model="item.done" />
<span :class="{ done: item.done }">{{item.content}}</span>
<button @click="removeItem(index)">remove</button>
</li>
</ul>
<footer>
<input type="text" v-model="newItem" @keyup.enter="addItem">
<button @click="addItem">Add</button>
</footer>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
newItem: '',
items: [{content: 'foo', done: false}],
},
methods: {
addItem() {
this.items.push({content: this.newItem, done: false});
this.newItem = "";
},
removeItem(i) {
this.items.splice(i, 1);
},
},
computed: {
numCompleted() {
return this.items.reduce(function(total, item) {
return item.done ? ++total : total;
}, 0);
}
}
})
</script>
</body>
</html>
Progress
TodoItem
AddItem
App
Progress
TodoItem
AddItem
App
display progressbar
display and change TodoItems
create new Item
What are Single File Components good for?
index.html
src
├── App.vue
├── components
│ ├── NewItem.vue
│ ├── Progress.vue
│ └── TodoItem.vue
└── main.js
File Structure
each component in
separate .vue file
What do we need to change on our app?
main.js entrypoint for webpack
index.html
import Vue from 'vue'
import App from './App.vue'
new Vue({
el: '#app',
render: h => h(App)
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>sfc</title>
</head>
<body>
<div id="app"></div>
<script src="/dist/build.js"></script>
</body>
</html>
main.js
create a 'root' Vue Instance
minimalistic index.html
App.vue
<template>
<div id="app">
<progressbar :items="items"></progressbar>
<ul>
<todo-item v-for="item in items" :item="item"></todo-item>
</ul>
<new-item @addItem="addItem"></new-item>
</div>
</template>
<script>
import Progress from './components/Progress.vue';
import TodoItem from './components/TodoItem.vue';
import NewItem from './components/NewItem.vue';
export default {
name: 'app',
components: {
'progressbar': Progress,
'todo-item': TodoItem,
'new-item': NewItem
},
data () {
return {
items: []
}
},
methods: {
addItem(item) {
this.items.push(item);
}
}
}
</script>
<style lang="scss">
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin-top: 60px;
}
</style>
import components
register components
use components like ordinary html items
pass down properties
TodoItem.vue
<template>
<div>
<input type="checkbox" v-model="item.done" />
<span :class="{ done: item.done }">
{{item.content}}
</span>
</div>
</template>
<script>
export default {
props: ['item']
}
</script>
<style lang="scss">
.done {
text-decoration: line-through;
color: #666;
}
</style>
define props
use props
Progress.vue
<template>
<div v-if="items.length > 0">
<code>
{{numCompleted}} / {{items.length}} completed
</code>
<div class="progress">
<div class="bar" :style="{ width: (numCompleted / items.length)*100 + '%'}"></div>
</div>
</div>
</template>
<script>
export default {
props: ['items'],
computed: {
numCompleted() {
return this.items.reduce(function(total, item) {
return item.done ? ++total : total;
}, 0);
}
}
}
</script>
<style scoped lang="scss">
.progress {
width: 100%;
height: 5px;
background-color: #eee;
.bar {
background-color: #0F0;
height: 5px;
display: block;
transition: width ease 1s;
}
}
</style>
scoped css
NewItem.vue
<template>
<div>
<input v-model="newItem" type="text" @keyup.enter="addItem"></input>
<button @click="addItem()">Add</button>
</div>
</template>
<script>
export default {
data () {
return {
newItem: ''
}
},
methods: {
addItem() {
let item = {content: this.newItem, done: false};
this.newItem = '';
this.$emit("addItem", item);
}
}
}
</script>
<style lang="scss"></style>
private data
emit events
Inter Component Communication
Application Structure
Data
Events
Progressively Advancing our Application
<template>
<div id="app">
<progressbar :items="items"></progressbar>
<ul>
<todo-item v-for="item in items" :item="item"></todo-item>
</ul>
<new-item @addItem="addItem"></new-item>
</div>
</template>
<script>
import Progress from './components/Progress.vue';
import TodoItem from './components/TodoItem.vue';
import NewItem from './components/NewItem.vue';
export default {
name: 'app',
components: {
'progressbar': Progress,
'todo-item': TodoItem,
'new-item': NewItem
},
data () {
return {
items: []
}
},
methods: {
addItem(item) {
this.items.push(item);
}
}
}
</script>
Progressively Advancing our Application
Progress
TodoItem
AddItem
App
TodoList
<template>
<div id="app">
<progressbar :items="items"></progressbar>
<todo-list :items="items"></todo-list>
</div>
</template>
<script>
import Progress from './components/Progress.vue';
import TodoList from './components/TodoList.vue';
export default {
name: 'app',
components: {
'progressbar': Progress,
'todo-list': TodoList
},
data () {
return {
items: []
}
},
}
</script>
<style lang="scss">
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin-top: 60px;
}
</style>
App.vue
add TodoList component
remove NewItem and TodoItems
<template>
<ul>
<todo-item v-for="item in items" :item="item"></todo-item>
</ul>
<new-item @addItem="addItem"></new-item>
</template>
<script>
import TodoItem from './components/TodoItem.vue';
import NewItem from './components/NewItem.vue';
export default {
props: [items],
components: {
'todo-item': TodoItem,
'new-item': NewItem
},
methods: {
addItem(item) {
this.items.push(item);
}
}
}
</script>
TodoList.vue
List Manipulation is handled inside the list itself
Hello World
Small App
structured component app
This way we can incrementally advance our application as it grows with us.
Problems that arise with component based architetures