KEMBAR78
An introduction to Vue.js
Vue.js
Hello!Javier Lafora
CTO at ASPgems
@eLafo
At the moment of preparing
this talk the latest version
of vue is 2.3
What are we going to talk about?
Why?
Reasons to use a JS
frontend framework
× UI Complexity
× Rapid Prototyping
× JS Spaguetti
What?
What Vue.js is
× Declarative
× MVVM
× Reactivity
× Virtual DOM
× Components
How?
How to build a Vue.js
project
× Components
identification
× model tree design
× Development
× Setup Vue
× Components
× Dev tools
What are we NOT going to talk about?
ES2015
Webpack
JS internals
Testing
Deploy
Authorization
Plugins
Routing
Middleware
Reactivity internals
Persistence
Server Side
Rendering
Vuex
XSS or CORS
Connecting to third
party services
Components
lifecycle
Transitions
Animations
1.
Why?
3 reasons why you would want to
use a proper front-end framework
UI Complexity
Web based apps UI have increased their
complexity, and they do not longer behave as
traditional web sites but as (browser) native
apps.
Rapid prototyping
HTML, CSS & JS are the new photoshop. You
want to prototype not only visual aspects of
your apps, but your app’s UX. You need to do
this as fast as possible… and you do not want to
be the bottleneck.
JS / JQUERY Spaguetti
Recipe:
1. Forget about separation of concerns
2. Mix structure, presentation and data logic
together.
3. Throw this mixture in the DOM
4. Enjoy your food
PS: Do not consume if you like sleeping
2.
What?
Basic VueJS concepts
Declarative
Tell what to render
instead of how
MVVM
Reactive Data Binding
Virtual DOM
Components
Components are one of the most powerful
features of Vue. WIth them you can build
reusable features to be used easily -thanks to
its declarative nature- by other developers.
Components concerns
× Structure -> <Template>
Components concerns
× Structure -> <Template>
× Presentation -> <Style>
Components concerns
× Structure -> <Template>
× Presentation -> <Styles>
× Data -> <Script>
<Template>
Managing information
structure
Directives
Directives & attributes
Special attributes
Directives
v-text (use handlebars {})
v-on (shorthand @)
v-bind (shorthand :)
v-show
v-if / v-else / v-else-if /
v-for / key
slot
v-model
v-pre / v-cloak / v-once
Directives & attributes
Special attributes
Directives
v-text (use handlebars {})
v-on (shorthand @)
v-bind (shorthand :)
v-show
v-if / v-else / v-else-if /
v-for / key
slot
v-model
v-pre / v-cloak / v-once
Directives & attributes
Special attributes
key
ref
slot
is
<SCRIPT>
Managing data
Component internals
Component internals
× data: Internal state
Component internals
× data: Internal state
× props: args
Component internals
× data: Internal state
× props: args
× computed: Cacheable read functions
Component internals
× data: Internal state
× props: args
× computed: Cacheable read functions
× methods: Event handling
Component internals
× data: Internal state
× props: args
× computed: Cacheable read functions
× methods: Event handling
× Provided by plugins: Extensions
Vuex
Vuex
× state: application state as a tree
Vuex
× state: application state as a tree
× getters: state read methods
Vuex
× state: application state as a tree
× getters: state read methods
× mutations: state write methods
Vuex
× state: application state as a tree
× getters: state read methods
× mutations: state write methods
× actions: user commands
Vuerouter + vuex-router-sync
× state.route
× path
× params
× query
<style>
presenting information
3.
HOW?
Building a VueJS application
My mental process is top-down
Component
identification
Model tree
design
Development
Place your screenshot here
Memo-vue
Let’s use this example from
https://github.com/akifo/vue-memo
Identifying
components
Identifying
components
× AppHeader
MAIN CONTENT
Identifying
components
× AppHeader
× [Main content]
Place your screenshot here
Identifying
components
× AppHeader
× [Main content]
× Index
Place your screenshot here
Identifying
components
× AppHeader
× [Main content]
× Index
× Memo
Place your screenshot here
Identifying
components
× AppHeader
× [Main content]
× Index
× Memo
× Viewer
Place your screenshot here
Identifying
components
× AppHeader
× [Main content]
× Index
× Memo
× Viewer
× Editor
Modeling
× State
State
{
// route: {} // vue-router has created state.route
}
State
{
user: {},
memos: {}
// route: {} // vue-router has created state.route
}
State
{
user: {
loggedIn: false
},
memos: {}
// route: {} // vue-router has created state.route
}
State
{
user: {
loggedIn: false,
uid: ''
},
memos: {}
// route: {} // vue-router has created state.route
}
State
{
user: {
loggedIn: false,
uid: '',
name: ''
},
memos: {}
// route: {} // vue-router has created state.route
}
State
{
user: {
loggedIn: false,
uid: '',
name: '',
profilePicUrl: ''
},
memos: {}
// route: {} // vue-router has created state.route
}
State
{
user: {
loggedIn: false,
uid: '',
name: '',
profilePicUrl: ''
},
memos: {}
// route: {} // vue-router has created state.route
}
memo = {
title: '',
body: ''
}
Modeling
× State
× getters
getters
Memos User
getters
Memos
× memos
User
getters
Memos
× memos
× currentMemo
User
getters
Memos
× memos
× currentMemo
× currentMemoId
User
getters
Memos
× memos
× currentMemo
× currentMemoId
User
× user
getters
Memos
× memos
× currentMemo
× currentMemoId
User
× user
× currentUserName
getters
Memos
× memos
× currentMemo
× currentMemoId
User
× user
× currentUserName
× currentUserId
Modeling
× State
× Getters
× mutations
Mutations
Memos User
Mutations
Memos
× setMemo
User
Mutations
Memos
× setMemo
× setMemos
User
Mutations
Memos
× setMemo
× setMemos
× deleteMemo
User
Mutations
Memos
× setMemo
× setMemos
× deleteMemo
User
× oAuthStateChanged
Mutations
Memos
× setMemo
× setMemos
× deleteMemo
User
× oAuthStateChanged
× setUser
Mutations
Memos
× setMemo
× setMemos
× deleteMemo
User
× oAuthStateChanged
× setUser
Modeling
× State
× Getters
× mutations
× Actions
Actions
User
× onAuthStateChanged
Memos
Actions
User
× onAuthStateChanged
× signIn
Memos
Actions
User
× onAuthStateChanged
× signIn
× signOut
Memos
Actions
User
× onAuthStateChanged
× signIn
× signOut
× setUserInfo
Memos
Actions
User
× onAuthStateChanged
× signIn
× signOut
× setUserInfo
Memos
× fetchMemos
Actions
User
× onAuthStateChanged
× signIn
× signOut
× setUserInfo
Memos
× fetchMemos
× fetchMemo
Actions
User
× onAuthStateChanged
× signIn
× signOut
× setUserInfo
Memos
× fetchMemos
× fetchMemo
× addMemo
Actions
User
× onAuthStateChanged
× signIn
× signOut
× setUserInfo
Memos
× fetchMemos
× fetchMemo
× addMemo
× deleteMemo
Actions
User
× onAuthStateChanged
× signIn
× signOut
× setUserInfo
Memos
× fetchMemos
× fetchMemo
× addMemo
× deleteMemo
× updateMemo
Development
Development
× Setup vue
// This content has been reduced. You can find the original content in
https://github.com/akifo/vue-memo/blob/dev/src/js/main.js
import Vue from 'vue'
import App from './App'
import { sync } from 'vuex-router-sync'
import store from './vuex'
import router from './router'
sync(store, router)
const app = new Vue({
router,
store,
el: '#app',
render: h => h(App)
})
global._App = app
// This content has been reduced. You can find the original content in
https://github.com/akifo/vue-memo/blob/dev/src/js/main.js
import Vue from 'vue'
import App from './App'
import { sync } from 'vuex-router-sync'
import store from './vuex'
import router from './router'
sync(store, router)
const app = new Vue({
router,
store,
el: '#app',
render: h => h(App)
})
global._App = app
Imports
// This content has been reduced. You can find the original content in
https://github.com/akifo/vue-memo/blob/dev/src/js/main.js
import Vue from 'vue'
import App from './App'
import { sync } from 'vuex-router-sync'
import store from './vuex'
import router from './router'
sync(store, router)
const app = new Vue({
router,
store,
el: '#app',
render: h => h(App)
})
global._App = app
Imports
Includes current route in state
// This content has been reduced. You can find the original content in
https://github.com/akifo/vue-memo/blob/dev/src/js/main.js
import Vue from 'vue'
import App from './App'
import { sync } from 'vuex-router-sync'
import store from './vuex'
import router from './router'
sync(store, router)
const app = new Vue({
router,
store,
el: '#app',
render: h => h(App)
})
global._App = app
Imports
Includes current route in state
Create Vue instance
Development
× Setup vue
× Store
// This content has been reduced. You can find the original content in
https://github.com/akifo/vue-memo/blob/dev/src/js/vuex/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
user: {
loggedIn: false,
uid: '',
name: '',
profilePicUrl: ''
},
memos: {}
// route: {} // vue-router has created state.route
}
const mutations = {// here come the mutations}
const actions = {// here come the actions}
const getters = {// here come the actions}
export default new Vuex.Store({ state, getters, actions, mutations, strict: debug })
// This content has been reduced. You can find the original content in
https://github.com/akifo/vue-memo/blob/dev/src/js/vuex/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
user: {
loggedIn: false,
uid: '',
name: '',
profilePicUrl: ''
},
memos: {}
// route: {} // vue-router has created state.route
}
const mutations = {// here come the mutations}
const actions = {// here come the actions}
const getters = {// here come the actions}
export default new Vuex.Store({ state, getters, actions, mutations, strict: debug })
Imports
// This content has been reduced. You can find the original content in
https://github.com/akifo/vue-memo/blob/dev/src/js/vuex/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
user: {
loggedIn: false,
uid: '',
name: '',
profilePicUrl: ''
},
memos: {}
// route: {} // vue-router has created state.route
}
const mutations = {// here come the mutations}
const actions = {// here come the actions}
const getters = {// here come the actions}
export default new Vuex.Store({ state, getters, actions, mutations, strict: debug })
Imports
Use it!!!
// This content has been reduced. You can find the original content in
https://github.com/akifo/vue-memo/blob/dev/src/js/vuex/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
user: {
loggedIn: false,
uid: '',
name: '',
profilePicUrl: ''
},
memos: {}
// route: {} // vue-router has created state.route
}
const mutations = {// here come the mutations}
const actions = {// here come the actions}
const getters = {// here come the actions}
export default new Vuex.Store({ state, getters, actions, mutations, strict: debug })
Imports
Use it!!!
Initial state
const mutations = {
onAuthStateChanged (state, { user }) {
state.user = user
},
setUser (state, { key, val }) {
Vue.set(state.user, key, val)
},
setMemo (state, { key, memo }) {
Vue.set(state.memos, key, memo)
},
setMemos (state, { memos }) {
state.memos = memos || {}
},
deleteMemo (state, { key }) {
Vue.delete(state.memos, key)
}
}
fetchMemo ({ commit, state }) {
Firebase.fetchMemo(state.route.params.id)
.then(obj => {
commit('setMemo', {
key: obj.key,
memo: obj.memo
})
})
},
fetchMemos ({ commit }, { count, type }) {
if (state.user.loggedIn || type === 'public') { // is signed
in.
Firebase.fetchMemos(count, type)
.then(memos => {
commit('setMemos', { memos })
})
} else { // is signed out. Localstrage
}
},
deleteMemo ({ commit, state }) {
return new Promise((resolve, reject) => {
if (state.user.loggedIn) { // is signed in. Firebase
Firebase.deleteMemo(state.route.params.id)
.then(key => {
commit('deleteMemo', { key })
resolve()
}).catch(reject)
} else { // is signed out. Localstrage
reject('still dev for guest')
}
})
const actions = {
onAuthStateChanged ({ commit }, user) {
commit('onAuthStateChanged', { user })
},
signIn () {
Firebase.signIn()
},
signOut () {
Firebase.signOut()
},
setUserInfo ({ commit, state }, { key, val }) {
return new Promise((resolve, reject) => {
if (state.user.loggedIn) { // is signed in. Firebase
Firebase.setUserInfo(key, val)
.then(() => {
commit('setUser', { key, val })
resolve()
}).catch(reject)
} else { // is signed out. Localstrage
reject('still dev for guest')
}
})
}
// Truncated code
fetchMemo ({ commit, state }) {
Firebase.fetchMemo(state.route.params.id)
.then(obj => {
commit('setMemo', {
key: obj.key,
memo: obj.memo
})
})
},
fetchMemos ({ commit }, { count, type }) {
if (state.user.loggedIn || type === 'public') { // is signed
in.
Firebase.fetchMemos(count, type)
.then(memos => {
commit('setMemos', { memos })
})
} else { // is signed out. Localstrage
}
},
deleteMemo ({ commit, state }) {
return new Promise((resolve, reject) => {
if (state.user.loggedIn) { // is signed in. Firebase
Firebase.deleteMemo(state.route.params.id)
.then(key => {
commit('deleteMemo', { key })
resolve()
}).catch(reject)
} else { // is signed out. Localstrage
reject('still dev for guest')
}
})
const actions = {
onAuthStateChanged ({ commit }, user) {
commit('onAuthStateChanged', { user })
},
signIn () {
Firebase.signIn()
},
signOut () {
Firebase.signOut()
},
setUserInfo ({ commit, state }, { key, val }) {
return new Promise((resolve, reject) => {
if (state.user.loggedIn) { // is signed in. Firebase
Firebase.setUserInfo(key, val)
.then(() => {
commit('setUser', { key, val })
resolve()
}).catch(reject)
} else { // is signed out. Localstrage
reject('still dev for guest')
}
})
}
// Truncated code
const getters = {
memos: state => state.memos,
currentMemoID: ({ route }) => route.params.id,
currentMemo: state => {
return state.route.params.id
? state.memos[state.route.params.id]
: {}
},
user: state => state.user,
currentUserName: state => state.user.name,
currentUserId: state => state.user.uid
}
Development
× Setup vue
× Store
× Router
// https://github.com/akifo/vue-memo/blob/dev/src/js/router/index.js
import VueRouter from 'vue-router'
import Vue from 'vue'
import Index from '../components/Index'
import Editor from '../components/Editor'
import Viewer from '../components/Viewer'
Vue.use(VueRouter)
var router = new VueRouter({
routes: [
{
path: '/',
name: 'index',
component: Index
}, {
path: '/editor',
name: 'newEditor',
component: Editor
}, {
path: '/editor/:id',
name: 'updateEditor',
component: Editor
}, {
path: '/viewer/:id',
name: 'viewer',
component: Viewer
}
]
})
export default router
// https://github.com/akifo/vue-memo/blob/dev/src/js/router/index.js
import VueRouter from 'vue-router'
import Vue from 'vue'
import Index from '../components/Index'
import Editor from '../components/Editor'
import Viewer from '../components/Viewer'
Vue.use(VueRouter)
var router = new VueRouter({
routes: [
{
path: '/',
name: 'index',
component: Index
}, {
path: '/editor',
name: 'newEditor',
component: Editor
}, {
path: '/editor/:id',
name: 'updateEditor',
component: Editor
}, {
path: '/viewer/:id',
name: 'viewer',
component: Viewer
}
]
})
export default router
Imports
// https://github.com/akifo/vue-memo/blob/dev/src/js/router/index.js
import VueRouter from 'vue-router'
import Vue from 'vue'
import Index from '../components/Index'
import Editor from '../components/Editor'
import Viewer from '../components/Viewer'
Vue.use(VueRouter)
var router = new VueRouter({
routes: [
{
path: '/',
name: 'index',
component: Index
}, {
path: '/editor',
name: 'newEditor',
component: Editor
}, {
path: '/editor/:id',
name: 'updateEditor',
component: Editor
}, {
path: '/viewer/:id',
name: 'viewer',
component: Viewer
}
]
})
export default router
Imports
Use it!!!
// https://github.com/akifo/vue-memo/blob/dev/src/js/router/index.js
import VueRouter from 'vue-router'
import Vue from 'vue'
import Index from '../components/Index'
import Editor from '../components/Editor'
import Viewer from '../components/Viewer'
Vue.use(VueRouter)
var router = new VueRouter({
routes: [
{
path: '/',
name: 'index',
component: Index
}, {
path: '/editor',
name: 'newEditor',
component: Editor
}, {
path: '/editor/:id',
name: 'updateEditor',
component: Editor
}, {
path: '/viewer/:id',
name: 'viewer',
component: Viewer
}
]
})
export default router
Imports
Use it!!!
Your routes
Development
× Setup vue
× Store
× Router
× Root app component
// https://github.com/akifo/vue-memo/blob/dev/src/js/App.vue
<template>
<div id="app">
<app-header></app-header>
<router-view></router-view>
</div>
</template>
<script>
import AppHeader from './components/AppHeader.vue'
export default {
components: { AppHeader },
created () {
this.$store.dispatch('fetchMemos', {
count: 10,
type: 'public'
})
}
}
</script>
<style lang="stylus">
body
margin: 0
padding: 0
</style>
// https://github.com/akifo/vue-memo/blob/dev/src/js/App.vue
<template>
<div id="app">
<app-header></app-header>
<router-view></router-view>
</div>
</template>
<script>
import AppHeader from './components/AppHeader.vue'
export default {
components: { AppHeader },
created () {
this.$store.dispatch('fetchMemos', {
count: 10,
type: 'public'
})
}
}
</script>
<style lang="stylus">
body
margin: 0
padding: 0
</style>
Using components
// https://github.com/akifo/vue-memo/blob/dev/src/js/App.vue
<template>
<div id="app">
<app-header></app-header>
<router-view></router-view>
</div>
</template>
<script>
import AppHeader from './components/AppHeader.vue'
export default {
components: { AppHeader },
created () {
this.$store.dispatch('fetchMemos', {
count: 10,
type: 'public'
})
}
}
</script>
<style lang="stylus">
body
margin: 0
padding: 0
</style>
Using components
Import subcomponents
// https://github.com/akifo/vue-memo/blob/dev/src/js/App.vue
<template>
<div id="app">
<app-header></app-header>
<router-view></router-view>
</div>
</template>
<script>
import AppHeader from './components/AppHeader.vue'
export default {
components: { AppHeader },
created () {
this.$store.dispatch('fetchMemos', {
count: 10,
type: 'public'
})
}
}
</script>
<style lang="stylus">
body
margin: 0
padding: 0
</style>
Using components
Export subcomponents
Import subcomponents
// https://github.com/akifo/vue-memo/blob/dev/src/js/App.vue
<template>
<div id="app">
<app-header></app-header>
<router-view></router-view>
</div>
</template>
<script>
import AppHeader from './components/AppHeader.vue'
export default {
components: { AppHeader },
created () {
this.$store.dispatch('fetchMemos', {
count: 10,
type: 'public'
})
}
}
</script>
<style lang="stylus">
body
margin: 0
padding: 0
</style>
Using components
Export subcomponents
Hook
Import subcomponents
Development
× Setup vue
× Store
× Router
× Root app component
× Create components
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue
<template>
<header class="appHeader navbar-fixed">
<nav>
<div class="nav-wrapper">
<h1 class="left">
<router-link :to="{ name: 'index' }">Vue-memo</router-link>
</h1>
<ul class="right">
<li>
<input type="text" :value="currentUserName" @input="update" @keyup.enter="submit">
</li>
<li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap">
<img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png">
<img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png">
<img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png">
</li>
<li v-if="user.loggedIn"><a @click="signOut">signout</a></li>
</ul>
</div>
</nav>
</header>
</template>
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue
<template>
<header class="appHeader navbar-fixed">
<nav>
<div class="nav-wrapper">
<h1 class="left">
<router-link :to="{ name: 'index' }">Vue-memo</router-link>
</h1>
<ul class="right">
<li>
<input type="text" :value="currentUserName" @input="update" @keyup.enter="submit">
</li>
<li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap">
<img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png">
<img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png">
<img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png">
</li>
<li v-if="user.loggedIn"><a @click="signOut">signout</a></li>
</ul>
</div>
</nav>
</header>
</template>
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue
<template>
<header class="appHeader navbar-fixed">
<nav>
<div class="nav-wrapper">
<h1 class="left">
<router-link :to="{ name: 'index' }">Vue-memo</router-link>
</h1>
<ul class="right">
<li>
<input type="text" :value="currentUserName" @input="update" @keyup.enter="submit">
</li>
<li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap">
<img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png">
<img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png">
<img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png">
</li>
<li v-if="user.loggedIn"><a @click="signOut">signout</a></li>
</ul>
</div>
</nav>
</header>
</template>
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue
<template>
<header class="appHeader navbar-fixed">
<nav>
<div class="nav-wrapper">
<h1 class="left">
<router-link :to="{ name: 'index' }">Vue-memo</router-link>
</h1>
<ul class="right">
<li>
<input type="text" :value="currentUserName" @input="update" @keyup.enter="submit">
</li>
<li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap">
<img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png">
<img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png">
<img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png">
</li>
<li v-if="user.loggedIn"><a @click="signOut">signout</a></li>
</ul>
</div>
</nav>
</header>
</template>
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue
<template>
<header class="appHeader navbar-fixed">
<nav>
<div class="nav-wrapper">
<h1 class="left">
<router-link :to="{ name: 'index' }">Vue-memo</router-link>
</h1>
<ul class="right">
<li>
<input type="text" :value="currentUserName" @input="update" @keyup.enter="submit">
</li>
<li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap">
<img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png">
<img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png">
<img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png">
</li>
<li v-if="user.loggedIn"><a @click="signOut">signout</a></li>
</ul>
</div>
</nav>
</header>
</template>
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue
<template>
<header class="appHeader navbar-fixed">
<nav>
<div class="nav-wrapper">
<h1 class="left">
<router-link :to="{ name: 'index' }">Vue-memo</router-link>
</h1>
<ul class="right">
<li>
<input type="text" :value="currentUserName" @input="update" @keyup.enter="submit">
</li>
<li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap">
<img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png">
<img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png">
<img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png">
</li>
<li v-if="user.loggedIn"><a @click="signOut">signout</a></li>
</ul>
</div>
</nav>
</header>
</template>
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue
<template>
<header class="appHeader navbar-fixed">
<nav>
<div class="nav-wrapper">
<h1 class="left">
<router-link :to="{ name: 'index' }">Vue-memo</router-link>
</h1>
<ul class="right">
<li>
<input type="text" :value="currentUserName" @input="update" @keyup.enter="submit">
</li>
<li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap">
<img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png">
<img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png">
<img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png">
</li>
<li v-if="user.loggedIn"><a @click="signOut">signout</a></li>
</ul>
</div>
</nav>
</header>
</template>
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue
<template>
<header class="appHeader navbar-fixed">
<nav>
<div class="nav-wrapper">
<h1 class="left">
<router-link :to="{ name: 'index' }">Vue-memo</router-link>
</h1>
<ul class="right">
<li>
<input type="text" :value="currentUserName" @input="update" @keyup.enter="submit">
</li>
<li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap">
<img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png">
<img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png">
<img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png">
</li>
<li v-if="user.loggedIn"><a @click="signOut">signout</a></li>
</ul>
</div>
</nav>
</header>
</template>
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue
<template>
<header class="appHeader navbar-fixed">
<nav>
<div class="nav-wrapper">
<h1 class="left">
<router-link :to="{ name: 'index' }">Vue-memo</router-link>
</h1>
<ul class="right">
<li>
<input type="text" :value="currentUserName" @input="update" @keyup.enter="submit">
</li>
<li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap">
<img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png">
<img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png">
<img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png">
</li>
<li v-if="user.loggedIn"><a @click="signOut">signout</a></li>
</ul>
</div>
</nav>
</header>
</template>
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue
<script>
import { mapGetters, mapActions } from 'vuex'
import _ from 'lodash'
export default {
name: 'AppHeader',
computed: mapGetters(['user', 'currentUserName']), // import easily getters from your store
data () { // data now stores internal component state, no application state e.g: active filters, active/inactive status, etc.
return {
name: ''
}
},
methods: {
...mapActions(['signIn', 'signOut']), // import easily actions from your store
update: _.debounce(function (e) {
this.submit(e)
}, 2000),
submit: _.throttle(function (e) {
// Here comes the implementation of this method
}, 800),
validate (text) {
// Here comes the implementation of this method
}
}
}
</script>
<style lang="stylus">
// Here comes some styling stuff
</style>
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue
<script>
import { mapGetters, mapActions } from 'vuex'
import _ from 'lodash'
export default {
name: 'AppHeader',
computed: mapGetters(['user', 'currentUserName']), // import easily getters from your store
data () { // data now stores internal component state, no application state e.g: active filters, active/inactive status, etc.
return {
name: ''
}
},
methods: {
...mapActions(['signIn', 'signOut']), // import easily actions from your store
update: _.debounce(function (e) {
this.submit(e)
}, 2000),
submit: _.throttle(function (e) {
// Here comes the implementation of this method
}, 800),
validate (text) {
// Here comes the implementation of this method
}
}
}
</script>
<style lang="stylus">
// Here comes some styling stuff
</style>
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue
<script>
import { mapGetters, mapActions } from 'vuex'
import _ from 'lodash'
export default {
name: 'AppHeader',
computed: mapGetters(['user', 'currentUserName']), // import easily getters from your store
data () { // data now stores internal component state, no application state e.g: active filters, active/inactive status, etc.
return {
name: ''
}
},
methods: {
...mapActions(['signIn', 'signOut']), // import easily actions from your store
update: _.debounce(function (e) {
this.submit(e)
}, 2000),
submit: _.throttle(function (e) {
// Here comes the implementation of this method
}, 800),
validate (text) {
// Here comes the implementation of this method
}
}
}
</script>
<style lang="stylus">
// Here comes some styling stuff
</style>
methods: {
...mapActions(['signIn', 'signOut']),
update: _.debounce(function (e) {
this.submit(e)
}, 2000),
submit: _.throttle(function (e) {
const name = e.target.value
this.validate(name)
.then(() => {
return this.$store.dispatch('setUserInfo', {
key: 'name',
val: name
})
}).then(() => {
Materialize.toast('Updated Name', 4000, 'green')
}).catch(err => {
Materialize.toast('Error ' + err, 4000, 'red')
})
}, 800),
validate (text) {
return new Promise((resolve, reject) => {
if (text.length < 3) reject('too short')
else if (text.length > 15)reject('too long')
else resolve()
})
}
}
}
methods: {
...mapActions(['signIn', 'signOut']),
update: _.debounce(function (e) {
this.submit(e)
}, 2000),
submit: _.throttle(function (e) {
const name = e.target.value
this.validate(name)
.then(() => {
return this.$store.dispatch('setUserInfo', {
key: 'name',
val: name
})
}).then(() => {
Materialize.toast('Updated Name', 4000, 'green')
}).catch(err => {
Materialize.toast('Error ' + err, 4000, 'red')
})
}, 800),
validate (text) {
return new Promise((resolve, reject) => {
if (text.length < 3) reject('too short')
else if (text.length > 15)reject('too long')
else resolve()
})
}
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/Index.vue
<template>
<div class="index container">
<div class="filter">
<a class="waves-effect waves-light btn"
:class="{ active: currentTimeLine === 'public' }"
@click="switchTimeLine('public')">Public</a>
<a class="waves-effect waves-light btn"
:class="{ active: currentTimeLine === 'myself' }"
@click="switchTimeLine('myself')">Myself</a>
</div>
<ul class="collection">
<memo v-for="(memo, key) in orderedMemos" :memo="memo">
</memo>
</ul>
<div>
<a class="waves-effect waves-light btn-large"
@click="fetchMemos({count: 10, type: currentTimeLine})">fetch more memos</a>
</div>
<div class="fixed-action-btn">
<router-link :to="{ name: 'newEditor' }"
class="btn-floating btn-large waves-effect waves-light red">
<i class="material-icons">add</i>
</router-link>
</div>
</div>
</template>
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/Index.vue
<template>
<div class="index container">
<div class="filter">
<a class="waves-effect waves-light btn"
:class="{ active: currentTimeLine === 'public' }"
@click="switchTimeLine('public')">Public</a>
<a class="waves-effect waves-light btn"
:class="{ active: currentTimeLine === 'myself' }"
@click="switchTimeLine('myself')">Myself</a>
</div>
<ul class="collection">
<memo v-for="(memo, key) in orderedMemos" :memo="memo">
</memo>
</ul>
<div>
<a class="waves-effect waves-light btn-large"
@click="fetchMemos({count: 10, type: currentTimeLine})">fetch more memos</a>
</div>
<div class="fixed-action-btn">
<router-link :to="{ name: 'newEditor' }"
class="btn-floating btn-large waves-effect waves-light red">
<i class="material-icons">add</i>
</router-link>
</div>
</div>
</template>
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/Index.vue
<template>
<div class="index container">
<div class="filter">
<a class="waves-effect waves-light btn"
:class="{ active: currentTimeLine === 'public' }"
@click="switchTimeLine('public')">Public</a>
<a class="waves-effect waves-light btn"
:class="{ active: currentTimeLine === 'myself' }"
@click="switchTimeLine('myself')">Myself</a>
</div>
<ul class="collection">
<memo v-for="(memo, key) in orderedMemos" :memo="memo">
</memo>
</ul>
<div>
<a class="waves-effect waves-light btn-large"
@click="fetchMemos({count: 10, type: currentTimeLine})">fetch more memos</a>
</div>
<div class="fixed-action-btn">
<router-link :to="{ name: 'newEditor' }"
class="btn-floating btn-large waves-effect waves-light red">
<i class="material-icons">add</i>
</router-link>
</div>
</div>
</template>
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/Index.vue
<template>
<div class="index container">
<div class="filter">
<a class="waves-effect waves-light btn"
:class="{ active: currentTimeLine === 'public' }"
@click="switchTimeLine('public')">Public</a>
<a class="waves-effect waves-light btn"
:class="{ active: currentTimeLine === 'myself' }"
@click="switchTimeLine('myself')">Myself</a>
</div>
<ul class="collection">
<memo v-for="(memo, key) in orderedMemos" :memo="memo">
</memo>
</ul>
<div>
<a class="waves-effect waves-light btn-large"
@click="fetchMemos({count: 10, type: currentTimeLine})">fetch more memos</a>
</div>
<div class="fixed-action-btn">
<router-link :to="{ name: 'newEditor' }"
class="btn-floating btn-large waves-effect waves-light red">
<i class="material-icons">add</i>
</router-link>
</div>
</div>
</template>
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/Index.vue
<template>
<div class="index container">
<div class="filter">
<a class="waves-effect waves-light btn"
:class="{ active: currentTimeLine === 'public' }"
@click="switchTimeLine('public')">Public</a>
<a class="waves-effect waves-light btn"
:class="{ active: currentTimeLine === 'myself' }"
@click="switchTimeLine('myself')">Myself</a>
</div>
<ul class="collection">
<memo v-for="(memo, key) in orderedMemos" :memo="memo">
</memo>
</ul>
<div>
<a class="waves-effect waves-light btn-large"
@click="fetchMemos({count: 10, type: currentTimeLine})">fetch more memos</a>
</div>
<div class="fixed-action-btn">
<router-link :to="{ name: 'newEditor' }"
class="btn-floating btn-large waves-effect waves-light red">
<i class="material-icons">add</i>
</router-link>
</div>
</div>
</template>
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/Index.vue
<script>
import { mapGetters, mapActions } from 'vuex'
import Memo from './Memo.vue'
export default {
components: { Memo },
computed: {
...mapGetters(['memos', 'currentUserId']),
orderedMemos () {
// Here comes the implementation
}
},
data () {
return {
currentTimeLine: 'public'
}
},
methods: {
...mapActions(['fetchMemos']),
switchTimeLine (val) {
// Here comes the implementation
}
}
}
</script>
<style lang="stylus">
// Here comes some styling stuff
</style>
// https://github.com/akifo/vue-memo/blob/dev/src/js/components/Memo.vue
<template>
<li class="memo collection-item">
<router-link :to="{ name: 'viewer', params: { id: memo.key }}">
<h2>{{memo.title}}</h2>
<p>{{memo.author.name}} | {{memo.created | formatDate }}</p>
</router-link>
</li>
</template>
<script>
export default {
name: 'Memo',
props: ['memo', 'id']
}
</script>
<style lang="stylus">
// Here comes some styling stuff
</style>
You can pass arguments to this component via props
Development
× Setup vue
× Store
× Router
× Root app component
× Create components
× Dev tools
Dev tools
× vue-devtools
https://github.com/vuejs/vue-devtools
Rails
Implicit dependencies
layer > concern hierarchy
You think about how to
store data
Mindset differences
Vue
Explicit dependencies
concern > layer hierarchy
You think about how to
present data
Authentication & authorization
Backend persistence
Async actions management (sagas)
Connecting to third party services
Styling
SSR?
Build process
Before going live you should know
after going live you should dive deeper in
Vuex
VueRouter
ES2015
Transitions & animations
GraphQL :-)
Functional programming
Progressive web apps
Vue native with Weex
Vue for desktop with Electron
SSR with Nuxt.js
(some of my) next learning paths
Web components & HTTP 2
Functional programming
TypeScript
Docker + GraphQL + Vue!!!
4.
what we learned
Building an application with Vue,
rails and graphql
Learning fact #1
If you need a “modern” UI
you should use something
like VUE.js
Learning fact #2
Developing with vue.js makes
working with JS more easy
to reason about
Learning fact #3
Working with vue.js implies
bigger effort…
...just because you need
something bigger
THANKS!Any questions?
You can find ME at @eLafo
You can find US at @aspgems & info@aspgems.com
References
× Learning Vue.js 2 - Olga Filipova
× https://vuejs.org/
× http://engineering.paiza.io/entry/2015/03/12/145216
× https://medium.com/js-dojo/whats-new-in-vue-js-2-0-virtual-dom-dc4b5b827f40
× http://www.valuecoders.com/blog/technology-and-apps/vue-js-comparison-angular-re
act/
× https://medium.com/@deathmood/how-to-write-your-own-virtual-dom-ee74acc13060
×
Credits
Special thanks to all the people who made and
released these awesome resources for free:
× Vue example app by afiko
× Presentation template by SlidesCarnival
× Photographs by Startupstockphotos
Presentation design
This presentation uses the following typographies:
× Titles: Bangers
× Body copy: Sniglet
You can download the fonts on this page:
https://www.google.com/fonts#UsePlace:use/Collection:Sniglet|Bangers
Click on the “arrow button” that appears on the top right
You don’t need to keep this slide in your presentation. It’s only here to serve you as a design guide if
you need to create new slides or download the fonts to edit the presentation in PowerPoint®

An introduction to Vue.js

  • 1.
  • 2.
  • 3.
    At the momentof preparing this talk the latest version of vue is 2.3
  • 4.
    What are wegoing to talk about? Why? Reasons to use a JS frontend framework × UI Complexity × Rapid Prototyping × JS Spaguetti What? What Vue.js is × Declarative × MVVM × Reactivity × Virtual DOM × Components How? How to build a Vue.js project × Components identification × model tree design × Development × Setup Vue × Components × Dev tools
  • 5.
    What are weNOT going to talk about? ES2015 Webpack JS internals Testing Deploy Authorization Plugins Routing Middleware Reactivity internals Persistence Server Side Rendering Vuex XSS or CORS Connecting to third party services Components lifecycle Transitions Animations
  • 6.
    1. Why? 3 reasons whyyou would want to use a proper front-end framework
  • 7.
    UI Complexity Web basedapps UI have increased their complexity, and they do not longer behave as traditional web sites but as (browser) native apps.
  • 10.
    Rapid prototyping HTML, CSS& JS are the new photoshop. You want to prototype not only visual aspects of your apps, but your app’s UX. You need to do this as fast as possible… and you do not want to be the bottleneck.
  • 11.
    JS / JQUERYSpaguetti Recipe: 1. Forget about separation of concerns 2. Mix structure, presentation and data logic together. 3. Throw this mixture in the DOM 4. Enjoy your food PS: Do not consume if you like sleeping
  • 12.
  • 13.
    Declarative Tell what torender instead of how
  • 15.
  • 16.
  • 17.
  • 18.
    Components Components are oneof the most powerful features of Vue. WIth them you can build reusable features to be used easily -thanks to its declarative nature- by other developers.
  • 19.
  • 20.
    Components concerns × Structure-> <Template> × Presentation -> <Style>
  • 21.
    Components concerns × Structure-> <Template> × Presentation -> <Styles> × Data -> <Script>
  • 22.
  • 23.
  • 24.
    Directives v-text (use handlebars{}) v-on (shorthand @) v-bind (shorthand :) v-show v-if / v-else / v-else-if / v-for / key slot v-model v-pre / v-cloak / v-once Directives & attributes Special attributes
  • 25.
    Directives v-text (use handlebars{}) v-on (shorthand @) v-bind (shorthand :) v-show v-if / v-else / v-else-if / v-for / key slot v-model v-pre / v-cloak / v-once Directives & attributes Special attributes key ref slot is
  • 26.
  • 27.
  • 28.
  • 29.
    Component internals × data:Internal state × props: args
  • 30.
    Component internals × data:Internal state × props: args × computed: Cacheable read functions
  • 31.
    Component internals × data:Internal state × props: args × computed: Cacheable read functions × methods: Event handling
  • 32.
    Component internals × data:Internal state × props: args × computed: Cacheable read functions × methods: Event handling × Provided by plugins: Extensions
  • 33.
  • 34.
  • 35.
    Vuex × state: applicationstate as a tree × getters: state read methods
  • 36.
    Vuex × state: applicationstate as a tree × getters: state read methods × mutations: state write methods
  • 37.
    Vuex × state: applicationstate as a tree × getters: state read methods × mutations: state write methods × actions: user commands
  • 38.
    Vuerouter + vuex-router-sync ×state.route × path × params × query
  • 39.
  • 41.
  • 42.
    My mental processis top-down Component identification Model tree design Development
  • 43.
    Place your screenshothere Memo-vue Let’s use this example from https://github.com/akifo/vue-memo
  • 44.
  • 45.
  • 46.
  • 47.
    Place your screenshothere Identifying components × AppHeader × [Main content] × Index
  • 48.
    Place your screenshothere Identifying components × AppHeader × [Main content] × Index × Memo
  • 49.
    Place your screenshothere Identifying components × AppHeader × [Main content] × Index × Memo × Viewer
  • 50.
    Place your screenshothere Identifying components × AppHeader × [Main content] × Index × Memo × Viewer × Editor
  • 51.
  • 52.
    State { // route: {}// vue-router has created state.route }
  • 53.
    State { user: {}, memos: {} //route: {} // vue-router has created state.route }
  • 54.
    State { user: { loggedIn: false }, memos:{} // route: {} // vue-router has created state.route }
  • 55.
    State { user: { loggedIn: false, uid:'' }, memos: {} // route: {} // vue-router has created state.route }
  • 56.
    State { user: { loggedIn: false, uid:'', name: '' }, memos: {} // route: {} // vue-router has created state.route }
  • 57.
    State { user: { loggedIn: false, uid:'', name: '', profilePicUrl: '' }, memos: {} // route: {} // vue-router has created state.route }
  • 58.
    State { user: { loggedIn: false, uid:'', name: '', profilePicUrl: '' }, memos: {} // route: {} // vue-router has created state.route } memo = { title: '', body: '' }
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
    getters Memos × memos × currentMemo ×currentMemoId User × user
  • 65.
    getters Memos × memos × currentMemo ×currentMemoId User × user × currentUserName
  • 66.
    getters Memos × memos × currentMemo ×currentMemoId User × user × currentUserName × currentUserId
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
    Mutations Memos × setMemo × setMemos ×deleteMemo User × oAuthStateChanged
  • 73.
    Mutations Memos × setMemo × setMemos ×deleteMemo User × oAuthStateChanged × setUser
  • 74.
    Mutations Memos × setMemo × setMemos ×deleteMemo User × oAuthStateChanged × setUser
  • 75.
    Modeling × State × Getters ×mutations × Actions
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
    Actions User × onAuthStateChanged × signIn ×signOut × setUserInfo Memos × fetchMemos
  • 81.
    Actions User × onAuthStateChanged × signIn ×signOut × setUserInfo Memos × fetchMemos × fetchMemo
  • 82.
    Actions User × onAuthStateChanged × signIn ×signOut × setUserInfo Memos × fetchMemos × fetchMemo × addMemo
  • 83.
    Actions User × onAuthStateChanged × signIn ×signOut × setUserInfo Memos × fetchMemos × fetchMemo × addMemo × deleteMemo
  • 84.
    Actions User × onAuthStateChanged × signIn ×signOut × setUserInfo Memos × fetchMemos × fetchMemo × addMemo × deleteMemo × updateMemo
  • 85.
  • 86.
  • 87.
    // This contenthas been reduced. You can find the original content in https://github.com/akifo/vue-memo/blob/dev/src/js/main.js import Vue from 'vue' import App from './App' import { sync } from 'vuex-router-sync' import store from './vuex' import router from './router' sync(store, router) const app = new Vue({ router, store, el: '#app', render: h => h(App) }) global._App = app
  • 88.
    // This contenthas been reduced. You can find the original content in https://github.com/akifo/vue-memo/blob/dev/src/js/main.js import Vue from 'vue' import App from './App' import { sync } from 'vuex-router-sync' import store from './vuex' import router from './router' sync(store, router) const app = new Vue({ router, store, el: '#app', render: h => h(App) }) global._App = app Imports
  • 89.
    // This contenthas been reduced. You can find the original content in https://github.com/akifo/vue-memo/blob/dev/src/js/main.js import Vue from 'vue' import App from './App' import { sync } from 'vuex-router-sync' import store from './vuex' import router from './router' sync(store, router) const app = new Vue({ router, store, el: '#app', render: h => h(App) }) global._App = app Imports Includes current route in state
  • 90.
    // This contenthas been reduced. You can find the original content in https://github.com/akifo/vue-memo/blob/dev/src/js/main.js import Vue from 'vue' import App from './App' import { sync } from 'vuex-router-sync' import store from './vuex' import router from './router' sync(store, router) const app = new Vue({ router, store, el: '#app', render: h => h(App) }) global._App = app Imports Includes current route in state Create Vue instance
  • 91.
  • 92.
    // This contenthas been reduced. You can find the original content in https://github.com/akifo/vue-memo/blob/dev/src/js/vuex/index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const state = { user: { loggedIn: false, uid: '', name: '', profilePicUrl: '' }, memos: {} // route: {} // vue-router has created state.route } const mutations = {// here come the mutations} const actions = {// here come the actions} const getters = {// here come the actions} export default new Vuex.Store({ state, getters, actions, mutations, strict: debug })
  • 93.
    // This contenthas been reduced. You can find the original content in https://github.com/akifo/vue-memo/blob/dev/src/js/vuex/index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const state = { user: { loggedIn: false, uid: '', name: '', profilePicUrl: '' }, memos: {} // route: {} // vue-router has created state.route } const mutations = {// here come the mutations} const actions = {// here come the actions} const getters = {// here come the actions} export default new Vuex.Store({ state, getters, actions, mutations, strict: debug }) Imports
  • 94.
    // This contenthas been reduced. You can find the original content in https://github.com/akifo/vue-memo/blob/dev/src/js/vuex/index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const state = { user: { loggedIn: false, uid: '', name: '', profilePicUrl: '' }, memos: {} // route: {} // vue-router has created state.route } const mutations = {// here come the mutations} const actions = {// here come the actions} const getters = {// here come the actions} export default new Vuex.Store({ state, getters, actions, mutations, strict: debug }) Imports Use it!!!
  • 95.
    // This contenthas been reduced. You can find the original content in https://github.com/akifo/vue-memo/blob/dev/src/js/vuex/index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const state = { user: { loggedIn: false, uid: '', name: '', profilePicUrl: '' }, memos: {} // route: {} // vue-router has created state.route } const mutations = {// here come the mutations} const actions = {// here come the actions} const getters = {// here come the actions} export default new Vuex.Store({ state, getters, actions, mutations, strict: debug }) Imports Use it!!! Initial state
  • 96.
    const mutations ={ onAuthStateChanged (state, { user }) { state.user = user }, setUser (state, { key, val }) { Vue.set(state.user, key, val) }, setMemo (state, { key, memo }) { Vue.set(state.memos, key, memo) }, setMemos (state, { memos }) { state.memos = memos || {} }, deleteMemo (state, { key }) { Vue.delete(state.memos, key) } }
  • 97.
    fetchMemo ({ commit,state }) { Firebase.fetchMemo(state.route.params.id) .then(obj => { commit('setMemo', { key: obj.key, memo: obj.memo }) }) }, fetchMemos ({ commit }, { count, type }) { if (state.user.loggedIn || type === 'public') { // is signed in. Firebase.fetchMemos(count, type) .then(memos => { commit('setMemos', { memos }) }) } else { // is signed out. Localstrage } }, deleteMemo ({ commit, state }) { return new Promise((resolve, reject) => { if (state.user.loggedIn) { // is signed in. Firebase Firebase.deleteMemo(state.route.params.id) .then(key => { commit('deleteMemo', { key }) resolve() }).catch(reject) } else { // is signed out. Localstrage reject('still dev for guest') } }) const actions = { onAuthStateChanged ({ commit }, user) { commit('onAuthStateChanged', { user }) }, signIn () { Firebase.signIn() }, signOut () { Firebase.signOut() }, setUserInfo ({ commit, state }, { key, val }) { return new Promise((resolve, reject) => { if (state.user.loggedIn) { // is signed in. Firebase Firebase.setUserInfo(key, val) .then(() => { commit('setUser', { key, val }) resolve() }).catch(reject) } else { // is signed out. Localstrage reject('still dev for guest') } }) } // Truncated code
  • 98.
    fetchMemo ({ commit,state }) { Firebase.fetchMemo(state.route.params.id) .then(obj => { commit('setMemo', { key: obj.key, memo: obj.memo }) }) }, fetchMemos ({ commit }, { count, type }) { if (state.user.loggedIn || type === 'public') { // is signed in. Firebase.fetchMemos(count, type) .then(memos => { commit('setMemos', { memos }) }) } else { // is signed out. Localstrage } }, deleteMemo ({ commit, state }) { return new Promise((resolve, reject) => { if (state.user.loggedIn) { // is signed in. Firebase Firebase.deleteMemo(state.route.params.id) .then(key => { commit('deleteMemo', { key }) resolve() }).catch(reject) } else { // is signed out. Localstrage reject('still dev for guest') } }) const actions = { onAuthStateChanged ({ commit }, user) { commit('onAuthStateChanged', { user }) }, signIn () { Firebase.signIn() }, signOut () { Firebase.signOut() }, setUserInfo ({ commit, state }, { key, val }) { return new Promise((resolve, reject) => { if (state.user.loggedIn) { // is signed in. Firebase Firebase.setUserInfo(key, val) .then(() => { commit('setUser', { key, val }) resolve() }).catch(reject) } else { // is signed out. Localstrage reject('still dev for guest') } }) } // Truncated code
  • 99.
    const getters ={ memos: state => state.memos, currentMemoID: ({ route }) => route.params.id, currentMemo: state => { return state.route.params.id ? state.memos[state.route.params.id] : {} }, user: state => state.user, currentUserName: state => state.user.name, currentUserId: state => state.user.uid }
  • 100.
  • 101.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/router/index.js import VueRouterfrom 'vue-router' import Vue from 'vue' import Index from '../components/Index' import Editor from '../components/Editor' import Viewer from '../components/Viewer' Vue.use(VueRouter) var router = new VueRouter({ routes: [ { path: '/', name: 'index', component: Index }, { path: '/editor', name: 'newEditor', component: Editor }, { path: '/editor/:id', name: 'updateEditor', component: Editor }, { path: '/viewer/:id', name: 'viewer', component: Viewer } ] }) export default router
  • 102.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/router/index.js import VueRouterfrom 'vue-router' import Vue from 'vue' import Index from '../components/Index' import Editor from '../components/Editor' import Viewer from '../components/Viewer' Vue.use(VueRouter) var router = new VueRouter({ routes: [ { path: '/', name: 'index', component: Index }, { path: '/editor', name: 'newEditor', component: Editor }, { path: '/editor/:id', name: 'updateEditor', component: Editor }, { path: '/viewer/:id', name: 'viewer', component: Viewer } ] }) export default router Imports
  • 103.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/router/index.js import VueRouterfrom 'vue-router' import Vue from 'vue' import Index from '../components/Index' import Editor from '../components/Editor' import Viewer from '../components/Viewer' Vue.use(VueRouter) var router = new VueRouter({ routes: [ { path: '/', name: 'index', component: Index }, { path: '/editor', name: 'newEditor', component: Editor }, { path: '/editor/:id', name: 'updateEditor', component: Editor }, { path: '/viewer/:id', name: 'viewer', component: Viewer } ] }) export default router Imports Use it!!!
  • 104.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/router/index.js import VueRouterfrom 'vue-router' import Vue from 'vue' import Index from '../components/Index' import Editor from '../components/Editor' import Viewer from '../components/Viewer' Vue.use(VueRouter) var router = new VueRouter({ routes: [ { path: '/', name: 'index', component: Index }, { path: '/editor', name: 'newEditor', component: Editor }, { path: '/editor/:id', name: 'updateEditor', component: Editor }, { path: '/viewer/:id', name: 'viewer', component: Viewer } ] }) export default router Imports Use it!!! Your routes
  • 105.
    Development × Setup vue ×Store × Router × Root app component
  • 106.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/App.vue <template> <div id="app"> <app-header></app-header> <router-view></router-view> </div> </template> <script> importAppHeader from './components/AppHeader.vue' export default { components: { AppHeader }, created () { this.$store.dispatch('fetchMemos', { count: 10, type: 'public' }) } } </script> <style lang="stylus"> body margin: 0 padding: 0 </style>
  • 107.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/App.vue <template> <div id="app"> <app-header></app-header> <router-view></router-view> </div> </template> <script> importAppHeader from './components/AppHeader.vue' export default { components: { AppHeader }, created () { this.$store.dispatch('fetchMemos', { count: 10, type: 'public' }) } } </script> <style lang="stylus"> body margin: 0 padding: 0 </style> Using components
  • 108.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/App.vue <template> <div id="app"> <app-header></app-header> <router-view></router-view> </div> </template> <script> importAppHeader from './components/AppHeader.vue' export default { components: { AppHeader }, created () { this.$store.dispatch('fetchMemos', { count: 10, type: 'public' }) } } </script> <style lang="stylus"> body margin: 0 padding: 0 </style> Using components Import subcomponents
  • 109.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/App.vue <template> <div id="app"> <app-header></app-header> <router-view></router-view> </div> </template> <script> importAppHeader from './components/AppHeader.vue' export default { components: { AppHeader }, created () { this.$store.dispatch('fetchMemos', { count: 10, type: 'public' }) } } </script> <style lang="stylus"> body margin: 0 padding: 0 </style> Using components Export subcomponents Import subcomponents
  • 110.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/App.vue <template> <div id="app"> <app-header></app-header> <router-view></router-view> </div> </template> <script> importAppHeader from './components/AppHeader.vue' export default { components: { AppHeader }, created () { this.$store.dispatch('fetchMemos', { count: 10, type: 'public' }) } } </script> <style lang="stylus"> body margin: 0 padding: 0 </style> Using components Export subcomponents Hook Import subcomponents
  • 111.
    Development × Setup vue ×Store × Router × Root app component × Create components
  • 112.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue <template> <header class="appHeadernavbar-fixed"> <nav> <div class="nav-wrapper"> <h1 class="left"> <router-link :to="{ name: 'index' }">Vue-memo</router-link> </h1> <ul class="right"> <li> <input type="text" :value="currentUserName" @input="update" @keyup.enter="submit"> </li> <li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap"> <img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png"> <img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png"> <img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png"> </li> <li v-if="user.loggedIn"><a @click="signOut">signout</a></li> </ul> </div> </nav> </header> </template>
  • 113.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue <template> <header class="appHeadernavbar-fixed"> <nav> <div class="nav-wrapper"> <h1 class="left"> <router-link :to="{ name: 'index' }">Vue-memo</router-link> </h1> <ul class="right"> <li> <input type="text" :value="currentUserName" @input="update" @keyup.enter="submit"> </li> <li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap"> <img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png"> <img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png"> <img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png"> </li> <li v-if="user.loggedIn"><a @click="signOut">signout</a></li> </ul> </div> </nav> </header> </template>
  • 114.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue <template> <header class="appHeadernavbar-fixed"> <nav> <div class="nav-wrapper"> <h1 class="left"> <router-link :to="{ name: 'index' }">Vue-memo</router-link> </h1> <ul class="right"> <li> <input type="text" :value="currentUserName" @input="update" @keyup.enter="submit"> </li> <li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap"> <img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png"> <img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png"> <img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png"> </li> <li v-if="user.loggedIn"><a @click="signOut">signout</a></li> </ul> </div> </nav> </header> </template>
  • 115.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue <template> <header class="appHeadernavbar-fixed"> <nav> <div class="nav-wrapper"> <h1 class="left"> <router-link :to="{ name: 'index' }">Vue-memo</router-link> </h1> <ul class="right"> <li> <input type="text" :value="currentUserName" @input="update" @keyup.enter="submit"> </li> <li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap"> <img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png"> <img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png"> <img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png"> </li> <li v-if="user.loggedIn"><a @click="signOut">signout</a></li> </ul> </div> </nav> </header> </template>
  • 116.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue <template> <header class="appHeadernavbar-fixed"> <nav> <div class="nav-wrapper"> <h1 class="left"> <router-link :to="{ name: 'index' }">Vue-memo</router-link> </h1> <ul class="right"> <li> <input type="text" :value="currentUserName" @input="update" @keyup.enter="submit"> </li> <li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap"> <img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png"> <img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png"> <img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png"> </li> <li v-if="user.loggedIn"><a @click="signOut">signout</a></li> </ul> </div> </nav> </header> </template>
  • 117.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue <template> <header class="appHeadernavbar-fixed"> <nav> <div class="nav-wrapper"> <h1 class="left"> <router-link :to="{ name: 'index' }">Vue-memo</router-link> </h1> <ul class="right"> <li> <input type="text" :value="currentUserName" @input="update" @keyup.enter="submit"> </li> <li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap"> <img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png"> <img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png"> <img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png"> </li> <li v-if="user.loggedIn"><a @click="signOut">signout</a></li> </ul> </div> </nav> </header> </template>
  • 118.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue <template> <header class="appHeadernavbar-fixed"> <nav> <div class="nav-wrapper"> <h1 class="left"> <router-link :to="{ name: 'index' }">Vue-memo</router-link> </h1> <ul class="right"> <li> <input type="text" :value="currentUserName" @input="update" @keyup.enter="submit"> </li> <li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap"> <img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png"> <img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png"> <img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png"> </li> <li v-if="user.loggedIn"><a @click="signOut">signout</a></li> </ul> </div> </nav> </header> </template>
  • 119.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue <template> <header class="appHeadernavbar-fixed"> <nav> <div class="nav-wrapper"> <h1 class="left"> <router-link :to="{ name: 'index' }">Vue-memo</router-link> </h1> <ul class="right"> <li> <input type="text" :value="currentUserName" @input="update" @keyup.enter="submit"> </li> <li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap"> <img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png"> <img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png"> <img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png"> </li> <li v-if="user.loggedIn"><a @click="signOut">signout</a></li> </ul> </div> </nav> </header> </template>
  • 120.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue <template> <header class="appHeadernavbar-fixed"> <nav> <div class="nav-wrapper"> <h1 class="left"> <router-link :to="{ name: 'index' }">Vue-memo</router-link> </h1> <ul class="right"> <li> <input type="text" :value="currentUserName" @input="update" @keyup.enter="submit"> </li> <li v-if="!user.loggedIn" @click="signIn" class="googleBtnWrap"> <img class="normal" src="./../assets/btn_google_signin_light_normal_web@2x.png"> <img class="focus" src="./../assets/btn_google_signin_light_focus_web@2x.png"> <img class="pressed" src="./../assets/btn_google_signin_light_pressed_web@2x.png"> </li> <li v-if="user.loggedIn"><a @click="signOut">signout</a></li> </ul> </div> </nav> </header> </template>
  • 121.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue <script> import {mapGetters, mapActions } from 'vuex' import _ from 'lodash' export default { name: 'AppHeader', computed: mapGetters(['user', 'currentUserName']), // import easily getters from your store data () { // data now stores internal component state, no application state e.g: active filters, active/inactive status, etc. return { name: '' } }, methods: { ...mapActions(['signIn', 'signOut']), // import easily actions from your store update: _.debounce(function (e) { this.submit(e) }, 2000), submit: _.throttle(function (e) { // Here comes the implementation of this method }, 800), validate (text) { // Here comes the implementation of this method } } } </script> <style lang="stylus"> // Here comes some styling stuff </style>
  • 122.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue <script> import {mapGetters, mapActions } from 'vuex' import _ from 'lodash' export default { name: 'AppHeader', computed: mapGetters(['user', 'currentUserName']), // import easily getters from your store data () { // data now stores internal component state, no application state e.g: active filters, active/inactive status, etc. return { name: '' } }, methods: { ...mapActions(['signIn', 'signOut']), // import easily actions from your store update: _.debounce(function (e) { this.submit(e) }, 2000), submit: _.throttle(function (e) { // Here comes the implementation of this method }, 800), validate (text) { // Here comes the implementation of this method } } } </script> <style lang="stylus"> // Here comes some styling stuff </style>
  • 123.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/AppHeader.vue <script> import {mapGetters, mapActions } from 'vuex' import _ from 'lodash' export default { name: 'AppHeader', computed: mapGetters(['user', 'currentUserName']), // import easily getters from your store data () { // data now stores internal component state, no application state e.g: active filters, active/inactive status, etc. return { name: '' } }, methods: { ...mapActions(['signIn', 'signOut']), // import easily actions from your store update: _.debounce(function (e) { this.submit(e) }, 2000), submit: _.throttle(function (e) { // Here comes the implementation of this method }, 800), validate (text) { // Here comes the implementation of this method } } } </script> <style lang="stylus"> // Here comes some styling stuff </style>
  • 124.
    methods: { ...mapActions(['signIn', 'signOut']), update:_.debounce(function (e) { this.submit(e) }, 2000), submit: _.throttle(function (e) { const name = e.target.value this.validate(name) .then(() => { return this.$store.dispatch('setUserInfo', { key: 'name', val: name }) }).then(() => { Materialize.toast('Updated Name', 4000, 'green') }).catch(err => { Materialize.toast('Error ' + err, 4000, 'red') }) }, 800), validate (text) { return new Promise((resolve, reject) => { if (text.length < 3) reject('too short') else if (text.length > 15)reject('too long') else resolve() }) } } }
  • 125.
    methods: { ...mapActions(['signIn', 'signOut']), update:_.debounce(function (e) { this.submit(e) }, 2000), submit: _.throttle(function (e) { const name = e.target.value this.validate(name) .then(() => { return this.$store.dispatch('setUserInfo', { key: 'name', val: name }) }).then(() => { Materialize.toast('Updated Name', 4000, 'green') }).catch(err => { Materialize.toast('Error ' + err, 4000, 'red') }) }, 800), validate (text) { return new Promise((resolve, reject) => { if (text.length < 3) reject('too short') else if (text.length > 15)reject('too long') else resolve() }) }
  • 126.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/Index.vue <template> <div class="indexcontainer"> <div class="filter"> <a class="waves-effect waves-light btn" :class="{ active: currentTimeLine === 'public' }" @click="switchTimeLine('public')">Public</a> <a class="waves-effect waves-light btn" :class="{ active: currentTimeLine === 'myself' }" @click="switchTimeLine('myself')">Myself</a> </div> <ul class="collection"> <memo v-for="(memo, key) in orderedMemos" :memo="memo"> </memo> </ul> <div> <a class="waves-effect waves-light btn-large" @click="fetchMemos({count: 10, type: currentTimeLine})">fetch more memos</a> </div> <div class="fixed-action-btn"> <router-link :to="{ name: 'newEditor' }" class="btn-floating btn-large waves-effect waves-light red"> <i class="material-icons">add</i> </router-link> </div> </div> </template>
  • 127.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/Index.vue <template> <div class="indexcontainer"> <div class="filter"> <a class="waves-effect waves-light btn" :class="{ active: currentTimeLine === 'public' }" @click="switchTimeLine('public')">Public</a> <a class="waves-effect waves-light btn" :class="{ active: currentTimeLine === 'myself' }" @click="switchTimeLine('myself')">Myself</a> </div> <ul class="collection"> <memo v-for="(memo, key) in orderedMemos" :memo="memo"> </memo> </ul> <div> <a class="waves-effect waves-light btn-large" @click="fetchMemos({count: 10, type: currentTimeLine})">fetch more memos</a> </div> <div class="fixed-action-btn"> <router-link :to="{ name: 'newEditor' }" class="btn-floating btn-large waves-effect waves-light red"> <i class="material-icons">add</i> </router-link> </div> </div> </template>
  • 128.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/Index.vue <template> <div class="indexcontainer"> <div class="filter"> <a class="waves-effect waves-light btn" :class="{ active: currentTimeLine === 'public' }" @click="switchTimeLine('public')">Public</a> <a class="waves-effect waves-light btn" :class="{ active: currentTimeLine === 'myself' }" @click="switchTimeLine('myself')">Myself</a> </div> <ul class="collection"> <memo v-for="(memo, key) in orderedMemos" :memo="memo"> </memo> </ul> <div> <a class="waves-effect waves-light btn-large" @click="fetchMemos({count: 10, type: currentTimeLine})">fetch more memos</a> </div> <div class="fixed-action-btn"> <router-link :to="{ name: 'newEditor' }" class="btn-floating btn-large waves-effect waves-light red"> <i class="material-icons">add</i> </router-link> </div> </div> </template>
  • 129.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/Index.vue <template> <div class="indexcontainer"> <div class="filter"> <a class="waves-effect waves-light btn" :class="{ active: currentTimeLine === 'public' }" @click="switchTimeLine('public')">Public</a> <a class="waves-effect waves-light btn" :class="{ active: currentTimeLine === 'myself' }" @click="switchTimeLine('myself')">Myself</a> </div> <ul class="collection"> <memo v-for="(memo, key) in orderedMemos" :memo="memo"> </memo> </ul> <div> <a class="waves-effect waves-light btn-large" @click="fetchMemos({count: 10, type: currentTimeLine})">fetch more memos</a> </div> <div class="fixed-action-btn"> <router-link :to="{ name: 'newEditor' }" class="btn-floating btn-large waves-effect waves-light red"> <i class="material-icons">add</i> </router-link> </div> </div> </template>
  • 130.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/Index.vue <template> <div class="indexcontainer"> <div class="filter"> <a class="waves-effect waves-light btn" :class="{ active: currentTimeLine === 'public' }" @click="switchTimeLine('public')">Public</a> <a class="waves-effect waves-light btn" :class="{ active: currentTimeLine === 'myself' }" @click="switchTimeLine('myself')">Myself</a> </div> <ul class="collection"> <memo v-for="(memo, key) in orderedMemos" :memo="memo"> </memo> </ul> <div> <a class="waves-effect waves-light btn-large" @click="fetchMemos({count: 10, type: currentTimeLine})">fetch more memos</a> </div> <div class="fixed-action-btn"> <router-link :to="{ name: 'newEditor' }" class="btn-floating btn-large waves-effect waves-light red"> <i class="material-icons">add</i> </router-link> </div> </div> </template>
  • 131.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/Index.vue <script> import {mapGetters, mapActions } from 'vuex' import Memo from './Memo.vue' export default { components: { Memo }, computed: { ...mapGetters(['memos', 'currentUserId']), orderedMemos () { // Here comes the implementation } }, data () { return { currentTimeLine: 'public' } }, methods: { ...mapActions(['fetchMemos']), switchTimeLine (val) { // Here comes the implementation } } } </script> <style lang="stylus"> // Here comes some styling stuff </style>
  • 132.
    // https://github.com/akifo/vue-memo/blob/dev/src/js/components/Memo.vue <template> <li class="memocollection-item"> <router-link :to="{ name: 'viewer', params: { id: memo.key }}"> <h2>{{memo.title}}</h2> <p>{{memo.author.name}} | {{memo.created | formatDate }}</p> </router-link> </li> </template> <script> export default { name: 'Memo', props: ['memo', 'id'] } </script> <style lang="stylus"> // Here comes some styling stuff </style> You can pass arguments to this component via props
  • 133.
    Development × Setup vue ×Store × Router × Root app component × Create components × Dev tools
  • 134.
  • 138.
    Rails Implicit dependencies layer >concern hierarchy You think about how to store data Mindset differences Vue Explicit dependencies concern > layer hierarchy You think about how to present data
  • 139.
    Authentication & authorization Backendpersistence Async actions management (sagas) Connecting to third party services Styling SSR? Build process Before going live you should know
  • 140.
    after going liveyou should dive deeper in Vuex VueRouter ES2015 Transitions & animations GraphQL :-) Functional programming
  • 141.
    Progressive web apps Vuenative with Weex Vue for desktop with Electron SSR with Nuxt.js (some of my) next learning paths Web components & HTTP 2 Functional programming TypeScript Docker + GraphQL + Vue!!!
  • 142.
    4. what we learned Buildingan application with Vue, rails and graphql
  • 143.
    Learning fact #1 Ifyou need a “modern” UI you should use something like VUE.js
  • 144.
    Learning fact #2 Developingwith vue.js makes working with JS more easy to reason about
  • 145.
    Learning fact #3 Workingwith vue.js implies bigger effort… ...just because you need something bigger
  • 146.
    THANKS!Any questions? You canfind ME at @eLafo You can find US at @aspgems & info@aspgems.com
  • 147.
    References × Learning Vue.js2 - Olga Filipova × https://vuejs.org/ × http://engineering.paiza.io/entry/2015/03/12/145216 × https://medium.com/js-dojo/whats-new-in-vue-js-2-0-virtual-dom-dc4b5b827f40 × http://www.valuecoders.com/blog/technology-and-apps/vue-js-comparison-angular-re act/ × https://medium.com/@deathmood/how-to-write-your-own-virtual-dom-ee74acc13060 ×
  • 148.
    Credits Special thanks toall the people who made and released these awesome resources for free: × Vue example app by afiko × Presentation template by SlidesCarnival × Photographs by Startupstockphotos
  • 149.
    Presentation design This presentationuses the following typographies: × Titles: Bangers × Body copy: Sniglet You can download the fonts on this page: https://www.google.com/fonts#UsePlace:use/Collection:Sniglet|Bangers Click on the “arrow button” that appears on the top right You don’t need to keep this slide in your presentation. It’s only here to serve you as a design guide if you need to create new slides or download the fonts to edit the presentation in PowerPoint®