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