Progressive Vue.js Development
Vuenna.js
Part One
Basic Vue.js Features
Part Two
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
- Data-Binding
- 2-Way Data-Binding
- List Rendering
- Event Binding
- Style and Class Binding
{{message}}
v-model
v-for="item in items"
@click @keyup
:style :class
Hello World
Small App
Hello World
Small App
???
Part Two
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
Single File Components
What are Single File Components good for?
- create small reusable components
- HTML, CSS and JavaScript at one place
- scoped CSS vs. global stylesheet
- additional compile step needed
- usage of es6, scss, typescript and others
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
- parents pass down props
- childs emit events
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.
Thanks
Problems that arise with component based architetures
- shared state (global state)
- two distant components need to communicate with each other
VuennaJS
By doebi
VuennaJS
- 1,039